using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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; using DooDesch.Localization; using DooDesch.Transition; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppFishNet.Managing; 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.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 MelonLoader.Utils; using Microsoft.CodeAnalysis; using RVRepairVan; using RVRepairVan.Config; using RVRepairVan.Dialogue; using RVRepairVan.Effects; using RVRepairVan.Localization; using RVRepairVan.Managers; using RVRepairVan.Net; using RVRepairVan.Net.Patches; 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.6.2", "DooDesch", "https://github.com/DooDesch-Mods/RVRepairVan")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DooDesch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © DooDesch")] [assembly: AssemblyFileVersion("2.6.2.0")] [assembly: AssemblyInformationalVersion("2.6.2+15f23afa4cee5b0c11188c792d68d9756cbf5378")] [assembly: AssemblyProduct("RVRepairVan")] [assembly: AssemblyTitle("RVRepairVan")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.6.2.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 DooDesch.Localization { internal static class L10n { private static readonly Dictionary> _tables = new Dictionary>(); private static Dictionary _active; private static string _lang; internal static string Language => _lang ?? (_lang = Detect()); private static string ModName => typeof(L10n).Assembly.GetName().Name; internal static void Register(string lang, Dictionary table) { _tables[lang] = table; _active = null; } internal static string T(string en) { if (_active == null) { _active = BuildActiveTable(); } if (!_active.TryGetValue(en, out var value)) { return en; } return value; } internal static string T(string en, params object[] args) { return string.Format(T(en), args); } private static Dictionary BuildActiveTable() { Dictionary value; Dictionary dictionary = (_tables.TryGetValue(Language, out value) ? new Dictionary(value) : new Dictionary()); try { ExportTemplate(); Overlay(dictionary, Path.Combine(PackDir(), Language + ".json")); Overlay(dictionary, Path.Combine(UserDir(), Language + ".json")); } catch (Exception ex) { MelonLogger.Warning("[L10n] user translations failed: " + ex.Message); } return dictionary; } private static void Overlay(Dictionary table, string file) { if (!File.Exists(file)) { return; } Dictionary dictionary = ParseJsonObject(File.ReadAllText(file)); if (dictionary == null) { MelonLogger.Warning("[L10n] ignoring invalid translation file (flat JSON object of \"english\": \"translated\" strings expected): " + file); return; } foreach (KeyValuePair item in dictionary) { table[item.Key] = item.Value; } } private static string PackDir() { return Path.Combine(MelonEnvironment.ModsDirectory, "Localization", ModName); } private static string UserDir() { return Path.Combine(MelonEnvironment.UserDataDirectory, "DooDesch", "Localization", ModName); } private static void ExportTemplate() { if (_tables.Count == 0) { return; } SortedSet sortedSet = new SortedSet(StringComparer.Ordinal); foreach (Dictionary value in _tables.Values) { foreach (string key in value.Keys) { sortedSet.Add(key); } } if (sortedSet.Count == 0) { return; } Directory.CreateDirectory(UserDir()); WriteTemplate("en", sortedSet, null); foreach (string key2 in _tables.Keys) { if (key2 != "en") { WriteTemplate(key2, sortedSet, _tables[key2]); } } } private static void WriteTemplate(string lang, SortedSet keys, Dictionary table) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n ").Append(Quote("_readme")).Append(": ") .Append(Quote("This file is regenerated on every game start - do not edit it. To change or add a translation, copy it to .json (for example " + lang + ".json), keep only the lines you want to change, and translate the VALUES only - the keys are the mod's English source strings and must stay unchanged. Keep placeholders like {0}. Set Language in MelonPreferences.cfg under [DooDesch] to force a language (auto = OS language). To share your translation as an installable mod, see https://github.com/DooDesch-Mods/ScheduleOne-L10n/wiki")) .Append(",\n"); foreach (string key in keys) { string value; string s = ((table != null && table.TryGetValue(key, out value)) ? value : key); stringBuilder.Append(" ").Append(Quote(key)).Append(": ") .Append(Quote(s)) .Append(",\n"); } stringBuilder.Length -= 2; stringBuilder.Append("\n}\n"); File.WriteAllText(Path.Combine(UserDir(), "_template." + lang + ".json"), stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } private static string Detect() { //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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Invalid comparison between Unknown and I4 //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Invalid comparison between Unknown and I4 //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Invalid comparison between Unknown and I4 //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected I4, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Invalid comparison between Unknown and I4 //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Invalid comparison between Unknown and I4 //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Invalid comparison between Unknown and I4 //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Invalid comparison between Unknown and I4 string text = "auto"; try { MelonPreferences_Category val = MelonPreferences.CreateCategory("DooDesch", "DooDesch Mods"); text = ((val.GetEntry("Language") ?? val.CreateEntry("Language", "auto", "Language", "Language for all DooDesch mods: auto (= OS language), en, de, ...", false, false, (ValueValidator)null, (string)null)).Value ?? "auto").Trim().ToLowerInvariant(); } catch { } if (text.Length > 0 && text != "auto") { return text; } SystemLanguage systemLanguage = Application.systemLanguage; if ((int)systemLanguage <= 15) { if ((int)systemLanguage == 6) { goto IL_0121; } if ((int)systemLanguage == 14) { return "fr"; } if ((int)systemLanguage == 15) { return "de"; } } else if ((int)systemLanguage <= 34) { switch (systemLanguage - 21) { default: if ((int)systemLanguage != 34) { break; } return "es"; case 0: return "it"; case 7: return "pt"; case 6: return "pl"; case 9: return "ru"; case 1: return "ja"; case 2: return "ko"; case 3: case 4: case 5: case 8: break; } } else { if ((int)systemLanguage == 37) { return "tr"; } if (systemLanguage - 40 <= 1) { goto IL_0121; } } return "en"; IL_0121: return "zh"; } private static Dictionary ParseJsonObject(string json) { if (json == null) { return null; } int i = 0; Dictionary dictionary = new Dictionary(); SkipWs(json, ref i); if (i >= json.Length || json[i] != '{') { return null; } i++; SkipWs(json, ref i); if (i < json.Length && json[i] == '}') { return dictionary; } while (true) { string text = ParseString(json, ref i); if (text == null) { return null; } SkipWs(json, ref i); if (i >= json.Length || json[i] != ':') { return null; } i++; SkipWs(json, ref i); string text2 = ParseString(json, ref i); if (text2 == null) { return null; } dictionary[text] = text2; SkipWs(json, ref i); if (i >= json.Length) { return null; } if (json[i] != ',') { break; } i++; SkipWs(json, ref i); if (i < json.Length && json[i] == '}') { return dictionary; } } if (json[i] != '}') { return null; } return dictionary; } private static void SkipWs(string s, ref int i) { while (i < s.Length && (char.IsWhiteSpace(s[i]) || s[i] == '\ufeff')) { i++; } } private static string ParseString(string s, ref int i) { SkipWs(s, ref i); if (i >= s.Length || s[i] != '"') { return null; } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < s.Length) { char c = s[i++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); break; case '\\': if (i >= s.Length) { return null; } switch (s[i++]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': if (i + 4 > s.Length) { return null; } try { stringBuilder.Append((char)Convert.ToInt32(s.Substring(i, 4), 16)); } catch { return null; } i += 4; break; default: return null; } break; } } return null; } private static string Quote(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } } namespace DooDesch.Transition { internal static class Veil { internal static class Ease { public static float In(float t) { t = Mathf.Clamp01(t); return t * t * t; } public static float Out(float t) { t = Mathf.Clamp01(t); float num = 1f - t; return 1f - num * num * num; } public static float InOut(float t) { t = Mathf.Clamp01(t); return t * t * (3f - 2f * t); } } internal static bool EyelidsAvailable { get { try { return Singleton.InstanceExists; } catch { return false; } } } internal static bool BlackAvailable { get { try { return Singleton.InstanceExists || Singleton.InstanceExists; } catch { return false; } } } private static EyelidOverlay Eyes() { try { return Singleton.InstanceExists ? Singleton.Instance : null; } catch { return null; } } internal static void SetEyelids(float openness) { EyelidOverlay val = Eyes(); if ((Object)(object)val == (Object)null) { return; } try { val.AutoUpdate = false; val.SetOpen(Mathf.Clamp01(openness)); } catch { } } internal static float CurrentEyelidOpen(float fallback = 1f) { EyelidOverlay val = Eyes(); if ((Object)(object)val == (Object)null) { return fallback; } try { return val.CurrentOpen; } catch { return fallback; } } internal static void ReleaseEyelids() { EyelidOverlay val = Eyes(); if ((Object)(object)val == (Object)null) { return; } try { val.AutoUpdate = true; } catch { } } internal static float EyelidTargetOpen() { return 1f; } internal static IEnumerator CloseEyelids(float dur) { float start = CurrentEyelidOpen(); if (dur <= 0f) { SetEyelids(0f); yield break; } for (float t = 0f; t < dur; t += Time.deltaTime) { SetEyelids(Mathf.Lerp(start, 0f, Ease.InOut(t / dur))); yield return null; } SetEyelids(0f); } internal static IEnumerator OpenEyelidsToTarget(float dur) { float target = EyelidTargetOpen(); if (dur <= 0f) { SetEyelids(target); ReleaseEyelids(); yield break; } for (float t = 0f; t < dur; t += Time.deltaTime) { SetEyelids(Mathf.Lerp(0f, target, Ease.Out(t / dur))); yield return null; } SetEyelids(target); ReleaseEyelids(); } internal static IEnumerator FadeEyelids(float target, float dur, bool releaseToGame) { if ((Object)(object)Eyes() == (Object)null) { yield break; } float start = CurrentEyelidOpen(target); if (dur > 0f) { for (float t = 0f; t < dur; t += Time.deltaTime) { SetEyelids(Mathf.Lerp(start, target, Ease.InOut(t / dur))); yield return null; } } SetEyelids(target); if (releaseToGame) { ReleaseEyelids(); } } internal static void BlackOpen(float t) { Black(toBlack: true, t); } internal static void BlackClose(float t) { Black(toBlack: false, t); } private static void Black(bool toBlack, float t) { try { if (Singleton.InstanceExists) { BlackOverlay instance = Singleton.Instance; if (toBlack) { instance.Open(t); } else { instance.Close(t); } } else if (Singleton.InstanceExists) { Singleton.Instance.SetBlackOverlayVisible(toBlack, t); } } catch (Exception ex) { ScreenTransition.Warn?.Invoke("Veil.Black failed: " + ex.Message); } } internal static void SetCanMove(bool canMove) { try { PlayerMovement instance = PlayerSingleton.Instance; if ((Object)(object)instance != (Object)null) { instance.CanMove = canMove; } } catch { } } internal static void SetCanLook(bool canLook) { try { PlayerCamera instance = PlayerSingleton.Instance; if ((Object)(object)instance != (Object)null) { instance.SetCanLook(canLook); } } catch { } } } internal enum VeilMode { Eyelids, Black, EyelidsAndBlack } internal struct TransitionRequest { public VeilMode Mechanism; public float CloseSeconds; public float OpenSeconds; public float HoldSeconds; public bool LockMovement; public bool LockCamera; public Action DuringBlack; public Action OnMidHold; public Action OnDone; public Func WaitUntilReady; public float WaitDeadlineSeconds; public Action WhileWaiting; public Func KeepClosedAfter; public string Key; } internal static class ScreenTransition { public static Action Warn = delegate(string msg) { try { MelonLogger.Warning(msg); } catch { } }; private static readonly HashSet Active = new HashSet(); public static bool IsRunning(string key) { return Active.Contains(Bucket(key)); } public static bool Play(TransitionRequest req) { string text = Bucket(req.Key); if (Active.Contains(text)) { return false; } bool flag = req.Mechanism == VeilMode.Eyelids || req.Mechanism == VeilMode.EyelidsAndBlack; bool flag2 = req.Mechanism == VeilMode.Black || req.Mechanism == VeilMode.EyelidsAndBlack; if ((!flag || !Veil.EyelidsAvailable) && (!flag2 || !Veil.BlackAvailable)) { Safe(req.DuringBlack); Safe(req.OnDone); return true; } Active.Add(text); try { MelonCoroutines.Start(Run(req, text, flag, flag2)); } catch (Exception ex) { Warn?.Invoke("ScreenTransition.Play start failed: " + ex.Message); Active.Remove(text); Safe(req.DuringBlack); Safe(req.OnDone); } return true; } public static void ForceReset(string key = null) { if (key == null) { Active.Clear(); } else { Active.Remove(Bucket(key)); } try { Veil.BlackClose(0f); Veil.SetEyelids(1f); Veil.ReleaseEyelids(); Veil.SetCanMove(canMove: true); Veil.SetCanLook(canLook: true); } catch (Exception ex) { Warn?.Invoke("ScreenTransition.ForceReset failed: " + ex.Message); } } private static IEnumerator Run(TransitionRequest req, string key, bool eye, bool black) { bool lockedMove = false; bool lockedLook = false; try { if (req.LockMovement) { Veil.SetCanMove(canMove: false); lockedMove = true; } if (req.LockCamera) { Veil.SetCanLook(canLook: false); lockedLook = true; } if (black) { Veil.BlackOpen(req.CloseSeconds); } if (eye) { yield return Veil.CloseEyelids(req.CloseSeconds); } else { yield return Wait(req.CloseSeconds); } Safe(req.DuringBlack); yield return null; yield return null; if (req.WaitUntilReady != null) { float deadline = Time.time + Mathf.Max(0f, req.WaitDeadlineSeconds); while (Time.time < deadline && !SafeReady(req.WaitUntilReady)) { Safe(req.WhileWaiting); yield return null; } Safe(req.WhileWaiting); } if (req.HoldSeconds > 0f) { float deadline = req.HoldSeconds * 0.5f; yield return Wait(deadline); Safe(req.OnMidHold); yield return Wait(req.HoldSeconds - deadline); } else { Safe(req.OnMidHold); } if (req.KeepClosedAfter == null || !SafeReady(req.KeepClosedAfter)) { if (black) { Veil.BlackClose(req.OpenSeconds); } if (eye) { yield return Veil.OpenEyelidsToTarget(req.OpenSeconds); } else { yield return Wait(req.OpenSeconds); } } Safe(req.OnDone); } finally { bool flag = req.KeepClosedAfter != null && SafeReady(req.KeepClosedAfter); if (eye) { if (flag) { Veil.SetEyelids(0f); } else { Veil.SetEyelids(Veil.EyelidTargetOpen()); Veil.ReleaseEyelids(); } } if (black && !flag) { Veil.BlackClose(0.25f); } if (lockedMove) { Veil.SetCanMove(canMove: true); } if (lockedLook) { Veil.SetCanLook(canLook: true); } Active.Remove(key); } } private static string Bucket(string key) { if (!string.IsNullOrEmpty(key)) { return key; } return "default"; } private static IEnumerator Wait(float sec) { if (!(sec <= 0f)) { for (float t = 0f; t < sec; t += Time.deltaTime) { yield return null; } } } private static bool SafeReady(Func f) { try { return f(); } catch { return false; } } private static void Safe(Action a) { if (a == null) { return; } try { a(); } catch (Exception ex) { Warn?.Invoke("ScreenTransition callback failed: " + ex.Message); } } } } 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(); German.Register(); ((MelonBase)this).HarmonyInstance.PatchAll(); NetworkBus.Init(((MelonBase)this).HarmonyInstance); 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(); Questline.InitNet(); 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); if (NetworkBus.Online && !NetworkBus.IsServer) { Log.Msg("[Restore] co-op client - deferring RV state to the host (no local restore)."); yield break; } 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 const int DEGRADED_AFTER_ATTEMPTS = 15; 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 bool _clientCheckedRv; private static int _gen; private static Transform _donnaT; private static Transform _mingT; private static Transform _marcoT; private static DialogueController _donnaDC; private static DialogueController _mingDC; private static DialogueController _marcoDC; private static int _injectedChoices; private static int _skippedChoices; private static DialogueChoice _marcoRepairChoice; private const int MAX_SAMPLE_CHOICES = 8; private static readonly DialogueChoice[] _sampleChoices = (DialogueChoice[])(object)new DialogueChoice[8]; private static readonly List _sampleSlots = new List(); private static bool _itemsDumped; private static bool _netWired; private static int Stage { get { return RepairStateStore.GetStage(); } set { RepairStateStore.SetStage(value); if (NetworkBus.Online && NetworkBus.IsServer) { NetworkBus.BroadcastToAll(RvOp.StageSync, value, DiscountTotal); } } } private static int Samples { get { return RepairStateStore.GetSamples(); } set { RepairStateStore.SetSamples(value); } } private static int DiscountTotal { get { return RepairStateStore.GetDiscountTotal(); } set { RepairStateStore.SetDiscountTotal(value); if (NetworkBus.Online && NetworkBus.IsServer) { NetworkBus.BroadcastToAll(RvOp.StageSync, Stage, 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) { if (!RVManager.IsDestroyed()) { if (NetworkBus.Online && !NetworkBus.IsServer && Stage >= 1) { return Stage < 9; } return false; } return true; } 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_005c: Unknown result type (might be due to invalid IL or missing references) _gen++; ResetDiag(); _donnaDone = (_mingDone = (_marcoDone = false)); _pickupActive = false; _hasPackage = false; _clientCheckedRv = false; _drop = null; _crateDrop = null; _cratePlaced = (_pkgPlaced = false); _dropPoint = Vector3.zero; _cratePoint = Vector3.zero; _donnaT = (_mingT = (_marcoT = null)); _donnaDC = (_mingDC = (_marcoDC = null)); _injectedChoices = (_skippedChoices = 0); _marcoRepairChoice = null; Array.Clear(_sampleChoices, 0, _sampleChoices.Length); _sampleSlots.Clear(); RepairStateStore.ResetClient(); } internal static void Start() { InitNet(); MelonCoroutines.Start(EnsureItemsCoroutine()); MelonCoroutines.Start(SetupCoroutine()); MelonCoroutines.Start(ProximityCoroutine()); MelonCoroutines.Start(RestoreCoroutine()); MelonCoroutines.Start(NetJoinCoroutine()); } private static IEnumerator RestoreCoroutine() { yield return (object)new WaitForSeconds(4f); if (NetworkBus.Online && !NetworkBus.IsServer) { yield break; } 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(attempt >= 15); if (!reported && attempt >= 20) { reported = true; } } if (myGen != _gen) { yield break; } try { if ((!NetworkBus.Online || NetworkBus.IsServer) && Active && Stage >= 1 && Stage < 10) { EnsureQuest(); } } catch (Exception ex) { Core.Log.Warning("[Questline] post-inject POI sync failed: " + ex.Message); } } private static void TryInject(bool degraded = false) { try { if (!_donnaDone) { _donnaDone = TryOne("donna_martin", out _donnaT, ref _donnaDC, InjectDonna, degraded); } if (!_mingDone) { _mingDone = TryOne("ming", out _mingT, ref _mingDC, InjectMing, degraded); } if (!_marcoDone) { _marcoDone = TryOne("marco_baron", out _marcoT, ref _marcoDC, InjectMarco, degraded); } } catch (Exception ex) { Core.Log.Warning("[Questline] inject attempt failed: " + ex.Message); } } private static bool TryOne(string id, out Transform t, ref DialogueController held, Func inject, bool degraded) { NPC val = FindNpc(id); DialogueController val2 = ControllerOf(val, out t); if ((Object)(object)val2 != (Object)null && inject(val2, val, degraded)) { held = val2; if (degraded) { Core.Log.Warning("[Questline] '" + id + "' injected WITHOUT its dialogue containers - the questline works but replies show as floating lines instead of in-conversation menus."); } return true; } return false; } private static bool InjectDonna(DialogueController donna, NPC npc, bool degraded) { DialogueContainer val = S1Container(npc, "rv_donna", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("Do I look like a mechanic, sweetheart? Go ask Mrs. Ming over at the Chinese place. She knows people."), (Action)null); }); if ((Object)(object)val == (Object)null && !degraded) { return false; } AddChoice(donna, L10n.T("My RV got blown up. Know anyone who can fix it?"), 90, () => Active && Stage == 1, OnAskDonna, val); return true; } private static bool InjectMing(DialogueController ming, NPC npc, bool degraded) { DialogueContainer val = S1Container(npc, "rv_ming_offer", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("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)delegate(ChoiceList c) { c.Add("MING_ACCEPT", L10n.T("I'll grab it."), "MING_ACCEPTED").Add("MING_DEFER", L10n.T("Not right now."), "MING_DEFERRED"); }).AddNode("MING_ACCEPTED", L10n.T("Good. Pick it up, bring it here, and don't open it."), (Action)null).AddNode("MING_DEFERRED", L10n.T("Then your RV can stay where it is."), (Action)null); }); DialogueContainer val2 = S1Container(npc, "rv_ming_deliver", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("Good. Go see Marco at the body shop down by the docks. Tell him Mrs. Ming sent you."), (Action)null); }); DialogueContainer val3 = S1Container(npc, "rv_ming_lost", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("You lost it? I don't lose things, and people who lose my things lose teeth. Five hundred buys you both back. Now."), (Action)delegate(ChoiceList c) { c.Add("MING_PAY", L10n.T("Pay ${0}", 500), (string)null).Add("MING_DEFER_LOSS", L10n.T("I'll get the money."), "MING_LOSS_DEFER"); }).AddNode("MING_LOSS_DEFER", L10n.T("Then don't come back until your hands are full."), (Action)null); }); if (!degraded && ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null)) { return false; } OnPick(npc, "MING_ACCEPT", OnAcceptErrand); AddChoice(ming, L10n.T("Donna said you might know someone who can fix my RV."), 92, () => Active && Stage == 2, ((Object)(object)val != (Object)null) ? null : ((Action)delegate { WorldSay(_mingT, L10n.T("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.")); OnAcceptErrand(); }), val); AddChoice(ming, L10n.T("Here's your crate."), 91, () => Active && Stage == 4 && PlayerHasItem("rv_ming_crate"), OnDeliverCrate, val2); OnPick(npc, "MING_PAY", OnMingPayLoss); AddChoice(ming, ((Object)(object)val3 != (Object)null) ? L10n.T("I lost your crate.") : L10n.T("I lost your crate. (Pay ${0})", 500), 90, () => Active && Stage == 4 && !PlayerHasItem("rv_ming_crate"), ((Object)(object)val3 != (Object)null) ? null : ((Action)delegate { WorldSay(_mingT, L10n.T("You lost it? I don't lose things, and people who lose my things lose teeth. Five hundred buys you both back. Now.")); OnMingPayLoss(); }), val3); return true; } private static bool InjectMarco(DialogueController marco, NPC npc, bool degraded) { DialogueContainer val = S1Container(npc, "rv_marco_favour", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("Maybe. I left a package at a dead drop nearby. Pick it up, bring it back, and don't make it weird."), (Action)null); }); DialogueContainer val2 = S1Container(npc, "rv_marco_gotpkg", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("Good. You can follow instructions. Bring me some of that good stuff now and then, and I'll keep shaving down the bill."), (Action)null); }); DialogueContainer val3 = S1Container(npc, "rv_marco_lost", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("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)delegate(ChoiceList c) { c.Add("MARCO_PAY", L10n.T("Pay ${0}", 500), (string)null).Add("MARCO_DEFER_LOSS", L10n.T("I'll get the money."), "MARCO_LOSS_DEFER"); }).AddNode("MARCO_LOSS_DEFER", L10n.T("Clock's running. Come back with it."), (Action)null); }); DialogueContainer val4 = S1Container(npc, "rv_marco_bring", delegate(DialogueContainerBuilder b) { b.AddNode("ENTRY", L10n.T("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)null); }); if (!degraded && ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null)) { return false; } AddChoice(marco, L10n.T("Can you fix my RV?"), 100, () => Active && Stage == 5, OnMarcoGreet); AddChoice(marco, L10n.T("Fifty grand?"), 99, () => Active && Stage == 6, OnMarcoFifty); AddChoice(marco, L10n.T("Mrs. Ming sent me."), 98, () => Active && Stage == 6, OnMarcoReferral); _marcoRepairChoice = AddChoice(marco, RepairChoiceText(), 97, () => Active && MarcoGreeted && Stage < 9, OnMarcoRepair); AddChoice(marco, L10n.T("Anything I can do to bring the price down?"), 96, () => Active && Stage == 7 && !_pickupActive, OnMarcoFavour, val); AddChoice(marco, L10n.T("Got your package."), 96, () => Active && _pickupActive && _hasPackage && PlayerHasItem("rv_marco_package"), OnGotPackage, val2); OnPick(npc, "MARCO_PAY", OnMarcoPayLoss); AddChoice(marco, ((Object)(object)val3 != (Object)null) ? L10n.T("I lost your package.") : L10n.T("I lost your package. (Pay ${0})", 500), 96, () => Active && _pickupActive && _hasPackage && !PlayerHasItem("rv_marco_package"), ((Object)(object)val3 != (Object)null) ? null : ((Action)delegate { WorldSay(_marcoT, L10n.T("You did what? You walk in here empty-handed and waste my time. Five hundred, or the next thing that goes missing is you.")); OnMarcoPayLoss(); }), val3); for (int num = 0; num < 8; num++) { int idx = num; _sampleChoices[idx] = AddChoice(marco, L10n.T("Give Marco a packaged sample"), 95 - idx, () => SampleChoiceVisible(idx), delegate { OnGiveSample(idx); }); } AddChoice(marco, L10n.T("What can I bring to lower the price?"), 94, () => Active && Trusted_ && Stage < 9 && !HoldingPackaged() && CurrentPrice() > RVRepairVanPreferences.RepairPrice, ((Object)(object)val4 != (Object)null) ? null : ((Action)delegate { WorldSay(_marcoT, L10n.T("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.")); }), val4); return true; } private static NPC FindNpc(string id) { try { List 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 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 val2 = (((Object)(object)dialogueHandler != (Object)null) ? dialogueHandler.dialogueContainers : null); if (val2 == null) { return 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 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(true); } if ((Object)(object)val == (Object)null) { val = ((Component)npc).GetComponentInChildren(true); } return val; } catch { return null; } } private static void OnAskDonna() { if (!RouteIntent(RvOp.AskDonna) && Stage == 1) { Stage = 2; SyncEntry(); } } private static void OnAcceptErrand() { //IL_0033: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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) if (!RouteIntent(RvOp.AcceptErrand) && 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() { RemovePlayerItem("rv_ming_crate"); if (!RouteIntent(RvOp.DeliverCrate)) { HostDeliverCrate(); } } private static void HostDeliverCrate() { if (Stage == 4) { Stage = 5; SyncEntry(); } } private static void OnMingPayLoss() { if (!RouteIntent(RvOp.MingPayLoss) && Stage == 4) { if (Money.GetCashBalance() < 500f) { WorldSay(_mingT, L10n.T("Then don't come back until your hands are full.")); return; } Money.ChangeCashBalance(-500f, true, true); Stage = 5; WorldSay(_mingT, L10n.T("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() { if (!RouteIntent(RvOp.MarcoGreet) && Stage == 5) { Stage = 6; WorldSay(_marcoT, L10n.T("Yeah, I can fix it. Fifty grand.")); SyncEntry(); } } private static void OnMarcoFifty() { WorldSay(_marcoT, L10n.T("You brought me a burnt-out shell. That's not a repair, that's a resurrection.")); } private static void OnMarcoReferral() { if (!RouteIntent(RvOp.MarcoReferral) && Stage == 6) { Stage = 7; RefreshRepairChoice(); WorldSay(_marcoT, L10n.T("Mrs. Ming sent you? Yeah, alright. Should've opened with that. Ten grand.")); SyncEntry(); } } private static void OnMarcoRepair() { try { if (!RVManager.IsDestroyed()) { WorldSay(_marcoT, L10n.T("Your RV looks fine to me.")); return; } int num = CurrentPrice(); if (Money.GetCashBalance() < (float)num) { WorldSay(_marcoT, L10n.T("You're short. Come back when you've got the cash.")); return; } WorldSay(_marcoT, L10n.T("Alright. Hold still, this won't take long.")); if (NetworkBus.Online && !NetworkBus.IsServer) { NetworkBus.SendToHost(RvOp.PayRepair); RepairCinematic.Play(null, delegate { WorldSay(_marcoT, L10n.T("There she is - back from the dead. Go take a look, and try not to total her again.")); }, delegate { GruntNpc(FindNpc("marco_baron")); }); return; } if (NetworkBus.IsServer) { HostPayRepair(withCinematic: true); return; } Money.ChangeCashBalance((float)(-num), true, true); int paid = num; 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, L10n.T("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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0094: 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_006f: 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_0088: 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) if (!RouteIntent(RvOp.MarcoFavour) && Stage == 7 && !_pickupActive) { _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() { RemovePlayerItem("rv_marco_package"); if (!RouteIntent(RvOp.GotPackage)) { HostGotPackage(); } } private static void HostGotPackage() { if (_pickupActive && _hasPackage) { _pickupActive = false; _hasPackage = false; _drop = null; Stage = 8; SyncEntry(); } } private static void OnMarcoPayLoss() { if (!RouteIntent(RvOp.MarcoPayLoss) && _pickupActive && _hasPackage) { if (Money.GetCashBalance() < 500f) { WorldSay(_marcoT, L10n.T("Clock's running. Come back with it.")); return; } Money.ChangeCashBalance(-500f, true, true); _pickupActive = false; _hasPackage = false; _drop = null; Stage = 8; WorldSay(_marcoT, L10n.T("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(int i) { try { RefreshSampleSlots(); ItemSlot val = ((i >= 0 && i < _sampleSlots.Count) ? _sampleSlots[i] : FindPackagedProductSlot()); object obj; if (val == null) { obj = null; } else { ItemInstance itemInstance = val.ItemInstance; obj = ((itemInstance != null) ? ((Il2CppObjectBase)itemInstance).TryCast() : null); } ProductItemInstance val2 = (ProductItemInstance)obj; if (val2 == null || (Object)(object)val2.AppliedPackaging == (Object)null) { WorldSay(_marcoT, L10n.T("That ain't packaged. Hand me something sealed.")); return; } int num = SampleUnitDiscount(val2); NPC val3 = FindNpc("marco_baron"); if (val != null) { _ = val.Quantity; } if ((Object)(object)val3 != (Object)null && (Object)(object)val3.Behaviour != (Object)null) { val3.Behaviour.ConsumeProduct(val2, false); } RemoveOneFromSlot(val); if (val != null) { _ = val.Quantity; } if (RouteIntent(RvOp.GiveSample, num)) { WorldSay(_marcoT, L10n.T("Appreciate it. Knocked {0} off the bill.", MoneyManager.FormatAmount((float)num, false, false))); } else { HostGiveSample(num); } } catch (Exception ex) { Core.Log.Warning("[Questline] give sample failed: " + ex.Message); } } private static int SampleUnitDiscount(ProductItemInstance p) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Max(1, ((BaseItemInstance)p).Quantity); return Mathf.Clamp(Mathf.RoundToInt(((BaseItemInstance)p).GetMonetaryValue() / (float)num * QualityMultiplier(((QualityItemInstance)p).Quality)), RVRepairVanPreferences.MinSampleDiscount, RVRepairVanPreferences.MaxSampleDiscount); } private static float QualityMultiplier(EQuality q) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected I4, but got Unknown return (int)q switch { 0 => 0.6f, 1 => 0.8f, 3 => 1.5f, 4 => 2f, _ => 1f, }; } private static void RefreshSampleSlots() { _sampleSlots.Clear(); PlayerInventory instance = PlayerSingleton.Instance; List val = ((instance != null) ? instance.GetAllInventorySlots() : null); if (val == null) { return; } for (int i = 0; i < val.Count; i++) { if (_sampleSlots.Count >= 8) { break; } ItemSlot val2 = val[i]; object obj; if (val2 == null) { obj = null; } else { ItemInstance itemInstance = val2.ItemInstance; obj = ((itemInstance != null) ? ((Il2CppObjectBase)itemInstance).TryCast() : null); } ProductItemInstance val3 = (ProductItemInstance)obj; if (val3 != null && (Object)(object)val3.AppliedPackaging != (Object)null) { _sampleSlots.Add(val2); } } } private static bool SampleChoiceVisible(int i) { if (!Active || !Trusted_ || Stage >= 9 || CurrentPrice() <= RVRepairVanPreferences.RepairPrice) { return false; } RefreshSampleSlots(); if (i >= _sampleSlots.Count) { return false; } ItemSlot obj = _sampleSlots[i]; object obj2; if (obj == null) { obj2 = null; } else { ItemInstance itemInstance = obj.ItemInstance; obj2 = ((itemInstance != null) ? ((Il2CppObjectBase)itemInstance).TryCast() : null); } ProductItemInstance val = (ProductItemInstance)obj2; if (val == null) { return false; } if (_sampleChoices[i] != null) { _sampleChoices[i].ChoiceText = SampleChoiceText(val); } return true; } private static string SampleChoiceText(ProductItemInstance p) { string text = L10n.T("product"); try { ItemDefinition definition = ((ItemInstance)p).Definition; if ((Object)(object)definition != (Object)null) { text = ((BaseItemDefinition)definition).Name; } } catch { } return L10n.T("Give Marco: {0} (-{1})", text, MoneyManager.FormatAmount((float)SampleUnitDiscount(p), false, false)); } private static void HostGiveSample(int discount) { Samples++; DiscountTotal += discount; RefreshRepairChoice(); WorldSay(_marcoT, L10n.T("Appreciate it. Knocked {0} off the bill.", MoneyManager.FormatAmount((float)discount, false, false))); } 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 { if (!(ItemManager.GetDefinition("grainbag") == (ItemDefinition)null)) { RegisterItem("rv_ming_crate", L10n.T("Ming's Crate"), L10n.T("A sealed crate for Mrs. Ming. She said not to open it."), new string[3] { "grainbag", "trashbag", "flashlight" }); RegisterItem("rv_marco_package", L10n.T("Marco's Package"), L10n.T("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 IEnumerator EnsureItemsCoroutine() { for (int i = 0; i < 30; i++) { if (_itemsRegistered) { break; } EnsureItems(); if (_itemsRegistered) { break; } yield return (object)new WaitForSeconds(1f); } } 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)(object)((StorableItemDefinitionBuilderBase)(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 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.Instance; List 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.Instance; List 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 (NetworkBus.Online && !NetworkBus.IsServer) { if (Stage == 9 && !_clientCheckedRv && RVManager.TryGetPosition(out var position) && Dist(PlayerPos(), position) < 14f) { _clientCheckedRv = true; NetworkBus.SendToHost(RvOp.CheckedRv); } else if ((Stage == 3 && PlayerHasItem("rv_ming_crate")) || (_pickupActive && !_hasPackage && PlayerHasItem("rv_marco_package"))) { NetworkBus.SendToHost(RvOp.ErrandItemPicked); } continue; } 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 position2) && Dist(PlayerPos(), position2) < 14f) { HostCheckedRv(); } if (!Active) { continue; } try { Vector3 a = PlayerPos(); if (Stage == 3) { if (_cratePoint == Vector3.zero && (Object)(object)_mingT != (Object)null) { _crateDrop = ReserveDeadDrop(_mingT.position); _cratePoint = ((_crateDrop != null) ? _crateDrop.Position : (_mingT.position + new Vector3(8f, 0f, 8f))); _cratePlaced = PlaceItem(_crateDrop, "rv_ming_crate"); SyncEntry(); } if (_cratePlaced ? PlayerHasItem("rv_ming_crate") : (_cratePoint != Vector3.zero && Dist(a, _cratePoint) < 5f)) { Stage = 4; SyncEntry(); } } if (_pickupActive && !_hasPackage && (_pkgPlaced ? PlayerHasItem("rv_marco_package") : (Dist(a, _dropPoint) < 5f))) { _hasPackage = true; SyncEntry(); } } catch { } } } private static void EnsureQuest() { RepairQuest.StartIfNeeded(); SyncEntry(); } private static void SyncEntry() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_00f9: 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_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) string text = null; string title; Vector3 val; if (_pickupActive) { title = (_hasPackage ? L10n.T("Bring Marco's package back") : L10n.T("Pick up Marco's package from the dead drop")); if (_hasPackage) { text = "marco_baron"; val = MarcoPos(); } else { val = _dropPoint; } } else { switch (Stage) { case 1: title = L10n.T("Ask the motel manager about the RV"); text = "donna_martin"; val = DonnaPos(); break; case 2: title = L10n.T("Talk to Mrs. Ming at the Chinese restaurant"); text = "ming"; val = MingPos(); break; case 3: title = L10n.T("Pick up Ming's crate from the dead drop"); val = _cratePoint; break; case 4: title = L10n.T("Bring Ming's crate back to Mrs. Ming"); text = "ming"; val = MingPos(); break; case 5: title = L10n.T("Talk to Marco at the body shop"); text = "marco_baron"; val = MarcoPos(); break; case 6: title = L10n.T("Tell Marco Mrs. Ming sent you"); text = "marco_baron"; val = MarcoPos(); break; case 7: case 8: title = L10n.T("Pay Marco for the repair"); text = "marco_baron"; val = MarcoPos(); break; case 9: title = L10n.T("Check on the RV"); val = RvPos(); break; default: title = L10n.T("Find a way to repair your RV"); val = RvPos(); break; } } if (text != null) { RepairQuest.UpdateEntry(title, FindNpc(text), val); } else { RepairQuest.UpdateEntry(title, val); } if (NetworkBus.Online && NetworkBus.IsServer) { HostBroadcastTransient(); } } private static DialogueChoice AddChoice(DialogueController dc, string text, int prio, Func show, Action onChosen, DialogueContainer conv = null) { //IL_004f: 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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown if ((Object)(object)conv == (Object)null && onChosen == null) { _skippedChoices++; Core.Log.Warning("[Questline] skipped a choice with neither reply nor action: '" + text + "'"); return null; } _injectedChoices++; DialogueChoice val = new DialogueChoice { Enabled = true, ChoiceText = text, Conversation = conv, Priority = prio }; Func func = delegate { try { return show(); } catch { return false; } }; val.shouldShowCheck = DelegateSupport.ConvertDelegate((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() { return FindPackagedProductSlot() != null; } private static ItemSlot FindPackagedProductSlot() { try { PlayerInventory instance = PlayerSingleton.Instance; List val = ((instance != null) ? instance.GetAllInventorySlots() : null); if (val == null) { return null; } for (int i = 0; i < val.Count; i++) { ItemSlot val2 = val[i]; object obj; if (val2 == null) { obj = null; } else { ItemInstance itemInstance = val2.ItemInstance; obj = ((itemInstance != null) ? ((Il2CppObjectBase)itemInstance).TryCast() : null); } ProductItemInstance val3 = (ProductItemInstance)obj; if (val3 != null && (Object)(object)val3.AppliedPackaging != (Object)null) { return val2; } } } catch { } return null; } private static void RefreshRepairChoice() { try { if (_marcoRepairChoice != null) { _marcoRepairChoice.ChoiceText = RepairChoiceText(); } } catch { } } internal static void RefreshPrice() { RefreshRepairChoice(); } private static string RepairChoiceText() { return L10n.T("Repair my RV ({0})", 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(); 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 void InitNet() { if (!_netWired) { _netWired = true; NetworkBus.OnServerIntent = OnServerIntent; NetworkBus.OnClientState = OnClientState; } } private static bool RouteIntent(RvOp op, int a = 0, int b = 0) { if (NetworkBus.Online && !NetworkBus.IsServer) { NetworkBus.SendToHost(op, a, b); return true; } return false; } private static IEnumerator NetJoinCoroutine() { for (int i = 0; i < 60; i++) { yield return (object)new WaitForSeconds(0.5f); if (NetworkBus.Online) { break; } } if (NetworkBus.Online && !NetworkBus.IsServer) { NetworkBus.SendToHost(RvOp.RequestSnapshot); yield return (object)new WaitForSeconds(2f); NetworkBus.SendToHost(RvOp.RequestSnapshot); } } private static void OnServerIntent(RvMsg m) { if (!NetworkBus.IsServer) { return; } try { switch (m.Op) { case RvOp.AskDonna: OnAskDonna(); break; case RvOp.AcceptErrand: OnAcceptErrand(); break; case RvOp.DeliverCrate: HostDeliverCrate(); break; case RvOp.MingPayLoss: OnMingPayLoss(); break; case RvOp.MarcoGreet: OnMarcoGreet(); break; case RvOp.MarcoReferral: OnMarcoReferral(); break; case RvOp.MarcoFavour: OnMarcoFavour(); break; case RvOp.GotPackage: HostGotPackage(); break; case RvOp.MarcoPayLoss: OnMarcoPayLoss(); break; case RvOp.GiveSample: HostGiveSample(m.A); break; case RvOp.PayRepair: HostPayRepair(withCinematic: false); break; case RvOp.CheckedRv: HostCheckedRv(); break; case RvOp.ErrandItemPicked: HostErrandItemPicked(); break; case RvOp.RequestSnapshot: HostSendSnapshot(); break; } } catch (Exception ex) { Core.Log.Warning("[Net] server intent " + m.Op.ToString() + " failed: " + ex.Message); } } private static void OnClientState(RvMsg m) { try { switch (m.Op) { case RvOp.StageSync: ApplyStageSync(m.A, m.B); break; case RvOp.TransientSync: ApplyTransient(m.A, m.B, m.C); break; case RvOp.RepairApplied: ApplyRepair(); break; } } catch (Exception ex) { Core.Log.Warning("[Net] client state " + m.Op.ToString() + " failed: " + ex.Message); } } private static void HostSendSnapshot() { NetworkBus.BroadcastToAll(RvOp.StageSync, Stage, DiscountTotal); HostBroadcastTransient(); if (RepairStateStore.GetRepaired()) { NetworkBus.BroadcastToAll(RvOp.RepairApplied); } HostResyncErrandItem(); } private static void HostResyncErrandItem() { try { if (Stage == 3 && _crateDrop != null && _cratePlaced) { _crateDrop.Storage.RemoveAllOfDefinition("rv_ming_crate"); PlaceItem(_crateDrop, "rv_ming_crate"); } if (_pickupActive && !_hasPackage && _drop != null && _pkgPlaced) { _drop.Storage.RemoveAllOfDefinition("rv_marco_package"); PlaceItem(_drop, "rv_marco_package"); } } catch (Exception ex) { Core.Log.Warning("[Net] errand item re-sync failed: " + ex.Message); } } private static void ApplyStageSync(int stage, int discount) { if (!NetworkBus.IsServer) { RepairStateStore.SetStage(stage); RepairStateStore.SetDiscountTotal(discount); if (stage >= 10) { RepairQuest.CompleteIfActive(); } else if (Active && stage >= 1) { EnsureQuest(); } SyncEntry(); RefreshRepairChoice(); } } private static void HostBroadcastTransient() { //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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) int a = (_pickupActive ? 1 : 0) | (_hasPackage ? 2 : 0); Vector3 val = Vector3.zero; if (_pickupActive) { val = ((_drop != null) ? _drop.Position : _dropPoint); } else if (Stage == 3) { val = ((_crateDrop != null) ? _crateDrop.Position : _cratePoint); } NetworkBus.BroadcastToAll(RvOp.TransientSync, a, Mathf.RoundToInt(val.x), Mathf.RoundToInt(val.z)); } private static void ApplyTransient(int flags, int x, int z) { //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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (!NetworkBus.IsServer) { _pickupActive = (flags & 1) != 0; _hasPackage = (flags & 2) != 0; DeadDropInstance val = ResolveDropByPos(x, z); Vector3 val2 = (Vector3)((val != null) ? val.Position : ((x == 0 && z == 0) ? Vector3.zero : new Vector3((float)x, 0f, (float)z))); if (_pickupActive) { _drop = val; _pkgPlaced = val != null; _dropPoint = val2; } else { _crateDrop = val; _cratePlaced = val != null; _cratePoint = val2; } SyncEntry(); } } private static DeadDropInstance ResolveDropByPos(int x, int z) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) try { if (x == 0 && z == 0) { return null; } DeadDropInstance[] all = DeadDropManager.All; if (all == null) { return null; } DeadDropInstance val = null; float num = float.MaxValue; for (int i = 0; i < all.Length; i++) { if (all[i] != null) { float num2 = all[i].Position.x - (float)x; float num3 = all[i].Position.z - (float)z; float num4 = num2 * num2 + num3 * num3; if (num4 < num) { num = num4; val = all[i]; } } } return (num <= 9f) ? val : null; } catch { return null; } } private static void HostErrandItemPicked() { if (Stage == 3) { Stage = 4; SyncEntry(); } else if (_pickupActive && !_hasPackage) { _hasPackage = true; SyncEntry(); } } private static void HostCheckedRv() { if (Stage == 9) { Stage = 10; RepairQuest.CompleteIfActive(); WorldSay(_marcoT, L10n.T("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)."); } } private static void ApplyRepair() { if (!NetworkBus.IsServer) { RVManager.RepairVisualOnly(); RepairStateStore.SetRepaired(repaired: true); RepairQuest.CompleteIfActive(); } } internal static void HostPayRepair(bool withCinematic) { try { if (!RVManager.IsDestroyed()) { return; } int num = (RVRepairVanPreferences.QuestlineEnabled ? CurrentPrice() : RVRepairVanPreferences.RepairPrice); if (Money.GetCashBalance() < (float)num) { Core.Log.Msg("[Net] host PayRepair rejected - shared pool short (" + MoneyManager.FormatAmount((float)num, false, false) + ")."); return; } Money.ChangeCashBalance((float)(-num), true, true); int paid = num; Action commit = delegate { if (RVManager.Repair()) { RepairStateStore.SetRepaired(repaired: true); NetworkBus.BroadcastToAll(RvOp.RepairApplied); if (RVRepairVanPreferences.QuestlineEnabled) { Stage = 9; SyncEntry(); } else { RepairQuest.CompleteIfActive(); } Core.Log.Msg("[Net] host repaired the RV for " + MoneyManager.FormatAmount((float)paid, false, false) + " (broadcast to clients)."); } }; if (withCinematic) { RepairCinematic.Play(delegate { commit(); }, delegate { WorldSay(_marcoT, L10n.T("There she is - back from the dead. Go take a look, and try not to total her again.")); }, delegate { GruntNpc(FindNpc("marco_baron")); }); } else { commit(); } } catch (Exception ex) { Core.Log.Error("[Net] host repair failed: " + ex); } } } internal static class RepairQuest { internal static readonly string Title = L10n.T("Back on the Road"); internal static bool IsActive() { try { return QuestManager.GetQuestByName(Title) != null; } catch { return false; } } internal static bool HasEntry() { try { Quest questByName = QuestManager.GetQuestByName(Title); return questByName?.QuestEntries != null && questByName.QuestEntries.Count > 0; } catch { return false; } } internal static void StartIfNeeded() { try { if (IsActive()) { Core.Log.Msg("[Quest] '" + Title + "' already active."); return; } QuestManager.CreateQuest((string)null); Core.Log.Msg("[Quest] '" + Title + "' 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(Title); 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 UpdateEntry(string title, NPC npc, Vector3 fallback) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) try { Quest questByName = QuestManager.GetQuestByName(Title); if (questByName?.QuestEntries != null && questByName.QuestEntries.Count != 0) { QuestEntry val = questByName.QuestEntries[0]; val.Title = title; NPC val2 = (((Object)(object)npc != (Object)null) ? NPC.Get(npc.ID) : null); if (val2 == null || !val.SetPOIToNPC(val2)) { val.POIPosition = fallback; } } } catch (Exception ex) { Core.Log.Warning("[Quest] update entry failed: " + ex.Message); } } internal static void CompleteIfActive() { try { Quest questByName = QuestManager.GetQuestByName(Title); if (questByName == null) { return; } if (questByName.QuestEntries != null) { foreach (QuestEntry questEntry in questByName.QuestEntries) { try { questEntry.Complete(); } catch { } } } Core.Log.Msg("[Quest] '" + Title + "' 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 => RepairQuest.Title; protected override string Description => L10n.T("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.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_002f: 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) ((Registerable)this).OnCreated(); try { ((Quest)this).AddEntry(L10n.T("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 bool _cRepaired; private static int _cStage; private static int _cSamples; private static int _cDiscount; private static RepairSave S => RepairSave.Instance; private static bool ClientMode { get { if (NetworkBus.Online) { return !NetworkBus.IsServer; } return false; } } internal static void ResetClient() { _cRepaired = false; _cStage = 0; _cSamples = 0; _cDiscount = 0; } internal static bool GetRepaired() { if (!ClientMode) { if (S != null) { return S.Repaired; } return false; } return _cRepaired; } internal static void SetRepaired(bool repaired) { if (ClientMode) { _cRepaired = repaired; } else if (S != null) { S.Repaired = repaired; } } internal static int GetStage() { if (!ClientMode) { if (S == null) { return 0; } return S.Stage; } return _cStage; } internal static void SetStage(int stage) { if (ClientMode) { _cStage = stage; } else if (S != null) { S.Stage = stage; } } internal static int GetSamples() { if (!ClientMode) { if (S == null) { return 0; } return S.Samples; } return _cSamples; } internal static void SetSamples(int samples) { if (ClientMode) { _cSamples = samples; } else if (S != null) { S.Samples = samples; } } internal static int GetDiscountTotal() { if (!ClientMode) { if (S == null) { return 0; } return S.Discount; } return _cDiscount; } internal static void SetDiscountTotal(int discount) { if (ClientMode) { _cDiscount = discount; } else if (S != null) { S.Discount = discount; } } } } namespace RVRepairVan.Net { internal static class NetworkBus { internal static Action OnServerIntent; internal static Action OnClientState; private static NetworkManager Nm { get { try { return InstanceFinder.NetworkManager; } catch { return null; } } } internal static bool Online { get { NetworkManager nm = Nm; try { return (Object)(object)nm != (Object)null && (nm.IsServer || nm.IsClient); } catch { return false; } } } internal static bool IsServer { get { NetworkManager nm = Nm; try { return (Object)(object)nm != (Object)null && nm.IsServer; } catch { return false; } } } private static QuestManager Qm { get { try { return NetworkSingleton.Instance; } catch { return null; } } } internal static void SendToHost(RvOp op, int a = 0, int b = 0, int c = 0) { try { QuestManager qm = Qm; if ((Object)(object)qm == (Object)null) { Core.Log.Warning("[Net] no QuestManager - intent " + op.ToString() + " dropped."); } else { qm.SendQuestState(RvMsg.Encode(op, a, b, c), (EQuestState)0); } } catch (Exception ex) { Core.Log.Warning("[Net] SendToHost failed: " + ex.Message); } } internal static void BroadcastToAll(RvOp op, int a = 0, int b = 0, int c = 0) { try { QuestManager qm = Qm; if (!((Object)(object)qm == (Object)null)) { qm.ReceiveQuestState((NetworkConnection)null, RvMsg.Encode(op, a, b, c), (EQuestState)0); } } catch (Exception ex) { Core.Log.Warning("[Net] BroadcastToAll failed: " + ex.Message); } } internal static void DispatchServerIntent(RvMsg m) { try { OnServerIntent?.Invoke(m); } catch (Exception ex) { Core.Log.Warning("[Net] server dispatch failed: " + ex.Message); } } internal static void DispatchClientState(RvMsg m) { try { if (IsServer) { OnServerIntent?.Invoke(m); } else { OnClientState?.Invoke(m); } } catch (Exception ex) { Core.Log.Warning("[Net] dispatch failed: " + ex.Message); } } internal static void Init(Harmony h) { QuestManagerNetPatches.Apply(h); } } internal enum RvOp { AcceptErrand = 0, DeliverCrate = 1, MingPayLoss = 2, MarcoGreet = 3, MarcoReferral = 4, MarcoFavour = 5, GotPackage = 6, MarcoPayLoss = 7, GiveSample = 8, PayRepair = 9, RequestSnapshot = 10, AskDonna = 11, CheckedRv = 12, ErrandItemPicked = 13, StageSync = 100, RepairApplied = 101, TransientSync = 102, Ping = 200 } internal struct RvMsg { internal const string Prefix = "RVRV:"; internal RvOp Op; internal int A; internal int B; internal int C; internal static string Encode(RvOp op, int a = 0, int b = 0, int c = 0) { string[] obj = new string[8] { "RVRV:", null, null, null, null, null, null, null }; int num = (int)op; obj[1] = num.ToString(); obj[2] = ":"; obj[3] = a.ToString(); obj[4] = ":"; obj[5] = b.ToString(); obj[6] = ":"; obj[7] = c.ToString(); return string.Concat(obj); } internal static bool TryDecode(string guid, out RvMsg msg) { msg = default(RvMsg); if (string.IsNullOrEmpty(guid) || !guid.StartsWith("RVRV:", StringComparison.Ordinal)) { return false; } try { string[] array = guid.Substring("RVRV:".Length).Split(':'); if (array.Length < 1 || !int.TryParse(array[0], out var result)) { return false; } msg.Op = (RvOp)result; if (array.Length > 1) { int.TryParse(array[1], out msg.A); } if (array.Length > 2) { int.TryParse(array[2], out msg.B); } if (array.Length > 3) { int.TryParse(array[3], out msg.C); } return true; } catch { return false; } } public override string ToString() { return Op.ToString() + "(" + A + "," + B + "," + C + ")"; } } } namespace RVRepairVan.Net.Patches { internal static class QuestManagerNetPatches { internal static void Apply(Harmony h) { TryPatch(h, "RpcLogic___SendQuestState", "SendStatePrefix"); TryPatch(h, "RpcLogic___ReceiveQuestState", "ReceiveStatePrefix"); } private static void TryPatch(Harmony h, string namePrefix, string prefixMethod) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown try { MethodInfo methodInfo = typeof(QuestManager).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name.StartsWith(namePrefix, StringComparison.Ordinal)); if (methodInfo == null) { Core.Log.Warning("[Net] could not find " + namePrefix + " - co-op sync disabled."); return; } MethodInfo method = typeof(QuestManagerNetPatches).GetMethod(prefixMethod, BindingFlags.Static | BindingFlags.NonPublic); h.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Core.Log.Warning("[Net] patch " + namePrefix + " failed: " + ex.Message); } } private static bool SendStatePrefix(string __0) { if (RvMsg.TryDecode(__0, out var msg)) { NetworkBus.DispatchServerIntent(msg); return false; } return true; } private static bool ReceiveStatePrefix(string __1) { if (RvMsg.TryDecode(__1, out var msg)) { NetworkBus.DispatchClientState(msg); return false; } return true; } } } 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 val = Object.FindObjectsOfType(true); int num = val?.Length ?? 0; Core.Log.Msg($"[RVManager] DIAG: FindObjectsOfType(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(); 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 { } return RepairVisualOnly(); } internal static bool RepairVisualOnly() { if (!TryLocate()) { Core.Log.Warning("[RVManager] RV not found - cannot repair (visual)."); return false; } 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 ex) { Core.Log.Warning("[RVManager] visual swap failed: " + ex.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 (visual)."); return true; } } } namespace RVRepairVan.Localization { internal static class German { internal static void Register() { L10n.Register("de", new Dictionary { ["Back on the Road"] = "Wieder auf Achse", ["Your RV's wrecked. Someone in Hyland Point has to know a guy."] = "Dein Wohnmobil ist Schrott. Irgendwer in Hyland Point kennt doch sicher jemanden.", ["Find a way to repair your RV"] = "Finde einen Weg, dein Wohnmobil zu reparieren", ["Ask the motel manager about the RV"] = "Frag die Motel-Managerin nach dem Wohnmobil", ["Talk to Mrs. Ming at the Chinese restaurant"] = "Sprich mit Mrs. Ming im Chinarestaurant", ["Pick up Ming's crate from the dead drop"] = "Hol Mings Kiste am Toten Briefkasten ab", ["Bring Ming's crate back to Mrs. Ming"] = "Bring Mings Kiste zurück zu Mrs. Ming", ["Talk to Marco at the body shop"] = "Sprich mit Marco in der Werkstatt", ["Tell Marco Mrs. Ming sent you"] = "Sag Marco, dass Mrs. Ming dich schickt", ["Pick up Marco's package from the dead drop"] = "Hol Marcos Paket am Toten Briefkasten ab", ["Bring Marco's package back"] = "Bring Marcos Paket zurück", ["Pay Marco for the repair"] = "Bezahl Marco für die Reparatur", ["Check on the RV"] = "Sieh nach dem Wohnmobil", ["My RV got blown up. Know anyone who can fix it?"] = "Mein Wohnmobil wurde in die Luft gejagt. Kennst du jemanden, der es reparieren kann?", ["Do I look like a mechanic, sweetheart? Go ask Mrs. Ming over at the Chinese place. She knows people."] = "Seh ich aus wie ein Mechaniker, Schätzchen? Frag Mrs. Ming drüben beim Chinesen. Die kennt Leute.", ["Donna said you might know someone who can fix my RV."] = "Donna meinte, du kennst vielleicht jemanden, der mein Wohnmobil reparieren kann.", ["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."] = "Marco am Hafen kann fast alles reparieren. Aber Gefallen beruhen auf Gegenseitigkeit. In einem Toten Briefkasten in der Nähe wartet eine Kiste auf mich. Bring sie her, und ich lege ein gutes Wort ein.", ["I'll grab it."] = "Ich hole sie.", ["Not right now."] = "Jetzt gerade nicht.", ["Good. Pick it up, bring it here, and don't open it."] = "Gut. Hol sie ab, bring sie her, und mach sie nicht auf.", ["Then your RV can stay where it is."] = "Dann bleibt dein Wohnmobil eben, wo es ist.", ["Here's your crate."] = "Hier ist deine Kiste.", ["Good. Go see Marco at the body shop down by the docks. Tell him Mrs. Ming sent you."] = "Gut. Geh zu Marco in der Werkstatt unten am Hafen. Sag ihm, Mrs. Ming schickt dich.", ["I lost your crate."] = "Ich habe deine Kiste verloren.", ["You lost it? I don't lose things, and people who lose my things lose teeth. Five hundred buys you both back. Now."] = "Du hast sie verloren? Ich verliere nichts, und wer meine Sachen verliert, verliert Zähne. Fünfhundert bringen dich wieder ins Reine. Sofort.", ["Smart. We're square. Now go see Marco at the body shop down by the docks, and tell him Mrs. Ming sent you."] = "Klug. Wir sind quitt. Jetzt geh zu Marco in der Werkstatt unten am Hafen und sag ihm, Mrs. Ming schickt dich.", ["Then don't come back until your hands are full."] = "Dann komm erst wieder, wenn deine Hände voll sind.", ["Can you fix my RV?"] = "Kannst du mein Wohnmobil reparieren?", ["Yeah, I can fix it. Fifty grand."] = "Klar kann ich das reparieren. Fünfzig Riesen.", ["Fifty grand?"] = "Fünfzig Riesen?", ["You brought me a burnt-out shell. That's not a repair, that's a resurrection."] = "Du bringst mir ein ausgebranntes Wrack. Das ist keine Reparatur, das ist eine Wiederauferstehung.", ["Mrs. Ming sent me."] = "Mrs. Ming schickt mich.", ["Mrs. Ming sent you? Yeah, alright. Should've opened with that. Ten grand."] = "Mrs. Ming schickt dich? Na gut. Damit hättest du anfangen sollen. Zehn Riesen.", ["Repair my RV ({0})"] = "Mein Wohnmobil reparieren ({0})", ["Anything I can do to bring the price down?"] = "Kann ich irgendwas tun, um den Preis zu drücken?", ["Maybe. I left a package at a dead drop nearby. Pick it up, bring it back, and don't make it weird."] = "Vielleicht. Ich habe ein Paket an einem Toten Briefkasten in der Nähe hinterlegt. Hol es ab, bring es her, und mach es nicht komisch.", ["Got your package."] = "Hab dein Paket.", ["Good. You can follow instructions. Bring me some of that good stuff now and then, and I'll keep shaving down the bill."] = "Gut. Du kannst Anweisungen folgen. Bring mir ab und zu was von dem guten Zeug, dann drücke ich die Rechnung weiter.", ["I lost your package."] = "Ich habe dein Paket verloren.", ["You did what? You walk in here empty-handed and waste my time. Five hundred, or the next thing that goes missing is you."] = "Du hast WAS? Du kommst mit leeren Händen rein und verschwendest meine Zeit. Fünfhundert, oder das Nächste, das verschwindet, bist du.", ["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."] = "Gut. Solche Ausrutscher sind vergessen, sobald das Geld da ist. Bring mir ab und zu was von dem guten Zeug, dann drücke ich die Rechnung weiter.", ["Clock's running. Come back with it."] = "Die Uhr läuft. Komm mit dem Ding zurück.", ["Your RV looks fine to me."] = "Dein Wohnmobil sieht für mich in Ordnung aus.", ["You're short. Come back when you've got the cash."] = "Zu wenig. Komm wieder, wenn du das Geld hast.", ["Alright. Hold still, this won't take long."] = "Na gut. Bleib stehen, dauert nicht lange.", ["There she is - back from the dead. Go take a look, and try not to total her again."] = "Da ist sie - zurück von den Toten. Sieh sie dir an, und versuch, sie nicht gleich wieder zu schrotten.", ["There she is - back from the dead. Try not to total her again."] = "Da ist sie - zurück von den Toten. Versuch, sie nicht gleich wieder zu schrotten.", ["There she is. Standing again. Interior's your problem. Try not to piss off whoever torched it the first time."] = "Da ist sie. Steht wieder. Der Innenraum ist dein Problem. Und verärgere besser nicht nochmal den, der sie abgefackelt hat.", ["Give Marco a packaged sample"] = "Marco eine verpackte Probe geben", ["Give Marco: {0} (-{1})"] = "Marco geben: {0} (-{1})", ["product"] = "Ware", ["That ain't packaged. Hand me something sealed."] = "Das ist nicht verpackt. Gib mir was Versiegeltes.", ["Appreciate it. Knocked {0} off the bill."] = "Danke dir. {0} gehen von der Rechnung runter.", ["What can I bring to lower the price?"] = "Was kann ich bringen, um den Preis zu senken?", ["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."] = "Bring mir verpackte Ware - versiegeltes Zeug, nichts Loses. Jedes Stück, das ich nehme, geht mit seinem Wert von der Rechnung ab, bis fünfhundert pro Stück, runter bis zu meiner Schmerzgrenze.", ["Pay ${0}"] = "{0} $ zahlen", ["I'll get the money."] = "Ich besorge das Geld.", ["Ming's Crate"] = "Mings Kiste", ["A sealed crate for Mrs. Ming. She said not to open it."] = "Eine versiegelte Kiste für Mrs. Ming. Sie hat gesagt: nicht öffnen.", ["Marco's Package"] = "Marcos Paket", ["A package Marco left at a drop. Don't make it weird."] = "Ein Paket, das Marco an einem Briefkasten hinterlegt hat. Mach es nicht komisch." }); } } } namespace RVRepairVan.Effects { internal static class RepairCinematic { private const float FadeTime = 0.6f; private const float HoldTime = 2.5f; private const float SoundVolume = 0.2f; private const string Key = "RV.Repair"; private static AudioClip _clip; private static bool _clipTried; internal static void Play(Action doWhileBlack, Action onDone, Action onMidHold = null) { ScreenTransition.Play(new TransitionRequest { Mechanism = VeilMode.Black, CloseSeconds = 0.6f, OpenSeconds = 0.6f, HoldSeconds = 2.5f, LockMovement = true, LockCamera = true, DuringBlack = delegate { PlaySound(); Safe(doWhileBlack); }, OnMidHold = onMidHold, OnDone = onDone, Key = "RV.Repair" }); } internal static void ForceReset() { ScreenTransition.ForceReset("RV.Repair"); } 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(); val3.clip = val; val3.volume = 0.2f; val3.spatialBlend = 0f; try { AudioManager instance = Singleton.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.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.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 func = (bool enabled) => enabled && RVManager.IsDestroyed(); val3.shouldShowCheck = DelegateSupport.ConvertDelegate((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 val = Object.FindObjectsOfType(); 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(); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren; } } } catch { } try { return ((Component)marco).GetComponentInChildren(); } catch { return null; } } private static string BuildChoiceText() { return L10n.T("Repair my RV ({0})", 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; } Marco marco = FindMarco(); if (NetworkBus.Online && !NetworkBus.IsServer) { NetworkBus.SendToHost(RvOp.PayRepair); RepairCinematic.Play(null, delegate { WorldSay((NPC)(object)marco, L10n.T("There she is - back from the dead. Try not to total her again.")); }, delegate { Questline.GruntNpc((NPC)(object)marco); }); return; } if (NetworkBus.IsServer) { Questline.HostPayRepair(withCinematic: true); return; } Money.ChangeCashBalance(0f - num, true, true); float paid = num; 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, L10n.T("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 _enabled; private static MelonPreferences_Entry _repairPrice; private static MelonPreferences_Entry _questMode; private static MelonPreferences_Entry _basePriceNoReferral; private static MelonPreferences_Entry _basePriceWithReferral; private static MelonPreferences_Entry _minSampleDiscount; private static MelonPreferences_Entry _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 CreateEntry(string identifier, T defaultValue, string displayName, string description = null) { return _category.CreateEntry(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null); } } }