Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of RVRepairVan v2.0.1
RVRepairVan.dll
Decompiled 2 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.Audio; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Dialogue; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.NPCs.CharacterClasses; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.Quests; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.VoiceOver; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using RVRepairVan; using RVRepairVan.Config; using RVRepairVan.Dialogue; using RVRepairVan.Effects; using RVRepairVan.Managers; using RVRepairVan.Persistence; using RVRepairVan.Quests; using S1API.DeadDrops; using S1API.Entities; using S1API.Entities.Dialogue; using S1API.Internal.Abstraction; using S1API.Items; using S1API.Items.Storable; using S1API.Leveling; 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.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "RVRepairVan", "2.0.1", "DooDesch", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("RVRepairVan")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.1.0")] [assembly: AssemblyInformationalVersion("2.0.1+eb1621fa18946731a7f7cc6861d2e376fbc23254")] [assembly: AssemblyProduct("RVRepairVan")] [assembly: AssemblyTitle("RVRepairVan")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.0.1.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 RVRepairVan { public sealed class Core : MelonMod { public static Core Instance { get; private set; } public static Instance Log { get; private set; } [Conditional("DEBUG")] public static void LogDebug(string msg) { Instance log = Log; if (log != null) { log.Msg(msg); } } public override void OnInitializeMelon() { Instance = this; Log = ((MelonBase)this).LoggerInstance; RVRepairVanPreferences.Initialize(); ((MelonBase)this).HarmonyInstance.PatchAll(); Log.Msg($"RVRepairVan initialized. Enabled={RVRepairVanPreferences.Enabled}, Questline={RVRepairVanPreferences.QuestlineEnabled}, RepairPrice={RVRepairVanPreferences.RepairPrice}"); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (!(sceneName != "Main")) { RepairSave.BeginLoad(); RVManager.Reset(); RepairCinematic.ForceReset(); if (RVRepairVanPreferences.QuestlineEnabled) { Questline.Reset(); Questline.Start(); } else { MarcoRepairDialogue.Reset(); MelonCoroutines.Start(MarcoRepairDialogue.SetupCoroutine()); } MelonCoroutines.Start(RestoreRepairCoroutine()); } } public override void OnPreferencesSaved() { try { MarcoRepairDialogue.RefreshPrice(); Questline.RefreshPrice(); } catch (Exception ex) { Log.Warning("[Prefs] OnPreferencesSaved failed: " + ex.Message); } } private static IEnumerator RestoreRepairCoroutine() { float waited = 0f; while (!RepairSave.Loaded && waited < 10f) { yield return (object)new WaitForSeconds(0.5f); waited += 0.5f; } yield return (object)new WaitForSeconds(2f); bool flag = false; try { flag = RVManager.TryLocate() && RepairStateStore.GetRepaired() && RVManager.IsDestroyed(); } catch (Exception ex) { Log.Warning("[Restore] check failed: " + ex.Message); } if (flag) { Log.Msg("[Restore] RV was previously repaired - restoring without charge."); RVManager.Repair(); } } } } namespace RVRepairVan.Quests { internal static class Questline { private const int None = 0; private const int Started = 1; private const int AskedDonna = 2; private const int MingErrand = 3; private const int MingCrate = 4; private const int Referred = 5; private const int MarcoMet = 6; private const int ReadyToPay = 7; private const int Trusted = 8; private const int Paid = 9; private const int Done = 10; private const string DonnaId = "donna_martin"; private const string MingId = "ming"; private const string MarcoId = "marco_baron"; private const string CrateId = "rv_ming_crate"; private const string PackageId = "rv_marco_package"; private const int LostPackageFee = 500; private const string MingAngry = "You lost it? I don't lose things, and people who lose my things lose teeth. Five hundred buys you both back. Now."; private const string MingPaid = "Smart. We're square. Now go see Marco at the body shop down by the docks, and tell him Mrs. Ming sent you."; private const string MingShort = "Then don't come back until your hands are full."; private const string MarcoAngry = "You did what? You walk in here empty-handed and waste my time. Five hundred, or the next thing that goes missing is you."; private const string MarcoPaid = "Good. Mess like that gets forgotten when the cash shows up. Bring me some of that good stuff now and then, and I'll keep shaving down the bill."; private const string MarcoShort = "Clock's running. Come back with it."; private static bool _donnaDone; private static bool _mingDone; private static bool _marcoDone; private static bool _pickupActive; private static bool _hasPackage; private static Vector3 _dropPoint; private static DeadDropInstance _drop; private static Vector3 _cratePoint; private static DeadDropInstance _crateDrop; private static bool _cratePlaced; private static bool _pkgPlaced; private static bool _itemsRegistered; private static int _gen; private static Transform _donnaT; private static Transform _mingT; private static Transform _marcoT; private static DialogueChoice _marcoRepairChoice; private static bool _itemsDumped; private static int Stage { get { return RepairStateStore.GetStage(); } set { RepairStateStore.SetStage(value); } } private static int Samples { get { return RepairStateStore.GetSamples(); } set { RepairStateStore.SetSamples(value); } } private static int DiscountTotal { get { return RepairStateStore.GetDiscountTotal(); } set { RepairStateStore.SetDiscountTotal(value); } } private static bool MarcoGreeted => Stage >= 6; private static bool ReferralUsed => Stage >= 7; private static bool Trusted_ => Stage >= 8; private static bool Active { get { if (RVRepairVanPreferences.Enabled) { return RVManager.IsDestroyed(); } return false; } } internal static int CurrentPrice() { int num = (ReferralUsed ? RVRepairVanPreferences.BasePriceWithReferral : RVRepairVanPreferences.BasePriceNoReferral); return Mathf.Max(RVRepairVanPreferences.RepairPrice, num - DiscountTotal); } private static bool ExplosionBeatPassed() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 try { Quest quest = Quest.GetQuest("Getting Started"); if ((Object)(object)quest != (Object)null && ((int)quest.State == 1 || (int)quest.State == 2)) { return true; } Quest quest2 = Quest.GetQuest("Welcome to Hyland Point"); if ((Object)(object)quest2 != (Object)null && (int)quest2.State == 2) { return true; } } catch { } return false; } internal static void Reset() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) _gen++; ResetDiag(); _donnaDone = (_mingDone = (_marcoDone = false)); _pickupActive = false; _hasPackage = false; _drop = null; _crateDrop = null; _cratePlaced = (_pkgPlaced = false); _dropPoint = Vector3.zero; _cratePoint = Vector3.zero; _donnaT = (_mingT = (_marcoT = null)); _marcoRepairChoice = null; } internal static void Start() { MelonCoroutines.Start(SetupCoroutine()); MelonCoroutines.Start(ProximityCoroutine()); MelonCoroutines.Start(RestoreCoroutine()); } private static IEnumerator RestoreCoroutine() { yield return (object)new WaitForSeconds(4f); try { if (Active && Stage >= 1 && Stage < 10) { EnsureQuest(); } } catch (Exception ex) { Core.Log.Warning("[Questline] restore failed: " + ex.Message); } } private static IEnumerator SetupCoroutine() { int myGen = _gen; int attempt = 0; bool reported = false; while (myGen == _gen && (!_donnaDone || !_mingDone || !_marcoDone)) { yield return (object)new WaitForSeconds((attempt < 20) ? 2f : 10f); attempt++; TryInject(); if (!reported && attempt >= 20) { reported = true; } } _ = myGen; _ = _gen; } private static void TryInject() { try { if (!_donnaDone) { _donnaDone = TryOne("donna_martin", out _donnaT, InjectDonna); } if (!_mingDone) { _mingDone = TryOne("ming", out _mingT, InjectMing); } if (!_marcoDone) { _marcoDone = TryOne("marco_baron", out _marcoT, InjectMarco); } } catch (Exception ex) { Core.Log.Warning("[Questline] inject attempt failed: " + ex.Message); } } private static bool TryOne(string id, out Transform t, Action<DialogueController, NPC> inject) { NPC val = FindNpc(id); DialogueController val2 = ControllerOf(val, out t); if ((Object)(object)val2 != (Object)null) { inject(val2, val); return true; } return false; } private static void InjectDonna(DialogueController donna, NPC npc) { DialogueContainer conv = S1Container(npc, "rv_donna", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "Do I look like a mechanic, sweetheart? Go ask Mrs. Ming over at the Chinese place. She knows people.", (Action<ChoiceList>)null); }); AddChoice(donna, "My RV got blown up. Know anyone who can fix it?", 90, () => Active && Stage == 1, OnAskDonna, conv); } private static void InjectMing(DialogueController ming, NPC npc) { DialogueContainer conv = S1Container(npc, "rv_ming_offer", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "Marco at the docks can fix almost anything. But favors move both ways. I have a crate waiting at a dead drop nearby. Bring it back, and I'll put in a word.", (Action<ChoiceList>)delegate(ChoiceList c) { c.Add("MING_ACCEPT", "I'll grab it.", "MING_ACCEPTED").Add("MING_DEFER", "Not right now.", "MING_DEFERRED"); }).AddNode("MING_ACCEPTED", "Good. Pick it up, bring it here, and don't open it.", (Action<ChoiceList>)null).AddNode("MING_DEFERRED", "Then your RV can stay where it is.", (Action<ChoiceList>)null); }); OnPick(npc, "MING_ACCEPT", OnAcceptErrand); AddChoice(ming, "Donna said you might know someone who can fix my RV.", 92, () => Active && Stage == 2, null, conv); DialogueContainer conv2 = S1Container(npc, "rv_ming_deliver", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "Good. Go see Marco at the body shop down by the docks. Tell him Mrs. Ming sent you.", (Action<ChoiceList>)null); }); AddChoice(ming, "Here's your crate.", 91, () => Active && Stage == 4 && (!_cratePlaced || PlayerHasItem("rv_ming_crate")), OnDeliverCrate, conv2); DialogueContainer conv3 = S1Container(npc, "rv_ming_lost", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "You lost it? I don't lose things, and people who lose my things lose teeth. Five hundred buys you both back. Now.", (Action<ChoiceList>)delegate(ChoiceList c) { c.Add("MING_PAY", "Pay $" + 500, (string)null).Add("MING_DEFER_LOSS", "I'll get the money.", "MING_LOSS_DEFER"); }).AddNode("MING_LOSS_DEFER", "Then don't come back until your hands are full.", (Action<ChoiceList>)null); }); OnPick(npc, "MING_PAY", OnMingPayLoss); AddChoice(ming, "I lost your crate.", 90, () => Active && Stage == 4 && _cratePlaced && !PlayerHasItem("rv_ming_crate"), null, conv3); } private static void InjectMarco(DialogueController marco, NPC npc) { AddChoice(marco, "Can you fix my RV?", 100, () => Active && Stage == 5, OnMarcoGreet); AddChoice(marco, "Fifty grand?", 99, () => Active && Stage == 6, OnMarcoFifty); AddChoice(marco, "Mrs. Ming sent me.", 98, () => Active && Stage == 6, OnMarcoReferral); _marcoRepairChoice = AddChoice(marco, RepairChoiceText(), 97, () => Active && MarcoGreeted && Stage < 9, OnMarcoRepair); DialogueContainer conv = S1Container(npc, "rv_marco_favour", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "Maybe. I left a package at a dead drop nearby. Pick it up, bring it back, and don't make it weird.", (Action<ChoiceList>)null); }); AddChoice(marco, "Anything I can do to bring the price down?", 96, () => Active && Stage == 7 && !_pickupActive, OnMarcoFavour, conv); DialogueContainer conv2 = S1Container(npc, "rv_marco_gotpkg", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "Good. You can follow instructions. Bring me some of that good stuff now and then, and I'll keep shaving down the bill.", (Action<ChoiceList>)null); }); AddChoice(marco, "Got your package.", 96, () => Active && _pickupActive && _hasPackage && (!_pkgPlaced || PlayerHasItem("rv_marco_package")), OnGotPackage, conv2); DialogueContainer conv3 = S1Container(npc, "rv_marco_lost", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "You did what? You walk in here empty-handed and waste my time. Five hundred, or the next thing that goes missing is you.", (Action<ChoiceList>)delegate(ChoiceList c) { c.Add("MARCO_PAY", "Pay $" + 500, (string)null).Add("MARCO_DEFER_LOSS", "I'll get the money.", "MARCO_LOSS_DEFER"); }).AddNode("MARCO_LOSS_DEFER", "Clock's running. Come back with it.", (Action<ChoiceList>)null); }); OnPick(npc, "MARCO_PAY", OnMarcoPayLoss); AddChoice(marco, "I lost your package.", 96, () => Active && _pickupActive && _hasPackage && _pkgPlaced && !PlayerHasItem("rv_marco_package"), null, conv3); AddChoice(marco, "Give Marco a packaged sample", 95, () => Active && Trusted_ && Stage < 9 && HoldingPackaged() && CurrentPrice() > RVRepairVanPreferences.RepairPrice, OnGiveSample); DialogueContainer conv4 = S1Container(npc, "rv_marco_bring", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", "Bring me packaged product - sealed stuff, not raw. Every piece I take knocks its value off the bill, up to five hundred a pop, right down to my floor.", (Action<ChoiceList>)null); }); AddChoice(marco, "What can I bring to lower the price?", 94, () => Active && Trusted_ && Stage < 9 && !HoldingPackaged() && CurrentPrice() > RVRepairVanPreferences.RepairPrice, null, conv4); } private static NPC FindNpc(string id) { try { List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { return null; } for (int i = 0; i < nPCRegistry.Count; i++) { NPC val = nPCRegistry[i]; if ((Object)(object)val != (Object)null && string.Equals(val.ID, id, StringComparison.OrdinalIgnoreCase)) { return val; } } } catch { } return null; } internal static void GruntNpc(NPC npc) { try { if ((Object)(object)npc != (Object)null && (Object)(object)npc.VoiceOverEmitter != (Object)null) { npc.VoiceOverEmitter.Play((EVOLineType)9); } } catch (Exception ex) { Core.Log.Warning("[Questline] grunt failed: " + ex.Message); } } private static DialogueContainer S1Container(NPC npc, string name, Action<DialogueContainerBuilder> build) { try { if ((Object)(object)npc == (Object)null) { return null; } NPC val = NPC.Get(npc.ID); if (val == null) { return null; } val.Dialogue.BuildAndRegisterContainer(name, build); DialogueHandler dialogueHandler = npc.DialogueHandler; List<DialogueContainer> val2 = (((Object)(object)dialogueHandler != (Object)null) ? dialogueHandler.dialogueContainers : null); if (val2 != null) { for (int i = 0; i < val2.Count; i++) { if ((Object)(object)val2[i] != (Object)null && ((Object)val2[i]).name == name) { return val2[i]; } } } } catch (Exception ex) { Core.Log.Warning("[Questline] S1 container '" + name + "' failed: " + ex.Message); } return null; } private static void OnPick(NPC npc, string label, Action cb) { try { if (!((Object)(object)npc == (Object)null)) { NPC obj = NPC.Get(npc.ID); if (obj != null) { obj.Dialogue.OnChoiceSelected(label, cb); } } } catch (Exception ex) { Core.Log.Warning("[Questline] OnPick '" + label + "' failed: " + ex.Message); } } internal static void DumpNpcDiagnostics() { DumpState(); try { List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { Core.Log.Msg("[NPC-DIAG] NPCRegistry == null"); return; } string text = ""; for (int i = 0; i < nPCRegistry.Count; i++) { NPC val = nPCRegistry[i]; if (!((Object)(object)val == (Object)null)) { try { text = text + val.ID + " "; } catch { text += "(err) "; } } } Core.Log.Msg("[NPC-DIAG] registry(" + nPCRegistry.Count + "): " + text); DiagTarget("donna_martin"); DiagTarget("ming"); DiagTarget("marco_baron"); } catch (Exception ex) { Core.Log.Warning("[NPC-DIAG] dump failed: " + ex.Message); } } internal static void DumpState() { try { Core.Log.Msg("[STATE] Enabled=" + RVRepairVanPreferences.Enabled + " Questline=" + RVRepairVanPreferences.QuestlineEnabled + " Stage=" + Stage + " Samples=" + Samples + " Discount=" + DiscountTotal + " Active(IsDestroyed)=" + Active + " ExplosionBeatPassed=" + ExplosionBeatPassed()); LogQuest("Getting Started"); LogQuest("Welcome to Hyland Point"); try { Core.Log.Msg("[STATE] S1API NPC.All count = " + NPC.All.Count); } catch (Exception ex) { Core.Log.Msg("[STATE] S1API NPC.All threw: " + ex.Message); } } catch (Exception ex2) { Core.Log.Warning("[STATE] dump failed: " + ex2.Message); } } private static void LogQuest(string name) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) try { Quest quest = Quest.GetQuest(name); Core.Log.Msg("[STATE] Quest '" + name + "': " + (((Object)(object)quest == (Object)null) ? "NOT FOUND" : ("state=" + ((object)quest.State/*cast due to .constrained prefix*/).ToString()))); } catch (Exception ex) { Core.Log.Msg("[STATE] Quest '" + name + "' lookup threw: " + ex.Message); } } private static void DiagTarget(string id) { NPC val = FindNpc(id); if ((Object)(object)val == (Object)null) { Core.Log.Msg("[NPC-DIAG] " + id + ": NOT in registry"); return; } string text = "?"; bool flag = false; bool flag2 = false; try { text = ((Object)((Component)val).gameObject).name; flag = ((Component)val).gameObject.activeInHierarchy; } catch { } try { flag2 = (Object)(object)ControllerOf(val, out var _) != (Object)null; } catch { } Core.Log.Msg("[NPC-DIAG] " + id + ": IN registry, go='" + text + "' active=" + flag + " controllerFound=" + flag2); } private static void ResetDiag() { } private static DialogueController ControllerOf(NPC npc, out Transform t) { t = null; if ((Object)(object)npc == (Object)null) { return null; } try { t = ((Component)npc).transform; DialogueHandler dialogueHandler = npc.DialogueHandler; DialogueController val = null; if ((Object)(object)dialogueHandler != (Object)null) { val = ((Component)dialogueHandler).GetComponentInChildren<DialogueController>(true); } if ((Object)(object)val == (Object)null) { val = ((Component)npc).GetComponentInChildren<DialogueController>(true); } return val; } catch { return null; } } private static void OnAskDonna() { Stage = 2; SyncEntry(); } private static void OnAcceptErrand() { //IL_0028: 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_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (Stage == 2) { Stage = 3; _crateDrop = ReserveDeadDrop(((Object)(object)_mingT != (Object)null) ? _mingT.position : RvPos()); _cratePoint = ((_crateDrop != null) ? _crateDrop.Position : (((Object)(object)_mingT != (Object)null) ? (_mingT.position + new Vector3(8f, 0f, 8f)) : RvPos())); _cratePlaced = PlaceItem(_crateDrop, "rv_ming_crate"); SyncEntry(); } } private static void OnDeliverCrate() { if (Stage == 4) { if (_cratePlaced) { RemovePlayerItem("rv_ming_crate"); } Stage = 5; SyncEntry(); } } private static void OnMingPayLoss() { if (Stage == 4) { if (Money.GetCashBalance() < 500f) { WorldSay(_mingT, "Then don't come back until your hands are full."); return; } Money.ChangeCashBalance(-500f, true, true); Stage = 5; WorldSay(_mingT, "Smart. We're square. Now go see Marco at the body shop down by the docks, and tell him Mrs. Ming sent you."); SyncEntry(); } } private static void OnMarcoGreet() { Stage = 6; WorldSay(_marcoT, "Yeah, I can fix it. Fifty grand."); SyncEntry(); } private static void OnMarcoFifty() { WorldSay(_marcoT, "You brought me a burnt-out shell. That's not a repair, that's a resurrection."); } private static void OnMarcoReferral() { if (Stage == 6) { Stage = 7; RefreshRepairChoice(); WorldSay(_marcoT, "Mrs. Ming sent you? Yeah, alright. Should've opened with that. Ten grand."); SyncEntry(); } } private static void OnMarcoRepair() { try { if (!RVManager.IsDestroyed()) { WorldSay(_marcoT, "Your RV looks fine to me."); return; } int num = CurrentPrice(); if (Money.GetCashBalance() < (float)num) { WorldSay(_marcoT, "You're short. Come back when you've got the cash."); return; } Money.ChangeCashBalance((float)(-num), true, true); int paid = num; WorldSay(_marcoT, "Alright. Hold still, this won't take long."); RepairCinematic.Play(delegate { if (RVManager.Repair()) { RepairStateStore.SetRepaired(repaired: true); Stage = 9; SyncEntry(); Core.Log.Msg("[Questline] RV repaired for " + MoneyManager.FormatAmount((float)paid, false, false) + "."); } }, delegate { WorldSay(_marcoT, "There she is - back from the dead. Go take a look, and try not to total her again."); }, delegate { GruntNpc(FindNpc("marco_baron")); }); } catch (Exception ex) { Core.Log.Error("[Questline] repair failed: " + ex); } } private static void OnMarcoFavour() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) _pickupActive = true; _hasPackage = false; _drop = ReserveDeadDrop(((Object)(object)_marcoT != (Object)null) ? _marcoT.position : RvPos()); _dropPoint = ((_drop != null) ? _drop.Position : (((Object)(object)_marcoT != (Object)null) ? (_marcoT.position + new Vector3(10f, 0f, 10f)) : RvPos())); _pkgPlaced = PlaceItem(_drop, "rv_marco_package"); SyncEntry(); } private static void OnGotPackage() { if (_pkgPlaced) { RemovePlayerItem("rv_marco_package"); } _pickupActive = false; _hasPackage = false; _drop = null; Stage = 8; SyncEntry(); } private static void OnMarcoPayLoss() { if (_pickupActive && _hasPackage) { if (Money.GetCashBalance() < 500f) { WorldSay(_marcoT, "Clock's running. Come back with it."); return; } Money.ChangeCashBalance(-500f, true, true); _pickupActive = false; _hasPackage = false; _drop = null; Stage = 8; WorldSay(_marcoT, "Good. Mess like that gets forgotten when the cash shows up. Bring me some of that good stuff now and then, and I'll keep shaving down the bill."); SyncEntry(); } } private static void OnGiveSample() { try { PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; object obj; if (instance == null) { obj = null; } else { ItemInstance equippedItem = instance.EquippedItem; obj = ((equippedItem != null) ? ((Il2CppObjectBase)equippedItem).TryCast<ProductItemInstance>() : null); } ProductItemInstance val = (ProductItemInstance)obj; if (val == null || (Object)(object)val.AppliedPackaging == (Object)null) { WorldSay(_marcoT, "That ain't packaged. Hand me something sealed."); return; } int num = Mathf.Clamp(Mathf.RoundToInt(((BaseItemInstance)val).GetMonetaryValue()), RVRepairVanPreferences.MinSampleDiscount, RVRepairVanPreferences.MaxSampleDiscount); NPC val2 = FindNpc("marco_baron"); ConsumeProductBehaviour val3 = (((Object)(object)val2 != (Object)null && (Object)(object)val2.Behaviour != (Object)null) ? val2.Behaviour.ConsumeProductBehaviour : null); if ((Object)(object)instance != (Object)null && instance.equippedSlot != null) { _ = ((ItemSlot)instance.equippedSlot).Quantity; } if ((Object)(object)val3 != (Object)null) { val3.SendProduct(val, false); } if ((Object)(object)instance != (Object)null && instance.equippedSlot != null) { RemoveOneFromSlot((ItemSlot)(object)instance.equippedSlot); } if ((Object)(object)instance != (Object)null && instance.equippedSlot != null) { _ = ((ItemSlot)instance.equippedSlot).Quantity; } Samples++; DiscountTotal += num; RefreshRepairChoice(); WorldSay(_marcoT, "Appreciate it. Knocked " + MoneyManager.FormatAmount((float)num, false, false) + " off the bill."); } catch (Exception ex) { Core.Log.Warning("[Questline] give sample failed: " + ex.Message); } } private static DeadDropInstance ReserveDeadDrop(Vector3 origin) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) try { DeadDropInstance[] array = DeadDropManager.Empty; if (array == null || array.Length == 0) { array = DeadDropManager.All; } if (array == null || array.Length == 0) { return null; } DeadDropInstance result = null; float num = -1f; foreach (DeadDropInstance val in array) { if (val != null) { float num2 = Vector3.Distance(origin, val.Position); if (num2 > num) { num = num2; result = val; } } } return result; } catch (Exception ex) { Core.Log.Warning("[Questline] reserve dead drop failed: " + ex.Message); return null; } } private static void EnsureItems() { if (_itemsRegistered) { return; } try { RegisterItem("rv_ming_crate", "Ming's Crate", "A sealed crate for Mrs. Ming. She said not to open it.", new string[3] { "grainbag", "trashbag", "flashlight" }); RegisterItem("rv_marco_package", "Marco's Package", "A package Marco left at a drop. Don't make it weird.", new string[3] { "trashbag", "grainbag", "flashlight" }); _itemsRegistered = true; } catch (Exception ex) { Core.Log.Warning("[Questline] item register failed: " + ex.Message); } } private static void RegisterItem(string id, string name, string desc, string[] baseIds) { if (ItemManager.GetDefinition(id) != (ItemDefinition)null) { return; } StorableItemDefinition val = null; foreach (string text in baseIds) { try { val = ((StorableItemDefinitionBuilderBase<StorableItemDefinitionBuilder>)(object)((StorableItemDefinitionBuilderBase<StorableItemDefinitionBuilder>)(object)ItemCreator.CloneFrom(text)).WithBasicInfo(id, name, desc, (ItemCategory)3)).WithStackLimit(1).Build(); } catch (Exception) { continue; } break; } if ((ItemDefinition)(object)val == (ItemDefinition)null) { val = ItemCreator.CreateItem(id, name, desc, (ItemCategory)3, 1, 10f, 0.5f, (LegalStatus)0, (FullRank?)null, (Sprite)null, (Equippable)null); Core.Log.Warning("[Questline] quest item '" + id + "' registered WITHOUT a model/icon (no clone base usable)."); } try { ItemManager.PreserveRuntimeItem((ItemDefinition)(object)val); } catch { } } [Conditional("DEBUG")] private static void DumpItemsOnce() { //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (_itemsDumped) { return; } _itemsDumped = true; try { List<ItemDefinition> allItemDefinitions = ItemManager.GetAllItemDefinitions(); Core.Log.Msg("[ITEMDUMP] " + (allItemDefinitions?.Count ?? 0) + " registered items:"); if (allItemDefinitions == null) { return; } for (int i = 0; i < allItemDefinitions.Count; i++) { ItemDefinition val = allItemDefinitions[i]; if (!(val == (ItemDefinition)null)) { try { Core.Log.Msg("[ITEMDUMP] id='" + val.ID + "' name='" + val.Name + "' cat=" + ((object)val.Category/*cast due to .constrained prefix*/).ToString() + " icon=" + ((Object)(object)val.Icon != (Object)null)); } catch { } } } } catch (Exception ex) { Core.Log.Warning("[ITEMDUMP] failed: " + ex.Message); } } private static bool PlaceItem(DeadDropInstance drop, string id) { try { if (drop == null) { return false; } EnsureItems(); ItemDefinition definition = ItemManager.GetDefinition(id); if (definition == (ItemDefinition)null) { return false; } drop.Storage.AddItem(definition.CreateInstance(1)); return true; } catch (Exception ex) { Core.Log.Warning("[Questline] place item failed: " + ex.Message); return false; } } private static bool PlayerHasItem(string id) { try { PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; List<HotbarSlot> val = (((Object)(object)instance != (Object)null) ? instance.hotbarSlots : null); if (val == null) { return false; } for (int i = 0; i < val.Count; i++) { ItemSlot val2 = (ItemSlot)(object)val[i]; ItemInstance val3 = ((val2 != null) ? val2.ItemInstance : null); if (val3 != null && string.Equals(((BaseItemInstance)val3).ID, id, StringComparison.OrdinalIgnoreCase)) { return true; } } } catch { } return false; } private static void RemovePlayerItem(string id) { try { PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; List<HotbarSlot> val = (((Object)(object)instance != (Object)null) ? instance.hotbarSlots : null); if (val == null) { Core.Log.Warning("[Questline] remove '" + id + "': no hotbar slots."); return; } for (int i = 0; i < val.Count; i++) { ItemSlot val2 = (ItemSlot)(object)val[i]; ItemInstance val3 = ((val2 != null) ? val2.ItemInstance : null); if (val3 != null && string.Equals(((BaseItemInstance)val3).ID, id, StringComparison.OrdinalIgnoreCase)) { RemoveOneFromSlot(val2); return; } } Core.Log.Warning("[Questline] remove '" + id + "': not found in hotbar."); } catch (Exception ex) { Core.Log.Warning("[Questline] remove item failed: " + ex.Message); } } private static void RemoveOneFromSlot(ItemSlot slot) { if (slot != null) { ItemInstance itemInstance = slot.ItemInstance; if (((itemInstance != null) ? ((BaseItemInstance)itemInstance).Quantity : 0) > 1) { slot.ChangeQuantity(-1, false); } else { slot.ClearStoredInstance(false); } } } private static IEnumerator ProximityCoroutine() { int myGen = _gen; int waitedForLoad = 0; while (myGen == _gen) { yield return (object)new WaitForSeconds(1f); if (!RepairSave.Loaded) { int num = waitedForLoad + 1; waitedForLoad = num; if (num < 10) { continue; } if (waitedForLoad == 10) { Core.Log.Warning("[Questline] save state not loaded after 10s - proceeding with current values."); } } if (RVRepairVanPreferences.Enabled && Stage == 0 && !RepairStateStore.GetRepaired() && RVManager.IsDestroyed() && ExplosionBeatPassed()) { Stage = 1; EnsureQuest(); Core.Log.Msg("[Questline] quest started (wrecked RV + explosion beat passed)."); } if (Stage == 9 && RVManager.TryGetPosition(out var position) && Dist(PlayerPos(), position) < 14f) { Stage = 10; RepairQuest.CompleteIfActive(); WorldSay(_marcoT, "There she is. Standing again. Interior's your problem. Try not to piss off whoever torched it the first time."); Core.Log.Msg("[Questline] quest complete (RV checked)."); } if (!Active) { continue; } try { Vector3 a = PlayerPos(); if (Stage == 3) { if (_cratePoint == Vector3.zero) { _crateDrop = ReserveDeadDrop(((Object)(object)_mingT != (Object)null) ? _mingT.position : RvPos()); _cratePoint = ((_crateDrop != null) ? _crateDrop.Position : (((Object)(object)_mingT != (Object)null) ? (_mingT.position + new Vector3(8f, 0f, 8f)) : RvPos())); _cratePlaced = PlaceItem(_crateDrop, "rv_ming_crate"); SyncEntry(); } if ((!_cratePlaced) ? (_cratePoint != Vector3.zero && Dist(a, _cratePoint) < 5f) : (_crateDrop != null && _crateDrop.IsEmpty)) { Stage = 4; SyncEntry(); } } if (_pickupActive && !_hasPackage && ((!_pkgPlaced) ? (Dist(a, _dropPoint) < 5f) : (_drop != null && _drop.IsEmpty))) { _hasPackage = true; SyncEntry(); } } catch { } } } private static void EnsureQuest() { RepairQuest.StartIfNeeded(); SyncEntry(); } private static void SyncEntry() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) string title; Vector3 poi; if (_pickupActive) { title = (_hasPackage ? "Bring Marco's package back" : "Pick up Marco's package from the dead drop"); poi = (_hasPackage ? MarcoPos() : _dropPoint); } else { switch (Stage) { case 1: title = "Ask the motel manager about the RV"; poi = DonnaPos(); break; case 2: title = "Talk to Mrs. Ming at the Chinese restaurant"; poi = MingPos(); break; case 3: title = "Pick up Ming's crate from the dead drop"; poi = _cratePoint; break; case 4: title = "Bring Ming's crate back to Mrs. Ming"; poi = MingPos(); break; case 5: title = "Talk to Marco at the body shop"; poi = MarcoPos(); break; case 6: title = "Tell Marco Mrs. Ming sent you"; poi = MarcoPos(); break; case 7: case 8: title = "Pay Marco for the repair"; poi = MarcoPos(); break; case 9: title = "Check on the RV"; poi = RvPos(); break; default: title = "Find a way to repair your RV"; poi = RvPos(); break; } } RepairQuest.UpdateEntry(title, poi); } private static DialogueChoice AddChoice(DialogueController dc, string text, int prio, Func<bool> show, Action onChosen, DialogueContainer conv = null) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_002f: 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 { Enabled = true, ChoiceText = text, Conversation = conv, Priority = prio }; Func<bool, bool> func = delegate { try { return show(); } catch { return false; } }; val.shouldShowCheck = DelegateSupport.ConvertDelegate<ShouldShowCheck>((Delegate)func); val.onChoosen = new UnityEvent(); if (onChosen != null) { val.onChoosen.AddListener(UnityAction.op_Implicit(onChosen)); } dc.AddDialogueChoice(val, prio); return val; } private static bool HoldingPackaged() { try { PlayerInventory instance = PlayerSingleton<PlayerInventory>.Instance; object obj; if (instance == null) { obj = null; } else { ItemInstance equippedItem = instance.EquippedItem; obj = ((equippedItem != null) ? ((Il2CppObjectBase)equippedItem).TryCast<ProductItemInstance>() : null); } ProductItemInstance val = (ProductItemInstance)obj; return val != null && (Object)(object)val.AppliedPackaging != (Object)null; } catch { return false; } } private static void RefreshRepairChoice() { try { if (_marcoRepairChoice != null) { _marcoRepairChoice.ChoiceText = RepairChoiceText(); } } catch { } } internal static void RefreshPrice() { RefreshRepairChoice(); } private static string RepairChoiceText() { return "Repair my RV (" + MoneyManager.FormatAmount((float)CurrentPrice(), false, false) + ")"; } private static void WorldSay(Transform npc, string line) { try { if (!((Object)(object)npc == (Object)null)) { NPC componentInParent = ((Component)npc).GetComponentInParent<NPC>(); if ((Object)(object)componentInParent != (Object)null) { componentInParent.SendWorldSpaceDialogue(line, 5f); } } } catch { } } private static Vector3 PlayerPos() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0028: Unknown result type (might be due to invalid IL or missing references) try { Player local = Player.Local; if ((Object)(object)local != (Object)null) { return ((Component)local).transform.position; } } catch { } return Vector3.zero; } private static float Dist(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(a, b); } private static Vector3 RvPos() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!RVManager.TryGetPosition(out var position)) { return Vector3.zero; } return position; } private static Vector3 DonnaPos() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_donnaT != (Object)null)) { return RvPos(); } return _donnaT.position; } private static Vector3 MingPos() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_mingT != (Object)null)) { return RvPos(); } return _mingT.position; } private static Vector3 MarcoPos() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_marcoT != (Object)null)) { return RvPos(); } return _marcoT.position; } } internal static class RepairQuest { internal const string Title = "Back on the Road"; internal static bool IsActive() { try { return QuestManager.GetQuestByName("Back on the Road") != null; } catch { return false; } } internal static void StartIfNeeded() { try { if (IsActive()) { Core.Log.Msg("[Quest] 'Back on the Road' already active."); return; } QuestManager.CreateQuest<RepairRVQuest>((string)null); Core.Log.Msg("[Quest] 'Back on the Road' started."); } catch (Exception ex) { Core.Log.Warning("[Quest] start failed: " + ex.Message); } } internal static void UpdateEntry(string title, Vector3 poi) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) try { Quest questByName = QuestManager.GetQuestByName("Back on the Road"); if (questByName?.QuestEntries != null && questByName.QuestEntries.Count > 0) { QuestEntry obj = questByName.QuestEntries[0]; obj.Title = title; obj.POIPosition = poi; } } catch (Exception ex) { Core.Log.Warning("[Quest] update entry failed: " + ex.Message); } } internal static void CompleteIfActive() { try { Quest questByName = QuestManager.GetQuestByName("Back on the Road"); if (questByName == null) { return; } if (questByName.QuestEntries != null) { foreach (QuestEntry questEntry in questByName.QuestEntries) { try { questEntry.Complete(); } catch { } } } Core.Log.Msg("[Quest] 'Back on the Road' completed."); } catch (Exception ex) { Core.Log.Warning("[Quest] complete failed: " + ex.Message); } } } public class RepairRVQuest : Quest { private static Sprite _icon; private static bool _iconTried; protected override string Title => "Back on the Road"; protected override string Description => "Your RV's wrecked. Someone in Hyland Point has to know a guy."; protected override bool AutoBegin => true; protected override Sprite QuestIcon { get { if (!_iconTried) { _iconTried = true; _icon = LoadIcon(); } return _icon; } } private static Sprite LoadIcon() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("RVRepairVan.quest_icon.png"); if (stream == null) { Core.Log.Warning("[Quest] icon resource missing"); return null; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { filterMode = (FilterMode)1 }; ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array), false); return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } catch (Exception ex) { Core.Log.Warning("[Quest] icon load failed: " + ex.Message); return null; } } protected override void OnCreated() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) ((Registerable)this).OnCreated(); try { ((Quest)this).AddEntry("Ask the motel manager about the RV", (Vector3?)null).POIPosition = (RVManager.TryGetPosition(out var position) ? position : Vector3.zero); } catch (Exception ex) { Core.Log.Warning("[Quest] OnCreated failed: " + ex.Message); } } } } namespace RVRepairVan.Persistence { public class RepairSave : Saveable { [SaveableField("rv_repaired")] private bool _repaired; [SaveableField("rv_stage")] private int _stage; [SaveableField("rv_samples")] private int _samples; [SaveableField("rv_discount")] private int _discount; internal static RepairSave Instance { get; private set; } internal static bool Loaded { get; private set; } internal bool Repaired { get { return _repaired; } set { _repaired = value; } } internal int Stage { get { return _stage; } set { _stage = value; } } internal int Samples { get { return _samples; } set { _samples = value; } } internal int Discount { get { return _discount; } set { _discount = value; } } public RepairSave() { Instance = this; } internal static void BeginLoad() { Loaded = false; if (Instance != null) { Instance._repaired = false; Instance._stage = 0; Instance._samples = 0; Instance._discount = 0; } } protected override void OnLoaded() { Instance = this; Loaded = true; Core.Log.Msg($"[State] loaded: repaired={_repaired} stage={_stage} samples={_samples} discount={_discount}"); } protected override void OnCreated() { Instance = this; _repaired = false; _stage = 0; _samples = 0; _discount = 0; Loaded = true; Core.Log.Msg("[State] created (fresh save) - defaults applied."); } protected override void OnSaved() { Core.Log.Msg($"[State] saved: repaired={_repaired} stage={_stage} samples={_samples} discount={_discount}"); } } internal static class RepairStateStore { private static RepairSave S => RepairSave.Instance; internal static bool GetRepaired() { if (S != null) { return S.Repaired; } return false; } internal static void SetRepaired(bool repaired) { if (S != null) { S.Repaired = repaired; } } internal static int GetStage() { if (S == null) { return 0; } return S.Stage; } internal static void SetStage(int stage) { if (S != null) { S.Stage = stage; } } internal static int GetSamples() { if (S == null) { return 0; } return S.Samples; } internal static void SetSamples(int samples) { if (S != null) { S.Samples = samples; } } internal static int GetDiscountTotal() { if (S == null) { return 0; } return S.Discount; } internal static void SetDiscountTotal(int discount) { if (S != null) { S.Discount = discount; } } } } namespace RVRepairVan.Managers { internal static class RVManager { private static Transform _root; private static RV _rv; private static Transform _model; private static Transform _destroyed; private static Transform _cartelNote; internal static bool IsReady { get { if ((Object)(object)_root != (Object)null) { return (Object)(object)_rv != (Object)null; } return false; } } internal static bool TryGetPosition(out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; try { if (!TryLocate()) { return false; } position = _root.position; return true; } catch { return false; } } internal static void Reset() { _root = null; _rv = null; _model = null; _destroyed = null; _cartelNote = null; } [Conditional("DEBUG")] internal static void LogState() { try { Il2CppArrayBase<RV> val = Object.FindObjectsOfType<RV>(true); int num = val?.Length ?? 0; Core.Log.Msg($"[RVManager] DIAG: FindObjectsOfType<RV>(true) -> {num} RV component(s)"); for (int i = 0; i < num; i++) { RV val2 = val[i]; if (!((Object)(object)val2 == (Object)null)) { Transform transform = ((Component)val2).transform; bool value = false; bool value2 = false; try { value = val2.IsDestroyed; } catch { } try { value2 = val2._exploded; } catch { } string text = ""; for (int j = 0; j < transform.childCount; j++) { Transform child = transform.GetChild(j); text = text + ((Object)child).name + "[" + (((Component)child).gameObject.activeSelf ? "ON" : "off") + "] "; } Core.Log.Msg($"[RVManager] DIAG: RV#{i} path='{FullPath(transform)}' activeInHierarchy={((Component)transform).gameObject.activeInHierarchy} IsDestroyed={value} _exploded={value2}"); Core.Log.Msg($"[RVManager] DIAG: RV#{i} children -> {text}"); } } GameObject val3 = GameObject.Find("@Properties"); if ((Object)(object)val3 != (Object)null) { string text2 = ""; for (int k = 0; k < val3.transform.childCount; k++) { Transform child2 = val3.transform.GetChild(k); text2 = text2 + ((Object)child2).name + "[" + (((Component)child2).gameObject.activeSelf ? "ON" : "off") + "] "; } Core.Log.Msg("[RVManager] DIAG: @Properties children -> " + text2); } } catch (Exception ex) { Core.Log.Warning("[RVManager] DIAG failed: " + ex.Message); } } private static string FullPath(Transform t) { string text = ((Object)t).name; Transform parent = t.parent; int num = 0; while ((Object)(object)parent != (Object)null && num++ < 12) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } internal static bool TryLocate() { if (IsReady) { return true; } try { GameObject val = GameObject.Find("@Properties"); if ((Object)(object)val == (Object)null) { return false; } Transform val2 = val.transform.Find("RV"); if ((Object)(object)val2 == (Object)null) { return false; } RV component = ((Component)val2).GetComponent<RV>(); if ((Object)(object)component == (Object)null) { return false; } _root = val2; _rv = component; _model = val2.Find("RV"); _destroyed = val2.Find("Destroyed RV"); _cartelNote = (((Object)(object)_destroyed != (Object)null) ? _destroyed.Find("CartelNote") : null); return true; } catch (Exception ex) { Core.Log.Warning("[RVManager] locate failed: " + ex.Message); return false; } } internal static bool IsDestroyed() { try { if (!TryLocate()) { return false; } if ((Object)(object)_destroyed != (Object)null && ((Component)_destroyed).gameObject.activeSelf) { return true; } return _rv.IsDestroyed; } catch { return false; } } internal static bool Repair() { if (!TryLocate()) { Core.Log.Warning("[RVManager] RV not found - cannot repair."); return false; } try { _rv.IsDestroyed = false; } catch (Exception ex) { Core.Log.Warning("[RVManager] set IsDestroyed failed: " + ex.Message); } try { _rv._exploded = false; } catch { } try { if ((Object)(object)_model != (Object)null && !((Component)_model).gameObject.activeSelf) { ((Component)_model).gameObject.SetActive(true); } if ((Object)(object)_destroyed != (Object)null && ((Component)_destroyed).gameObject.activeSelf) { ((Component)_destroyed).gameObject.SetActive(false); } if ((Object)(object)_cartelNote != (Object)null && ((Component)_cartelNote).gameObject.activeSelf) { ((Component)_cartelNote).gameObject.SetActive(false); } } catch (Exception ex2) { Core.Log.Warning("[RVManager] visual swap failed: " + ex2.Message); } try { if ((Object)(object)_rv.FXContainer != (Object)null && !((Component)_rv.FXContainer).gameObject.activeSelf) { ((Component)_rv.FXContainer).gameObject.SetActive(true); } } catch { } Core.Log.Msg("[RVManager] RV repaired."); return true; } } } namespace RVRepairVan.Effects { internal static class RepairCinematic { private const float FadeTime = 0.6f; private const float HoldTime = 2.5f; private const float MidHoldDelay = 1.25f; private const float SoundVolume = 0.2f; private static AudioClip _clip; private static bool _clipTried; internal static void Play(Action doWhileBlack, Action onDone, Action onMidHold = null) { MelonCoroutines.Start(Run(doWhileBlack, onDone, onMidHold)); } internal static void ForceReset() { Fade(toBlack: false); LockInput(locked: false); } private static IEnumerator Run(Action doWhileBlack, Action onDone, Action onMidHold) { try { LockInput(locked: true); Fade(toBlack: true); yield return (object)new WaitForSeconds(0.6f); PlaySound(); yield return (object)new WaitForSeconds(1.25f); Safe(onMidHold); yield return (object)new WaitForSeconds(1.25f); Safe(doWhileBlack); Fade(toBlack: false); yield return (object)new WaitForSeconds(0.6f); Safe(onDone); } finally { Fade(toBlack: false); LockInput(locked: false); } } private static void Fade(bool toBlack) { try { if (Singleton<BlackOverlay>.InstanceExists) { BlackOverlay instance = Singleton<BlackOverlay>.Instance; if (toBlack) { instance.Open(0.6f); } else { instance.Close(0.6f); } } else if (Singleton<HUD>.InstanceExists) { Singleton<HUD>.Instance.SetBlackOverlayVisible(toBlack, 0.6f); } } catch (Exception ex) { Core.Log.Warning("[Cinematic] fade failed: " + ex.Message); } } private static void LockInput(bool locked) { try { PlayerMovement instance = PlayerSingleton<PlayerMovement>.Instance; if ((Object)(object)instance != (Object)null) { instance.CanMove = !locked; } } catch { } try { PlayerCamera instance2 = PlayerSingleton<PlayerCamera>.Instance; if ((Object)(object)instance2 != (Object)null) { instance2.SetCanLook(!locked); } } catch { } } private static void PlaySound() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) try { AudioClip val = EnsureClip(); if ((Object)(object)val == (Object)null) { return; } GameObject val2 = new GameObject("RVRepairSound"); val2.transform.position = PlayerPos(); AudioSource val3 = val2.AddComponent<AudioSource>(); val3.clip = val; val3.volume = 0.2f; val3.spatialBlend = 0f; try { AudioManager instance = Singleton<AudioManager>.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.MainGameMixer != (Object)null) { val3.outputAudioMixerGroup = instance.MainGameMixer; } } catch { } val3.Play(); Object.Destroy((Object)(object)val2, val.length + 0.3f); } catch (Exception ex) { Core.Log.Warning("[Cinematic] sound failed: " + ex.Message); } } private static Vector3 PlayerPos() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0028: Unknown result type (might be due to invalid IL or missing references) try { PlayerMovement instance = PlayerSingleton<PlayerMovement>.Instance; if ((Object)(object)instance != (Object)null) { return ((Component)instance).transform.position; } } catch { } return Vector3.zero; } private static AudioClip EnsureClip() { if (_clipTried) { return _clip; } _clipTried = true; try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("RVRepairVan.repair.wav"); if (stream == null) { Core.Log.Warning("[Cinematic] repair.wav resource missing"); return null; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } _clip = WavToClip(array); } catch (Exception ex) { Core.Log.Warning("[Cinematic] clip load failed: " + ex.Message); } return _clip; } private static AudioClip WavToClip(byte[] wav) { if (wav == null || wav.Length < 44) { return null; } int num = 1; int num2 = 22050; int num3 = 16; int num4 = -1; int num5 = 0; int num6 = 12; while (num6 + 8 <= wav.Length) { string text = Encoding.ASCII.GetString(wav, num6, 4); int num7 = BitConverter.ToInt32(wav, num6 + 4); int num8 = num6 + 8; if (text == "fmt " && num8 + 16 <= wav.Length) { num = BitConverter.ToInt16(wav, num8 + 2); num2 = BitConverter.ToInt32(wav, num8 + 4); num3 = BitConverter.ToInt16(wav, num8 + 14); } else if (text == "data") { num4 = num8; num5 = num7; } num6 = num8 + num7 + (num7 & 1); } if (num4 < 0 || num3 != 16 || num < 1) { Core.Log.Warning("[Cinematic] unsupported WAV (need 16-bit PCM)"); return null; } if (num4 + num5 > wav.Length) { num5 = wav.Length - num4; } int num9 = num5 / 2; float[] array = new float[num9]; for (int i = 0; i < num9; i++) { array[i] = (float)BitConverter.ToInt16(wav, num4 + i * 2) / 32768f; } AudioClip val = AudioClip.Create("rv_repair", num9 / num, num, num2, false); val.SetData(Il2CppStructArray<float>.op_Implicit(array), 0); Core.Log.Msg("[Cinematic] repair clip loaded (" + num9 / num + " frames, " + num + "ch, " + num2 + "Hz)."); return val; } private static void Safe(Action a) { if (a == null) { return; } try { a(); } catch (Exception ex) { Core.Log.Warning("[Cinematic] callback failed: " + ex.Message); } } } } namespace RVRepairVan.Dialogue { internal static class MarcoRepairDialogue { private static bool _injected; private static DialogueChoice _repairChoice; internal static void Reset() { _injected = false; _repairChoice = null; } internal static void RefreshPrice() { try { if (_repairChoice != null) { _repairChoice.ChoiceText = BuildChoiceText(); } } catch (Exception ex) { Core.Log.Warning("[Marco] price refresh failed: " + ex.Message); } } internal static IEnumerator SetupCoroutine() { for (int attempt = 0; attempt < 20; attempt++) { if (_injected) { break; } yield return (object)new WaitForSeconds(2f); TryInject(); } if (!_injected) { Core.Log.Warning("[Marco] Could not inject RV repair dialogue (Marco not found within ~40s)."); } } private static void TryInject() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown try { Marco val = FindMarco(); if ((Object)(object)val == (Object)null) { return; } DialogueController val2 = FindController(val); if (!((Object)(object)val2 == (Object)null)) { DialogueChoice val3 = new DialogueChoice { Enabled = true, ChoiceText = BuildChoiceText(), Conversation = null, Priority = 100 }; Func<bool, bool> func = (bool enabled) => enabled && RVManager.IsDestroyed(); val3.shouldShowCheck = DelegateSupport.ConvertDelegate<ShouldShowCheck>((Delegate)func); val3.onChoosen = new UnityEvent(); val3.onChoosen.AddListener(UnityAction.op_Implicit((Action)OnRepairChosen)); val2.AddDialogueChoice(val3, 100); _repairChoice = val3; _injected = true; Core.Log.Msg("[Marco] RV repair dialogue injected."); } } catch (Exception ex) { Core.Log.Warning("[Marco] inject attempt failed: " + ex.Message); } } private static Marco FindMarco() { Il2CppArrayBase<Marco> val = Object.FindObjectsOfType<Marco>(); if (val == null || val.Length == 0) { return null; } return val[0]; } private static DialogueController FindController(Marco marco) { try { DialogueHandler dialogueHandler = ((NPC)marco).DialogueHandler; if ((Object)(object)dialogueHandler != (Object)null) { DialogueController componentInChildren = ((Component)dialogueHandler).GetComponentInChildren<DialogueController>(); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren; } } } catch { } try { return ((Component)marco).GetComponentInChildren<DialogueController>(); } catch { return null; } } private static string BuildChoiceText() { return "Repair my RV (" + MoneyManager.FormatAmount((float)RVRepairVanPreferences.RepairPrice, false, false) + ")"; } private static void WorldSay(NPC npc, string line) { try { if ((Object)(object)npc != (Object)null) { npc.SendWorldSpaceDialogue(line, 5f); } } catch { } } private static void OnRepairChosen() { try { if (!RVRepairVanPreferences.Enabled) { return; } if (!RVManager.IsDestroyed()) { Core.Log.Msg("[Marco] RV is not destroyed - nothing to repair."); return; } float num = RVRepairVanPreferences.RepairPrice; if (Money.GetCashBalance() < num) { Core.Log.Msg("[Marco] Not enough cash for repair (" + MoneyManager.FormatAmount(num, false, false) + ")."); return; } Money.ChangeCashBalance(0f - num, true, true); float paid = num; Marco marco = FindMarco(); RepairCinematic.Play(delegate { if (RVManager.Repair()) { RepairStateStore.SetRepaired(repaired: true); RepairQuest.CompleteIfActive(); Core.Log.Msg("[Marco] RV repaired for " + MoneyManager.FormatAmount(paid, false, false) + "."); } }, delegate { WorldSay((NPC)(object)marco, "There she is - back from the dead. Try not to total her again."); }, delegate { Questline.GruntNpc((NPC)(object)marco); }); } catch (Exception ex) { Core.Log.Error("[Marco] repair failed: " + ex); } } } } namespace RVRepairVan.Config { internal static class RVRepairVanPreferences { private const string CategoryId = "RVRepairVan_01_Main"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry<bool> _enabled; private static MelonPreferences_Entry<int> _repairPrice; private static MelonPreferences_Entry<string> _questMode; private static MelonPreferences_Entry<int> _basePriceNoReferral; private static MelonPreferences_Entry<int> _basePriceWithReferral; private static MelonPreferences_Entry<int> _minSampleDiscount; private static MelonPreferences_Entry<int> _maxSampleDiscount; internal static bool Enabled => _enabled?.Value ?? true; internal static int RepairPrice => Mathf.Max(0, _repairPrice?.Value ?? 1500); internal static bool QuestlineEnabled => string.Equals(_questMode?.Value, "Questline", StringComparison.OrdinalIgnoreCase); internal static int BasePriceNoReferral => Mathf.Max(0, _basePriceNoReferral?.Value ?? 50000); internal static int BasePriceWithReferral => Mathf.Max(0, _basePriceWithReferral?.Value ?? 10000); internal static int MinSampleDiscount => Mathf.Max(0, _minSampleDiscount?.Value ?? 100); internal static int MaxSampleDiscount => Mathf.Max(MinSampleDiscount, _maxSampleDiscount?.Value ?? 500); internal static void Initialize() { if (_category == null) { _category = MelonPreferences.CreateCategory("RVRepairVan_01_Main", "RV Repair Van"); _enabled = CreateEntry("Enabled", defaultValue: true, "Enabled", "Enable the RV repair feature."); _repairPrice = CreateEntry("RepairPrice", 1500, "Repair Price", "Cash required to repair the RV (Simple mode, and the price floor)."); _questMode = CreateEntry("QuestMode", "Questline", "Quest Mode", "Questline (default) = the full Donna -> Ming -> Marco story (see docs/QUESTLINE.md). Simple = just talk to Marco and pay."); _basePriceNoReferral = CreateEntry("BasePriceNoReferral", 50000, "Questline: Price without referral", "Marco's price in Questline mode if you go straight to him."); _basePriceWithReferral = CreateEntry("BasePriceWithReferral", 10000, "Questline: Price with Ming's referral", "Marco's price in Questline mode after Mrs. Ming puts in a word."); _minSampleDiscount = CreateEntry("MinSampleDiscount", 100, "Questline: Min sample discount", "Smallest price cut a single free sample can give Marco (cheap product floors here)."); _maxSampleDiscount = CreateEntry("MaxSampleDiscount", 500, "Questline: Max sample discount", "Largest price cut a single free sample can give. Each packaged sample cuts the price by its value, clamped between min and max, down to the Repair Price floor."); } } private static MelonPreferences_Entry<T> CreateEntry<T>(string identifier, T defaultValue, string displayName, string description = null) { return _category.CreateEntry<T>(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null); } } }