Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BetterSpecialCustomers v1.0.0
BetterSpecialCustomers_Mono.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using BetterSpecialCustomers; using BetterSpecialCustomers.Diagnostics; using BetterSpecialCustomers.Quests; using BetterSpecialCustomers.Relationships; using FishNet; using FishNet.Connection; using HarmonyLib; using MelonLoader; using Microsoft.CodeAnalysis; using S1API.Entities; using S1API.Entities.Appearances.AccessoryFields; using S1API.Entities.Appearances.Base; using S1API.Entities.Appearances.BodyLayerFields; using S1API.Entities.Appearances.CustomizationFields; using S1API.Entities.Appearances.FaceLayerFields; using S1API.Entities.Schedule; using S1API.Internal.Abstraction; using S1API.Leveling; using S1API.Map; using S1API.Map.Buildings; using S1API.Money; using S1API.Quests; using S1API.Saveables; using ScheduleOne; using ScheduleOne.Core.Items.Framework; using ScheduleOne.DevUtilities; using ScheduleOne.Dialogue; using ScheduleOne.Economy; using ScheduleOne.ItemFramework; using ScheduleOne.Levelling; using ScheduleOne.Map; using ScheduleOne.NPCs; using ScheduleOne.NPCs.Relation; using ScheduleOne.NPCs.Responses; using ScheduleOne.PlayerScripts; using ScheduleOne.Police; using ScheduleOne.SpecialCustomers; using ScheduleOne.Storage; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "BetterSpecialCustomers", "1.0.0", "Lumahex", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: HarmonyDontPatchAll] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("BetterSpecialCustomers_Mono")] [assembly: AssemblyConfiguration("MONO")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+c7e4d29411e07561897b504f24ae00b062ef3bf0")] [assembly: AssemblyProduct("BetterSpecialCustomers_Mono")] [assembly: AssemblyTitle("BetterSpecialCustomers_Mono")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 MONO_IL2CPP_Template.TemplateUtils { public static class AssetBundleUtils { private static Core mod = MelonAssembly.FindMelonInstance<Core>(); private static MelonAssembly melonAssembly = ((MelonBase)mod).MelonAssembly; public static AssetBundle LoadAssetBundle(string bundleFileName) { try { string text = typeof(Core).Namespace + ".Assets." + bundleFileName; Stream manifestResourceStream = melonAssembly.Assembly.GetManifestResourceStream(text ?? ""); if (manifestResourceStream == null) { ((MelonBase)mod).Unregister("AssetBundle: '" + text + "' not found. \nOpen .csproj file and search for '" + bundleFileName + "'.\nIf it doesn't exist,\nCopy your asset to Assets/ folder then look for 'your.assetbundle' in .csproj file.", false); return null; } return AssetBundle.LoadFromStream(manifestResourceStream); } catch (Exception arg) { ((MelonBase)mod).Unregister($"Failed to load AssetBundle. Please report to dev: {arg}", false); return null; } } public static AssetBundle GetLoadedAssetBundle(string asset_name_flag) { AssetBundle[] array = AssetBundle.GetAllLoadedAssetBundles().ToArray(); try { AssetBundle[] array2 = array; foreach (AssetBundle val in array2) { if (val.Contains(asset_name_flag)) { return val; } } string text = ""; AssetBundle[] array3 = array; foreach (AssetBundle val2 in array3) { string[] allAssetNames = val2.GetAllAssetNames(); string text2 = string.Join("\n\r -", allAssetNames); text = text + ((Object)val2).name + $"({allAssetNames.Length} assets):" + text2; } throw new Exception($"Asset '{asset_name_flag}' not found in {array.Length} bundle(s).\n{text}"); } catch (Exception arg) { ((MelonBase)mod).Unregister($"Failed to get loaded AssetBundle. Please report to dev: \n{arg}", false); return null; } } public static GameObject LoadAssetFromBundle(string asset_name) { AssetBundle loadedAssetBundle = GetLoadedAssetBundle(asset_name); return loadedAssetBundle.LoadAsset<GameObject>(asset_name); } } } namespace BetterSpecialCustomers { public class Core : MelonMod { private float _nextQuestTick; public override void OnInitializeMelon() { ((MelonBase)this).LoggerInstance.Msg("Initialized."); PatchDiagnostics.Probe(); PatchDiagnostics.PatchAllClasses(((MelonBase)this).HarmonyInstance); PatchDiagnostics.ListPatchedMethods(((MelonBase)this).HarmonyInstance); } public override void OnUpdate() { if (Time.unscaledTime >= _nextQuestTick) { _nextQuestTick = Time.unscaledTime + 1f; HippiesTask.Tick(); } } } } namespace BetterSpecialCustomers.Relationships { public static class GroupRelationships { private class State { public float Current = 2.5f; public float Applied = 2.5f; public bool InVisit; public bool GainedThisVisit; public int SoldThisVisit; } public const float Min = 0f; public const float Max = 5f; public const float DefaultValue = 2.5f; public const float MultiplierAtMin = 0.5f; public const float MultiplierAtMax = 1.5f; public const float GainPerVisit = 0.5f; public const float MinFractionOfLimitForGain = 0.75f; private static readonly Dictionary<string, State> States = new Dictionary<string, State>(); public const string JsonKey = "BetterSpecialCustomers_Relationships"; private static readonly Regex SectionRegex = new Regex("\"BetterSpecialCustomers_Relationships\"\\s*:\\s*\\[(.*?)\\]", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex EntryRegex = new Regex("\\{([^}]*)\\}", RegexOptions.Compiled); private static string Key(string groupId) { return (groupId ?? string.Empty).ToLowerInvariant(); } private static State GetState(string groupId) { string key = Key(groupId); if (!States.TryGetValue(key, out var value)) { value = new State(); States[key] = value; } return value; } private static float Clamp(float value) { return Math.Max(0f, Math.Min(5f, value)); } public static float GetCurrent(string groupId) { return GetState(groupId).Current; } public static float GetApplied(string groupId) { return GetState(groupId).Applied; } public static ERelationshipCategory GetCategory(float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return RelationshipCategory.GetCategory(value); } public static float GetMultiplier(string groupId) { float num = (GetApplied(groupId) - 0f) / 5f; return 0.5f + 1f * num; } public static void BeginVisit(string groupId) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) State state = GetState(groupId); if (!state.InVisit) { state.InVisit = true; state.GainedThisVisit = false; state.SoldThisVisit = 0; state.Applied = state.Current; Melon<Core>.Logger.Msg($"[{groupId}] new visit. Relationship {state.Applied:0.00} ({GetCategory(state.Applied)}) now applies: buy multiplier x{GetMultiplier(groupId):0.00}"); } } public static void EndVisit(string groupId) { GetState(groupId).InVisit = false; } public static void RegisterSale(string groupId, int unitsSold, int limit) { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) State state = GetState(groupId); if (!state.GainedThisVisit && limit > 0) { state.SoldThisVisit += unitsSold; float num = (float)state.SoldThisVisit / (float)limit; if (num < 0.75f) { Melon<Core>.Logger.Msg($"[{groupId}] sold {state.SoldThisVisit} so far this visit ({num:P0} of their limit of {limit}); need {0.75f:P0} for a relationship gain"); } else { state.GainedThisVisit = true; float current = state.Current; state.Current = Clamp(current + 0.5f); Melon<Core>.Logger.Msg($"[{groupId}] relationship {current:0.00} -> {state.Current:0.00} ({GetCategory(state.Current)}). It applies from their next visit; this visit stays at x{GetMultiplier(groupId):0.00}"); } } } public static void Penalize(string groupId, float amount, string reason) { State state = GetState(groupId); float current = state.Current; state.Current = Clamp(current - amount); Melon<Core>.Logger.Msg($"[{groupId}] relationship {current:0.00} -> {state.Current:0.00} ({reason})"); } public static void Reward(string groupId, float amount, string reason) { State state = GetState(groupId); float current = state.Current; state.Current = Clamp(current + amount); Melon<Core>.Logger.Msg($"[{groupId}] relationship {current:0.00} -> {state.Current:0.00} ({reason})"); } private static string ReadField(string entry, string name) { Match match = Regex.Match(entry, "\"" + name + "\"\\s*:\\s*\"?([^\",\\s}]+)"); return match.Success ? match.Groups[1].Value : null; } private static float ReadFloat(string entry, string name, float fallback) { string text = ReadField(entry, name); float result; return (text != null && float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) ? Clamp(result) : fallback; } public static void Clear() { States.Clear(); } public static string ToJsonProperty() { List<string> list = new List<string>(); foreach (KeyValuePair<string, State> state in States) { State value = state.Value; list.Add(string.Format(CultureInfo.InvariantCulture, "{{ \"id\": \"{0}\", \"current\": {1}, \"applied\": {2}, \"inVisit\": {3}, \"gained\": {4}, \"sold\": {5} }}", state.Key, value.Current.ToString("R", CultureInfo.InvariantCulture), value.Applied.ToString("R", CultureInfo.InvariantCulture), value.InVisit ? 1 : 0, value.GainedThisVisit ? 1 : 0, value.SoldThisVisit)); } return "\"BetterSpecialCustomers_Relationships\": [ " + string.Join(", ", list) + " ]"; } public static void LoadFromJson(string json) { States.Clear(); if (string.IsNullOrEmpty(json)) { return; } Match match = SectionRegex.Match(json); if (!match.Success) { return; } foreach (Match item in EntryRegex.Matches(match.Groups[1].Value)) { string value = item.Groups[1].Value; string text = ReadField(value, "id"); if (!string.IsNullOrEmpty(text)) { State state = GetState(text); state.Current = ReadFloat(value, "current", 2.5f); state.Applied = ReadFloat(value, "applied", state.Current); state.InVisit = ReadField(value, "inVisit") == "1"; state.GainedThisVisit = ReadField(value, "gained") == "1"; state.SoldThisVisit = (int.TryParse(ReadField(value, "sold"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : 0); } } } } } namespace BetterSpecialCustomers.Quests { public class BikersQuestSave : Saveable { [SaveableField("bikers_quest")] public JobSaveData Data = new JobSaveData(); public static BikersQuestSave Instance { get; private set; } public BikersQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; Informant.Refresh(); } } public sealed class Informant : NPC { private const string NpcId = "bikers_informant"; private const string HideoutName = "The Piss Hut"; private static Informant _instance; private static int _lookAppliedForRun = -1; private static readonly string[] FirstNames = new string[12] { "Dale", "Rusty", "Vince", "Eddie", "Skeet", "Lenny", "Wade", "Cody", "Marv", "Gus", "Terry", "Bo" }; private static readonly string[] LastNames = new string[12] { "Hollis", "Brandt", "Keane", "Mercer", "Voss", "Pruitt", "Lund", "Garrity", "Boone", "Sykes", "Dunn", "Rourke" }; private static readonly Color32[] HairColors = (Color32[])(object)new Color32[5] { new Color32((byte)30, (byte)22, (byte)18, byte.MaxValue), new Color32((byte)60, (byte)40, (byte)28, byte.MaxValue), new Color32((byte)95, (byte)65, (byte)40, byte.MaxValue), new Color32((byte)150, (byte)120, (byte)80, byte.MaxValue), new Color32((byte)120, (byte)120, (byte)120, byte.MaxValue) }; private static readonly string[] HairStyles = new string[7] { "Avatar/Hair/buzzcut/BuzzCut", "Avatar/Hair/closebuzzcut/CloseBuzzCut", "Avatar/Hair/balding/Balding", "Avatar/Hair/receding/Receding", "Avatar/Hair/longslicked/LongSlicked", "Avatar/Hair/shoulderlength/ShoulderLength", "Avatar/Hair/peaked/Peaked" }; private static readonly Color32[] ShirtColors = (Color32[])(object)new Color32[4] { new Color32((byte)25, (byte)25, (byte)25, byte.MaxValue), new Color32((byte)60, (byte)60, (byte)60, byte.MaxValue), new Color32((byte)110, (byte)25, (byte)25, byte.MaxValue), new Color32((byte)90, (byte)90, (byte)90, byte.MaxValue) }; private static readonly Color32[] JeansColors = (Color32[])(object)new Color32[3] { new Color32((byte)30, (byte)38, (byte)60, byte.MaxValue), new Color32((byte)20, (byte)20, (byte)24, byte.MaxValue), new Color32((byte)55, (byte)65, (byte)90, byte.MaxValue) }; public override bool IsPhysical => true; public static string PickName() { return QuestNpcUtil.RandomName(FirstNames, LastNames); } protected override void ConfigurePrefab(NPCPrefabBuilder builder) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) builder.WithIdentity("bikers_informant", "Informant", string.Empty).WithSpawnPosition(QuestNpcUtil.ParkedPosition).WithSchedule((Action<PrefabScheduleBuilder>)delegate(PrefabScheduleBuilder plan) { plan.StayInBuilding(Building.Get<ThePissHut>(), 0, 1439, (int?)null, (string)null); }); } protected override void OnCreated() { ((NPC)this).OnCreated(); ((NPC)this).Appearance.Build(); _instance = this; Melon<Core>.Logger.Msg("[quest] the informant NPC was created"); ((NPC)this).OnDeath += BikersQuest.Job.OnObjectiveDone; Refresh(); } public static void Refresh() { if (_instance == null) { return; } JobSaveData jobSaveData = BikersQuestSave.Instance?.Data; bool flag = jobSaveData != null && jobSaveData.Accepted && !jobSaveData.TargetKilled; try { if (flag) { _instance.Deploy(jobSaveData); } else { QuestNpcUtil.Park((NPC)(object)_instance, "bikers_informant"); } } catch (Exception arg) { Melon<Core>.Logger.Error(string.Format("[quest] informant {0} failed: {1}", flag ? "deploy" : "park", arg)); } } private void Deploy(JobSaveData save) { QuestNpcUtil.ApplyName((NPC)(object)this, save.TargetName); if (_lookAppliedForRun != save.Runs) { _lookAppliedForRun = save.Runs; ApplyBikerLook(); } QuestNpcUtil.Revive((NPC)(object)this); QuestNpcUtil.EnterBuilding("bikers_informant", "The Piss Hut"); ((NPC)this).Schedule.Enable(); } private void ApplyBikerLook() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: 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_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Unknown result type (might be due to invalid IL or missing references) AppearanceUtil.ClearLayers(((NPC)this).Appearance); Color val = Color32.op_Implicit(QuestNpcUtil.Pick(HairColors)); ((NPC)this).Appearance.Set<Gender>((object)0f); ((NPC)this).Appearance.Set<Height>((object)Random.Range(0.95f, 1.05f)); ((NPC)this).Appearance.Set<Weight>((object)Random.Range(0.45f, 0.75f)); ((NPC)this).Appearance.Set<SkinColor>((object)Color32.op_Implicit(QuestNpcUtil.Pick(QuestNpcUtil.SkinTones))); ((NPC)this).Appearance.Set<EyeBallTint>((object)Color.white); ((NPC)this).Appearance.Set<HairColor>((object)val); ((NPC)this).Appearance.Set<HairStyle>((object)QuestNpcUtil.Pick(HairStyles)); Color val2 = default(Color); ((Color)(ref val2))..ctor(0.15f, 0.1f, 0.1f); ((NPC)this).Appearance.WithFaceLayer<Face>((Random.value < 0.5f) ? "Avatar/Layers/Face/Face_Neutral" : "Avatar/Layers/Face/Face_SlightFrown", val2); if (Random.value < 0.6f) { ((NPC)this).Appearance.WithFaceLayer<FacialHair>((Random.value < 0.5f) ? "Avatar/Layers/Face/FacialHair_Stubble" : "Avatar/Layers/Face/FacialHair_Goatee", val); } string[] items = new string[5] { "Avatar/Layers/Top/T-Shirt", "Avatar/Layers/Top/T-Shirt", "Avatar/Layers/Top/V-Neck", "Avatar/Layers/Top/FlannelButtonUp", "Avatar/Layers/Top/RolledButtonUp" }; ((NPC)this).Appearance.WithBodyLayer<Shirts>(QuestNpcUtil.Pick(items), Color32.op_Implicit(QuestNpcUtil.Pick(ShirtColors))); ((NPC)this).Appearance.WithBodyLayer<Pants>((Random.value < 0.7f) ? "Avatar/Layers/Bottom/Jeans" : "Avatar/Layers/Bottom/CargoPants", Color32.op_Implicit(QuestNpcUtil.Pick(JeansColors))); Color color = ((Random.value < 0.7f) ? new Color(0.08f, 0.08f, 0.08f) : new Color(0.2f, 0.12f, 0.07f)); float value = Random.value; if (value < 0.6f) { AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, color, new string[2] { "Avatar/Accessories/Chest/BikerJacket/BikerJacket", "Avatar/Accessories/Chest/BikerJacket/Biker Jacket" }); } else if (value < 0.85f) { AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, color, new string[2] { "Avatar/Accessories/Chest/LeatherJacket/LeatherJacket", "Avatar/Accessories/Chest/LeatherJacket/leatherJacket" }); } else { AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, color, new string[1] { "Avatar/Accessories/Chest/OpenVest/OpenVest" }); } AppearanceUtil.Wear<Feet>(((NPC)this).Appearance, new Color(0.1f, 0.1f, 0.1f), new string[1] { (Random.value < 0.85f) ? "Avatar/Accessories/Feet/CombatBoots/CombatBoots" : "Avatar/Accessories/Feet/Sneakers/Sneakers" }); if (Random.value < 0.5f) { AppearanceUtil.Wear<Waist>(((NPC)this).Appearance, new Color(0.1f, 0.07f, 0.05f), new string[1] { "Avatar/Accessories/Waist/Belt/Belt" }); } float value2 = Random.value; if (value2 < 0.3f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, new Color(0.1f, 0.1f, 0.1f), new string[2] { "Avatar/Accessories/Head/Bandana/Bandana", "Avatar/Accessories/Head/PaisleyBandana/PaisleyBandana" }); } else if (value2 < 0.5f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, new Color(0.12f, 0.12f, 0.12f), new string[1] { "Avatar/Accessories/Head/Beanie/Beanie" }); } else if (value2 < 0.7f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Head/Oakleys/Oakleys" }); } if (Random.value < 0.4f) { AppearanceUtil.Wear<Hands>(((NPC)this).Appearance, new Color(0.1f, 0.1f, 0.1f), new string[1] { "Avatar/Accessories/Hands/FingerlessGloves/FingerlessGloves" }); } if (Random.value < 0.4f) { AppearanceUtil.Wear<Neck>(((NPC)this).Appearance, Color.white, new string[2] { "Avatar/Accessories/Neck/GoldChain/GoldChain", "Avatar/Accessories/Neck/SilverChain/SilverChain" }); } AppearanceUtil.ApplyNow(((NPC)this).Appearance); } } public class BikersQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "bikers_informant_quest", RewardLogLabel = "Diesel's informant job", GetSave = () => BikersQuestSave.Instance?.Data, PickName = Informant.PickName, RefreshTarget = Informant.Refresh, CreateQuest = (string id) => QuestManager.CreateQuest<BikersQuest>(id) }); protected override string Title => "Loose Lips"; protected override string Description => "Someone in the club has been talking to the cops. Diesel Rodd wants the informant dealt with."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); string text = Job.TargetNameOr("the informant"); QuestEntry val = ((Quest)this).AddEntry("Knock on the door of The Piss Hut and kill " + text, (Vector3?)null); val.SetPOIToNPC<Informant>(); ((Quest)this).AddEntry("Tell Diesel " + text + " is dealt with", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class BusinessmenQuestSave : Saveable { [SaveableField("businessmen_quest")] public JobSaveData Data = new JobSaveData(); public static BusinessmenQuestSave Instance { get; private set; } public BusinessmenQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; Auditor.Refresh(); } } public sealed class Auditor : NPC { private const string NpcId = "businessmen_auditor"; private const string HomeName = "Upscale Apartments"; private static Auditor _instance; private static int _lookAppliedForRun = -1; private static readonly string[] FirstNames = new string[10] { "Gordon", "Nigel", "Howard", "Clive", "Warren", "Preston", "Desmond", "Edwin", "Leonard", "Franklin" }; private static readonly string[] LastNames = new string[10] { "Whitlock", "Ashcroft", "Pembroke", "Thorne", "Hargrove", "Fairbanks", "Lockhart", "Sterling", "Winslow", "Crane" }; private static readonly Color32[] HairColors = (Color32[])(object)new Color32[5] { new Color32((byte)30, (byte)22, (byte)18, byte.MaxValue), new Color32((byte)60, (byte)40, (byte)28, byte.MaxValue), new Color32((byte)95, (byte)65, (byte)40, byte.MaxValue), new Color32((byte)120, (byte)120, (byte)120, byte.MaxValue), new Color32((byte)190, (byte)190, (byte)190, byte.MaxValue) }; private static readonly string[] HairStyles = new string[6] { "Avatar/Hair/closebuzzcut/CloseBuzzCut", "Avatar/Hair/peaked/Peaked", "Avatar/Hair/receding/Receding", "Avatar/Hair/balding/Balding", "Avatar/Hair/midfringe/MidFringe", "Avatar/Hair/sidepartbob/SidePartBob" }; private static readonly Color32[] ShirtColors = (Color32[])(object)new Color32[3] { new Color32((byte)235, (byte)235, (byte)235, byte.MaxValue), new Color32((byte)180, (byte)200, (byte)225, byte.MaxValue), new Color32((byte)210, (byte)210, (byte)200, byte.MaxValue) }; private static readonly Color32[] SuitColors = (Color32[])(object)new Color32[4] { new Color32((byte)25, (byte)28, (byte)40, byte.MaxValue), new Color32((byte)45, (byte)45, (byte)50, byte.MaxValue), new Color32((byte)20, (byte)20, (byte)20, byte.MaxValue), new Color32((byte)55, (byte)45, (byte)38, byte.MaxValue) }; public override bool IsPhysical => true; public static string PickName() { return QuestNpcUtil.RandomName(FirstNames, LastNames); } protected override void ConfigurePrefab(NPCPrefabBuilder builder) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) builder.WithIdentity("businessmen_auditor", "Auditor", string.Empty).WithSpawnPosition(QuestNpcUtil.ParkedPosition).WithSchedule((Action<PrefabScheduleBuilder>)delegate(PrefabScheduleBuilder plan) { Building val = Building.Get<UpscaleApartments>(); Building val2 = Building.Get<TallTower>(); Building val3 = Building.Get<Cafe>(); Building val4 = Building.Get<ChineseRestaurant>(); plan.StayInBuilding(val, 0, 450, (int?)null, (string)null); plan.StayInBuilding(val2, 800, 240, (int?)null, (string)null); plan.StayInBuilding(val3, 1215, 60, (int?)null, (string)null); plan.StayInBuilding(val2, 1330, 210, (int?)null, (string)null); plan.StayInBuilding(val4, 1800, 120, (int?)null, (string)null); plan.StayInBuilding(val, 2100, 179, (int?)null, (string)null); }); } protected override void OnCreated() { ((NPC)this).OnCreated(); ((NPC)this).Appearance.Build(); _instance = this; Melon<Core>.Logger.Msg("[quest] the auditor NPC was created"); ((NPC)this).OnDeath += AuditQuest.Job.OnObjectiveDone; Refresh(); } public static void Refresh() { if (_instance == null) { return; } JobSaveData jobSaveData = BusinessmenQuestSave.Instance?.Data; bool flag = jobSaveData != null && jobSaveData.Accepted && !jobSaveData.TargetKilled; try { if (flag) { _instance.Deploy(jobSaveData); } else { QuestNpcUtil.Park((NPC)(object)_instance, "businessmen_auditor"); } } catch (Exception arg) { Melon<Core>.Logger.Error(string.Format("[quest] auditor {0} failed: {1}", flag ? "deploy" : "park", arg)); } } private void Deploy(JobSaveData save) { QuestNpcUtil.ApplyName((NPC)(object)this, save.TargetName); if (_lookAppliedForRun != save.Runs) { _lookAppliedForRun = save.Runs; ApplyBusinessLook(); } QuestNpcUtil.Revive((NPC)(object)this); QuestNpcUtil.EnterBuilding("businessmen_auditor", "Upscale Apartments"); ((NPC)this).Schedule.Enable(); ((NPC)this).Schedule.EnforceState(); } private void ApplyBusinessLook() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: 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_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: 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_023b: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) AppearanceUtil.ClearLayers(((NPC)this).Appearance); Color val = Color32.op_Implicit(QuestNpcUtil.Pick(HairColors)); ((NPC)this).Appearance.Set<Gender>((object)0f); ((NPC)this).Appearance.Set<Height>((object)Random.Range(0.95f, 1.05f)); ((NPC)this).Appearance.Set<Weight>((object)Random.Range(0.35f, 0.65f)); ((NPC)this).Appearance.Set<SkinColor>((object)Color32.op_Implicit(QuestNpcUtil.Pick(QuestNpcUtil.SkinTones))); ((NPC)this).Appearance.Set<EyeBallTint>((object)Color.white); ((NPC)this).Appearance.Set<HairColor>((object)val); ((NPC)this).Appearance.Set<HairStyle>((object)QuestNpcUtil.Pick(HairStyles)); ((NPC)this).Appearance.WithFaceLayer<Face>((Random.value < 0.6f) ? "Avatar/Layers/Face/Face_Neutral" : "Avatar/Layers/Face/Face_SlightFrown", new Color(0.15f, 0.1f, 0.1f)); if (Random.value < 0.25f) { ((NPC)this).Appearance.WithFaceLayer<FacialHair>("Avatar/Layers/Face/FacialHair_Stubble", val); } Color val2 = Color32.op_Implicit(QuestNpcUtil.Pick(SuitColors)); ((NPC)this).Appearance.WithBodyLayer<Shirts>((Random.value < 0.8f) ? "Avatar/Layers/Top/Buttonup" : "Avatar/Layers/Top/RolledButtonUp", Color32.op_Implicit(QuestNpcUtil.Pick(ShirtColors))); ((NPC)this).Appearance.WithBodyLayer<Pants>("Avatar/Layers/Bottom/Jeans", val2); AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, val2, new string[1] { "Avatar/Accessories/Chest/Blazer/Blazer" }); AppearanceUtil.Wear<Feet>(((NPC)this).Appearance, new Color(0.08f, 0.06f, 0.05f), new string[1] { "Avatar/Accessories/Feet/DressShoes/DressShoes" }); string[] items = new string[4] { "Avatar/Accessories/Neck/StripedTie/StripedTie", "Avatar/Accessories/Neck/CheckedTie/CheckedTie", "Avatar/Accessories/Neck/DottedTie/DottedTie", "Avatar/Accessories/Neck/CrossTie/CrossTie" }; AppearanceUtil.Wear<Neck>(((NPC)this).Appearance, new Color(0.35f, 0.08f, 0.1f), new string[1] { QuestNpcUtil.Pick(items) }); float value = Random.value; if (value < 0.35f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Head/RectangleFrameGlasses/RectangleFrameGlasses" }); } else if (value < 0.5f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Head/SmallRoundGlasses/SmallRoundGlasses" }); } if (Random.value < 0.5f) { AppearanceUtil.Wear<Hands>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Hands/Polex/Polex" }); } AppearanceUtil.ApplyNow(((NPC)this).Appearance); } } public class AuditQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "businessmen_audit_quest", RewardLogLabel = "Johnny's audit job", GetSave = () => BusinessmenQuestSave.Instance?.Data, PickName = Auditor.PickName, RefreshTarget = Auditor.Refresh, CreateQuest = (string id) => QuestManager.CreateQuest<AuditQuest>(id) }); protected override string Title => "The Audit"; protected override string Description => "An auditor is getting too close to the books. Johnny Smith wants him dealt with."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); string text = Job.TargetNameOr("the auditor"); QuestEntry val = ((Quest)this).AddEntry("Find and kill " + text + ". He keeps a strict daily routine", (Vector3?)null); val.SetPOIToNPC<Auditor>(); ((Quest)this).AddEntry("Tell Johnny Smith " + text + " is dealt with", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class DropRun { public string FromGuid = string.Empty; public string ToGuid = string.Empty; public string FromName = string.Empty; public string ToName = string.Empty; public bool Placed; public bool PickedUp; } public class HippiesQuestSave : Saveable { [SaveableField("hippies_quest")] public JobSaveData Data = new JobSaveData(); [SaveableField("hippies_quest_drops")] public DropRun Drops = new DropRun(); public static HippiesQuestSave Instance { get; private set; } public HippiesQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; } } internal static class HippiesTask { private const string PackageItemId = "paisleybandana"; private static readonly KeyValuePair<string, int>[] Supplies = new KeyValuePair<string, int>[4] { new KeyValuePair<string, int>("extralonglifesoil", 3), new KeyValuePair<string, int>("speedgrow", 3), new KeyValuePair<string, int>("pgr", 1), new KeyValuePair<string, int>("fertilizer", 1) }; private const float MinDropDistance = 80f; private static HippiesQuestSave Save => HippiesQuestSave.Instance; private static DeadDrop FindDrop(string guid) { if (string.IsNullOrEmpty(guid)) { return null; } foreach (DeadDrop deadDrop in DeadDrop.DeadDrops) { if ((Object)(object)deadDrop != (Object)null && string.Equals(deadDrop.GUID.ToString(), guid, StringComparison.OrdinalIgnoreCase)) { return deadDrop; } } return null; } public static Vector3? PositionOf(string guid) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) DeadDrop val = FindDrop(guid); return ((Object)(object)val != (Object)null) ? new Vector3?(((Component)val).transform.position) : ((Vector3?)null); } private static int Count(DeadDrop drop, string itemId) { int num = 0; foreach (ItemSlot itemSlot in ((StorageEntity)drop.Storage).ItemSlots) { ItemInstance itemInstance = itemSlot.ItemInstance; if (itemInstance != null && ((BaseItemDefinition)itemInstance.Definition).ID == itemId) { num += ((BaseItemInstance)itemInstance).Quantity; } } return num; } private static void RemoveAll(DeadDrop drop, string itemId) { foreach (ItemSlot itemSlot in ((StorageEntity)drop.Storage).ItemSlots) { ItemInstance itemInstance = itemSlot.ItemInstance; if (itemInstance != null && ((BaseItemDefinition)itemInstance.Definition).ID == itemId) { itemSlot.ClearStoredInstance(false); } } } public static void Refresh() { HippiesQuestSave save = Save; if (save == null) { return; } if (save.Data.Accepted && !save.Data.TargetKilled) { if (!save.Drops.Placed) { ChooseAndPlace(save.Drops); } } else { Cleanup(save); } } private static void ChooseAndPlace(DropRun run) { List<DeadDrop> list = new List<DeadDrop>(); foreach (DeadDrop deadDrop in DeadDrop.DeadDrops) { if ((Object)(object)deadDrop != (Object)null && ((StorageEntity)deadDrop.Storage).ItemCount == 0) { list.Add(deadDrop); } } if (list.Count < 2) { Melon<Core>.Logger.Warning("[quest] not enough empty dead drops for the hippies' job"); return; } DeadDrop from = list[Random.Range(0, list.Count)]; List<DeadDrop> list2 = list.FindAll(delegate(DeadDrop d) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Vector3 val3 = ((Component)d).transform.position - ((Component)from).transform.position; return ((Vector3)(ref val3)).magnitude >= 80f; }); DeadDrop val = (DeadDrop)((list2.Count > 0) ? ((object)list2[Random.Range(0, list2.Count)]) : ((object)list.Find((DeadDrop d) => (Object)(object)d != (Object)(object)from))); ItemInstance val2 = RewardUtil.CreateItem("paisleybandana", 1); if (val2 != null) { ((StorageEntity)from.Storage).InsertItem(val2, true); run.FromGuid = from.GUID.ToString(); run.ToGuid = val.GUID.ToString(); run.FromName = from.DeadDropName; run.ToName = val.DeadDropName; run.Placed = true; run.PickedUp = false; Melon<Core>.Logger.Msg("[quest] package placed at '" + run.FromName + "', to be delivered to '" + run.ToName + "'"); } } private static void Cleanup(HippiesQuestSave save) { DropRun drops = save.Drops; if (drops.Placed) { DeadDrop val = FindDrop(drops.FromGuid); DeadDrop val2 = FindDrop(drops.ToGuid); if ((Object)(object)val != (Object)null) { RemoveAll(val, "paisleybandana"); } if ((Object)(object)val2 != (Object)null) { RemoveAll(val2, "paisleybandana"); } save.Drops = new DropRun(); } } public static void Tick() { try { HippiesQuestSave save = Save; if (save == null || !save.Data.Accepted || save.Data.TargetKilled || save.Data.Completed) { return; } DropRun drops = save.Drops; if (!drops.Placed) { return; } JobController job = HippiesQuest.Job; if (!drops.PickedUp) { DeadDrop val = FindDrop(drops.FromGuid); if ((Object)(object)val == (Object)null || Count(val, "paisleybandana") > 0) { return; } drops.PickedUp = true; List<QuestEntry> list = job.Quest?.QuestEntries; if (list != null && list.Count > 1) { list[0].Complete(); list[1].Begin(); } Melon<Core>.Logger.Msg("[quest] package picked up"); } DeadDrop val2 = FindDrop(drops.ToGuid); if ((Object)(object)val2 != (Object)null && Count(val2, "paisleybandana") > 0) { job.OnObjectiveDone(); } } catch (Exception ex) { Melon<Core>.Logger.Warning("[quest] hippies tick failed: " + ex.Message); } } public static void GiveSupplies() { RewardUtil.GiveItems(Supplies); } } public class HippiesQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "hippies_package_quest", RewardLogLabel = "Krystal's package job", CashReward = 0f, GetSave = () => HippiesQuestSave.Instance?.Data, PickName = () => string.Empty, RefreshTarget = HippiesTask.Refresh, GrantExtraReward = HippiesTask.GiveSupplies, CreateQuest = (string id) => QuestManager.CreateQuest<HippiesQuest>(id) }); protected override string Title => "Special Delivery"; protected override string Description => "Krystal Bird needs a package carried between two dead drops."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); DropRun dropRun = HippiesQuestSave.Instance?.Drops ?? new DropRun(); ((Quest)this).AddEntry("Pick up the package from the dead drop at " + dropRun.FromName, HippiesTask.PositionOf(dropRun.FromGuid)); ((Quest)this).AddEntry("Deliver it to the dead drop at " + dropRun.ToName, HippiesTask.PositionOf(dropRun.ToGuid)); ((Quest)this).AddEntry("Tell Krystal Bird the package has been delivered", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class PartyRun { public List<string> Asked = new List<string>(); public int Accepted; } public class PartyBusQuestSave : Saveable { [SaveableField("partybus_quest")] public JobSaveData Data = new JobSaveData(); [SaveableField("partybus_quest_run")] public PartyRun Run = new PartyRun(); public static PartyBusQuestSave Instance { get; private set; } public PartyBusQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; } } internal static class PartyTask { public const int PeopleNeeded = 5; public const float RejectChance = 0.3f; private static readonly KeyValuePair<string, int>[] PartySupplies = new KeyValuePair<string, int>[1] { new KeyValuePair<string, int>("energydrink", 5) }; private static PartyBusQuestSave Save => PartyBusQuestSave.Instance; public static void GiveSupplies() { RewardUtil.GiveItems(PartySupplies); } public static bool CanInvite(string npcId) { PartyBusQuestSave save = Save; if (save == null || string.IsNullOrEmpty(npcId)) { return false; } JobSaveData data = save.Data; return data.Accepted && !data.TargetKilled && !data.Completed && !save.Run.Asked.Contains(npcId); } public static bool Invite(string npcId) { PartyBusQuestSave save = Save; if (save == null || !CanInvite(npcId)) { return false; } save.Run.Asked.Add(npcId); bool flag = Random.value >= 0.3f; if (flag) { save.Run.Accepted++; } UpdateObjectiveTitle(); Melon<Core>.Logger.Msg(string.Format("[quest] {0} {1} the invite ({2}/{3})", npcId, flag ? "accepted" : "turned down", save.Run.Accepted, 5)); if (save.Run.Accepted >= 5) { PartyBusQuest.Job.OnObjectiveDone(); } return flag; } public static string ObjectiveText(int accepted) { return $"Tell people the Party Bus is in town ({Math.Min(accepted, 5)}/{5})"; } private static void UpdateObjectiveTitle() { List<QuestEntry> list = PartyBusQuest.Job.Quest?.QuestEntries; if (list != null && list.Count > 0) { list[0].Title = ObjectiveText(Save.Run.Accepted); } } public static void Refresh() { PartyBusQuestSave save = Save; if (save != null && (!save.Data.Accepted || save.Data.TargetKilled) && (save.Run.Asked.Count > 0 || save.Run.Accepted > 0)) { save.Run = new PartyRun(); } } } public class PartyBusQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "partybus_word_quest", RewardLogLabel = "Chad's spread-the-word job", CashReward = 2500f, GrantExtraReward = PartyTask.GiveSupplies, GetSave = () => PartyBusQuestSave.Instance?.Data, PickName = () => string.Empty, RefreshTarget = PartyTask.Refresh, CreateQuest = (string id) => QuestManager.CreateQuest<PartyBusQuest>(id) }); protected override string Title => "Spread the Word"; protected override string Description => "Chad Chaddington wants the whole town to know the Party Bus is here."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); int accepted = PartyBusQuestSave.Instance?.Run.Accepted ?? 0; ((Quest)this).AddEntry(PartyTask.ObjectiveText(accepted), (Vector3?)null); ((Quest)this).AddEntry("Tell Chad Chaddington the word is out", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class JobSaveData { public bool Accepted; public string GroupId = string.Empty; public bool TargetKilled; public bool Completed; public int OfferState; public string TargetName = string.Empty; public int Runs; } internal sealed class JobDefinition { public string QuestIdPrefix; public string RewardLogLabel; public float RelationshipReward = 0.5f; public float CashReward = 5000f; public int XpReward = 100; public Action GrantExtraReward; public Func<JobSaveData> GetSave; public Func<string> PickName; public Action RefreshTarget; public Func<string, Quest> CreateQuest; } internal sealed class JobLines { public Func<string, string> OfferLine; public string[] NoJobLines; public string AlreadyDoneLine; public string HandInLine; } internal sealed class JobController { public static readonly bool DebugMode = false; private static readonly List<JobController> All = new List<JobController>(); private readonly JobDefinition _def; private Quest _quest; public static float OfferChance => DebugMode ? 1f : 0.5f; public JobSaveData Save => _def.GetSave(); public Quest Quest => _quest; public JobController(JobDefinition def) { _def = def; All.Add(this); } public void Attach(Quest quest) { _quest = quest; quest.OnComplete -= HandleComplete; quest.OnComplete += HandleComplete; } public string TargetNameOr(string fallback) { string text = Save?.TargetName; return string.IsNullOrEmpty(text) ? fallback : text; } public bool CanAsk() { JobSaveData save = Save; return save == null || !save.Accepted || save.Completed; } public bool IsReadyToHandIn() { JobSaveData save = Save; return save != null && save.TargetKilled && !save.Completed; } public string Ask(string groupId, JobLines lines) { JobSaveData save = Save; if (save == null) { return null; } if (!string.IsNullOrEmpty(groupId)) { save.GroupId = groupId; } if (save.Completed && !DebugMode) { return lines.AlreadyDoneLine; } if (save.Completed) { ResetRun(); } if (save.OfferState == 0 || DebugMode) { save.OfferState = ((Random.value < OfferChance) ? 1 : 2); } if (save.OfferState == 1) { Start(groupId); return lines.OfferLine(save.TargetName); } return lines.NoJobLines[Random.Range(0, lines.NoJobLines.Length)]; } public void Start(string groupId) { JobSaveData save = Save; if (save != null) { save.Accepted = true; save.GroupId = groupId ?? string.Empty; save.Runs++; save.TargetName = _def.PickName(); } if (_quest == null) { _quest = _def.CreateQuest($"{_def.QuestIdPrefix}_{save?.Runs ?? 0}"); } if (_quest == null) { Melon<Core>.Logger.Error("[quest] could not create the quest"); return; } _def.RefreshTarget(); Melon<Core>.Logger.Msg("[quest] created; S1API sets up its entries and begins it on the next frame"); } public void OnObjectiveDone() { JobSaveData save = Save; if (_quest != null && save != null && save.Accepted && !save.TargetKilled) { save.TargetKilled = true; int num = _quest.QuestEntries.Count - 1; for (int i = 0; i < num; i++) { _quest.QuestEntries[i].Complete(); } if (num > 0) { _quest.QuestEntries[num].Begin(); } Melon<Core>.Logger.Msg("[quest] task done, report back to the leader"); } } public void HandIn() { JobSaveData save = Save; if (_quest != null && save != null && save.TargetKilled && !save.Completed) { save.Completed = true; int num = _quest.QuestEntries.Count - 1; if (num > 0) { _quest.QuestEntries[num].Complete(); } _quest.Complete(); _def.RefreshTarget(); } } private void HandleComplete() { if (_def.CashReward > 0f) { Money.ChangeCashBalance(_def.CashReward, true, true); } LevelManager.AddXP(_def.XpReward); _def.GrantExtraReward?.Invoke(); Melon<Core>.Logger.Msg($"[quest] paid ${_def.CashReward}, {_def.XpReward} XP"); string text = Save?.GroupId; if (!string.IsNullOrEmpty(text)) { GroupRelationships.Reward(text, _def.RelationshipReward, "completed " + _def.RewardLogLabel); } else { Melon<Core>.Logger.Warning("[quest] no saved group id, relationship reward skipped"); } } public void ResetRun() { JobSaveData save = Save; if (save != null) { if (_quest != null && save.Accepted && !save.Completed) { _quest.Cancel(); } _quest = null; save.OfferState = 0; save.TargetName = string.Empty; save.Accepted = false; save.TargetKilled = false; save.Completed = false; _def.RefreshTarget(); } } public static void OnGroupLeft(string groupId) { foreach (JobController item in All) { JobSaveData save = item.Save; if (save != null && string.Equals(save.GroupId, groupId, StringComparison.OrdinalIgnoreCase)) { item.ResetRun(); Melon<Core>.Logger.Msg("[quest] " + groupId + " left, their job can be taken again next visit"); } } } } internal static class RewardUtil { public static ItemInstance CreateItem(string itemId, int quantity) { ItemDefinition item = Registry.GetItem(itemId); if ((Object)(object)item == (Object)null) { Melon<Core>.Logger.Warning("[quest] item '" + itemId + "' does not exist"); return null; } return item.GetDefaultInstance(quantity); } public static void GiveItems(params KeyValuePair<string, int>[] items) { PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance == (Object)null) { Melon<Core>.Logger.Warning("[quest] no inventory to give the reward to"); return; } for (int i = 0; i < items.Length; i++) { KeyValuePair<string, int> keyValuePair = items[i]; ItemInstance val = CreateItem(keyValuePair.Key, keyValuePair.Value); if (val != null) { instance.AddItemToInventory(val); } } } } internal static class QuestNpcUtil { public static readonly Vector3 ParkedPosition = new Vector3(0f, -200f, 0f); public static readonly Color32[] SkinTones = (Color32[])(object)new Color32[5] { new Color32((byte)236, (byte)200, (byte)170, byte.MaxValue), new Color32((byte)224, (byte)172, (byte)138, byte.MaxValue), new Color32((byte)198, (byte)140, (byte)100, byte.MaxValue), new Color32((byte)168, (byte)112, (byte)78, byte.MaxValue), new Color32((byte)130, (byte)84, (byte)58, byte.MaxValue) }; public static T Pick<T>(T[] items) { return items[Random.Range(0, items.Length)]; } public static string RandomName(string[] firstNames, string[] lastNames) { return Pick(firstNames) + " " + Pick(lastNames); } public static void ApplyName(NPC npc, string fullName) { if (!string.IsNullOrEmpty(fullName)) { int num = fullName.IndexOf(' '); npc.FirstName = ((num > 0) ? fullName.Substring(0, num) : fullName); npc.LastName = ((num > 0) ? fullName.Substring(num + 1) : string.Empty); } } private static NPC FindGameNpc(string npcId) { NPC[] array = Object.FindObjectsOfType<NPC>(true); NPC[] array2 = array; foreach (NPC val in array2) { if ((Object)(object)val != (Object)null && val.ID == npcId) { return val; } } Melon<Core>.Logger.Warning($"[quest] NPC '{npcId}' is not among the {array.Length} NPCs in the scene"); return null; } private static NPCEnterableBuilding FindBuilding(string name) { NPCEnterableBuilding[] array = Object.FindObjectsOfType<NPCEnterableBuilding>(true); foreach (NPCEnterableBuilding val in array) { if ((Object)(object)val != (Object)null && string.Equals(val.BuildingName, name, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } public static void EnterBuilding(string npcId, string buildingName) { NPC val = FindGameNpc(npcId); NPCEnterableBuilding val2 = FindBuilding(buildingName); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || val2.Doors.Length == 0) { Melon<Core>.Logger.Warning($"[quest] could not hide '{npcId}' in '{buildingName}' (npc found: {(Object)(object)val != (Object)null}, building found: {(Object)(object)val2 != (Object)null}, doors: {val2?.Doors.Length})"); } else if (!((Object)(object)val.CurrentBuilding == (Object)(object)val2)) { if (val.isInBuilding) { val.ExitBuilding(""); } val.EnterBuilding((NetworkConnection)null, val2.GUID.ToString(), 0); Melon<Core>.Logger.Msg("[quest] '" + npcId + "' is now hidden in '" + buildingName + "'"); } } public static void LeaveBuilding(string npcId) { NPC val = FindGameNpc(npcId); if ((Object)(object)val != (Object)null && val.isInBuilding) { val.ExitBuilding(""); } } public static void Park(NPC npc, string npcId) { //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_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) //IL_0040: Unknown result type (might be due to invalid IL or missing references) npc.Schedule.Disable(); LeaveBuilding(npcId); npc.IsInvincible = true; Vector3 val = npc.Position - ParkedPosition; if (((Vector3)(ref val)).sqrMagnitude > 1f) { npc.Position = ParkedPosition; } } public static void Revive(NPC npc) { if (npc.IsDead) { npc.Revive(); } npc.Heal((int)npc.MaxHealth); npc.IsInvincible = false; } } internal static class AppearanceUtil { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; public static void ClearLayers(NPCAppearance appearance) { try { object obj = typeof(NPCAppearance).GetField("_customAvatarSettings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(appearance); if (obj != null) { string[] array = new string[3] { "FaceLayerSettings", "BodyLayerSettings", "AccessorySettings" }; foreach (string name in array) { object obj2 = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) ?? obj.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); obj2?.GetType().GetMethod("Clear", Type.EmptyTypes)?.Invoke(obj2, null); } } } catch (Exception ex) { Melon<Core>.Logger.Warning("[quest] clearing old layers failed: " + ex.Message); } } public static void ApplyNow(NPCAppearance appearance) { try { Type typeFromHandle = typeof(NPCAppearance); object obj = typeFromHandle.GetField("_runtimeAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(appearance); MethodInfo method = typeFromHandle.GetMethod("ApplyToAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (obj == null || method == null) { Melon<Core>.Logger.Warning($"[quest] could not apply the look (avatar found: {obj != null}, method found: {method != null})"); return; } method.Invoke(appearance, new object[1] { obj }); } catch (Exception ex) { Melon<Core>.Logger.Warning("[quest] applying the look failed: " + ex.Message); } } public static void Wear<T>(NPCAppearance appearance, Color color, params string[] candidatePaths) where T : BaseAccessoryAppearance { //IL_002f: Unknown result type (might be due to invalid IL or missing references) foreach (string text in candidatePaths) { bool flag = false; try { flag = Resources.Load(text) != (Object)null; } catch { } if (flag) { appearance.WithAccessoryLayer<T>(text, color); break; } Melon<Core>.Logger.Warning("[quest] outfit path not found: " + text); } } } } namespace BetterSpecialCustomers.Patches { [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class BikersQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "diesel_rodd", AskText = "You got any jobs for me?", HandInText = "He's dealt with.", Job = BikersQuest.Job, Lines = new JobLines { OfferLine = (string name) => "Somebody's been feedin' the cops about club business. Name's " + name + ". He's holed up at The Piss Hut. Knock on the door, and make sure he never talks again.", NoJobLines = new string[3] { "Not this time, prospect. Road's quiet. Come find me when it ain't.", "Nothin' right now. The club'll let you know when we need a hand.", "Ain't got nothin' for you today. Go count your money." }, AlreadyDoneLine = "You already did right by us this run. Come back when we roll through again.", HandInLine = "Good. Rats don't get to walk away. The club won't forget this." } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class BusinessmenQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "johnny_smith", AskText = "Anything I can help with?", HandInText = "The audit won't be a problem anymore.", AskSeconds = 8f, Job = AuditQuest.Job, Lines = new JobLines { OfferLine = (string name) => "As it happens, yes. An auditor, " + name + ", has been looking at the books for the wrong reasons. He keeps to a strict routine: home, the office, lunch. Make sure he doesn't finish his report.", NoJobLines = new string[3] { "Nothing at the moment. Business is... stable. Enjoy it.", "Not today. If something comes up, you'll be the first call.", "We're fully staffed on problems right now. Check back later." }, AlreadyDoneLine = "You've done enough for us this time. We'll be in touch.", HandInLine = "Excellent. A tidy solution. Our accountants will be very pleased." } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } internal static class BuyLimitFieldSync { public static void Apply(SpecialCustomerManager manager, string groupId) { } } internal static class DialogueChoiceUtil { public static void Add(DialogueController controller, string text, Func<bool> shouldShow, Action onChosen) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown DialogueChoice val = new DialogueChoice(); val.ChoiceText = text; val.Enabled = true; val.shouldShowCheck = (ShouldShowCheck)((bool enabled) => enabled && shouldShow()); val.onChoosen.AddListener((UnityAction)delegate { onChosen(); }); controller.AddDialogueChoice(val, 0); } } [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class HippiesQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "krystal_bird", AskText = "Can I do anything for the group?", HandInText = "The package has been delivered.", AskSeconds = 8f, Job = HippiesQuest.Job, Lines = new JobLines { OfferLine = (string _) => "Oh, bless you. There's a little something that needs to get to a friend, and we can't be seen with it. I've left it at a drop. Take it to the other one, and be gentle with it.", NoJobLines = new string[3] { "The universe has been quiet today, friend. Come back when it speaks.", "Not right now, sweetheart. Go enjoy the sunshine.", "Nothing this time. Everything is as it should be." }, AlreadyDoneLine = "You've done so much already. We're grateful. Rest a while.", HandInLine = "Wonderful. The garden will be so happy. Please, take these, with our love." } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } internal sealed class JobDialogue { public string LeaderId; public string AskText; public string HandInText; public JobController Job; public JobLines Lines; public float AskSeconds = 7f; public float HandInSeconds = 6f; private SpecialCustomerLeader _leader; private readonly HashSet<int> _added = new HashSet<int>(); public void Register(SpecialCustomerLeader leader) { _leader = leader; DialogueController component = ((Component)((NPC)leader).DialogueHandler).GetComponent<DialogueController>(); if (!((Object)(object)component == (Object)null) && _added.Add(((Object)component).GetInstanceID())) { DialogueChoiceUtil.Add(component, AskText, () => ((NPC)leader).ID == LeaderId && Job.CanAsk(), OnAsk); DialogueChoiceUtil.Add(component, HandInText, () => ((NPC)leader).ID == LeaderId && Job.IsReadyToHandIn(), OnHandIn); MelonLogger.Msg("[quest] added the quest choices for leader '" + LeaderId + "'"); } } private void OnAsk() { if (!((Object)(object)_leader == (Object)null)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; string text = Job.Ask((currentGroupData != null) ? currentGroupData.GroupId : null, Lines); if (text != null) { ((NPC)_leader).DialogueHandler.ShowWorldspaceDialogue(text, AskSeconds); } } } private void OnHandIn() { if ((Object)(object)_leader != (Object)null) { ((NPC)_leader).DialogueHandler.ShowWorldspaceDialogue(Lines.HandInLine, HandInSeconds); } Job.HandIn(); } } [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class PartyBusQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "chad_chaddington", AskText = "Need a hand with anything?", HandInText = "The word is out.", AskSeconds = 8f, Job = PartyBusQuest.Job, Lines = new JobLines { OfferLine = (string _) => "Dude, yes! We need people to know the bus is in town and tonight is ON. " + $"Go tell {5} people. Some will say no. Their loss. Keep going!", NoJobLines = new string[3] { "We're good for now, bro. The vibe is already unreal.", "Nothing right now! Just enjoy the party.", "Not today, my guy. We got it handled." }, AlreadyDoneLine = "You already got the word out, legend! Go have some fun.", HandInLine = "LEGEND! The crowd is gonna be insane. Here, you earned it!" } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } [HarmonyPatch(typeof(DialogueController), "Start")] internal class PartyInvitePatch { private const string InviteText = "The Party Bus is in town tonight. You should come!"; private static readonly string[] YesLines = new string[4] { "A party bus? Count me in!", "Sounds like a good time. I'll be there.", "Finally, something fun around here.", "Oh, nice. I'll swing by." }; private static readonly string[] NoLines = new string[4] { "Not really my scene, sorry.", "I've got stuff to do tonight.", "No thanks, I'm good.", "Parties aren't for me." }; public static void Postfix(DialogueController __instance) { NPC npc = ((Component)__instance).GetComponentInParent<NPC>(); if (!((Object)(object)npc == (Object)null) && IsInvitable(npc)) { DialogueChoiceUtil.Add(__instance, "The Party Bus is in town tonight. You should come!", () => PartyTask.CanInvite(npc.ID), delegate { OnInvite(npc); }); } } private static bool IsInvitable(NPC npc) { if ((Object)(object)((Component)npc).GetComponent<SpecialCustomerLeader>() != (Object)null) { return false; } if ((Object)(object)((Component)npc).GetComponent<SpecialCustomer>() != (Object)null) { return false; } if ((Object)(object)((Component)npc).GetComponent<PoliceOfficer>() != (Object)null) { return false; } return npc.ID != "bikers_informant" && npc.ID != "businessmen_auditor"; } private static void OnInvite(NPC npc) { string[] array = (PartyTask.Invite(npc.ID) ? YesLines : NoLines); npc.DialogueHandler.ShowWorldspaceDialogue(array[Random.Range(0, array.Length)], 5f); } } [HarmonyPatch(typeof(SpecialCustomerData), "GetBuyQuantityForPlayerRank")] public class MaxBuyPatch { public const int NewMaxBuyQuantity = 200; private const bool DebugLogging = false; public static bool Prefix(SpecialCustomerData __instance, ERank rank, ref int __result) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected I4, but got Unknown PatchDiagnostics.Hit("MaxBuyPatch (rank-based buy limit)"); int baseBuyQuantity = __instance.BaseBuyQuantity; int num = 10; int num2 = Math.Max(0, Math.Min((int)rank, num)); __result = baseBuyQuantity + (200 - baseBuyQuantity) * num2 / num; return false; } [HarmonyPriority(0)] public static void Postfix(SpecialCustomerData __instance, ERank rank, int __result) { bool flag = true; } } [HarmonyPatch(typeof(SpecialCustomerData), "GetBuyQuantityForPlayerRank")] public class RelationshipBuyQuantityPatch { public static void Postfix(SpecialCustomerData __instance, ref int __result) { PatchDiagnostics.Hit("RelationshipBuyQuantityPatch (relationship multiplier)"); float multiplier = GroupRelationships.GetMultiplier(__instance.GroupId); __result = Math.Max(1, (int)Math.Round((float)__result * multiplier)); } } [HarmonyPatch(typeof(SpecialCustomerManager), "DealCompleted_Client")] public class RelationshipGainPatch { public static void Postfix(SpecialCustomerManager __instance, SpecialCustomerDealReceipt dealRecipe) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) PatchDiagnostics.Hit("RelationshipGainPatch (sale completed)"); SpecialCustomerData currentGroupData = __instance.CurrentGroupData; SpecialCustomerLeader val = dealRecipe?.Leader; if ((Object)(object)currentGroupData == (Object)null || (Object)(object)val == (Object)null || dealRecipe.Items == null) { return; } int num = 0; foreach (ItemInstance item in dealRecipe.Items) { num += ((BaseItemInstance)item).GetTotalAmount(); } int num2 = val.StartingBuyQuantity; if (num2 <= 0) { num2 = currentGroupData.GetBuyQuantityForPlayerRank(NetworkSingleton<LevelManager>.Instance.Rank); } if (num2 > 0) { GroupRelationships.RegisterSale(currentGroupData.GroupId, num, num2); } } } [HarmonyPatch(typeof(SpecialCustomer), "SetGroupMates")] internal class RelationshipLossPatch { private static readonly HashSet<int> Subscribed = new HashSet<int>(); private static float _lastPunchTime = -100f; private static float _lastKillTime = -100f; private static float _lastAimTime = -100f; private static float _lastPickpocketTime = -100f; public static void Postfix(SpecialCustomer __instance) { PatchDiagnostics.Hit("RelationshipLossPatch (subscribing to NPC reactions)"); if (Subscribed.Add(((Object)__instance).GetInstanceID())) { NPCResponses responses = ((NPC)__instance).Responses; responses.OnNonLethallyAttackedByPlayer = (Action<Player>)Delegate.Combine(responses.OnNonLethallyAttackedByPlayer, new Action<Player>(OnPunched)); responses.OnRepeatedlyNonLethallyAttackedByPlayer = (Action<Player>)Delegate.Combine(responses.OnRepeatedlyNonLethallyAttackedByPlayer, new Action<Player>(OnPunched)); responses.OnLethallyAttackedByPlayer = (Action<Player>)Delegate.Combine(responses.OnLethallyAttackedByPlayer, new Action<Player>(OnKilledSomeone)); responses.OnAimedAtByPlayer = (Action<Player>)Delegate.Combine(responses.OnAimedAtByPlayer, new Action<Player>(OnAimedAt)); responses.OnPickpocketFailed = (Action<Player>)Delegate.Combine(responses.OnPickpocketFailed, new Action<Player>(OnPickpocketFailed)); } } private static void OnPunched(Player attacker) { if (InstanceFinder.IsServer && !(Time.time - _lastPunchTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastPunchTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 0.25f, "punched"); } } } private static void OnKilledSomeone(Player attacker) { if (InstanceFinder.IsServer && !(Time.time - _lastKillTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastKillTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 1f, "customer killed"); } } } private static void OnAimedAt(Player attacker) { if (InstanceFinder.IsServer && !(Time.time - _lastAimTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastAimTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 0.25f, "aimed weapon"); } } } private static void OnPickpocketFailed(Player thief) { if (InstanceFinder.IsServer && !(Time.time - _lastPickpocketTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastPickpocketTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 0.25f, "pickpocket failed"); } } } } [HarmonyPatch(typeof(SpecialCustomerManager), "GetSaveString")] public class RelationshipSavePatch { public static void Postfix(ref string __result) { PatchDiagnostics.Hit("RelationshipSavePatch (writing save)"); try { int num = __result.LastIndexOf('}'); if (num >= 0) { __result = __result.Substring(0, num).TrimEnd() + ",\n " + GroupRelationships.ToJsonProperty() + "\n}"; } } catch (Exception ex) { Melon<Core>.Logger.Error("Failed to add relationships to the save string: " + ex); } } } [HarmonyPatch(typeof(SpecialCustomerLoader), "Load")] public class RelationshipLoadPatch { public static void Prefix(string mainPath) { PatchDiagnostics.Hit("RelationshipLoadPatch (reading save)"); try { string path = mainPath + ".json"; GroupRelationships.LoadFromJson(File.Exists(path) ? File.ReadAllText(path) : null); } catch (Exception ex) { GroupRelationships.Clear(); Melon<Core>.Logger.Error("Failed to load relationships: " + ex); } } } [HarmonyPatch(typeof(SpecialCustomerManager), "RunArrivalPhase")] public class RelationshipArrivalPatch { private static readonly FieldInfo CurrentDataField = AccessTools.Field(typeof(SpecialCustomerManager), "_currentData"); private static readonly PropertyInfo CurrentDataProperty = AccessTools.Property(typeof(SpecialCustomerManager), "_currentData"); private static bool _reportedMissing; private static string GetArrivingGroupId(SpecialCustomerManager manager) { object obj = ((CurrentDataField != null) ? CurrentDataField.GetValue(manager) : CurrentDataProperty?.GetValue(manager)); if (obj == null && CurrentDataField == null && CurrentDataProperty == null && !_reportedMissing) { _reportedMissing = true; Melon<Core>.Logger.Error("RelationshipArrivalPatch: can't find SpecialCustomerManager._currentData as a field or property; visits will NOT be tracked. See the [diag] Probe lines."); } return ((SpecialCustomerSaveData)(((obj is SpecialCustomerSaveData) ? obj : null)?)).GroupId; } public static void Prefix(SpecialCustomerManager __instance) { PatchDiagnostics.Hit("RelationshipArrivalPatch (group arriving)"); string arrivingGroupId = GetArrivingGroupId(__instance); if (!string.IsNullOrEmpty(arrivingGroupId)) { GroupRelationships.BeginVisit(arrivingGroupId); BuyLimitFieldSync.Apply(__instance, arrivingGroupId); } } } [HarmonyPatch(typeof(SpecialCustomerManager), "RemoveCustomerGroup")] public class RelationshipDeparturePatch { public static void Prefix(SpecialCustomerManager __instance) { PatchDiagnostics.Hit("RelationshipDeparturePatch (group leaving)"); SpecialCustomerData currentGroupData = __instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { GroupRelationships.EndVisit(currentGroupData.GroupId); JobController.OnGroupLeft(currentGroupData.GroupId); } } } } namespace BetterSpecialCustomers.Diagnostics { public static class PatchDiagnostics { public static readonly bool Enabled = false; private static readonly HashSet<string> Seen = new HashSet<string>(); public static void Hit(string patchName) { if (Enabled && Seen.Add(patchName)) { Melon<Core>.Logger.Msg("[diag] first call: " + patchName + " (the game DID call this patch)"); } } public static void PatchAllClasses(Harmony harmony) { IEnumerable<Type> enumerable = from t in typeof(Core).Assembly.GetTypes() where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0 orderby t.Name select t; int num = 0; int num2 = 0; foreach (Type item in enumerable) { try { harmony.CreateClassProcessor(item).Patch(); num++; if (Enabled) { Melon<Core>.Logger.Msg("[diag] Patch OK: " + item.Name); } } catch (Exception ex) { num2++; Exception ex2 = ex; while (ex2.InnerException != null) { ex2 = ex2.InnerException; } Melon<Core>.Logger.Error("[diag] Patch FAILED: " + item.Name + " -> " + ex2.GetType().Name + ": " + ex2.Message); } } Melon<Core>.Logger.Msg($"[diag] Patches applied: {num} ok, {num2} failed."); } public static void Probe() { if (Enabled) { Melon<Core>.Logger.Msg(string.Format("[diag] Build: {0}. Harmony {1}", "MONO", typeof(Harmony).Assembly.GetName().Version)); ProbeMethod(typeof(SpecialCustomerData), "GetBuyQuantityForPlayerRank"); ProbeMethod(typeof(SpecialCustomerManager), "DealCompleted_Client"); ProbeMethod(typeof(SpecialCustomerManager), "GetSaveString"); ProbeMethod(typeof(SpecialCustomerManager), "RunArrivalPhase"); ProbeMethod(typeof(SpecialCustomerManager), "RemoveCustomerGroup"); ProbeMethod(typeof(SpecialCustomerLoader), "Load"); ProbeMembers(typeof(SpecialCustomerManager), "currentData"); ProbeMembers(typeof(SpecialCustomerData), "baseBuyQuantity"); ProbeMembers(typeof(SpecialCustomerData), "additionalBuyQuantityPerRank"); ProbeMembers(typeof(SpecialCustomerData), "maxBuyQuantity"); } } private static void ProbeMembers(Type type, string namePart) { MemberInfo[] array = (from m in type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name.IndexOf(namePart, StringComparison.OrdinalIgnoreCase) >= 0 select m).ToArray(); if (array.Length == 0) { Melon<Core>.Logger.Warning("[diag] Probe: " + type.Name + " has NO member containing '" + namePart + "'."); } MemberInfo[] array2 = array; foreach (MemberInfo memberInfo in array2) { Melon<Core>.Logger.Msg($"[diag] Probe: {type.Name}.{memberInfo.Name} is a {memberInfo.MemberType}"); } } private static void ProbeMethod(Type type, string name) { MethodBase methodBase = AccessTools.Method(type, name, (Type[])null, (Type[])null); if (methodBase == null) { Melon<Core>.Logger.Warning("[diag] Probe: " + type.Name + "." + name + " NOT FOUND"); } else { Melon<Core>.Logger.Msg("[diag] Probe: " + type.Name + "." + name + " found (" + (methodBase.IsPublic ? "public" : "non-public") + ")"); } } public static void ListPatchedMethods(Harmony harmony) { if (!Enabled) { return; } foreach (MethodBase patchedMethod in harmony.GetPatchedMethods()) { Melon<Core>.Logger.Msg("[diag] Harmony reports patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }
BetterSpecialCustomers_IL2Cpp.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using BetterSpecialCustomers; using BetterSpecialCustomers.Diagnostics; using BetterSpecialCustomers.Quests; using BetterSpecialCustomers.Relationships; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Dialogue; using Il2CppScheduleOne.Doors; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Levelling; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Relation; using Il2CppScheduleOne.NPCs.Responses; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Police; using Il2CppScheduleOne.SpecialCustomers; using Il2CppScheduleOne.Storage; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.IO; using MelonLoader; using Microsoft.CodeAnalysis; using S1API.Entities; using S1API.Entities.Appearances.AccessoryFields; using S1API.Entities.Appearances.Base; using S1API.Entities.Appearances.BodyLayerFields; using S1API.Entities.Appearances.CustomizationFields; using S1API.Entities.Appearances.FaceLayerFields; using S1API.Entities.Schedule; using S1API.Internal.Abstraction; using S1API.Leveling; using S1API.Map; using S1API.Map.Buildings; using S1API.Money; using S1API.Quests; using S1API.Saveables; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "BetterSpecialCustomers", "1.0.0", "Lumahex", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: HarmonyDontPatchAll] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("BetterSpecialCustomers_IL2Cpp")] [assembly: AssemblyConfiguration("IL2CPP")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+c7e4d29411e07561897b504f24ae00b062ef3bf0")] [assembly: AssemblyProduct("BetterSpecialCustomers_IL2Cpp")] [assembly: AssemblyTitle("BetterSpecialCustomers_IL2Cpp")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 MONO_IL2CPP_Template.TemplateUtils { public static class AssetBundleUtils { private static Core mod = MelonAssembly.FindMelonInstance<Core>(); private static MelonAssembly melonAssembly = ((MelonBase)mod).MelonAssembly; public static Il2CppAssetBundle LoadAssetBundle(string bundleFileName) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown try { string text = typeof(Core).Namespace + ".Assets." + bundleFileName; Stream manifestResourceStream = melonAssembly.Assembly.GetManifestResourceStream(text ?? ""); if (manifestResourceStream == null) { ((MelonBase)mod).Unregister($"AssetBundle: '{text}' not found. \nOpen .csproj file and search for '{bundleFileName}'.\nIf it doesn't exist,\nCopy your asset to Assets/ folder then look for 'your.assetbundle' in .csproj file.", false); return null; } byte[] array; using (MemoryStream memoryStream = new MemoryStream()) { manifestResourceStream.CopyTo(memoryStream); array = memoryStream.ToArray(); } Stream val = (Stream)new MemoryStream(Il2CppStructArray<byte>.op_Implicit(array)); return Il2CppAssetBundleManager.LoadFromStream(val); } catch (Exception value) { ((MelonBase)mod).Unregister($"Failed to load AssetBundle. Please report to dev: {value}", false); return null; } } public static Il2CppAssetBundle GetLoadedAssetBundle(string asset_name_flag) { Il2CppAssetBundle[] allLoadedAssetBundles = Il2CppAssetBundleManager.GetAllLoadedAssetBundles(); try { Il2CppAssetBundle[] array = allLoadedAssetBundles; foreach (Il2CppAssetBundle val in array) { if (val.Contains(asset_name_flag)) { return val; } } string text = ""; Il2CppAssetBundle[] array2 = allLoadedAssetBundles; foreach (Il2CppAssetBundle val2 in array2) { string[] array3 = Il2CppArrayBase<string>.op_Implicit((Il2CppArrayBase<string>)(object)val2.GetAllAssetNames()); string text2 = string.Join("\n\r -", array3); text = text + ((object)val2)?.ToString() + $"({array3.Length} assets):" + text2; } throw new Exception($"Asset '{asset_name_flag}' not found in {allLoadedAssetBundles.Length} bundle(s).\n{text}"); } catch (Exception value) { ((MelonBase)mod).Unregister($"Failed to get loaded AssetBundle. Please report to dev: \n{value}", false); return null; } } public static GameObject LoadAssetFromBundle(string asset_name) { Il2CppAssetBundle loadedAssetBundle = GetLoadedAssetBundle(asset_name); return loadedAssetBundle.LoadAsset<GameObject>(asset_name); } } } namespace BetterSpecialCustomers { public class Core : MelonMod { private float _nextQuestTick; public override void OnInitializeMelon() { ((MelonBase)this).LoggerInstance.Msg("Initialized."); PatchDiagnostics.Probe(); PatchDiagnostics.PatchAllClasses(((MelonBase)this).HarmonyInstance); PatchDiagnostics.ListPatchedMethods(((MelonBase)this).HarmonyInstance); } public override void OnUpdate() { if (Time.unscaledTime >= _nextQuestTick) { _nextQuestTick = Time.unscaledTime + 1f; HippiesTask.Tick(); } } } } namespace BetterSpecialCustomers.Relationships { public static class GroupRelationships { private class State { public float Current = 2.5f; public float Applied = 2.5f; public bool InVisit; public bool GainedThisVisit; public int SoldThisVisit; } public const float Min = 0f; public const float Max = 5f; public const float DefaultValue = 2.5f; public const float MultiplierAtMin = 0.5f; public const float MultiplierAtMax = 1.5f; public const float GainPerVisit = 0.5f; public const float MinFractionOfLimitForGain = 0.75f; private static readonly Dictionary<string, State> States = new Dictionary<string, State>(); public const string JsonKey = "BetterSpecialCustomers_Relationships"; private static readonly Regex SectionRegex = new Regex("\"BetterSpecialCustomers_Relationships\"\\s*:\\s*\\[(.*?)\\]", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex EntryRegex = new Regex("\\{([^}]*)\\}", RegexOptions.Compiled); private static string Key(string groupId) { return (groupId ?? string.Empty).ToLowerInvariant(); } private static State GetState(string groupId) { string key = Key(groupId); if (!States.TryGetValue(key, out var value)) { value = new State(); States[key] = value; } return value; } private static float Clamp(float value) { return Math.Max(0f, Math.Min(5f, value)); } public static float GetCurrent(string groupId) { return GetState(groupId).Current; } public static float GetApplied(string groupId) { return GetState(groupId).Applied; } public static ERelationshipCategory GetCategory(float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return RelationshipCategory.GetCategory(value); } public static float GetMultiplier(string groupId) { float num = (GetApplied(groupId) - 0f) / 5f; return 0.5f + 1f * num; } public static void BeginVisit(string groupId) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) State state = GetState(groupId); if (!state.InVisit) { state.InVisit = true; state.GainedThisVisit = false; state.SoldThisVisit = 0; state.Applied = state.Current; Melon<Core>.Logger.Msg($"[{groupId}] new visit. Relationship {state.Applied:0.00} ({GetCategory(state.Applied)}) now applies: buy multiplier x{GetMultiplier(groupId):0.00}"); } } public static void EndVisit(string groupId) { GetState(groupId).InVisit = false; } public static void RegisterSale(string groupId, int unitsSold, int limit) { //IL_0192: Unknown result type (might be due to invalid IL or missing references) State state = GetState(groupId); if (!state.GainedThisVisit && limit > 0) { state.SoldThisVisit += unitsSold; float num = (float)state.SoldThisVisit / (float)limit; if (num < 0.75f) { Melon<Core>.Logger.Msg($"[{groupId}] sold {state.SoldThisVisit} so far this visit ({num:P0} of their limit of {limit}); need {0.75f:P0} for a relationship gain"); } else { state.GainedThisVisit = true; float current = state.Current; state.Current = Clamp(current + 0.5f); Melon<Core>.Logger.Msg($"[{groupId}] relationship {current:0.00} -> {state.Current:0.00} ({GetCategory(state.Current)}). It applies from their next visit; this visit stays at x{GetMultiplier(groupId):0.00}"); } } } public static void Penalize(string groupId, float amount, string reason) { State state = GetState(groupId); float current = state.Current; state.Current = Clamp(current - amount); Melon<Core>.Logger.Msg($"[{groupId}] relationship {current:0.00} -> {state.Current:0.00} ({reason})"); } public static void Reward(string groupId, float amount, string reason) { State state = GetState(groupId); float current = state.Current; state.Current = Clamp(current + amount); Melon<Core>.Logger.Msg($"[{groupId}] relationship {current:0.00} -> {state.Current:0.00} ({reason})"); } private static string ReadField(string entry, string name) { Match match = Regex.Match(entry, "\"" + name + "\"\\s*:\\s*\"?([^\",\\s}]+)"); return match.Success ? match.Groups[1].Value : null; } private static float ReadFloat(string entry, string name, float fallback) { string text = ReadField(entry, name); float result; return (text != null && float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) ? Clamp(result) : fallback; } public static void Clear() { States.Clear(); } public static string ToJsonProperty() { List<string> list = new List<string>(); foreach (KeyValuePair<string, State> state in States) { State value = state.Value; list.Add(string.Format(CultureInfo.InvariantCulture, "{{ \"id\": \"{0}\", \"current\": {1}, \"applied\": {2}, \"inVisit\": {3}, \"gained\": {4}, \"sold\": {5} }}", state.Key, value.Current.ToString("R", CultureInfo.InvariantCulture), value.Applied.ToString("R", CultureInfo.InvariantCulture), value.InVisit ? 1 : 0, value.GainedThisVisit ? 1 : 0, value.SoldThisVisit)); } return $"\"{"BetterSpecialCustomers_Relationships"}\": [ {string.Join(", ", list)} ]"; } public static void LoadFromJson(string json) { States.Clear(); if (string.IsNullOrEmpty(json)) { return; } Match match = SectionRegex.Match(json); if (!match.Success) { return; } foreach (Match item in EntryRegex.Matches(match.Groups[1].Value)) { string value = item.Groups[1].Value; string text = ReadField(value, "id"); if (!string.IsNullOrEmpty(text)) { State state = GetState(text); state.Current = ReadFloat(value, "current", 2.5f); state.Applied = ReadFloat(value, "applied", state.Current); state.InVisit = ReadField(value, "inVisit") == "1"; state.GainedThisVisit = ReadField(value, "gained") == "1"; state.SoldThisVisit = (int.TryParse(ReadField(value, "sold"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : 0); } } } } } namespace BetterSpecialCustomers.Quests { public class BikersQuestSave : Saveable { [SaveableField("bikers_quest")] public JobSaveData Data = new JobSaveData(); public static BikersQuestSave Instance { get; private set; } public BikersQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; Informant.Refresh(); } } public sealed class Informant : NPC { private const string NpcId = "bikers_informant"; private const string HideoutName = "The Piss Hut"; private static Informant _instance; private static int _lookAppliedForRun = -1; private static readonly string[] FirstNames = new string[12] { "Dale", "Rusty", "Vince", "Eddie", "Skeet", "Lenny", "Wade", "Cody", "Marv", "Gus", "Terry", "Bo" }; private static readonly string[] LastNames = new string[12] { "Hollis", "Brandt", "Keane", "Mercer", "Voss", "Pruitt", "Lund", "Garrity", "Boone", "Sykes", "Dunn", "Rourke" }; private static readonly Color32[] HairColors = (Color32[])(object)new Color32[5] { new Color32((byte)30, (byte)22, (byte)18, byte.MaxValue), new Color32((byte)60, (byte)40, (byte)28, byte.MaxValue), new Color32((byte)95, (byte)65, (byte)40, byte.MaxValue), new Color32((byte)150, (byte)120, (byte)80, byte.MaxValue), new Color32((byte)120, (byte)120, (byte)120, byte.MaxValue) }; private static readonly string[] HairStyles = new string[7] { "Avatar/Hair/buzzcut/BuzzCut", "Avatar/Hair/closebuzzcut/CloseBuzzCut", "Avatar/Hair/balding/Balding", "Avatar/Hair/receding/Receding", "Avatar/Hair/longslicked/LongSlicked", "Avatar/Hair/shoulderlength/ShoulderLength", "Avatar/Hair/peaked/Peaked" }; private static readonly Color32[] ShirtColors = (Color32[])(object)new Color32[4] { new Color32((byte)25, (byte)25, (byte)25, byte.MaxValue), new Color32((byte)60, (byte)60, (byte)60, byte.MaxValue), new Color32((byte)110, (byte)25, (byte)25, byte.MaxValue), new Color32((byte)90, (byte)90, (byte)90, byte.MaxValue) }; private static readonly Color32[] JeansColors = (Color32[])(object)new Color32[3] { new Color32((byte)30, (byte)38, (byte)60, byte.MaxValue), new Color32((byte)20, (byte)20, (byte)24, byte.MaxValue), new Color32((byte)55, (byte)65, (byte)90, byte.MaxValue) }; public override bool IsPhysical => true; public static string PickName() { return QuestNpcUtil.RandomName(FirstNames, LastNames); } protected override void ConfigurePrefab(NPCPrefabBuilder builder) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) builder.WithIdentity("bikers_informant", "Informant", string.Empty).WithSpawnPosition(QuestNpcUtil.ParkedPosition).WithSchedule((Action<PrefabScheduleBuilder>)delegate(PrefabScheduleBuilder plan) { plan.StayInBuilding(Building.Get<ThePissHut>(), 0, 1439, (int?)null, (string)null); }); } protected override void OnCreated() { ((NPC)this).OnCreated(); ((NPC)this).Appearance.Build(); _instance = this; Melon<Core>.Logger.Msg("[quest] the informant NPC was created"); ((NPC)this).OnDeath += BikersQuest.Job.OnObjectiveDone; Refresh(); } public static void Refresh() { if (_instance == null) { return; } JobSaveData jobSaveData = BikersQuestSave.Instance?.Data; bool flag = jobSaveData != null && jobSaveData.Accepted && !jobSaveData.TargetKilled; try { if (flag) { _instance.Deploy(jobSaveData); } else { QuestNpcUtil.Park((NPC)(object)_instance, "bikers_informant"); } } catch (Exception value) { Melon<Core>.Logger.Error($"[quest] informant {(flag ? "deploy" : "park")} failed: {value}"); } } private void Deploy(JobSaveData save) { QuestNpcUtil.ApplyName((NPC)(object)this, save.TargetName); if (_lookAppliedForRun != save.Runs) { _lookAppliedForRun = save.Runs; ApplyBikerLook(); } QuestNpcUtil.Revive((NPC)(object)this); QuestNpcUtil.EnterBuilding("bikers_informant", "The Piss Hut"); ((NPC)this).Schedule.Enable(); } private void ApplyBikerLook() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: 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_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Unknown result type (might be due to invalid IL or missing references) AppearanceUtil.ClearLayers(((NPC)this).Appearance); Color val = Color32.op_Implicit(QuestNpcUtil.Pick(HairColors)); ((NPC)this).Appearance.Set<Gender>((object)0f); ((NPC)this).Appearance.Set<Height>((object)Random.Range(0.95f, 1.05f)); ((NPC)this).Appearance.Set<Weight>((object)Random.Range(0.45f, 0.75f)); ((NPC)this).Appearance.Set<SkinColor>((object)Color32.op_Implicit(QuestNpcUtil.Pick(QuestNpcUtil.SkinTones))); ((NPC)this).Appearance.Set<EyeBallTint>((object)Color.white); ((NPC)this).Appearance.Set<HairColor>((object)val); ((NPC)this).Appearance.Set<HairStyle>((object)QuestNpcUtil.Pick(HairStyles)); Color val2 = default(Color); ((Color)(ref val2))..ctor(0.15f, 0.1f, 0.1f); ((NPC)this).Appearance.WithFaceLayer<Face>((Random.value < 0.5f) ? "Avatar/Layers/Face/Face_Neutral" : "Avatar/Layers/Face/Face_SlightFrown", val2); if (Random.value < 0.6f) { ((NPC)this).Appearance.WithFaceLayer<FacialHair>((Random.value < 0.5f) ? "Avatar/Layers/Face/FacialHair_Stubble" : "Avatar/Layers/Face/FacialHair_Goatee", val); } string[] items = new string[5] { "Avatar/Layers/Top/T-Shirt", "Avatar/Layers/Top/T-Shirt", "Avatar/Layers/Top/V-Neck", "Avatar/Layers/Top/FlannelButtonUp", "Avatar/Layers/Top/RolledButtonUp" }; ((NPC)this).Appearance.WithBodyLayer<Shirts>(QuestNpcUtil.Pick(items), Color32.op_Implicit(QuestNpcUtil.Pick(ShirtColors))); ((NPC)this).Appearance.WithBodyLayer<Pants>((Random.value < 0.7f) ? "Avatar/Layers/Bottom/Jeans" : "Avatar/Layers/Bottom/CargoPants", Color32.op_Implicit(QuestNpcUtil.Pick(JeansColors))); Color color = ((Random.value < 0.7f) ? new Color(0.08f, 0.08f, 0.08f) : new Color(0.2f, 0.12f, 0.07f)); float value = Random.value; if (value < 0.6f) { AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, color, new string[2] { "Avatar/Accessories/Chest/BikerJacket/BikerJacket", "Avatar/Accessories/Chest/BikerJacket/Biker Jacket" }); } else if (value < 0.85f) { AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, color, new string[2] { "Avatar/Accessories/Chest/LeatherJacket/LeatherJacket", "Avatar/Accessories/Chest/LeatherJacket/leatherJacket" }); } else { AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, color, new string[1] { "Avatar/Accessories/Chest/OpenVest/OpenVest" }); } AppearanceUtil.Wear<Feet>(((NPC)this).Appearance, new Color(0.1f, 0.1f, 0.1f), new string[1] { (Random.value < 0.85f) ? "Avatar/Accessories/Feet/CombatBoots/CombatBoots" : "Avatar/Accessories/Feet/Sneakers/Sneakers" }); if (Random.value < 0.5f) { AppearanceUtil.Wear<Waist>(((NPC)this).Appearance, new Color(0.1f, 0.07f, 0.05f), new string[1] { "Avatar/Accessories/Waist/Belt/Belt" }); } float value2 = Random.value; if (value2 < 0.3f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, new Color(0.1f, 0.1f, 0.1f), new string[2] { "Avatar/Accessories/Head/Bandana/Bandana", "Avatar/Accessories/Head/PaisleyBandana/PaisleyBandana" }); } else if (value2 < 0.5f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, new Color(0.12f, 0.12f, 0.12f), new string[1] { "Avatar/Accessories/Head/Beanie/Beanie" }); } else if (value2 < 0.7f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Head/Oakleys/Oakleys" }); } if (Random.value < 0.4f) { AppearanceUtil.Wear<Hands>(((NPC)this).Appearance, new Color(0.1f, 0.1f, 0.1f), new string[1] { "Avatar/Accessories/Hands/FingerlessGloves/FingerlessGloves" }); } if (Random.value < 0.4f) { AppearanceUtil.Wear<Neck>(((NPC)this).Appearance, Color.white, new string[2] { "Avatar/Accessories/Neck/GoldChain/GoldChain", "Avatar/Accessories/Neck/SilverChain/SilverChain" }); } AppearanceUtil.ApplyNow(((NPC)this).Appearance); } } public class BikersQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "bikers_informant_quest", RewardLogLabel = "Diesel's informant job", GetSave = () => BikersQuestSave.Instance?.Data, PickName = Informant.PickName, RefreshTarget = Informant.Refresh, CreateQuest = (string id) => QuestManager.CreateQuest<BikersQuest>(id) }); protected override string Title => "Loose Lips"; protected override string Description => "Someone in the club has been talking to the cops. Diesel Rodd wants the informant dealt with."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); string text = Job.TargetNameOr("the informant"); QuestEntry val = ((Quest)this).AddEntry("Knock on the door of The Piss Hut and kill " + text, (Vector3?)null); val.SetPOIToNPC<Informant>(); ((Quest)this).AddEntry("Tell Diesel " + text + " is dealt with", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class BusinessmenQuestSave : Saveable { [SaveableField("businessmen_quest")] public JobSaveData Data = new JobSaveData(); public static BusinessmenQuestSave Instance { get; private set; } public BusinessmenQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; Auditor.Refresh(); } } public sealed class Auditor : NPC { private const string NpcId = "businessmen_auditor"; private const string HomeName = "Upscale Apartments"; private static Auditor _instance; private static int _lookAppliedForRun = -1; private static readonly string[] FirstNames = new string[10] { "Gordon", "Nigel", "Howard", "Clive", "Warren", "Preston", "Desmond", "Edwin", "Leonard", "Franklin" }; private static readonly string[] LastNames = new string[10] { "Whitlock", "Ashcroft", "Pembroke", "Thorne", "Hargrove", "Fairbanks", "Lockhart", "Sterling", "Winslow", "Crane" }; private static readonly Color32[] HairColors = (Color32[])(object)new Color32[5] { new Color32((byte)30, (byte)22, (byte)18, byte.MaxValue), new Color32((byte)60, (byte)40, (byte)28, byte.MaxValue), new Color32((byte)95, (byte)65, (byte)40, byte.MaxValue), new Color32((byte)120, (byte)120, (byte)120, byte.MaxValue), new Color32((byte)190, (byte)190, (byte)190, byte.MaxValue) }; private static readonly string[] HairStyles = new string[6] { "Avatar/Hair/closebuzzcut/CloseBuzzCut", "Avatar/Hair/peaked/Peaked", "Avatar/Hair/receding/Receding", "Avatar/Hair/balding/Balding", "Avatar/Hair/midfringe/MidFringe", "Avatar/Hair/sidepartbob/SidePartBob" }; private static readonly Color32[] ShirtColors = (Color32[])(object)new Color32[3] { new Color32((byte)235, (byte)235, (byte)235, byte.MaxValue), new Color32((byte)180, (byte)200, (byte)225, byte.MaxValue), new Color32((byte)210, (byte)210, (byte)200, byte.MaxValue) }; private static readonly Color32[] SuitColors = (Color32[])(object)new Color32[4] { new Color32((byte)25, (byte)28, (byte)40, byte.MaxValue), new Color32((byte)45, (byte)45, (byte)50, byte.MaxValue), new Color32((byte)20, (byte)20, (byte)20, byte.MaxValue), new Color32((byte)55, (byte)45, (byte)38, byte.MaxValue) }; public override bool IsPhysical => true; public static string PickName() { return QuestNpcUtil.RandomName(FirstNames, LastNames); } protected override void ConfigurePrefab(NPCPrefabBuilder builder) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) builder.WithIdentity("businessmen_auditor", "Auditor", string.Empty).WithSpawnPosition(QuestNpcUtil.ParkedPosition).WithSchedule((Action<PrefabScheduleBuilder>)delegate(PrefabScheduleBuilder plan) { Building val = Building.Get<UpscaleApartments>(); Building val2 = Building.Get<TallTower>(); Building val3 = Building.Get<Cafe>(); Building val4 = Building.Get<ChineseRestaurant>(); plan.StayInBuilding(val, 0, 450, (int?)null, (string)null); plan.StayInBuilding(val2, 800, 240, (int?)null, (string)null); plan.StayInBuilding(val3, 1215, 60, (int?)null, (string)null); plan.StayInBuilding(val2, 1330, 210, (int?)null, (string)null); plan.StayInBuilding(val4, 1800, 120, (int?)null, (string)null); plan.StayInBuilding(val, 2100, 179, (int?)null, (string)null); }); } protected override void OnCreated() { ((NPC)this).OnCreated(); ((NPC)this).Appearance.Build(); _instance = this; Melon<Core>.Logger.Msg("[quest] the auditor NPC was created"); ((NPC)this).OnDeath += AuditQuest.Job.OnObjectiveDone; Refresh(); } public static void Refresh() { if (_instance == null) { return; } JobSaveData jobSaveData = BusinessmenQuestSave.Instance?.Data; bool flag = jobSaveData != null && jobSaveData.Accepted && !jobSaveData.TargetKilled; try { if (flag) { _instance.Deploy(jobSaveData); } else { QuestNpcUtil.Park((NPC)(object)_instance, "businessmen_auditor"); } } catch (Exception value) { Melon<Core>.Logger.Error($"[quest] auditor {(flag ? "deploy" : "park")} failed: {value}"); } } private void Deploy(JobSaveData save) { QuestNpcUtil.ApplyName((NPC)(object)this, save.TargetName); if (_lookAppliedForRun != save.Runs) { _lookAppliedForRun = save.Runs; ApplyBusinessLook(); } QuestNpcUtil.Revive((NPC)(object)this); QuestNpcUtil.EnterBuilding("businessmen_auditor", "Upscale Apartments"); ((NPC)this).Schedule.Enable(); ((NPC)this).Schedule.EnforceState(); } private void ApplyBusinessLook() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: 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_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: 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_023b: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) AppearanceUtil.ClearLayers(((NPC)this).Appearance); Color val = Color32.op_Implicit(QuestNpcUtil.Pick(HairColors)); ((NPC)this).Appearance.Set<Gender>((object)0f); ((NPC)this).Appearance.Set<Height>((object)Random.Range(0.95f, 1.05f)); ((NPC)this).Appearance.Set<Weight>((object)Random.Range(0.35f, 0.65f)); ((NPC)this).Appearance.Set<SkinColor>((object)Color32.op_Implicit(QuestNpcUtil.Pick(QuestNpcUtil.SkinTones))); ((NPC)this).Appearance.Set<EyeBallTint>((object)Color.white); ((NPC)this).Appearance.Set<HairColor>((object)val); ((NPC)this).Appearance.Set<HairStyle>((object)QuestNpcUtil.Pick(HairStyles)); ((NPC)this).Appearance.WithFaceLayer<Face>((Random.value < 0.6f) ? "Avatar/Layers/Face/Face_Neutral" : "Avatar/Layers/Face/Face_SlightFrown", new Color(0.15f, 0.1f, 0.1f)); if (Random.value < 0.25f) { ((NPC)this).Appearance.WithFaceLayer<FacialHair>("Avatar/Layers/Face/FacialHair_Stubble", val); } Color val2 = Color32.op_Implicit(QuestNpcUtil.Pick(SuitColors)); ((NPC)this).Appearance.WithBodyLayer<Shirts>((Random.value < 0.8f) ? "Avatar/Layers/Top/Buttonup" : "Avatar/Layers/Top/RolledButtonUp", Color32.op_Implicit(QuestNpcUtil.Pick(ShirtColors))); ((NPC)this).Appearance.WithBodyLayer<Pants>("Avatar/Layers/Bottom/Jeans", val2); AppearanceUtil.Wear<Chest>(((NPC)this).Appearance, val2, new string[1] { "Avatar/Accessories/Chest/Blazer/Blazer" }); AppearanceUtil.Wear<Feet>(((NPC)this).Appearance, new Color(0.08f, 0.06f, 0.05f), new string[1] { "Avatar/Accessories/Feet/DressShoes/DressShoes" }); string[] items = new string[4] { "Avatar/Accessories/Neck/StripedTie/StripedTie", "Avatar/Accessories/Neck/CheckedTie/CheckedTie", "Avatar/Accessories/Neck/DottedTie/DottedTie", "Avatar/Accessories/Neck/CrossTie/CrossTie" }; AppearanceUtil.Wear<Neck>(((NPC)this).Appearance, new Color(0.35f, 0.08f, 0.1f), new string[1] { QuestNpcUtil.Pick(items) }); float value = Random.value; if (value < 0.35f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Head/RectangleFrameGlasses/RectangleFrameGlasses" }); } else if (value < 0.5f) { AppearanceUtil.Wear<Head>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Head/SmallRoundGlasses/SmallRoundGlasses" }); } if (Random.value < 0.5f) { AppearanceUtil.Wear<Hands>(((NPC)this).Appearance, Color.white, new string[1] { "Avatar/Accessories/Hands/Polex/Polex" }); } AppearanceUtil.ApplyNow(((NPC)this).Appearance); } } public class AuditQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "businessmen_audit_quest", RewardLogLabel = "Johnny's audit job", GetSave = () => BusinessmenQuestSave.Instance?.Data, PickName = Auditor.PickName, RefreshTarget = Auditor.Refresh, CreateQuest = (string id) => QuestManager.CreateQuest<AuditQuest>(id) }); protected override string Title => "The Audit"; protected override string Description => "An auditor is getting too close to the books. Johnny Smith wants him dealt with."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); string text = Job.TargetNameOr("the auditor"); QuestEntry val = ((Quest)this).AddEntry("Find and kill " + text + ". He keeps a strict daily routine", (Vector3?)null); val.SetPOIToNPC<Auditor>(); ((Quest)this).AddEntry("Tell Johnny Smith " + text + " is dealt with", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class DropRun { public string FromGuid = string.Empty; public string ToGuid = string.Empty; public string FromName = string.Empty; public string ToName = string.Empty; public bool Placed; public bool PickedUp; } public class HippiesQuestSave : Saveable { [SaveableField("hippies_quest")] public JobSaveData Data = new JobSaveData(); [SaveableField("hippies_quest_drops")] public DropRun Drops = new DropRun(); public static HippiesQuestSave Instance { get; private set; } public HippiesQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; } } internal static class HippiesTask { private const string PackageItemId = "paisleybandana"; private static readonly KeyValuePair<string, int>[] Supplies = new KeyValuePair<string, int>[4] { new KeyValuePair<string, int>("extralonglifesoil", 3), new KeyValuePair<string, int>("speedgrow", 3), new KeyValuePair<string, int>("pgr", 1), new KeyValuePair<string, int>("fertilizer", 1) }; private const float MinDropDistance = 80f; private static HippiesQuestSave Save => HippiesQuestSave.Instance; private static DeadDrop FindDrop(string guid) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(guid)) { return null; } Enumerator<DeadDrop> enumerator = DeadDrop.DeadDrops.GetEnumerator(); while (enumerator.MoveNext()) { DeadDrop current = enumerator.Current; if ((Object)(object)current != (Object)null && string.Equals(((object)current.GUID/*cast due to .constrained prefix*/).ToString(), guid, StringComparison.OrdinalIgnoreCase)) { return current; } } return null; } public static Vector3? PositionOf(string guid) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) DeadDrop val = FindDrop(guid); return ((Object)(object)val != (Object)null) ? new Vector3?(((Component)val).transform.position) : ((Vector3?)null); } private static int Count(DeadDrop drop, string itemId) { int num = 0; Enumerator<ItemSlot> enumerator = ((StorageEntity)drop.Storage).ItemSlots.GetEnumerator(); while (enumerator.MoveNext()) { ItemSlot current = enumerator.Current; ItemInstance itemInstance = current.ItemInstance; if (itemInstance != null && ((BaseItemDefinition)itemInstance.Definition).ID == itemId) { num += ((BaseItemInstance)itemInstance).Quantity; } } return num; } private static void RemoveAll(DeadDrop drop, string itemId) { Enumerator<ItemSlot> enumerator = ((StorageEntity)drop.Storage).ItemSlots.GetEnumerator(); while (enumerator.MoveNext()) { ItemSlot current = enumerator.Current; ItemInstance itemInstance = current.ItemInstance; if (itemInstance != null && ((BaseItemDefinition)itemInstance.Definition).ID == itemId) { current.ClearStoredInstance(false); } } } public static void Refresh() { HippiesQuestSave save = Save; if (save == null) { return; } if (save.Data.Accepted && !save.Data.TargetKilled) { if (!save.Drops.Placed) { ChooseAndPlace(save.Drops); } } else { Cleanup(save); } } private static void ChooseAndPlace(DropRun run) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) List<DeadDrop> list = new List<DeadDrop>(); Enumerator<DeadDrop> enumerator = DeadDrop.DeadDrops.GetEnumerator(); while (enumerator.MoveNext()) { DeadDrop current = enumerator.Current; if ((Object)(object)current != (Object)null && ((StorageEntity)current.Storage).ItemCount == 0) { list.Add(current); } } if (list.Count < 2) { Melon<Core>.Logger.Warning("[quest] not enough empty dead drops for the hippies' job"); return; } DeadDrop from = list[Random.Range(0, list.Count)]; List<DeadDrop> list2 = list.FindAll(delegate(DeadDrop d) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Vector3 val3 = ((Component)d).transform.position - ((Component)from).transform.position; return ((Vector3)(ref val3)).magnitude >= 80f; }); DeadDrop val = (DeadDrop)((list2.Count > 0) ? ((object)list2[Random.Range(0, list2.Count)]) : ((object)list.Find((DeadDrop d) => (Object)(object)d != (Object)(object)from))); ItemInstance val2 = RewardUtil.CreateItem("paisleybandana", 1); if (val2 != null) { ((StorageEntity)from.Storage).InsertItem(val2, true); run.FromGuid = ((object)from.GUID/*cast due to .constrained prefix*/).ToString(); run.ToGuid = ((object)val.GUID/*cast due to .constrained prefix*/).ToString(); run.FromName = from.DeadDropName; run.ToName = val.DeadDropName; run.Placed = true; run.PickedUp = false; Melon<Core>.Logger.Msg($"[quest] package placed at '{run.FromName}', to be delivered to '{run.ToName}'"); } } private static void Cleanup(HippiesQuestSave save) { DropRun drops = save.Drops; if (drops.Placed) { DeadDrop val = FindDrop(drops.FromGuid); DeadDrop val2 = FindDrop(drops.ToGuid); if ((Object)(object)val != (Object)null) { RemoveAll(val, "paisleybandana"); } if ((Object)(object)val2 != (Object)null) { RemoveAll(val2, "paisleybandana"); } save.Drops = new DropRun(); } } public static void Tick() { try { HippiesQuestSave save = Save; if (save == null || !save.Data.Accepted || save.Data.TargetKilled || save.Data.Completed) { return; } DropRun drops = save.Drops; if (!drops.Placed) { return; } JobController job = HippiesQuest.Job; if (!drops.PickedUp) { DeadDrop val = FindDrop(drops.FromGuid); if ((Object)(object)val == (Object)null || Count(val, "paisleybandana") > 0) { return; } drops.PickedUp = true; List<QuestEntry> list = job.Quest?.QuestEntries; if (list != null && list.Count > 1) { list[0].Complete(); list[1].Begin(); } Melon<Core>.Logger.Msg("[quest] package picked up"); } DeadDrop val2 = FindDrop(drops.ToGuid); if ((Object)(object)val2 != (Object)null && Count(val2, "paisleybandana") > 0) { job.OnObjectiveDone(); } } catch (Exception ex) { Melon<Core>.Logger.Warning("[quest] hippies tick failed: " + ex.Message); } } public static void GiveSupplies() { RewardUtil.GiveItems(Supplies); } } public class HippiesQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "hippies_package_quest", RewardLogLabel = "Krystal's package job", CashReward = 0f, GetSave = () => HippiesQuestSave.Instance?.Data, PickName = () => string.Empty, RefreshTarget = HippiesTask.Refresh, GrantExtraReward = HippiesTask.GiveSupplies, CreateQuest = (string id) => QuestManager.CreateQuest<HippiesQuest>(id) }); protected override string Title => "Special Delivery"; protected override string Description => "Krystal Bird needs a package carried between two dead drops."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); DropRun dropRun = HippiesQuestSave.Instance?.Drops ?? new DropRun(); ((Quest)this).AddEntry("Pick up the package from the dead drop at " + dropRun.FromName, HippiesTask.PositionOf(dropRun.FromGuid)); ((Quest)this).AddEntry("Deliver it to the dead drop at " + dropRun.ToName, HippiesTask.PositionOf(dropRun.ToGuid)); ((Quest)this).AddEntry("Tell Krystal Bird the package has been delivered", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class PartyRun { public List<string> Asked = new List<string>(); public int Accepted; } public class PartyBusQuestSave : Saveable { [SaveableField("partybus_quest")] public JobSaveData Data = new JobSaveData(); [SaveableField("partybus_quest_run")] public PartyRun Run = new PartyRun(); public static PartyBusQuestSave Instance { get; private set; } public PartyBusQuestSave() { Instance = this; } protected override void OnLoaded() { Instance = this; } } internal static class PartyTask { public const int PeopleNeeded = 5; public const float RejectChance = 0.3f; private static readonly KeyValuePair<string, int>[] PartySupplies = new KeyValuePair<string, int>[1] { new KeyValuePair<string, int>("energydrink", 5) }; private static PartyBusQuestSave Save => PartyBusQuestSave.Instance; public static void GiveSupplies() { RewardUtil.GiveItems(PartySupplies); } public static bool CanInvite(string npcId) { PartyBusQuestSave save = Save; if (save == null || string.IsNullOrEmpty(npcId)) { return false; } JobSaveData data = save.Data; return data.Accepted && !data.TargetKilled && !data.Completed && !save.Run.Asked.Contains(npcId); } public static bool Invite(string npcId) { PartyBusQuestSave save = Save; if (save == null || !CanInvite(npcId)) { return false; } save.Run.Asked.Add(npcId); bool flag = Random.value >= 0.3f; if (flag) { save.Run.Accepted++; } UpdateObjectiveTitle(); Melon<Core>.Logger.Msg($"[quest] {npcId} {(flag ? "accepted" : "turned down")} the invite ({save.Run.Accepted}/{5})"); if (save.Run.Accepted >= 5) { PartyBusQuest.Job.OnObjectiveDone(); } return flag; } public static string ObjectiveText(int accepted) { return $"Tell people the Party Bus is in town ({Math.Min(accepted, 5)}/{5})"; } private static void UpdateObjectiveTitle() { List<QuestEntry> list = PartyBusQuest.Job.Quest?.QuestEntries; if (list != null && list.Count > 0) { list[0].Title = ObjectiveText(Save.Run.Accepted); } } public static void Refresh() { PartyBusQuestSave save = Save; if (save != null && (!save.Data.Accepted || save.Data.TargetKilled) && (save.Run.Asked.Count > 0 || save.Run.Accepted > 0)) { save.Run = new PartyRun(); } } } public class PartyBusQuest : Quest { internal static readonly JobController Job = new JobController(new JobDefinition { QuestIdPrefix = "partybus_word_quest", RewardLogLabel = "Chad's spread-the-word job", CashReward = 2500f, GrantExtraReward = PartyTask.GiveSupplies, GetSave = () => PartyBusQuestSave.Instance?.Data, PickName = () => string.Empty, RefreshTarget = PartyTask.Refresh, CreateQuest = (string id) => QuestManager.CreateQuest<PartyBusQuest>(id) }); protected override string Title => "Spread the Word"; protected override string Description => "Chad Chaddington wants the whole town to know the Party Bus is here."; protected override bool AutoBegin => true; protected override void OnCreated() { ((Registerable)this).OnCreated(); Job.Attach((Quest)(object)this); int accepted = PartyBusQuestSave.Instance?.Run.Accepted ?? 0; ((Quest)this).AddEntry(PartyTask.ObjectiveText(accepted), (Vector3?)null); ((Quest)this).AddEntry("Tell Chad Chaddington the word is out", (Vector3?)null); } protected override void OnLoaded() { ((Saveable)this).OnLoaded(); Job.Attach((Quest)(object)this); } } public class JobSaveData { public bool Accepted; public string GroupId = string.Empty; public bool TargetKilled; public bool Completed; public int OfferState; public string TargetName = string.Empty; public int Runs; } internal sealed class JobDefinition { public string QuestIdPrefix; public string RewardLogLabel; public float RelationshipReward = 0.5f; public float CashReward = 5000f; public int XpReward = 100; public Action GrantExtraReward; public Func<JobSaveData> GetSave; public Func<string> PickName; public Action RefreshTarget; public Func<string, Quest> CreateQuest; } internal sealed class JobLines { public Func<string, string> OfferLine; public string[] NoJobLines; public string AlreadyDoneLine; public string HandInLine; } internal sealed class JobController { public static readonly bool DebugMode = false; private static readonly List<JobController> All = new List<JobController>(); private readonly JobDefinition _def; private Quest _quest; public static float OfferChance => DebugMode ? 1f : 0.5f; public JobSaveData Save => _def.GetSave(); public Quest Quest => _quest; public JobController(JobDefinition def) { _def = def; All.Add(this); } public void Attach(Quest quest) { _quest = quest; quest.OnComplete -= HandleComplete; quest.OnComplete += HandleComplete; } public string TargetNameOr(string fallback) { string text = Save?.TargetName; return string.IsNullOrEmpty(text) ? fallback : text; } public bool CanAsk() { JobSaveData save = Save; return save == null || !save.Accepted || save.Completed; } public bool IsReadyToHandIn() { JobSaveData save = Save; return save != null && save.TargetKilled && !save.Completed; } public string Ask(string groupId, JobLines lines) { JobSaveData save = Save; if (save == null) { return null; } if (!string.IsNullOrEmpty(groupId)) { save.GroupId = groupId; } if (save.Completed && !DebugMode) { return lines.AlreadyDoneLine; } if (save.Completed) { ResetRun(); } if (save.OfferState == 0 || DebugMode) { save.OfferState = ((Random.value < OfferChance) ? 1 : 2); } if (save.OfferState == 1) { Start(groupId); return lines.OfferLine(save.TargetName); } return lines.NoJobLines[Random.Range(0, lines.NoJobLines.Length)]; } public void Start(string groupId) { JobSaveData save = Save; if (save != null) { save.Accepted = true; save.GroupId = groupId ?? string.Empty; save.Runs++; save.TargetName = _def.PickName(); } if (_quest == null) { _quest = _def.CreateQuest($"{_def.QuestIdPrefix}_{save?.Runs ?? 0}"); } if (_quest == null) { Melon<Core>.Logger.Error("[quest] could not create the quest"); return; } _def.RefreshTarget(); Melon<Core>.Logger.Msg("[quest] created; S1API sets up its entries and begins it on the next frame"); } public void OnObjectiveDone() { JobSaveData save = Save; if (_quest != null && save != null && save.Accepted && !save.TargetKilled) { save.TargetKilled = true; int num = _quest.QuestEntries.Count - 1; for (int i = 0; i < num; i++) { _quest.QuestEntries[i].Complete(); } if (num > 0) { _quest.QuestEntries[num].Begin(); } Melon<Core>.Logger.Msg("[quest] task done, report back to the leader"); } } public void HandIn() { JobSaveData save = Save; if (_quest != null && save != null && save.TargetKilled && !save.Completed) { save.Completed = true; int num = _quest.QuestEntries.Count - 1; if (num > 0) { _quest.QuestEntries[num].Complete(); } _quest.Complete(); _def.RefreshTarget(); } } private void HandleComplete() { if (_def.CashReward > 0f) { Money.ChangeCashBalance(_def.CashReward, true, true); } LevelManager.AddXP(_def.XpReward); _def.GrantExtraReward?.Invoke(); Melon<Core>.Logger.Msg($"[quest] paid ${_def.CashReward}, {_def.XpReward} XP"); string text = Save?.GroupId; if (!string.IsNullOrEmpty(text)) { GroupRelationships.Reward(text, _def.RelationshipReward, "completed " + _def.RewardLogLabel); } else { Melon<Core>.Logger.Warning("[quest] no saved group id, relationship reward skipped"); } } public void ResetRun() { JobSaveData save = Save; if (save != null) { if (_quest != null && save.Accepted && !save.Completed) { _quest.Cancel(); } _quest = null; save.OfferState = 0; save.TargetName = string.Empty; save.Accepted = false; save.TargetKilled = false; save.Completed = false; _def.RefreshTarget(); } } public static void OnGroupLeft(string groupId) { foreach (JobController item in All) { JobSaveData save = item.Save; if (save != null && string.Equals(save.GroupId, groupId, StringComparison.OrdinalIgnoreCase)) { item.ResetRun(); Melon<Core>.Logger.Msg("[quest] " + groupId + " left, their job can be taken again next visit"); } } } } internal static class RewardUtil { public static ItemInstance CreateItem(string itemId, int quantity) { ItemDefinition item = Registry.GetItem(itemId); if ((Object)(object)item == (Object)null) { Melon<Core>.Logger.Warning("[quest] item '" + itemId + "' does not exist"); return null; } return item.GetDefaultInstance(quantity); } public static void GiveItems(params KeyValuePair<string, int>[] items) { PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance == (Object)null) { Melon<Core>.Logger.Warning("[quest] no inventory to give the reward to"); return; } for (int i = 0; i < items.Length; i++) { KeyValuePair<string, int> keyValuePair = items[i]; ItemInstance val = CreateItem(keyValuePair.Key, keyValuePair.Value); if (val != null) { instance.AddItemToInventory(val); } } } } internal static class QuestNpcUtil { public static readonly Vector3 ParkedPosition = new Vector3(0f, -200f, 0f); public static readonly Color32[] SkinTones = (Color32[])(object)new Color32[5] { new Color32((byte)236, (byte)200, (byte)170, byte.MaxValue), new Color32((byte)224, (byte)172, (byte)138, byte.MaxValue), new Color32((byte)198, (byte)140, (byte)100, byte.MaxValue), new Color32((byte)168, (byte)112, (byte)78, byte.MaxValue), new Color32((byte)130, (byte)84, (byte)58, byte.MaxValue) }; public static T Pick<T>(T[] items) { return items[Random.Range(0, items.Length)]; } public static string RandomName(string[] firstNames, string[] lastNames) { return Pick(firstNames) + " " + Pick(lastNames); } public static void ApplyName(NPC npc, string fullName) { if (!string.IsNullOrEmpty(fullName)) { int num = fullName.IndexOf(' '); npc.FirstName = ((num > 0) ? fullName.Substring(0, num) : fullName); npc.LastName = ((num > 0) ? fullName.Substring(num + 1) : string.Empty); } } private static NPC FindGameNpc(string npcId) { Il2CppArrayBase<NPC> val = Object.FindObjectsOfType<NPC>(true); foreach (NPC item in val) { if ((Object)(object)item != (Object)null && item.ID == npcId) { return item; } } Melon<Core>.Logger.Warning($"[quest] NPC '{npcId}' is not among the {val.Length} NPCs in the scene"); return null; } private static NPCEnterableBuilding FindBuilding(string name) { foreach (NPCEnterableBuilding item in Object.FindObjectsOfType<NPCEnterableBuilding>(true)) { if ((Object)(object)item != (Object)null && string.Equals(item.BuildingName, name, StringComparison.OrdinalIgnoreCase)) { return item; } } return null; } public static void EnterBuilding(string npcId, string buildingName) { //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) NPC val = FindGameNpc(npcId); NPCEnterableBuilding val2 = FindBuilding(buildingName); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || ((Il2CppArrayBase<StaticDoor>)(object)val2.Doors).Length == 0) { Melon<Core>.Logger.Warning($"[quest] could not hide '{npcId}' in '{buildingName}' (npc found: {(Object)(object)val != (Object)null}, building found: {(Object)(object)val2 != (Object)null}, doors: {((val2 != null) ? new int?(((Il2CppArrayBase<StaticDoor>)(object)val2.Doors).Length) : ((int?)null))})"); } else if (!((Object)(object)val.CurrentBuilding == (Object)(object)val2)) { if (val.isInBuilding) { val.ExitBuilding(""); } val.EnterBuilding((NetworkConnection)null, ((object)val2.GUID/*cast due to .constrained prefix*/).ToString(), 0); Melon<Core>.Logger.Msg($"[quest] '{npcId}' is now hidden in '{buildingName}'"); } } public static void LeaveBuilding(string npcId) { NPC val = FindGameNpc(npcId); if ((Object)(object)val != (Object)null && val.isInBuilding) { val.ExitBuilding(""); } } public static void Park(NPC npc, string npcId) { //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_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) //IL_0040: Unknown result type (might be due to invalid IL or missing references) npc.Schedule.Disable(); LeaveBuilding(npcId); npc.IsInvincible = true; Vector3 val = npc.Position - ParkedPosition; if (((Vector3)(ref val)).sqrMagnitude > 1f) { npc.Position = ParkedPosition; } } public static void Revive(NPC npc) { if (npc.IsDead) { npc.Revive(); } npc.Heal((int)npc.MaxHealth); npc.IsInvincible = false; } } internal static class AppearanceUtil { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; public static void ClearLayers(NPCAppearance appearance) { try { object obj = typeof(NPCAppearance).GetField("_customAvatarSettings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(appearance); if (obj != null) { string[] array = new string[3] { "FaceLayerSettings", "BodyLayerSettings", "AccessorySettings" }; foreach (string name in array) { object obj2 = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) ?? obj.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); obj2?.GetType().GetMethod("Clear", Type.EmptyTypes)?.Invoke(obj2, null); } } } catch (Exception ex) { Melon<Core>.Logger.Warning("[quest] clearing old layers failed: " + ex.Message); } } public static void ApplyNow(NPCAppearance appearance) { try { Type typeFromHandle = typeof(NPCAppearance); object obj = typeFromHandle.GetField("_runtimeAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(appearance); MethodInfo method = typeFromHandle.GetMethod("ApplyToAvatar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (obj == null || method == null) { Melon<Core>.Logger.Warning($"[quest] could not apply the look (avatar found: {obj != null}, method found: {method != null})"); } else { method.Invoke(appearance, new object[1] { obj }); } } catch (Exception ex) { Melon<Core>.Logger.Warning("[quest] applying the look failed: " + ex.Message); } } public static void Wear<T>(NPCAppearance appearance, Color color, params string[] candidatePaths) where T : BaseAccessoryAppearance { //IL_002f: Unknown result type (might be due to invalid IL or missing references) foreach (string text in candidatePaths) { bool flag = false; try { flag = Resources.Load(text) != (Object)null; } catch { } if (flag) { appearance.WithAccessoryLayer<T>(text, color); break; } Melon<Core>.Logger.Warning("[quest] outfit path not found: " + text); } } } } namespace BetterSpecialCustomers.Patches { [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class BikersQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "diesel_rodd", AskText = "You got any jobs for me?", HandInText = "He's dealt with.", Job = BikersQuest.Job, Lines = new JobLines { OfferLine = (string name) => "Somebody's been feedin' the cops about club business. Name's " + name + ". He's holed up at The Piss Hut. Knock on the door, and make sure he never talks again.", NoJobLines = new string[3] { "Not this time, prospect. Road's quiet. Come find me when it ain't.", "Nothin' right now. The club'll let you know when we need a hand.", "Ain't got nothin' for you today. Go count your money." }, AlreadyDoneLine = "You already did right by us this run. Come back when we roll through again.", HandInLine = "Good. Rats don't get to walk away. The club won't forget this." } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class BusinessmenQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "johnny_smith", AskText = "Anything I can help with?", HandInText = "The audit won't be a problem anymore.", AskSeconds = 8f, Job = AuditQuest.Job, Lines = new JobLines { OfferLine = (string name) => "As it happens, yes. An auditor, " + name + ", has been looking at the books for the wrong reasons. He keeps to a strict routine: home, the office, lunch. Make sure he doesn't finish his report.", NoJobLines = new string[3] { "Nothing at the moment. Business is... stable. Enjoy it.", "Not today. If something comes up, you'll be the first call.", "We're fully staffed on problems right now. Check back later." }, AlreadyDoneLine = "You've done enough for us this time. We'll be in touch.", HandInLine = "Excellent. A tidy solution. Our accountants will be very pleased." } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } internal static class BuyLimitFieldSync { private sealed class IntMember { private readonly FieldInfo _field; private readonly PropertyInfo _property; public bool Exists => _field != null || _property != null; public IntMember(Type type, string name) { _field = AccessTools.Field(type, name); _property = ((_field == null) ? AccessTools.Property(type, name) : null); } public int Get(object target) { return Convert.ToInt32((_field != null) ? _field.GetValue(target) : _property.GetValue(target)); } public void Set(object target, int value) { if (_field != null) { _field.SetValue(target, value); } else { _property.SetValue(target, value); } } } private static readonly IntMember BaseQuantity = new IntMember(typeof(SpecialCustomerData), "_baseBuyQuantity"); private static readonly IntMember PerRank = new IntMember(typeof(SpecialCustomerData), "_additionalBuyQuantityPerRank"); private static readonly IntMember MaxQuantity = new IntMember(typeof(SpecialCustomerData), "_maxBuyQuantity"); private static bool _reportedMissing; private static readonly Dictionary<string, int> OriginalBase = new Dictionary<string, int>(); public static void Apply(SpecialCustomerManager manager, string groupId) { try { if (!PerRank.Exists || !MaxQuantity.Exists) { if (!_reportedMissing) { _reportedMissing = true; Melon<Core>.Logger.Error($"BuyLimitFieldSync: can't find the game's per-rank / max buy quantity values (perRank found: {PerRank.Exists}, max found: {MaxQuantity.Exists}). See the [diag] Probe lines. The buy limit will stay vanilla."); } return; } SpecialCustomerData specialCustomerData = manager.GetSpecialCustomerData(groupId); if (!((Object)(object)specialCustomerData == (Object)null)) { int num = 10; double num2 = GroupRelationships.GetMultiplier(groupId); string key = groupId.ToLowerInvariant(); if (!OriginalBase.TryGetValue(key, out var value)) { value = specialCustomerData.BaseBuyQuantity; OriginalBase[key] = value; } int num3 = (BaseQuantity.Exists ? ((int)Math.Round((double)value * num2)) : value); int num4 = (int)Math.Round(200.0 * num2); int num5 = Math.Max(0, (int)Math.Ceiling((double)(num4 - num3) / (double)num)); int value2 = PerRank.Get(specialCustomerData); int value3 = MaxQuantity.Get(specialCustomerData); if (BaseQuantity.Exists) { BaseQuantity.Set(specialCustomerData, num3); } PerRank.Set(specialCustomerData, num5); MaxQuantity.Set(specialCustomerData, num4); if (PatchDiagnostics.Enabled) { Melon<Core>.Logger.Msg($"[diag] BuyLimitFieldSync [{groupId}]: base {value} -> {num3}, perRank {value2} -> {num5}, max {value3} -> {num4} (relationship x{GroupRelationships.GetMultiplier(groupId):0.00}; Kingpin limit = {Math.Min(num3 + num5 * num, num4)})"); } } } catch (Exception ex) { Melon<Core>.Logger.Error("BuyLimitFieldSync failed: " + ex); } } } internal static class DialogueChoiceUtil { public static void Add(DialogueController controller, string text, Func<bool> shouldShow, Action onChosen) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown DialogueChoice val = new DialogueChoice(); val.ChoiceText = text; val.Enabled = true; Func<bool, bool> func = (bool enabled) => enabled && shouldShow(); val.shouldShowCheck = DelegateSupport.ConvertDelegate<ShouldShowCheck>((Delegate)func); val.onChoosen.AddListener(DelegateSupport.ConvertDelegate<UnityAction>((Delegate)onChosen)); controller.AddDialogueChoice(val, 0); } } [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class HippiesQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "krystal_bird", AskText = "Can I do anything for the group?", HandInText = "The package has been delivered.", AskSeconds = 8f, Job = HippiesQuest.Job, Lines = new JobLines { OfferLine = (string _) => "Oh, bless you. There's a little something that needs to get to a friend, and we can't be seen with it. I've left it at a drop. Take it to the other one, and be gentle with it.", NoJobLines = new string[3] { "The universe has been quiet today, friend. Come back when it speaks.", "Not right now, sweetheart. Go enjoy the sunshine.", "Nothing this time. Everything is as it should be." }, AlreadyDoneLine = "You've done so much already. We're grateful. Rest a while.", HandInLine = "Wonderful. The garden will be so happy. Please, take these, with our love." } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } internal sealed class JobDialogue { public string LeaderId; public string AskText; public string HandInText; public JobController Job; public JobLines Lines; public float AskSeconds = 7f; public float HandInSeconds = 6f; private SpecialCustomerLeader _leader; private readonly HashSet<int> _added = new HashSet<int>(); public void Register(SpecialCustomerLeader leader) { _leader = leader; DialogueController component = ((Component)((NPC)leader).DialogueHandler).GetComponent<DialogueController>(); if (!((Object)(object)component == (Object)null) && _added.Add(((Object)component).GetInstanceID())) { DialogueChoiceUtil.Add(component, AskText, () => ((NPC)leader).ID == LeaderId && Job.CanAsk(), OnAsk); DialogueChoiceUtil.Add(component, HandInText, () => ((NPC)leader).ID == LeaderId && Job.IsReadyToHandIn(), OnHandIn); MelonLogger.Msg("[quest] added the quest choices for leader '" + LeaderId + "'"); } } private void OnAsk() { if (!((Object)(object)_leader == (Object)null)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; string text = Job.Ask((currentGroupData != null) ? currentGroupData.GroupId : null, Lines); if (text != null) { ((NPC)_leader).DialogueHandler.ShowWorldspaceDialogue(text, AskSeconds); } } } private void OnHandIn() { if ((Object)(object)_leader != (Object)null) { ((NPC)_leader).DialogueHandler.ShowWorldspaceDialogue(Lines.HandInLine, HandInSeconds); } Job.HandIn(); } } [HarmonyPatch(typeof(SpecialCustomerLeader), "SetCustomerData")] internal class PartyBusQuestChoicePatch { private static readonly JobDialogue Dialogue = new JobDialogue { LeaderId = "chad_chaddington", AskText = "Need a hand with anything?", HandInText = "The word is out.", AskSeconds = 8f, Job = PartyBusQuest.Job, Lines = new JobLines { OfferLine = (string _) => $"Dude, yes! We need people to know the bus is in town and tonight is ON. Go tell {5} people. Some will say no. Their loss. Keep going!", NoJobLines = new string[3] { "We're good for now, bro. The vibe is already unreal.", "Nothing right now! Just enjoy the party.", "Not today, my guy. We got it handled." }, AlreadyDoneLine = "You already got the word out, legend! Go have some fun.", HandInLine = "LEGEND! The crowd is gonna be insane. Here, you earned it!" } }; public static void Postfix(SpecialCustomerLeader __instance) { Dialogue.Register(__instance); } } [HarmonyPatch(typeof(DialogueController), "Start")] internal class PartyInvitePatch { private const string InviteText = "The Party Bus is in town tonight. You should come!"; private static readonly string[] YesLines = new string[4] { "A party bus? Count me in!", "Sounds like a good time. I'll be there.", "Finally, something fun around here.", "Oh, nice. I'll swing by." }; private static readonly string[] NoLines = new string[4] { "Not really my scene, sorry.", "I've got stuff to do tonight.", "No thanks, I'm good.", "Parties aren't for me." }; public static void Postfix(DialogueController __instance) { NPC npc = ((Component)__instance).GetComponentInParent<NPC>(); if (!((Object)(object)npc == (Object)null) && IsInvitable(npc)) { DialogueChoiceUtil.Add(__instance, "The Party Bus is in town tonight. You should come!", () => PartyTask.CanInvite(npc.ID), delegate { OnInvite(npc); }); } } private static bool IsInvitable(NPC npc) { if ((Object)(object)((Component)npc).GetComponent<SpecialCustomerLeader>() != (Object)null) { return false; } if ((Object)(object)((Component)npc).GetComponent<SpecialCustomer>() != (Object)null) { return false; } if ((Object)(object)((Component)npc).GetComponent<PoliceOfficer>() != (Object)null) { return false; } return npc.ID != "bikers_informant" && npc.ID != "businessmen_auditor"; } private static void OnInvite(NPC npc) { string[] array = (PartyTask.Invite(npc.ID) ? YesLines : NoLines); npc.DialogueHandler.ShowWorldspaceDialogue(array[Random.Range(0, array.Length)], 5f); } } [HarmonyPatch(typeof(SpecialCustomerData), "GetBuyQuantityForPlayerRank")] public class MaxBuyPatch { public const int NewMaxBuyQuantity = 200; private const bool DebugLogging = false; public static bool Prefix(SpecialCustomerData __instance, ERank rank, ref int __result) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected I4, but got Unknown PatchDiagnostics.Hit("MaxBuyPatch (rank-based buy limit)"); int baseBuyQuantity = __instance.BaseBuyQuantity; int num = 10; int num2 = Math.Max(0, Math.Min((int)rank, num)); __result = baseBuyQuantity + (200 - baseBuyQuantity) * num2 / num; return false; } [HarmonyPriority(0)] public static void Postfix(SpecialCustomerData __instance, ERank rank, int __result) { bool flag = true; } } [HarmonyPatch(typeof(SpecialCustomerData), "GetBuyQuantityForPlayerRank")] public class RelationshipBuyQuantityPatch { public static void Postfix(SpecialCustomerData __instance, ref int __result) { PatchDiagnostics.Hit("RelationshipBuyQuantityPatch (relationship multiplier)"); float multiplier = GroupRelationships.GetMultiplier(__instance.GroupId); __result = Math.Max(1, (int)Math.Round((float)__result * multiplier)); } } [HarmonyPatch(typeof(SpecialCustomerManager), "DealCompleted_Client")] public class RelationshipGainPatch { public static void Postfix(SpecialCustomerManager __instance, SpecialCustomerDealReceipt dealRecipe) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) PatchDiagnostics.Hit("RelationshipGainPatch (sale completed)"); SpecialCustomerData currentGroupData = __instance.CurrentGroupData; SpecialCustomerLeader val = ((dealRecipe != null) ? dealRecipe.Leader : null); if (!((Object)(object)currentGroupData == (Object)null) && !((Object)(object)val == (Object)null) && dealRecipe.Items != null) { int num = 0; Enumerator<ItemInstance> enumerator = dealRecipe.Items.GetEnumerator(); while (enumerator.MoveNext()) { ItemInstance current = enumerator.Current; num += ((BaseItemInstance)current).GetTotalAmount(); } int num2 = val.StartingBuyQuantity; if (num2 <= 0) { num2 = currentGroupData.GetBuyQuantityForPlayerRank(NetworkSingleton<LevelManager>.Instance.Rank); } if (num2 > 0) { GroupRelationships.RegisterSale(currentGroupData.GroupId, num, num2); } } } } [HarmonyPatch(typeof(SpecialCustomer), "SetGroupMates")] internal class RelationshipLossPatch { private static readonly HashSet<int> Subscribed = new HashSet<int>(); private static float _lastPunchTime = -100f; private static float _lastKillTime = -100f; private static float _lastAimTime = -100f; private static float _lastPickpocketTime = -100f; public static void Postfix(SpecialCustomer __instance) { PatchDiagnostics.Hit("RelationshipLossPatch (subscribing to NPC reactions)"); if (Subscribed.Add(((Object)__instance).GetInstanceID())) { NPCResponses responses = ((NPC)__instance).Responses; responses.OnNonLethallyAttackedByPlayer = AddHandler(responses.OnNonLethallyAttackedByPlayer, OnPunched); responses.OnRepeatedlyNonLethallyAttackedByPlayer = AddHandler(responses.OnRepeatedlyNonLethallyAttackedByPlayer, OnPunched); responses.OnLethallyAttackedByPlayer = AddHandler(responses.OnLethallyAttackedByPlayer, OnKilledSomeone); responses.OnAimedAtByPlayer = AddHandler(responses.OnAimedAtByPlayer, OnAimedAt); responses.OnPickpocketFailed = AddHandler(responses.OnPickpocketFailed, OnPickpocketFailed); } } private static Action<Player> AddHandler(Action<Player> existing, Action<Player> handler) { Action<Player> val = DelegateSupport.ConvertDelegate<Action<Player>>((Delegate)handler); return ((Il2CppObjectBase)Delegate.Combine((Delegate)(object)existing, (Delegate)(object)val)).Cast<Action<Player>>(); } private static void OnPunched(Player attacker) { if (InstanceFinder.IsServer && !(Time.time - _lastPunchTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastPunchTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 0.25f, "punched"); } } } private static void OnKilledSomeone(Player attacker) { if (InstanceFinder.IsServer && !(Time.time - _lastKillTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastKillTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 1f, "customer killed"); } } } private static void OnAimedAt(Player attacker) { if (InstanceFinder.IsServer && !(Time.time - _lastAimTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastAimTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 0.25f, "aimed weapon"); } } } private static void OnPickpocketFailed(Player thief) { if (InstanceFinder.IsServer && !(Time.time - _lastPickpocketTime < 20f)) { SpecialCustomerData currentGroupData = NetworkSingleton<SpecialCustomerManager>.Instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { _lastPickpocketTime = Time.time; GroupRelationships.Penalize(currentGroupData.GroupId, 0.25f, "pickpocket failed"); } } } } [HarmonyPatch(typeof(SpecialCustomerManager), "GetSaveString")] public class RelationshipSavePatch { public static void Postfix(ref string __result) { PatchDiagnostics.Hit("RelationshipSavePatch (writing save)"); try { int num = __result.LastIndexOf('}'); if (num >= 0) { __result = __result.Substring(0, num).TrimEnd() + ",\n " + GroupRelationships.ToJsonProperty() + "\n}"; } } catch (Exception ex) { Melon<Core>.Logger.Error("Failed to add relationships to the save string: " + ex); } } } [HarmonyPatch(typeof(SpecialCustomerLoader), "Load")] public class RelationshipLoadPatch { public static void Prefix(string mainPath) { PatchDiagnostics.Hit("RelationshipLoadPatch (reading save)"); try { string path = mainPath + ".json"; GroupRelationships.LoadFromJson(File.Exists(path) ? File.ReadAllText(path) : null); } catch (Exception ex) { GroupRelationships.Clear(); Melon<Core>.Logger.Error("Failed to load relationships: " + ex); } } } [HarmonyPatch(typeof(SpecialCustomerManager), "RunArrivalPhase")] public class RelationshipArrivalPatch { private static readonly FieldInfo CurrentDataField = AccessTools.Field(typeof(SpecialCustomerManager), "_currentData"); private static readonly PropertyInfo CurrentDataProperty = AccessTools.Property(typeof(SpecialCustomerManager), "_currentData"); private static bool _reportedMissing; private static string GetArrivingGroupId(SpecialCustomerManager manager) { object obj = ((CurrentDataField != null) ? CurrentDataField.GetValue(manager) : CurrentDataProperty?.GetValue(manager)); if (obj == null && CurrentDataField == null && CurrentDataProperty == null && !_reportedMissing) { _reportedMissing = true; Melon<Core>.Logger.Error("RelationshipArrivalPatch: can't find SpecialCustomerManager._currentData as a field or property; visits will NOT be tracked. See the [diag] Probe lines."); } object obj2 = ((obj is SpecialCustomerSaveData) ? obj : null); return (obj2 != null) ? ((SpecialCustomerSaveData)obj2).GroupId : null; } public static void Prefix(SpecialCustomerManager __instance) { PatchDiagnostics.Hit("RelationshipArrivalPatch (group arriving)"); string arrivingGroupId = GetArrivingGroupId(__instance); if (!string.IsNullOrEmpty(arrivingGroupId)) { GroupRelationships.BeginVisit(arrivingGroupId); BuyLimitFieldSync.Apply(__instance, arrivingGroupId); } } } [HarmonyPatch(typeof(SpecialCustomerManager), "RemoveCustomerGroup")] public class RelationshipDeparturePatch { public static void Prefix(SpecialCustomerManager __instance) { PatchDiagnostics.Hit("RelationshipDeparturePatch (group leaving)"); SpecialCustomerData currentGroupData = __instance.CurrentGroupData; if (!((Object)(object)currentGroupData == (Object)null)) { GroupRelationships.EndVisit(currentGroupData.GroupId); JobController.OnGroupLeft(currentGroupData.GroupId); } } } } namespace BetterSpecialCustomers.Diagnostics { public static class PatchDiagnostics { public static readonly bool Enabled = false; private static readonly HashSet<string> Seen = new HashSet<string>(); public static void Hit(string patchName) { if (Enabled && Seen.Add(patchName)) { Melon<Core>.Logger.Msg("[diag] first call: " + patchName + " (the game DID call this patch)"); } } public static void PatchAllClasses(Harmony harmony) { IEnumerable<Type> enumerable = from t in typeof(Core).Assembly.GetTypes() where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0 orderby t.Name select t; int num = 0; int num2 = 0; foreach (Type item in enumerable) { try { harmony.CreateClassProcessor(item).Patch(); num++; if (Enabled) { Melon<Core>.Logger.Msg("[diag] Patch OK: " + item.Name); } } catch (Exception ex) { num2++; Exception ex2 = ex; while (ex2.InnerException != null) { ex2 = ex2.InnerException; } Melon<Core>.Logger.Error($"[diag] Patch FAILED: {item.Name} -> {ex2.GetType().Name}: {ex2.Message}"); } } Melon<Core>.Logger.Msg($"[diag] Patches applied: {num} ok, {num2} failed."); } public static void Probe() { if (Enabled) { Melon<Core>.Logger.Msg($"[diag] Build: {"IL2CPP"}. Harmony {typeof(Harmony).Assembly.GetName().Version}"); ProbeMethod(typeof(SpecialCustomerData), "GetBuyQuantityForPlayerRank"); ProbeMethod(typeof(SpecialCustomerManager), "DealCompleted_Client"); ProbeMethod(typeof(SpecialCustomerManager), "GetSaveString"); ProbeMethod(typeof(SpecialCustomerManager), "RunArrivalPhase"); ProbeMethod(typeof(SpecialCustomerManager), "RemoveCustomerGroup"); ProbeMethod(typeof(SpecialCustomerLoader), "Load"); ProbeMembers(typeof(SpecialCustomerManager), "currentData"); ProbeMembers(typeof(SpecialCustomerData), "baseBuyQuantity"); ProbeMembers(typeof(SpecialCustomerData), "additionalBuyQuantityPerRank"); ProbeMembers(typeof(SpecialCustomerData), "maxBuyQuantity"); } } private static void ProbeMembers(Type type, string namePart) { MemberInfo[] array = (from m in type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name.IndexOf(namePart, StringComparison.OrdinalIgnoreCase) >= 0 select m).ToArray(); if (array.Length == 0) { Melon<Core>.Logger.Warning($"[diag] Probe: {type.Name} has NO member containing '{namePart}'."); } MemberInfo[] array2 = array; foreach (MemberInfo memberInfo in array2) { Melon<Core>.Logger.Msg($"[diag] Probe: {type.Name}.{memberInfo.Name} is a {memberInfo.MemberType}"); } } private static void ProbeMethod(Type type, string name) { MethodBase methodBase = AccessTools.Method(type, name, (Type[])null, (Type[])null); if (methodBase == null) { Melon<Core>.Logger.Warning($"[diag] Probe: {type.Name}.{name} NOT FOUND"); } else { Melon<Core>.Logger.Msg($"[diag] Probe: {type.Name}.{name} found ({(methodBase.IsPublic ? "public" : "non-public")})"); } } public static void ListPatchedMethods(Harmony harmony) { if (!Enabled) { return; } foreach (MethodBase patchedMethod in harmony.GetPatchedMethods()) { Melon<Core>.Logger.Msg("[diag] Harmony reports patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }