using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using NodeCanvas.DialogueTrees; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ForgeKit")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.10.0")] [assembly: AssemblyInformationalVersion("0.4.10+83254ebc1e0f377c73c2296a9412cdb0c1c00f84")] [assembly: AssemblyProduct("ForgeKit")] [assembly: AssemblyTitle("ForgeKit")] [assembly: AssemblyMetadata("BuildStamp", "83254ebc 2026-08-28")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] namespace ForgeKit; public static class BuildScenes { public static string Resolve(string want, out List similar) { List list = new List(); int sceneCountInBuildSettings = SceneManager.sceneCountInBuildSettings; for (int i = 0; i < sceneCountInBuildSettings; i++) { string scenePathByBuildIndex = SceneUtility.GetScenePathByBuildIndex(i); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(scenePathByBuildIndex); if (string.Equals(fileNameWithoutExtension, want, StringComparison.OrdinalIgnoreCase)) { similar = new List(); return fileNameWithoutExtension; } list.Add(fileNameWithoutExtension); } similar = Suggest.Bidirectional(list, want); return null; } public static void Dump(ManualLogSource log) { int sceneCountInBuildSettings = SceneManager.sceneCountInBuildSettings; log.LogMessage((object)$"[SCENEDUMP] {sceneCountInBuildSettings} scenes in build settings."); for (int i = 0; i < sceneCountInBuildSettings; i++) { string scenePathByBuildIndex = SceneUtility.GetScenePathByBuildIndex(i); log.LogMessage((object)$"[SCENEDUMP] {i}: {scenePathByBuildIndex}"); } } } public static class BuildStamp { public const string Unknown = "unknown"; private const string Key = "BuildStamp"; private static string _local; public static string Local => _local ?? (_local = Read(typeof(BuildStamp).Assembly)); public static string Read(Assembly asm) { if (asm == null) { return "unknown"; } try { foreach (AssemblyMetadataAttribute customAttribute in asm.GetCustomAttributes()) { if (customAttribute.Key == "BuildStamp" && !string.IsNullOrEmpty(customAttribute.Value)) { return customAttribute.Value; } } } catch { } return "unknown"; } } public sealed class CatalogInfo { public string ModGuid; public string ModName; public string ModVersion; public Func ConfigSource; } public static class CatalogDump { public const int SchemaVersion = 1; private static readonly Dictionary _writers = new Dictionary(); public static string Dir => Path.Combine(Paths.BepInExRootPath, "forge_catalog"); public static void Announce(CatalogInfo info, CommandRegistry registry, string channelFile, bool firstLineOnly, float pollSeconds) { if (info == null || string.IsNullOrEmpty(info.ModGuid) || registry == null) { return; } lock (_writers) { _writers[info.ModGuid] = delegate { Write(info, registry, channelFile, firstLineOnly, pollSeconds); }; } } public static void Refresh(string guid) { Action value; lock (_writers) { _writers.TryGetValue(guid, out value); } try { value?.Invoke(); } catch { } } public static void RefreshAll() { List list; lock (_writers) { list = new List(_writers.Values); } foreach (Action item in list) { try { item(); } catch { } } } private static void Write(CatalogInfo info, CommandRegistry registry, string channelFile, bool firstLineOnly, float pollSeconds) { ForgeJson forgeJson = new ForgeJson(); forgeJson.BeginObj(); forgeJson.Prop("schema", 1L); forgeJson.Name("mod").BeginObj().Prop("guid", info.ModGuid) .Prop("name", info.ModName) .Prop("version", info.ModVersion) .EndObj(); forgeJson.Name("channel").BeginObj().Prop("file", channelFile) .Prop("firstLineOnly", firstLineOnly) .Name("pollSeconds") .Value(pollSeconds) .Prop("responseProtocol", (!firstLineOnly) ? 1 : 0) .EndObj(); forgeJson.Prop("generatedUtc", DateTime.UtcNow.ToString("o")); forgeJson.Name("verbs").BeginArr(); foreach (VerbSpec spec in registry.Specs) { forgeJson.BeginObj(); forgeJson.Prop("verbs", spec.Verbs); forgeJson.Prop("help", spec.Help); if (!string.IsNullOrEmpty(spec.Tag)) { forgeJson.Prop("tag", spec.Tag); } forgeJson.Prop("needsPlayer", spec.NeedsPlayer); if (spec.MasterOnly) { forgeJson.Prop("masterOnly", v: true); } ArgSpec[] array = spec.Args; bool flag = false; if (array == null) { try { array = UsageSpec.Derive(spec.Verbs, spec.Help); } catch { } flag = array != null; } if (array != null) { if (flag) { forgeJson.Prop("argsDerived", v: true); } forgeJson.Name("args").BeginArr(); ArgSpec[] array2 = array; foreach (ArgSpec argSpec in array2) { forgeJson.BeginObj(); forgeJson.Prop("name", argSpec.Name); forgeJson.Prop("type", argSpec.Type); if (argSpec.Optional) { forgeJson.Prop("optional", v: true); } if (argSpec.Choices != null) { forgeJson.Prop("choices", argSpec.Choices); } if (argSpec.Default != null) { forgeJson.Prop("default", argSpec.Default); } forgeJson.EndObj(); } forgeJson.EndArr(); } forgeJson.EndObj(); } forgeJson.EndArr(); ConfigFile val = null; try { val = info.ConfigSource?.Invoke(); } catch { } forgeJson.Name("config").BeginArr(); if (val != null) { List list = new List(val.Keys); foreach (ConfigDefinition item in list) { ConfigEntryBase val2; try { val2 = val[item]; } catch { continue; } forgeJson.BeginObj(); forgeJson.Prop("section", item.Section); forgeJson.Prop("key", item.Key); forgeJson.Prop("type", val2.SettingType.Name); string v = null; string v2 = null; try { v = val2.GetSerializedValue(); } catch { } try { v2 = TomlTypeConverter.ConvertToString(val2.DefaultValue, val2.SettingType); } catch { } forgeJson.Prop("value", v); forgeJson.Prop("default", v2); ConfigDescription description = val2.Description; string text = ((description != null) ? description.Description : null); if (!string.IsNullOrEmpty(text)) { forgeJson.Prop("description", text); } if (val2.SettingType.IsEnum) { forgeJson.Prop("choices", Enum.GetNames(val2.SettingType)); } else { ConfigDescription description2 = val2.Description; if (((description2 != null) ? description2.AcceptableValues : null) != null) { string text2 = null; try { text2 = val2.Description.AcceptableValues.ToDescriptionString(); } catch { } if (!string.IsNullOrEmpty(text2)) { forgeJson.Prop("acceptable", text2.TrimStart('#', ' ')); } } } forgeJson.EndObj(); } } forgeJson.EndArr(); forgeJson.Name("customItems").BeginArr(); foreach (KeyValuePair item2 in ItemNameIndex.CustomSnapshot()) { forgeJson.BeginObj().Prop("name", item2.Key).Prop("id", item2.Value) .EndObj(); } forgeJson.EndArr(); forgeJson.EndObj(); Directory.CreateDirectory(Dir); string text3 = Path.Combine(Dir, SafeFileName(info.ModGuid) + ".json"); string text4 = text3 + ".tmp"; File.WriteAllText(text4, forgeJson.ToString()); if (File.Exists(text3)) { File.Delete(text3); } File.Move(text4, text3); } private static string SafeFileName(string s) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { stringBuilder.Append((Array.IndexOf(invalidFileNameChars, c) >= 0) ? '_' : c); } return stringBuilder.ToString(); } } public static class CfgSkew { public static List Drifted(ConfigFile cfg, string sectionFilter, out int total) { List list = new List(); total = 0; if (cfg == null) { return list; } foreach (ConfigDefinition item in new List(cfg.Keys)) { if (DevCfg.SectionMatches(sectionFilter, item.Section) || DevCfg.MatchKey(sectionFilter, item.Section, item.Key)) { total++; ConfigEntryBase val = cfg[item]; if (DevCfg.Skew(val, out var shippedDefault)) { list.Add(new CfgSkewRules.Row(item.Section, item.Key, val.GetSerializedValue(), shippedDefault)); } } } return list; } public static List Sweep() { List list = new List(); foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (value == null || pluginInfo.Key == null || !pluginInfo.Key.StartsWith("cobalt.", StringComparison.Ordinal)) { continue; } BaseUnityPlugin instance = value.Instance; if ((Object)(object)instance == (Object)null) { continue; } ConfigFile config; try { config = instance.Config; } catch { continue; } if (config != null) { int total; List drifted = Drifted(config, null, out total); BepInPlugin metadata = value.Metadata; string text = CfgSkewRules.BootLine(((metadata != null) ? metadata.Name : null) ?? pluginInfo.Key, total, drifted); if (text != null) { list.Add(text); } } } return list; } } public static class CfgSkewRules { public readonly struct Row { public readonly string Section; public readonly string Key; public readonly string Live; public readonly string Default; public Row(string section, string key, string live, string def) { Section = section; Key = key; Live = live; Default = def; } } public const string Tag = "[CFGSKEW]"; public const int MaxKeys = 8; public static bool Differs(string live, string def) { if (live != null && def != null) { return !string.Equals(live, def, StringComparison.Ordinal); } return false; } public static string Describe(Row r) { return r.Section + "." + r.Key + "=" + r.Live + " (default " + r.Default + ")"; } public static string BootLine(string mod, int total, IList drifted, int maxKeys = 8) { if (drifted == null || drifted.Count == 0) { return null; } if (maxKeys < 1) { maxKeys = 1; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[CFGSKEW]").Append(' ').Append(string.IsNullOrEmpty(mod) ? "?" : mod) .Append(": ") .Append(drifted.Count) .Append(" of ") .Append(total) .Append((total == 1) ? " entry differs" : " entries differ") .Append(" from shipped defaults — "); int num = Math.Min(maxKeys, drifted.Count); for (int i = 0; i < num; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(Describe(drifted[i])); } if (drifted.Count > num) { stringBuilder.Append(", …and ").Append(drifted.Count - num).Append(" more"); } return stringBuilder.ToString(); } } public sealed class CommandChannel { private readonly string _path; private readonly ManualLogSource _log; private readonly CommandRegistry _registry; private readonly float _pollSeconds; private readonly bool _allLines; private readonly ScriptRunner _runner; private float _check; private long _stamp; private long _length = -1L; private readonly CatalogInfo _catalog; private int _catalogWrites; private float _bootTime = -1f; private ForgeOut.Capture _pendingCap; private int _pendingVerbs; private float _pendingDeadline; private const float PendingCaptureMaxSeconds = 120f; public string Path => _path; public CommandChannel(string fileName, ManualLogSource log, CommandRegistry registry, float pollSeconds, bool allLines, bool primeStamp) : this(fileName, log, registry, pollSeconds, allLines, primeStamp, null) { } public CommandChannel(string fileName, ManualLogSource log, CommandRegistry registry, float pollSeconds = 0.5f, bool allLines = true, bool primeStamp = false, CatalogInfo catalog = null) { _catalog = catalog; if (catalog != null) { CatalogDump.Announce(catalog, registry, fileName, !allLines, pollSeconds); ForgeOut.PruneOld(); } _path = System.IO.Path.Combine(Paths.ConfigPath, fileName); _log = log; _registry = registry; _pollSeconds = pollSeconds; _allLines = allLines; if (primeStamp && File.Exists(_path)) { _stamp = File.GetLastWriteTimeUtc(_path).Ticks; _length = new FileInfo(_path).Length; } _runner = new ScriptRunner(log, Run); _registry.Register("script", "Run several verbs with real time between them ('script moveto Bandit 2 ; wait 1 ; swing'; timing: wait | waitframes | waitloaded [s]); one step per frame; a new script replaces the running one.", delegate(string[] args) { _runner.Start(Tail(args)); }); _registry.Register("scriptcancel", "Abort the running script (if any).", delegate { _runner.Cancel(); }); _registry.Register("scriptstatus", "One line: which step the running script is on.", delegate { _runner.Status(); }); } private static string Tail(string[] parts) { if (parts != null && parts.Length >= 2) { return string.Join(" ", parts, 1, parts.Length - 1); } return ""; } private static bool IsScriptControl(string cmd) { int num = cmd.IndexOf(' '); string a = ((num < 0) ? cmd : cmd.Substring(0, num)); if (!string.Equals(a, "scriptcancel", StringComparison.OrdinalIgnoreCase)) { return string.Equals(a, "scriptstatus", StringComparison.OrdinalIgnoreCase); } return true; } public void Tick() { _runner.Pump(); if (_pendingCap != null) { bool flag = Time.unscaledTime >= _pendingDeadline; bool flag2 = _runner.IsRunning || DeferredReport.IsPending; if (!flag2 || flag) { if (flag && flag2) { _log.LogWarning((object)($"[CMD] script response closed after {120f:F0}s " + "while the script is STILL running (or a deferred verb report is still owed) — the rest of its output goes to the log only. (Capture released so the channel isn't wedged; 'scriptstatus' still works.)")); } _pendingCap.Finish(_pendingVerbs); _pendingCap = null; _pendingVerbs = 0; } } if (_catalog != null && _catalogWrites < 2) { if (_bootTime < 0f) { _bootTime = Time.unscaledTime; } if (_catalogWrites == 0) { _catalogWrites = 1; CatalogDump.Refresh(_catalog.ModGuid); } else if (Time.unscaledTime - _bootTime > 60f) { _catalogWrites = 2; CatalogDump.Refresh(_catalog.ModGuid); } } if (!(Time.unscaledTime - _check <= _pollSeconds)) { _check = Time.unscaledTime; Poll(); } } private void Poll() { try { if (!File.Exists(_path)) { return; } long ticks = File.GetLastWriteTimeUtc(_path).Ticks; long length = new FileInfo(_path).Length; if (ticks == _stamp && length == _length) { return; } string[] array = File.ReadAllLines(_path); _stamp = ticks; _length = length; if (_allLines) { string text = ForgeOut.ParseReqId(array); if (_pendingCap != null) { ForgeOut.Capture capture = ((text != null) ? ForgeOut.Begin(_log, text) : null); int num = 0; string[] array2 = array; foreach (string text2 in array2) { string text3 = text2.Trim(); if (text3.Length != 0 && !text3.StartsWith("#") && IsScriptControl(text3)) { Run(text3); num++; } } if (num == 0) { _pendingCap.Suspend(); try { _log.LogWarning((object)"[CMD] refused: a script batch is still running (its response file is still open). Use scriptcancel, or wait for it to finish."); } finally { _pendingCap.Resume(); } } capture?.Finish(num); return; } ForgeOut.Capture capture2 = ((text != null) ? ForgeOut.Begin(_log, text) : null); int num2 = 0; try { string[] array3 = array; foreach (string text4 in array3) { string text5 = text4.Trim(); if (text5.Length != 0 && !text5.StartsWith("#")) { Run(text5); num2++; } } return; } finally { if (capture2 != null && (_runner.IsRunning || DeferredReport.IsPending)) { _pendingCap = capture2; _pendingVerbs = num2; _pendingDeadline = Time.unscaledTime + 120f; } else { capture2?.Finish(num2); } } } string text6 = null; string[] array4 = array; foreach (string text7 in array4) { if (!string.IsNullOrWhiteSpace(text7)) { text6 = text7; break; } } if (!string.IsNullOrWhiteSpace(text6)) { Run(text6.Trim()); } } catch (Exception ex) { _log.LogWarning((object)("[CMD] poll error: " + ex.Message)); } } public void Run(string cmd) { using (ModLog.Requested()) { _log.LogMessage((object)("[CMD] run: " + cmd)); string[] array = cmd.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string verb = ((array.Length != 0) ? array[0] : ""); if (_registry.TryGet(verb, out var run)) { run(array); } else { _registry.PrintHelp(verb); } } } } public sealed class CommandRegistry { private readonly ManualLogSource _log; private readonly Dictionary> _run = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly List _specs = new List(); public IReadOnlyList Specs => _specs; public CommandRegistry(ManualLogSource log) { _log = log; } public void Register(string verb, string help, Action run) { Register(new string[1] { verb }, help, run); } public void Register(string[] verbs, string help, Action run) { Register(new VerbSpec { Verbs = verbs, Help = help }, run); } public void Register(VerbSpec spec, Action run) { string[] verbs = spec.Verbs; string help = spec.Help; string[] array = verbs; foreach (string v in array) { if (_run.ContainsKey(v)) { _log.LogWarning((object)("[CMD] verb '" + v + "' registered twice — the later registration wins (drop the local copy, or Exclude it from the shared pack).")); } _run[v] = delegate(string[] args) { try { run(args); } catch (Exception arg) { _log.LogError((object)$"[CMD] '{v}' failed: {arg}"); } }; } _specs.Add(spec); } public bool TryGet(string verb, out Action run) { return _run.TryGetValue(verb, out run); } public void PrintHelp(string verb) { if (string.IsNullOrEmpty(verb) || string.Equals(verb, "help", StringComparison.OrdinalIgnoreCase)) { _log.LogMessage((object)$"[CMD] {_specs.Count} registered verbs:"); } else { _log.LogWarning((object)$"[CMD] unknown verb '{verb}' — {_specs.Count} registered verbs:"); } foreach (VerbSpec spec in _specs) { _log.LogMessage((object)("[CMD] " + spec.JoinedVerbs + " — " + spec.Help)); } } } public static class Inventories { public readonly struct ConsumeResult { public readonly int Requested; public readonly int Before; public readonly int After; public readonly int Shortfall; public readonly bool Emptied; public readonly bool DestroyRequested; public readonly bool OnNonMasterClient; public int EffectiveAfter { get { if (!Emptied) { return After; } return 0; } } public bool Consumed { get { if (Shortfall == 0) { if (!Emptied) { return After == Before - Requested; } return true; } return false; } } public ConsumeResult(int requested, int before, int after, int shortfall, bool emptied, bool destroyRequested, bool onNonMasterClient) { Requested = requested; Before = before; After = after; Shortfall = shortfall; Emptied = emptied; DestroyRequested = destroyRequested; OnNonMasterClient = onNonMasterClient; } public string Describe() { return $"requested={Requested} before={Before} after={After} shortfall={Shortfall} " + $"emptied={Emptied} destroyRequested={DestroyRequested} nonMaster={OnNonMasterClient}"; } } public static IEnumerable All(Character player) { foreach (var item in AllByContainer(player)) { yield return item.Item; } } public static IEnumerable<(Item Item, string Where)> AllByContainer(Character player) { CharacterInventory val = (((Object)(object)player != (Object)null) ? player.Inventory : null); if ((Object)(object)val == (Object)null) { yield break; } HashSet seen = new HashSet(); Bag bag = val.EquippedBag; Item[] componentsInChildren; if ((Object)(object)val.Pouch != (Object)null) { componentsInChildren = ((Component)val.Pouch).GetComponentsInChildren(true); foreach (Item val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && (object)val2 != bag && val2.RemainingAmount > 0 && !val2.DestroyWanted && seen.Add(val2)) { yield return (Item: val2, Where: "pouch"); } } } if (!((Object)(object)bag != (Object)null)) { yield break; } componentsInChildren = ((Component)bag).GetComponentsInChildren(true); foreach (Item val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (object)val3 != bag && val3.RemainingAmount > 0 && !val3.DestroyWanted && seen.Add(val3)) { yield return (Item: val3, Where: "bag"); } } } public static ConsumeResult ConsumeOne(Item item, int qty = 1) { int remainingAmount = item.RemainingAmount; if (qty <= 0) { return new ConsumeResult(qty, remainingAmount, remainingAmount, 0, emptied: false, destroyRequested: false, PhotonNetwork.isNonMasterClientInRoom); } int shortfall = item.RemoveQuantity(qty); bool isNonMasterClientInRoom = PhotonNetwork.isNonMasterClientInRoom; bool destroyRequested = false; if (isNonMasterClientInRoom && (!item.HasMultipleUses || item.RemainingAmount <= 0) && (Object)(object)ItemManager.Instance != (Object)null) { ItemManager.Instance.SendDestroyItem(item.UID); item.SetDestroyWanted(); destroyRequested = true; } return new ConsumeResult(qty, remainingAmount, item.RemainingAmount, shortfall, remainingAmount <= qty, destroyRequested, isNonMasterClientInRoom); } } public static class Converge { public static bool To(Func read, T want, Action write, Func same = null) { if (read == null || write == null) { return false; } T val = read(); if (same?.Invoke(val, want) ?? EqualityComparer.Default.Equals(val, want)) { return false; } write(want); return true; } } public sealed class Stamp where TTarget : class { private readonly Func _alive; private readonly Func _read; private readonly Action _write; private readonly TValue _neutral; private readonly Func _wantsNothing; private readonly Func _same; private TTarget _target; private TValue _value; private bool _warnedForeign; public Action OnForeignWriter; public Action OnStamp; public Action OnWithdraw; public string TargetLostReason = "no longer the stamped target"; public string NothingWantedReason = "nothing is wanted right now"; public TTarget Target => _target; public TValue Value => _value; public Stamp(Func alive, Func read, Action write, TValue neutral, Func wantsNothing = null, Func same = null) { Stamp stamp = this; _alive = alive ?? throw new ArgumentNullException("alive"); _read = read ?? throw new ArgumentNullException("read"); _write = write ?? throw new ArgumentNullException("write"); _neutral = neutral; _same = same ?? new Func(EqualityComparer.Default.Equals); _wantsNothing = wantsNothing ?? ((Func)((TValue v) => stamp._same(v, neutral))); } public void Sync(TTarget target, TValue want) { if (_target != null && _target != target) { Clear(TargetLostReason); } if (target == null || !_alive(target)) { return; } TValue val = _read(target); if (target == _target && !_same(val, _value) && !_warnedForeign) { _warnedForeign = true; OnForeignWriter?.Invoke(target, val, _value); } if (_wantsNothing(want)) { if (target == _target) { Clear(NothingWantedReason); } return; } bool flag = false; if (!_same(val, want)) { _write.Invoke(target, want); OnStamp?.Invoke(target, want); flag = true; } if (flag || target == _target) { _target = target; _value = want; } } public void Clear(string why) { if (_target != null && _alive(_target) && !_same(_read(_target), _neutral)) { _write.Invoke(_target, _neutral); OnWithdraw?.Invoke(_target, _value, why); } _target = null; _value = _neutral; } public void ResetForeignWarning() { _warnedForeign = false; } } public interface ITableSource where TTable : class { TTable Table { get; } TTable Reload(); } public sealed class DelegateTableSource : ITableSource where TTable : class { private readonly Func _load; private readonly Action _announceReload; private TTable _table; public TTable Table { get { if (_table == null) { _table = _load(); } return _table; } } public DelegateTableSource(Func load, Action announceReload = null) { _load = load ?? throw new ArgumentNullException("load"); _announceReload = announceReload; } public TTable Reload() { _table = _load(); _announceReload?.Invoke(_table); return _table; } } public sealed class DataAxis where TTable : class { private readonly ITableSource _source; private readonly Action _bootValidateHook; private readonly Func _registryReady; private readonly Action _validate; public TTable Table => _source.Table; public DataAxis(ITableSource source, Action bootValidateHook, Func registryReady, Action validate) { _source = source ?? throw new ArgumentNullException("source"); if (validate != null && (bootValidateHook == null || registryReady == null)) { throw new ArgumentException("a DataAxis validate requires BOTH a boot hook and a registry gate — the gate-less boot check is the exact bug class this type exists to prevent."); } _bootValidateHook = bootValidateHook; _registryReady = registryReady; _validate = validate; } public void Init() { if (_validate != null) { _bootValidateHook(Validate); } } public TTable Reload() { TTable result = _source.Reload(); if (_validate != null) { Validate(); } return result; } public void Validate() { if (_validate != null && _registryReady()) { _validate(); } } } public static class DeferredReport { private static readonly PendingSet _pending = new PendingSet(); private static readonly DateTime _epoch = DateTime.UtcNow; private static double Now => (DateTime.UtcNow - _epoch).TotalSeconds; public static bool IsPending => _pending.AnyAt(Now); public static IEnumerator Wrap(IEnumerator body, ManualLogSource log, string what) { long id = _pending.Open(Now); return Drive(body, log, what, id); } private static IEnumerator Drive(IEnumerator body, ManualLogSource log, string what, long id) { try { while (true) { object current; try { if (!body.MoveNext()) { break; } current = body.Current; } catch (Exception ex) { log.LogWarning((object)("[CMD] deferred report '" + what + "' threw: " + ex.Message + " — no contract line for this call.")); break; } yield return current; } } finally { _pending.Close(id); } } } public static class EmbeddedRes { public static string Text(Assembly asm, string suffix, string logTag, ManualLogSource log = null) { string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); if (text == null) { ManualLogSource obj = log ?? Plugin.Log; if (obj != null) { obj.LogWarning((object)(logTag + " embedded " + suffix + " not found.")); } return ""; } using Stream stream = asm.GetManifestResourceStream(text); using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } public static Texture2D Texture(Assembly asm, string suffix, string logTag, ManualLogSource log = null) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown ManualLogSource val = log ?? Plugin.Log; try { string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); if (text == null) { if (val != null) { val.LogWarning((object)(logTag + " embedded icon '" + suffix + "' missing from the DLL — keeping the donor icon.")); } return null; } byte[] array; using (Stream stream = asm.GetManifestResourceStream(text)) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val2, array)) { if (val != null) { val.LogWarning((object)(logTag + " icon '" + suffix + "' failed to decode.")); } return null; } ((Object)val2).hideFlags = (HideFlags)61; return val2; } catch (Exception ex) { if (val != null) { val.LogWarning((object)(logTag + " icon '" + suffix + "' load failed: " + ex.Message)); } return null; } } } public sealed class ForgeJson { private readonly StringBuilder _sb = new StringBuilder(4096); private bool _needComma; public override string ToString() { return _sb.ToString(); } private void Sep() { if (_needComma) { _sb.Append(','); } _needComma = false; } public ForgeJson BeginObj() { Sep(); _sb.Append('{'); return this; } public ForgeJson EndObj() { _sb.Append('}'); _needComma = true; return this; } public ForgeJson BeginArr() { Sep(); _sb.Append('['); return this; } public ForgeJson EndArr() { _sb.Append(']'); _needComma = true; return this; } public ForgeJson Name(string name) { Sep(); WriteString(name); _sb.Append(':'); _needComma = false; return this; } public ForgeJson Value(string v) { Sep(); if (v == null) { _sb.Append("null"); } else { WriteString(v); } _needComma = true; return this; } public ForgeJson Value(bool v) { Sep(); _sb.Append(v ? "true" : "false"); _needComma = true; return this; } public ForgeJson Value(long v) { Sep(); _sb.Append(v.ToString(CultureInfo.InvariantCulture)); _needComma = true; return this; } public ForgeJson Value(double v) { Sep(); if (double.IsNaN(v) || double.IsInfinity(v)) { _sb.Append("null"); } else { _sb.Append(v.ToString("R", CultureInfo.InvariantCulture)); } _needComma = true; return this; } public ForgeJson Prop(string name, string v) { return Name(name).Value(v); } public ForgeJson Prop(string name, bool v) { return Name(name).Value(v); } public ForgeJson Prop(string name, long v) { return Name(name).Value(v); } public ForgeJson Prop(string name, string[] items) { Name(name).BeginArr(); if (items != null) { foreach (string v in items) { Value(v); } } return EndArr(); } private void WriteString(string s) { _sb.Append('"'); foreach (char c in s) { switch (c) { case '"': _sb.Append("\\\""); continue; case '\\': _sb.Append("\\\\"); continue; case '\b': _sb.Append("\\b"); continue; case '\f': _sb.Append("\\f"); continue; case '\n': _sb.Append("\\n"); continue; case '\r': _sb.Append("\\r"); continue; case '\t': _sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = _sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { _sb.Append(c); } } _sb.Append('"'); } } public static class ForgeOut { public sealed class Capture : ILogListener, IDisposable { private readonly ILogSource _source; private readonly string _reqid; private readonly List _lines = new List(); private bool _done; private bool _suspended; public void Suspend() { _suspended = true; } public void Resume() { _suspended = false; } internal Capture(ManualLogSource source, string reqid) { _source = (ILogSource)(object)source; _reqid = reqid; Logger.Listeners.Add((ILogListener)(object)this); } public void LogEvent(object sender, LogEventArgs e) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (_suspended || e.Source != _source) { return; } lock (_lines) { _lines.Add($"[{e.Level,-7}] {e.Data}"); } } public void Finish(int verbsRun) { if (_done) { return; } _done = true; Logger.Listeners.Remove((ILogListener)(object)this); try { Directory.CreateDirectory(Dir); StringBuilder stringBuilder = new StringBuilder(); lock (_lines) { foreach (string line in _lines) { stringBuilder.AppendLine(line); } } stringBuilder.AppendLine($"#forge done reqid={_reqid} verbs={verbsRun}"); string text = Path.Combine(Dir, _reqid + ".txt"); string text2 = text + ".tmp"; File.WriteAllText(text2, stringBuilder.ToString()); if (File.Exists(text)) { try { File.Replace(text2, text, null); return; } catch (IOException) { File.Delete(text); File.Move(text2, text); return; } catch (PlatformNotSupportedException) { File.Delete(text); File.Move(text2, text); return; } } File.Move(text2, text); } catch { } } public void Dispose() { Finish(0); } } public static string Dir => Path.Combine(Paths.BepInExRootPath, "forge_out"); public static string ParseReqId(string[] lines) { if (lines == null) { return null; } foreach (string text in lines) { if (string.IsNullOrWhiteSpace(text)) { continue; } string text2 = text.Trim(); if (!text2.StartsWith("#forge reqid=", StringComparison.OrdinalIgnoreCase)) { return null; } string text3 = text2.Substring("#forge reqid=".Length).Trim(); if (text3.Length == 0 || text3.Length > 64) { return null; } string text4 = text3; foreach (char c in text4) { if (!char.IsLetterOrDigit(c) && c != '-' && c != '_') { return null; } } return text3; } return null; } public static Capture Begin(ManualLogSource source, string reqid) { return new Capture(source, reqid); } public static void PruneOld() { try { if (!Directory.Exists(Dir)) { return; } DateTime dateTime = DateTime.UtcNow.AddHours(-24.0); string[] files = Directory.GetFiles(Dir, "*.txt"); foreach (string path in files) { try { if (File.GetLastWriteTimeUtc(path) < dateTime) { File.Delete(path); } } catch { } } } catch { } } } public static class IdPool { private static readonly Dictionary _claimed = new Dictionary(); public static readonly int[][] Ranges = new int[1][] { new int[2] { 87000, 87999 } }; public static readonly int[][] Forbidden = new int[1][] { new int[2] { 91007000, 91009999 } }; public static bool InPool(int id) { int[][] ranges = Ranges; foreach (int[] array in ranges) { if (id >= array[0] && id <= array[1]) { return true; } } return false; } public static bool IsForbidden(int id) { int[][] forbidden = Forbidden; foreach (int[] array in forbidden) { if (id >= array[0] && id <= array[1]) { return true; } } return false; } public static bool Claim(int id, string what, Action refuse) { if (IsForbidden(id)) { refuse?.Invoke($"[IDS] {what}: id {id} is from the RETIRED block — those numbers were " + "re-minted and are dead. Not registering it."); return false; } if (!InPool(id)) { refuse?.Invoke($"[IDS] {what}: id {id} is outside the range allocated to these mods " + "(" + RangeText() + "). Registering it would collide with whatever mod DOES own that number, in a save that has already written it down. Not registering it."); return false; } if (_claimed.TryGetValue(id, out var value) && value != what) { refuse?.Invoke($"[IDS] {what}: id {id} was already registered this session by '{value}'. " + "Two things cannot share one id. Not registering the second."); return false; } _claimed[id] = what; return true; } public static bool ClaimShared(int id, string what, Action warn) { if (IsForbidden(id)) { warn?.Invoke($"[IDS] {what}: id {id} is from a retired block — not registering it."); return false; } if (_claimed.TryGetValue(id, out var value) && value != what) { warn?.Invoke($"[IDS] {what}: id {id} was already registered this session by '{value}'. " + "Two things cannot share one id. Not registering the second."); return false; } if (!InPool(id)) { warn?.Invoke($"[IDS] {what}: id {id} is outside this workspace's allocation ({RangeText()}) " + "— assuming it comes from your own. Registering it."); } _claimed[id] = what; return true; } public static IEnumerable Census() { yield return $"[IDS] pool {RangeText()} — {_claimed.Count} id(s) claimed this session"; foreach (KeyValuePair item in _claimed) { yield return $"[IDS] {item.Key} {item.Value}"; } } public static void Reset() { _claimed.Clear(); } private static string RangeText() { List list = new List(); int[][] ranges = Ranges; foreach (int[] array in ranges) { list.Add(array[0] + "-" + array[1]); } return string.Join(", ", list.ToArray()); } } public static class ItemNameIndex { private static Dictionary _index; private static readonly Dictionary _custom = new Dictionary(StringComparer.OrdinalIgnoreCase); public static int Count => Ensure().Count + _custom.Count; public static void RegisterCustom(string displayName, int itemId) { if (!string.IsNullOrEmpty(displayName)) { _custom[displayName.Trim()] = itemId; } } public static List> CustomSnapshot() { return new List>(_custom); } public static bool TryResolve(string displayName, out int itemId) { if (string.IsNullOrEmpty(displayName)) { itemId = 0; return false; } string key = displayName.Trim(); if (_custom.TryGetValue(key, out itemId)) { return true; } return Ensure().TryGetValue(key, out itemId); } public static bool TryResolveCatalog(string candidate, out int itemId, out string via) { Dictionary iTEM_PREFABS = ResourcesPrefabManager.ITEM_PREFABS; if (iTEM_PREFABS == null) { itemId = 0; via = null; return false; } foreach (KeyValuePair item in iTEM_PREFABS) { Item value = item.Value; if (!((Object)(object)value == (Object)null)) { string text = (((Object)(object)((Component)value).gameObject != (Object)null) ? ((Object)((Component)value).gameObject).name : null); if (text != null && (string.Equals(text, candidate, StringComparison.OrdinalIgnoreCase) || text.EndsWith("_" + candidate, StringComparison.OrdinalIgnoreCase))) { itemId = value.ItemID; via = "prefab name '" + candidate + "'"; return true; } } } if (TryResolve(candidate, out var itemId2)) { itemId = itemId2; via = "display name '" + candidate + "'"; return true; } itemId = 0; via = null; return false; } public static bool TryResolveArg(string key, Func filter, string noun, out int itemId, out string via, out string problem) { itemId = 0; via = null; problem = null; string text = key?.Trim(); if (string.IsNullOrEmpty(text)) { problem = "no name or ItemID given."; return false; } if (DevNum.TryInt(text, out itemId)) { via = "numeric id"; return true; } if (filter == null) { if (TryResolveCatalog(text, out itemId, out via)) { return true; } } else if (TryResolveFiltered(text, filter, out itemId, out via)) { return true; } List list = new List(); foreach (KeyValuePair item in Suggest(text, 8, filter)) { list.Add($"'{item.Key}' ({item.Value})"); } string text2 = (string.IsNullOrEmpty(noun) ? "matched nothing" : ("matched no " + noun)); problem = "'" + text + "' " + text2 + " — " + ((list.Count > 0) ? ("did you mean: " + string.Join(", ", list.ToArray())) : "no similar display names") + "."; itemId = 0; return false; } private static bool TryResolveFiltered(string candidate, Func filter, out int itemId, out string via) { itemId = 0; via = null; Dictionary iTEM_PREFABS = ResourcesPrefabManager.ITEM_PREFABS; if (iTEM_PREFABS == null) { return false; } int num = 0; int num2 = 0; foreach (Item value in iTEM_PREFABS.Values) { if (!((Object)(object)value == (Object)null) && filter(value)) { string text = (((Object)(object)((Component)value).gameObject != (Object)null) ? ((Object)((Component)value).gameObject).name : null); if (text != null && (string.Equals(text, candidate, StringComparison.OrdinalIgnoreCase) || text.EndsWith("_" + candidate, StringComparison.OrdinalIgnoreCase)) && (num == 0 || value.ItemID < num)) { num = value.ItemID; } if (!string.IsNullOrEmpty(value.Name) && string.Equals(value.Name.Trim(), candidate, StringComparison.OrdinalIgnoreCase) && (num2 == 0 || value.ItemID < num2)) { num2 = value.ItemID; } } } if (num != 0) { itemId = num; via = "prefab name '" + candidate + "'"; return true; } if (num2 != 0) { itemId = num2; via = "display name '" + candidate + "'"; return true; } return false; } public static List> Suggest(string fragment, int max = 8, Func filter = null) { List> result = new List>(); if (string.IsNullOrEmpty(fragment)) { return result; } string needle = fragment.Trim(); if (needle.Length == 0) { return result; } result = ForgeKit.Suggest.TwoPass(new IEnumerable>[2] { Ensure(), _custom }, (KeyValuePair kv, Suggest.Pass pass) => ForgeKit.Suggest.Matches(kv.Key, needle, pass) && Passes(kv.Value, filter)); result.Sort(delegate(KeyValuePair a, KeyValuePair b) { int num = string.Compare(a.Key, b.Key, StringComparison.OrdinalIgnoreCase); return (num == 0) ? a.Value.CompareTo(b.Value) : num; }); if (result.Count > max) { result.RemoveRange(max, result.Count - max); } return result; } private static bool Passes(int itemId, Func filter) { if (filter == null) { return true; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(itemId) : null); if ((Object)(object)val != (Object)null) { return filter(val); } return false; } private static Dictionary Ensure() { if (_index != null) { return _index; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Dictionary iTEM_PREFABS = ResourcesPrefabManager.ITEM_PREFABS; if (iTEM_PREFABS == null) { return dictionary; } foreach (Item value2 in iTEM_PREFABS.Values) { if (!((Object)(object)value2 == (Object)null) && !string.IsNullOrEmpty(value2.Name)) { string key = value2.Name.Trim(); if (!dictionary.TryGetValue(key, out var value) || value2.ItemID < value) { dictionary[key] = value2.ItemID; } } } if (dictionary.Count > 0) { _index = dictionary; } return dictionary; } } internal static class KeybindRegistry { internal struct Bound { public string Mod; public string Action; public string ConfigHint; public string MainKey; public string[] Modifiers; } internal const string NoneKey = "None"; private static readonly Dictionary _claims = new Dictionary(); internal static int Count => _claims.Count; internal static void Reset() { _claims.Clear(); } internal static bool SameCombo(string aMain, string[] aMods, string bMain, string[] bMods) { if (aMain != bMain) { return false; } List list = new List(aMods ?? new string[0]); List list2 = new List(bMods ?? new string[0]); if (list.Count != list2.Count) { return false; } list.Sort(StringComparer.Ordinal); list2.Sort(StringComparer.Ordinal); for (int i = 0; i < list.Count; i++) { if (list[i] != list2[i]) { return false; } } return true; } internal static string ComboText(string mainKey, string[] modifiers) { StringBuilder stringBuilder = new StringBuilder(); if (modifiers != null) { foreach (string value in modifiers) { stringBuilder.Append(value).Append('+'); } } return stringBuilder.Append(mainKey).ToString(); } internal static string Claim(string mod, string action, string mainKey, string[] modifiers, string configHint) { string text = mod + "|" + action; if (mainKey == "None") { _claims.Remove(text); return null; } _claims[text] = new Bound { Mod = mod, Action = action, ConfigHint = configHint, MainKey = mainKey, Modifiers = (modifiers ?? new string[0]) }; List list = Others(mainKey, modifiers, text); if (list.Count == 0) { return null; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[KEYBIND] CONFLICT on " + ComboText(mainKey, modifiers) + ": "); stringBuilder.Append(Describe(_claims[text])); foreach (Bound item in list) { stringBuilder.Append(" AND ").Append(Describe(item)); } stringBuilder.Append(". One keypress fires ALL of them — that is a real gameplay bug, not just noise "); stringBuilder.Append("(Bug 26: opening the spawn menu silently cast Hunt as One). Rebind one of them: "); stringBuilder.Append(RebindAdvice(mainKey, modifiers, text, list)); return stringBuilder.ToString(); } internal static string Report() { if (_claims.Count == 0) { return "[KEYBIND] no keys claimed."; } SortedDictionary> sortedDictionary = new SortedDictionary>(StringComparer.Ordinal); foreach (Bound value2 in _claims.Values) { string key = ComboText(value2.MainKey, value2.Modifiers); if (!sortedDictionary.TryGetValue(key, out var value)) { value = (sortedDictionary[key] = new List()); } value.Add(value2); } StringBuilder stringBuilder = new StringBuilder("[KEYBIND] claimed keys:"); int num = 0; foreach (KeyValuePair> item in sortedDictionary) { bool flag = item.Value.Count > 1; if (flag) { num++; } stringBuilder.Append(string.Format("\n {0,-12}{1}", item.Key, flag ? "*** CONFLICT *** " : "")); for (int i = 0; i < item.Value.Count; i++) { stringBuilder.Append((i == 0) ? "" : " AND ").Append(Describe(item.Value[i])); } } stringBuilder.Append($"\n {num} conflict(s) across {sortedDictionary.Count} key(s)."); return stringBuilder.ToString(); } internal static bool IsFree(string mainKey, string[] modifiers) { if (mainKey == "None") { return true; } foreach (KeyValuePair claim in _claims) { if (SameCombo(claim.Value.MainKey, claim.Value.Modifiers, mainKey, modifiers)) { return false; } } return true; } internal static bool HasConflicts() { foreach (KeyValuePair claim in _claims) { if (Others(claim.Value.MainKey, claim.Value.Modifiers, claim.Key).Count > 0) { return true; } } return false; } private static List Others(string mainKey, string[] modifiers, string selfId) { List list = new List(); foreach (KeyValuePair claim in _claims) { if (claim.Key != selfId && SameCombo(claim.Value.MainKey, claim.Value.Modifiers, mainKey, modifiers)) { list.Add(claim.Value); } } return list; } private static string Describe(Bound c) { return c.Mod + " '" + c.Action + "'" + ((c.ConfigHint == null) ? " (HARDCODED — cannot be rebound)" : (" (" + c.ConfigHint + ")")); } private static string RebindAdvice(string mainKey, string[] modifiers, string selfId, List others) { List list = new List(); Bound item = _claims[selfId]; if (item.ConfigHint != null) { list.Add(item); } foreach (Bound other in others) { if (other.ConfigHint != null) { list.Add(other); } } if (list.Count == 0) { return "BOTH are hardcoded — this needs a code fix, there is nothing the player can do about " + ComboText(mainKey, modifiers) + "."; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < list.Count; i++) { stringBuilder.Append((i == 0) ? "" : " or ").Append(list[i].Mod + "'s " + list[i].ConfigHint); } return stringBuilder.ToString() + "."; } } public static class Keybinds { private static readonly HashSet> _subscribed = new HashSet>(); private static ManualLogSource L => Plugin.Log; private static string Main(KeyboardShortcut c) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) return ((object)((KeyboardShortcut)(ref c)).MainKey/*cast due to .constrained prefix*/).ToString(); } private static string[] Mods(KeyboardShortcut c) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (KeyCode modifier in ((KeyboardShortcut)(ref c)).Modifiers) { list.Add(((object)modifier/*cast due to .constrained prefix*/).ToString()); } return list.ToArray(); } public static void Claim(string mod, string action, ConfigEntry entry) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) if (entry == null) { ManualLogSource l = L; if (l != null) { l.LogWarning((object)("[KEYBINDS] " + mod + "/" + action + ": no config entry — key NOT registered, so a collision on it cannot be detected.")); } return; } string hint = "[" + ((ConfigEntryBase)entry).Definition.Section + "] " + ((ConfigEntryBase)entry).Definition.Key; Claim(mod, action, entry.Value, hint); if (_subscribed.Add(entry)) { entry.SettingChanged += delegate { //IL_0012: Unknown result type (might be due to invalid IL or missing references) Claim(mod, action, entry.Value, hint); }; } } public static void Claim(string mod, string action, KeyCode key, string configHint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Keybinds.Claim(mod, action, new KeyboardShortcut(key, Array.Empty()), configHint); } public static void Claim(string mod, string action, KeyboardShortcut combo, string configHint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) string text = KeybindRegistry.Claim(mod, action, Main(combo), Mods(combo), configHint); if (text != null) { ManualLogSource l = L; if (l != null) { l.LogWarning((object)text); } } } public static string Report() { return KeybindRegistry.Report(); } public static bool IsFree(KeyboardShortcut combo) { //IL_0000: 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) return KeybindRegistry.IsFree(Main(combo), Mods(combo)); } public static bool IsFree(KeyCode key) { //IL_0000: 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) return Keybinds.IsFree(new KeyboardShortcut(key, Array.Empty())); } public static bool HasConflicts() { return KeybindRegistry.HasConflicts(); } } public static class KitContract { public readonly struct Declaration { public readonly string Consumer; public readonly string KitGuid; public readonly string BuiltAgainst; public Declaration(string consumer, string kitGuid, string builtAgainst) { Consumer = consumer; KitGuid = kitGuid; BuiltAgainst = builtAgainst; } } public const string CompatSinceField = "COMPAT_SINCE"; public const string GuidPrefix = "cobalt."; private static readonly List s_declared = new List(); public static IReadOnlyList Declared => s_declared; public static void Declare(string consumer, string kitGuid, string builtAgainstVersion) { if (string.IsNullOrEmpty(consumer) || string.IsNullOrEmpty(kitGuid)) { return; } for (int i = 0; i < s_declared.Count; i++) { if (s_declared[i].Consumer == consumer && s_declared[i].KitGuid == kitGuid) { return; } } s_declared.Add(new Declaration(consumer, kitGuid, builtAgainstVersion ?? "")); } public static List> Report(out bool anyError) { anyError = false; List> list = new List>(); if (s_declared.Count == 0) { list.Add(new KeyValuePair(key: false, "[CONTRACT] no consumer declared a kit version. Either only kits are installed, or every consumer predates KitContract — the handshake cannot check anything; see [STAMP] below.")); return list; } foreach (Declaration item in s_declared) { string kitName = item.KitGuid; string text = null; string compatSince = null; if (Chainloader.PluginInfos.TryGetValue(item.KitGuid, out var value) && value != null) { BepInPlugin metadata = value.Metadata; kitName = ((metadata != null) ? metadata.Name : null) ?? item.KitGuid; BepInPlugin metadata2 = value.Metadata; text = ((metadata2 == null) ? null : metadata2.Version?.ToString()); BaseUnityPlugin instance = value.Instance; compatSince = ReadConst(((Object)(object)instance != (Object)null) ? ((object)instance).GetType() : null, "COMPAT_SINCE"); } KitContractRules.Verdict v = ((text == null) ? KitContractRules.Verdict.Unknown : KitContractRules.Judge(item.BuiltAgainst, text, compatSince)); bool flag = KitContractRules.IsError(v); anyError |= flag; list.Add(new KeyValuePair(flag, (text == null) ? ("[CONTRACT] " + item.Consumer + " built against " + item.KitGuid + " " + item.BuiltAgainst + " — kit not loaded (BepInEx refused or skipped it; see the chainloader lines above).") : KitContractRules.Describe(item.Consumer, kitName, item.BuiltAgainst, text, compatSince, v))); } return list; } public static string StampCensus(out bool skew) { SortedDictionary> sortedDictionary = new SortedDictionary>(StringComparer.Ordinal); foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { if (pluginInfo.Key == null || !pluginInfo.Key.StartsWith("cobalt.", StringComparison.Ordinal)) { continue; } Assembly asm = null; try { PluginInfo value = pluginInfo.Value; BaseUnityPlugin val = ((value != null) ? value.Instance : null); if ((Object)(object)val != (Object)null) { asm = ((object)val).GetType().Assembly; } } catch { } string key = BuildStamp.Read(asm); if (!sortedDictionary.TryGetValue(key, out var value2)) { value2 = (sortedDictionary[key] = new List()); } List list2 = value2; PluginInfo value3 = pluginInfo.Value; object obj2; if (value3 == null) { obj2 = null; } else { BepInPlugin metadata = value3.Metadata; obj2 = ((metadata != null) ? metadata.Name : null); } if (obj2 == null) { obj2 = pluginInfo.Key; } PluginInfo value4 = pluginInfo.Value; object arg; if (value4 == null) { arg = null; } else { BepInPlugin metadata2 = value4.Metadata; arg = ((metadata2 != null) ? metadata2.Version : null); } list2.Add($"{obj2} {arg}"); } int num = 0; foreach (string key2 in sortedDictionary.Keys) { if (key2 != "unknown") { num++; } } skew = num > 1; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(skew ? ($"[STAMP] BUILD SKEW: {num} different builds are installed side by side. Kits are shared DLLs — " + "a mod built against one build of a kit and running on another fails at first call, not at load. Install everything from ONE bundle/release:") : $"[STAMP] {sortedDictionary.Count} build(s) installed:"); foreach (KeyValuePair> item in sortedDictionary) { item.Value.Sort(StringComparer.Ordinal); stringBuilder.Append(string.Format("\n[STAMP] {0,-20} {1}", item.Key, string.Join(", ", item.Value))); } return stringBuilder.ToString(); } public static bool IsAlphaBundleInstall() { try { string path = Path.Combine(Paths.BepInExRootPath, "cobalt-bundles"); return Directory.Exists(path) && Directory.GetFiles(path, "*.json").Length != 0; } catch { return false; } } public static string ToastText(bool contractError, bool stampSkew) { if (contractError) { return "Mod version mismatch: a mod was built against a different kit build. Reinstall all mods from ONE bundle. Details: BepInEx/LogOutput.log [CONTRACT]"; } if (!stampSkew) { return null; } return "Mods from two different alpha bundles are installed. Reinstall from ONE bundle. See [STAMP] in BepInEx/LogOutput.log"; } private static string ReadConst(Type t, string name) { if (t == null) { return null; } try { FieldInfo field = t.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); if (field == null) { return null; } object obj = (field.IsLiteral ? field.GetRawConstantValue() : field.GetValue(null)); return obj as string; } catch { return null; } } } public static class KitContractRules { public enum Verdict { Match, KitNewerCompatible, ConsumerBelowFloor, ConsumerAhead, Unknown } public static bool IsError(Verdict v) { if (v != Verdict.ConsumerBelowFloor) { return v == Verdict.ConsumerAhead; } return true; } public static Version Parse(string s) { if (string.IsNullOrEmpty(s)) { return null; } s = s.Trim(); if (s.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { s = s.Substring(1); } if (!Version.TryParse(s, out Version result) || s.IndexOf('-') >= 0 || s.IndexOf('+') >= 0) { return null; } return result; } public static Verdict Judge(string builtAgainst, string running, string compatSince) { Version version = Parse(builtAgainst); Version version2 = Parse(running); if (version == null || version2 == null) { return Verdict.Unknown; } int num = Compare(version, version2); if (num == 0) { return Verdict.Match; } if (num > 0) { return Verdict.ConsumerAhead; } Version version3 = Parse(compatSince); if (version3 != null && Compare(version, version3) < 0) { return Verdict.ConsumerBelowFloor; } return Verdict.KitNewerCompatible; } public static int Compare(Version a, Version b) { return Norm(a).CompareTo(Norm(b)); } private static Version Norm(Version v) { return new Version(Math.Max(v.Major, 0), Math.Max(v.Minor, 0), Math.Max(v.Build, 0), 0); } public static string Describe(string consumer, string kitName, string builtAgainst, string running, string compatSince, Verdict v) { string text = "[CONTRACT] " + consumer + " built against " + kitName + " " + builtAgainst + ", running " + running; return v switch { Verdict.Match => text + " — match.", Verdict.KitNewerCompatible => text + " — newer kit, binary-compatible (floor " + (compatSince ?? "none") + ").", Verdict.ConsumerBelowFloor => text + " — VERSION SKEW: " + kitName + " only binds consumers built against >= " + compatSince + " (COMPAT_SINCE). Public members " + consumer + " was compiled to call no longer exist in this shape; expect MissingMethod/MissingFieldException at first use. Remedy: install " + consumer + " and " + kitName + " from the SAME bundle/release.", Verdict.ConsumerAhead => text + " — VERSION SKEW (consumer AHEAD): " + consumer + " was built against a NEWER " + kitName + " than is installed. " + kitName + " is the stale half — update it. Expect MissingMethodException the first time a newer seam is touched.", _ => text + " — could not compare (unparseable version).", }; } } public static class Lifecycle { private static readonly Dictionary _generations = new Dictionary(); private static bool _pollThrowNoted; public static bool IsSanePosition(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (p.y > -3000f) { return ((Vector3)(ref p)).sqrMagnitude < 100000000f; } return false; } public static bool TryGetFirstLocalCharacter(out Character player) { player = null; CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } try { player = instance.GetFirstLocalCharacter(); } catch (NullReferenceException) { return false; } return (Object)(object)player != (Object)null; } public static Character FirstLocalCharacterOrNull() { if (!TryGetFirstLocalCharacter(out var player)) { return null; } return player; } public static int InvalidateWaits(object waitKey) { if (waitKey == null) { return 0; } _generations.TryGetValue(waitKey, out var value); value++; _generations[waitKey] = value; return value; } private static bool IsStale(object waitKey, int myGen) { if (waitKey != null && _generations.TryGetValue(waitKey, out var value)) { return value != myGen; } return false; } public static IEnumerator WhenPlayerReady(Func getPlayer, Action onReady, Action onTimeout = null, float timeoutSeconds = 30f, object waitKey = null) { int myGen = ((waitKey != null) ? InvalidateWaits(waitKey) : 0); float t0 = Time.unscaledTime; Character player = null; while (Time.unscaledTime - t0 < timeoutSeconds) { if (IsStale(waitKey, myGen)) { LogSuperseded(waitKey); yield break; } try { player = getPlayer(); } catch (Exception ex) { if (!_pollThrowNoted) { _pollThrowNoted = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)("[LIFECYCLE] a WhenPlayerReady resolver threw (" + ex.GetType().Name + ") — treated as 'not ready yet' (MP9). Said once per session.")); } } player = null; } if ((Object)(object)player != (Object)null && IsSanePosition(((Component)player).transform.position)) { break; } player = null; yield return (object)new WaitForSecondsRealtime(0.5f); } if (IsStale(waitKey, myGen)) { LogSuperseded(waitKey); } else if ((Object)(object)player == (Object)null) { onTimeout?.Invoke($"player not ready within {timeoutSeconds:F0}s"); } else { onReady(player); } } private static void LogSuperseded(object waitKey) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogMessage((object)$"[LIFECYCLE] superseded a stale WhenPlayerReady wait (key={waitKey})."); } } } public static class LoadGate { public const float DefaultTimeoutSeconds = 180f; internal const float VanillaFixedDelta = 0.022f; private const float SettleSeconds = 2f; private static readonly HashSet _armed = new HashSet(); private static int _nextToken; private static bool _gotoLatched; private static float _gotoLatchedAt; public static bool Armed => _armed.Count > 0; public static void LatchGoto() { _gotoLatched = true; _gotoLatchedAt = Time.unscaledTime; } public static bool IsGotoLatched() { if (!_gotoLatched) { return false; } float num = Time.unscaledTime - _gotoLatchedAt; if (num > 180f) { _gotoLatched = false; return false; } if (num > 2f) { NetworkLevelLoader instance = NetworkLevelLoader.Instance; bool flag = false; try { flag = (Object)(object)instance != (Object)null && instance.IsOverallLoadingDone; } catch { } if (flag) { _gotoLatched = false; return false; } } return true; } public static string FormatGateState(NetworkLevelLoader nll) { if ((Object)(object)nll == (Object)null) { return "n/a (NetworkLevelLoader.Instance null — main menu?)"; } Func, string> func = delegate(Func f) { try { object obj = f(); return (obj == null) ? "null" : obj.ToString(); } catch (Exception ex) { return "threw:" + ex.GetType().Name; } }; return "continueAfter=" + func(() => nll.ContinueAfterLoading) + " gameplayLoading=" + func(() => nll.IsGameplayLoading) + " sceneLoading=" + func(() => nll.IsSceneLoading) + " preping=" + func(() => nll.m_prepingLoadLevel) + " allDone=" + func(() => nll.AllPlayerDoneLoading) + " allReady=" + func(() => nll.AllPlayerReadyToContinue) + " overallDone=" + func(() => nll.IsOverallLoadingDone) + " masterLoadingUI=" + func(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsMasterLoadingDisplayed) + " joiningWorld=" + func(() => nll.IsJoiningWorld) + " doneIds=[" + func(() => string.Join(",", ToStrings(nll.m_doneLoadingPlayers))) + "] readyIds=[" + func(() => string.Join(",", ToStrings(nll.m_readyToContinuePlayers))) + "] waitingOthers=" + func(() => nll.m_waitingForOtherPlayers) + " prologuePanel=" + func(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsProloguePanelDisplayed) + " scene='" + func(delegate { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; }) + "'"; } private static string[] ToStrings(List ids) { if (ids == null) { return new string[0]; } string[] array = new string[ids.Count]; for (int i = 0; i < ids.Count; i++) { array[i] = ids[i].ToString(); } return array; } public static LoadSnapshot Snapshot(NetworkLevelLoader nll) { LoadSnapshot result = default(LoadSnapshot); if ((Object)(object)nll == (Object)null) { return result; } try { result.OverallDone = nll.IsOverallLoadingDone; } catch { } try { result.SaveInProgress = nll.IsSaveInProgress; } catch { } try { result.Preping = nll.m_prepingLoadLevel; } catch { } try { result.GameplayLoading = nll.IsGameplayLoading; } catch { } try { result.SceneLoading = nll.IsSceneLoading; } catch { } try { result.AllDone = nll.AllPlayerDoneLoading; } catch { } try { result.ContinueAfter = nll.ContinueAfterLoading; } catch { } try { result.MasterLoadingUi = (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsMasterLoadingDisplayed; } catch { } try { result.ProloguePanelUp = (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsProloguePanelDisplayed; } catch { } return result; } public static bool LoadInFlight(NetworkLevelLoader nll, out string why) { why = null; if ((Object)(object)nll == (Object)null) { return false; } try { if (nll.IsSceneLoading) { why = "IsSceneLoading"; return true; } } catch { } try { if (nll.IsGameplayLoading) { why = "IsGameplayLoading"; return true; } } catch { } try { if (nll.m_prepingLoadLevel) { why = "m_prepingLoadLevel"; return true; } } catch { } try { if (!nll.IsOverallLoadingDone) { bool flag = false; try { flag = nll.m_waitingForOtherPlayers; } catch { } why = (flag ? "!IsOverallLoadingDone (m_waitingForOtherPlayers — a peer never checked in; 'unstick force forceready' if you are sure)" : "!IsOverallLoadingDone"); return true; } } catch { } if (IsGotoLatched()) { why = "a previous dev goto is still latched"; return true; } return false; } public static int ArmForVerb(ManualLogSource log, string reason) { return Arm(log, reason, "[LOADGATE]", null, null, null, null, 180f, releasesGotoLatch: true); } public static int Arm(ManualLogSource log, string reason, string tag = "[LOADGATE]", Func alive = null, Func gatePhaseReached = null, Action onPassed = null, Func extraFields = null, float timeoutSeconds = 180f, bool releasesGotoLatch = false) { int num = ++_nextToken; _armed.Add(num); Runner.Instance.StartCoroutine(Watch(num, log, reason ?? "?", tag ?? "[LOADGATE]", alive, gatePhaseReached, onPassed, extraFields, timeoutSeconds, releasesGotoLatch)); return num; } public static void Disarm(int token) { _armed.Remove(token); } private static IEnumerator Watch(int token, ManualLogSource log, string reason, string tag, Func alive, Func gatePhaseReached, Action onPassed, Func extraFields, float timeoutSeconds, bool releasesGotoLatch) { float armedAt = Time.unscaledTime; float lastReport = armedAt; bool sawLoading = false; bool passed = false; bool warnedMsgQueue = false; bool rescuedTimeScale = false; float msgQueueDownSince = -1f; string phase = "unknown"; List phases = new List(); while (_armed.Contains(token)) { bool flag = false; float num = Time.unscaledTime - armedAt; try { if (alive != null && !alive()) { flag = true; } } catch (Exception ex) { log.LogWarning((object)(tag + " liveness check threw (" + ex.GetType().Name + ") — disarming '" + reason + "'.")); flag = true; } if (!flag) { try { NetworkLevelLoader instance = NetworkLevelLoader.Instance; LoadSnapshot s = Snapshot(instance); string text = LoadPhase.Classify(s); if (text != phase) { phase = text; phases.Add(text); } if (s.GameplayLoading) { sawLoading = true; } bool flag2 = gatePhaseReached?.Invoke() ?? (sawLoading && !s.GameplayLoading && num > 1f); if (!passed && flag2 && LoadPhase.GateGuardHolds(s) && (Object)(object)instance != (Object)null) { passed = true; if (onPassed != null) { onPassed(); } else { instance.SetContinueAfterLoading(); } log.LogMessage((object)$"{tag} passed the continue gate for '{reason}' after {num:F1}s — no keypress needed."); } float timeScale = Time.timeScale; if (!rescuedTimeScale && timeScale < 0.01f && !s.OverallDone) { rescuedTimeScale = true; int gamePausedByPlayer = Global.GamePausedByPlayer; if (gamePausedByPlayer != -1) { PauseMenu.Pause(false); log.LogWarning((object)($"{tag} timeScale was {timeScale:F3} with the pause menu claimed by player {gamePausedByPlayer} " + "during '" + reason + "' — closed it (PauseMenu.Pause(false)); a scaled-time load never advances.")); } else { Time.timeScale = 1f; Time.fixedDeltaTime = 0.022f; log.LogWarning((object)($"{tag} timeScale was {timeScale:F3} (unclaimed) during '{reason}' — restored to 1; " + "a scaled-time load never advances (BlackFade/Invoke are scaled).")); } } bool flag3 = true; try { flag3 = PhotonNetwork.isMessageQueueRunning; } catch { } if (!flag3) { if (msgQueueDownSince < 0f) { msgQueueDownSince = Time.unscaledTime; } if (!warnedMsgQueue && Time.unscaledTime - msgQueueDownSince > 10f) { warnedMsgQueue = true; log.LogWarning((object)(tag + " PhotonNetwork.isMessageQueueRunning has been FALSE for " + $"{Time.unscaledTime - msgQueueDownSince:F0}s during '{reason}' — the loader disables it for the " + "load and re-enables it only at NetworkLevelLoader.cs:1307, so this machine is network-dead until the load gets past that point. A peer will see this box as frozen, not as slow.")); } } else { msgQueueDownSince = -1f; } if (Time.unscaledTime - lastReport >= 5f) { lastReport = Time.unscaledTime; string text2; try { text2 = extraFields?.Invoke(); } catch { text2 = "extraFields:threw"; } float num2 = -1f; try { if ((Object)(object)instance != (Object)null && instance.m_async != null) { num2 = instance.m_async.progress; } } catch { } log.LogMessage((object)($"{tag} phase={phase} t={num:F0}s " + (string.IsNullOrEmpty(text2) ? "" : (text2 + " ")) + FormatGateState(instance) + $" msgQueue={flag3} timeScale={timeScale:F2} asyncProgress={num2:F2} sawLoading={sawLoading}")); } if (s.OverallDone && sawLoading && num > 2f) { log.LogMessage((object)($"{tag} load '{reason}' reached overallDone after {num:F1}s " + "(phases: " + string.Join(" -> ", phases.ToArray()) + ")")); if (releasesGotoLatch) { _gotoLatched = false; } flag = true; } else if (num > timeoutSeconds) { log.LogError((object)($"{tag} ERROR: load '{reason}' still not done after {timeoutSeconds:F0}s — " + "disarming; last phase=" + phase + ". Run 'unstick' for the full state.")); flag = true; } } catch (Exception ex2) { log.LogWarning((object)(tag + " watch poll threw (watch continues): " + ex2.GetType().Name + ": " + ex2.Message)); } } if (flag) { break; } yield return (object)new WaitForSecondsRealtime(0.25f); } _armed.Remove(token); } } public struct LoadSnapshot { public bool OverallDone; public bool SaveInProgress; public bool Preping; public bool GameplayLoading; public bool SceneLoading; public bool AllDone; public bool ContinueAfter; public bool MasterLoadingUi; public bool ProloguePanelUp; } public static class LoadPhase { public const string Done = "done"; public const string Saving = "saving"; public const string Preping = "preping"; public const string Prologue = "prologue"; public const string SceneLoading = "scene-loading"; public const string Fading = "fading"; public const string Gate = "gate"; public const string WaitingPlayers = "waiting-players"; public const string PostGate = "post-gate"; public const string Unknown = "unknown"; public static string Classify(LoadSnapshot s) { if (s.SaveInProgress) { return "saving"; } if (s.Preping) { return "preping"; } if (s.ProloguePanelUp) { return "prologue"; } if (s.OverallDone) { return "done"; } if (s.SceneLoading) { return "scene-loading"; } if (s.GameplayLoading) { return "fading"; } if (s.AllDone && !s.ContinueAfter && s.MasterLoadingUi) { return "gate"; } if (!s.AllDone) { return "waiting-players"; } if (s.ContinueAfter) { return "post-gate"; } return "unknown"; } public static bool GateGuardHolds(LoadSnapshot s) { if (!s.ContinueAfter && !s.GameplayLoading && s.AllDone && s.MasterLoadingUi) { return !s.ProloguePanelUp; } return false; } } public enum LogTier { Quiet, Normal, Verbose, Trace } public static class LogTiers { public const int RankAlways = 0; public const int RankMessage = 1; public const int RankInfo = 2; public const int RankDebug = 3; public static bool Emits(LogTier tier, int rank) { return rank <= (int)tier; } public static bool Emits(LogTier tier, int rank, bool requested) { if (!requested) { return Emits(tier, rank); } return true; } public static bool TryParse(string text, ref LogTier tier) { if (string.IsNullOrEmpty(text)) { return false; } switch (text.Trim().ToLowerInvariant()) { case "quiet": case "q": tier = LogTier.Quiet; return true; case "n": case "default": case "normal": tier = LogTier.Normal; return true; case "v": case "verbose": tier = LogTier.Verbose; return true; case "debug": case "trace": case "t": tier = LogTier.Trace; return true; default: return false; } } public static string Names() { return "Quiet|Normal|Verbose|Trace"; } } public sealed class ModLog { private sealed class RequestedScope : IDisposable { private bool _done; internal RequestedScope() { s_requestedDepth++; } public void Dispose() { if (!_done) { _done = true; if (s_requestedDepth > 0) { s_requestedDepth--; } } } } private readonly ManualLogSource _sink; private static int s_requestedDepth; internal const string Section = "Diag"; internal const string Key = "LogLevel"; public const LogTier ShippedDefault = LogTier.Verbose; private const string Help = "How much this mod writes to the log. Quiet = warnings/errors only. Normal adds boot lines, on-screen notices and self-test results. Verbose (default) adds the per-feature diagnostics — the default is deliberately chatty while these mods are still being debugged, so a bug report arrives with context. Trace adds per-tick detail. NOTE: Trace also needs BepInEx.cfg's [Logging.Disk] LogLevels to include Debug — stock BepInEx drops it. Verbose does not: Info is passed by default."; private static bool s_sinkWarned; public ManualLogSource Sink => _sink; public LogTier Tier { get; set; } public static bool RequestedOutput => s_requestedDepth > 0; public bool TraceOn => LogTiers.Emits(Tier, 3, RequestedOutput); public bool VerboseOn => LogTiers.Emits(Tier, 2, RequestedOutput); public bool MessageOn => LogTiers.Emits(Tier, 1, RequestedOutput); public ModLog(ManualLogSource sink, LogTier tier = LogTier.Verbose) { if (sink == null) { throw new ArgumentNullException("sink"); } _sink = sink; Tier = tier; } public static IDisposable Requested() { return new RequestedScope(); } public void LogFatal(object data) { _sink.LogFatal(data); } public void LogError(object data) { _sink.LogError(data); } public void LogWarning(object data) { _sink.LogWarning(data); } public void LogMessage(object data) { if (LogTiers.Emits(Tier, 1, RequestedOutput)) { _sink.LogMessage(data); } } public void LogInfo(object data) { if (LogTiers.Emits(Tier, 2, RequestedOutput)) { _sink.LogInfo(data); } } public void LogDebug(object data) { if (LogTiers.Emits(Tier, 3, RequestedOutput)) { _sink.LogDebug(data); } } public void Log(LogLevel level, object data) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (LogTiers.Emits(Tier, RankOf(level), RequestedOutput)) { _sink.Log(level, data); } } public static int RankOf(LogLevel level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((level & 0x20) != 0) { return 3; } if ((level & 0x10) != 0) { return 2; } if ((level & 8) != 0) { return 1; } return 0; } public static LogLevel FloorOf(LogTier tier) { return (LogLevel)(tier switch { LogTier.Trace => 32, LogTier.Verbose => 16, LogTier.Quiet => 4, _ => 8, }); } public static ModLog Ungated(ManualLogSource sink) { if (sink != null) { return new ModLog(sink, LogTier.Trace); } return null; } public static implicit operator ManualLogSource(ModLog log) { return log?._sink; } public static ModLog Bind(BaseUnityPlugin plugin, ManualLogSource sink) { if ((Object)(object)plugin == (Object)null) { throw new ArgumentNullException("plugin"); } ConfigEntry entry = plugin.Config.Bind("Diag", "LogLevel", LogTier.Verbose, "How much this mod writes to the log. Quiet = warnings/errors only. Normal adds boot lines, on-screen notices and self-test results. Verbose (default) adds the per-feature diagnostics — the default is deliberately chatty while these mods are still being debugged, so a bug report arrives with context. Trace adds per-tick detail. NOTE: Trace also needs BepInEx.cfg's [Logging.Disk] LogLevels to include Debug — stock BepInEx drops it. Verbose does not: Info is passed by default."); ModLog log = new ModLog(sink, entry.Value); entry.SettingChanged += delegate { log.Tier = entry.Value; }; WarnIfSinkDrops(sink, entry.Value); return log; } private unsafe static void WarnIfSinkDrops(ManualLogSource sink, LogTier tier) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_007c: 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) if (s_sinkWarned || sink == null) { return; } try { LogLevel val = FloorOf(tier); foreach (ILogListener listener in Logger.Listeners) { if (listener == null || ((object)listener).GetType().Name.IndexOf("Disk", StringComparison.OrdinalIgnoreCase) < 0) { continue; } PropertyInfo property = ((object)listener).GetType().GetProperty("DisplayedLogLevel", BindingFlags.Instance | BindingFlags.Public); if (!(property == null)) { LogLevel val2 = (LogLevel)property.GetValue(listener, null); if ((val2 & val) == 0) { s_sinkWarned = true; sink.LogWarning((object)("[LOGLEVEL] Diag.LogLevel = " + tier.ToString() + " needs " + ((object)(*(LogLevel*)(&val))/*cast due to .constrained prefix*/).ToString() + ", but BepInEx's disk log is set to '" + ((object)(*(LogLevel*)(&val2))/*cast due to .constrained prefix*/).ToString() + "'. Those lines are being dropped before they reach the file — add " + ((object)(*(LogLevel*)(&val))/*cast due to .constrained prefix*/).ToString() + " to [Logging.Disk] LogLevels in BepInEx/config/BepInEx.cfg.")); } } break; } } catch { } } } public sealed class NameCandidates { public struct Validation { public string Raw { get; } public IReadOnlyList Known { get; } public IReadOnlyList Unknown { get; } public bool NoneKnown => Known.Count == 0; internal Validation(string raw, IReadOnlyList known, IReadOnlyList unknown) { Raw = raw; Known = known; Unknown = unknown; } } private readonly Func _readRaw; private readonly Func _exists; private readonly Action _onResolved; private readonly Action _onUnresolved; private readonly HashSet _warned = new HashSet(); private string _resolvedKey; private string _resolvedId; private string _validatedKey; private string _missParkedKey; private float _missRetryAt; public const float MissRetrySeconds = 30f; internal static Func Clock; public string Label { get; } public string Raw => _readRaw() ?? ""; public string Cached => _resolvedId; static NameCandidates() { Clock = () => 0f; Clock = () => Time.unscaledTime; } public static bool StatusPrefabExists(string name) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; return (Object)(object)((instance != null) ? instance.GetStatusEffectPrefab(name) : null) != (Object)null; } public static bool ItemPrefabExists(string name) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; return (Object)(object)((instance != null) ? instance.GetItemPrefab(name) : null) != (Object)null; } public NameCandidates(string label, Func readRaw, Func exists, Action onResolved = null, Action onUnresolved = null) { Label = label; _readRaw = readRaw ?? throw new ArgumentNullException("readRaw"); _exists = exists ?? throw new ArgumentNullException("exists"); _onResolved = onResolved; _onUnresolved = onUnresolved; } public IReadOnlyList Parse() { return Split(Raw); } public static IReadOnlyList Split(string raw) { List list = new List(); if (string.IsNullOrEmpty(raw)) { return list; } string[] array = raw.Split(new char[1] { ',' }); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length > 0 && !list.Contains(text2)) { list.Add(text2); } } return list; } public string Resolve() { string raw = Raw; if (_resolvedId != null && raw == _resolvedKey) { return _resolvedId; } if (_missParkedKey != null) { if (raw == _missParkedKey && Clock() < _missRetryAt) { return null; } _missParkedKey = null; } foreach (string item in Split(raw)) { if (_exists(item)) { _resolvedKey = raw; _resolvedId = item; _onResolved?.Invoke(item); return item; } } _resolvedKey = null; _resolvedId = null; _missParkedKey = raw; _missRetryAt = Clock() + 30f; if (_warned.Add(raw)) { _onUnresolved?.Invoke(raw); } return null; } public bool TryValidate(out Validation result) { string raw = Raw; if (raw == _validatedKey) { result = default(Validation); return false; } _validatedKey = raw; List list = new List(); List list2 = new List(); foreach (string item in Split(raw)) { (_exists(item) ? list : list2).Add(item); } result = new Validation(raw, list, list2); return true; } public T FindFirst(Func lookup) where T : class { if (lookup == null) { return null; } foreach (string item in Split(Raw)) { T val = lookup(item); if (val != null) { return val; } } return null; } public void Invalidate() { _resolvedKey = null; _resolvedId = null; _validatedKey = null; _missParkedKey = null; _warned.Clear(); } } public static class Notify { public static ManualLogSource Log; private static ManualLogSource L => Log ?? Plugin.Log; public static void Player(Character character, string message) { L.LogMessage((object)("[NOTIFY] " + message)); if ((Object)(object)character != (Object)null && (Object)(object)character.CharacterUI != (Object)null) { character.CharacterUI.ShowInfoNotification(message); } else { L.LogWarning((object)"[NOTIFY] no CharacterUI to show the toast on (logged only)."); } } } public sealed class PendingSet { public const double AbandonedAfterSeconds = 30.0; private readonly Dictionary _open = new Dictionary(); private readonly List _scratch = new List(); private long _nextId; public int RawCount => _open.Count; public long Open(double now) { long num = ++_nextId; _open[num] = now; return num; } public void Close(long id) { _open.Remove(id); } public int CountAt(double now) { Reap(now); return _open.Count; } public bool AnyAt(double now) { return CountAt(now) > 0; } private void Reap(double now) { if (_open.Count == 0) { return; } _scratch.Clear(); foreach (KeyValuePair item in _open) { if (now - item.Value >= 30.0) { _scratch.Add(item.Key); } } for (int i = 0; i < _scratch.Count; i++) { _open.Remove(_scratch[i]); } } } [BepInPlugin("cobalt.forgekit", "ForgeKit", "0.4.10")] public class Plugin : BaseUnityPlugin { public const string GUID = "cobalt.forgekit"; public const string NAME = "ForgeKit"; public const string VERSION = "0.4.10"; public const string COMPAT_SINCE = "0.4.4"; internal static ManualLogSource Log; private bool _censusLogged; internal void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogMessage((object)"ForgeKit 0.4.10 loaded."); Log.LogMessage((object)("[FORGEKIT] build " + BuildStamp.Local)); } internal void Update() { if (_censusLogged) { return; } _censusLogged = true; ((Behaviour)this).enabled = false; try { Log.LogMessage((object)Keybinds.Report()); } catch (Exception ex) { Log.LogWarning((object)("[KEYBINDS] census failed: " + ex.GetType().Name + ": " + ex.Message + " — the [CONTRACT]/[STAMP] lines below are unaffected.")); } bool anyError = false; bool skew = false; try { foreach (KeyValuePair item in KitContract.Report(out anyError)) { if (item.Key) { Log.LogError((object)item.Value); } else { Log.LogMessage((object)item.Value); } } string text = KitContract.StampCensus(out skew); if (skew) { Log.LogWarning((object)text); } else { Log.LogMessage((object)text); } } catch (Exception ex2) { Log.LogWarning((object)("[CONTRACT] handshake report failed: " + ex2.GetType().Name + ": " + ex2.Message)); } try { foreach (string item2 in CfgSkew.Sweep()) { Log.LogWarning((object)item2); } } catch (Exception ex3) { Log.LogWarning((object)("[CFGSKEW] config-drift census failed: " + ex3.GetType().Name + ": " + ex3.Message + " — the [CONTRACT]/[STAMP] lines above are unaffected.")); } string toast = KitContract.ToastText(anyError, skew && KitContract.IsAlphaBundleInstall()); if (toast != null) { ((MonoBehaviour)this).StartCoroutine(Lifecycle.WhenPlayerReady(Lifecycle.FirstLocalCharacterOrNull, delegate(Character c) { Notify.Player(c, toast); }, null, 1800f)); } } } public static class ParticleProbe { public static void Dump(bool listAll, ManualLogSource log) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0064: Invalid comparison between Unknown and I4 //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Invalid comparison between Unknown and I4 //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) ParticleSystem[] array = Resources.FindObjectsOfTypeAll(); List list = new List(); List list2 = new List(); List list3 = new List(); int num = 0; ParticleSystem[] array2 = array; foreach (ParticleSystem val in array2) { if ((Object)(object)val == (Object)null) { continue; } ShapeModule shape; try { shape = val.shape; } catch { continue; } if (!((ShapeModule)(ref shape)).enabled) { continue; } ParticleSystemShapeType shapeType = ((ShapeModule)(ref shape)).shapeType; if ((int)shapeType == 6 || (int)shapeType == 13 || (int)shapeType == 14) { string source; bool baked; Mesh val2 = MeshOf(shape, shapeType, out source, out baked); float num2 = MeshArea(val2); string arg = (((Object)(object)val2 == (Object)null) ? "NULL" : ((Object)val2).name); int num3 = ((!((Object)(object)val2 == (Object)null)) ? val2.vertexCount : 0); bool isPlaying = val.isPlaying; if (isPlaying) { num++; } bool flag = (Object)(object)val2 == (Object)null || num3 == 0 || (num2 >= 0f && num2 <= 1E-07f); bool flag2 = !flag && isPlaying && num2 < 0f; if (baked) { Object.Destroy((Object)(object)val2); } string item = " " + Path(((Component)val).transform) + "\n" + $" shape={shapeType} mesh={arg} ({source}) " + $"verts={num3} " + "area=" + ((num2 < 0f) ? "UNREADABLE" : num2.ToString("F6")) + " " + $"playing={isPlaying} activeInHierarchy={((Component)val).gameObject.activeInHierarchy} " + "emissionEnabled=" + EmissionOn(val) + " emissionRate=" + Rate(val); if (flag) { list.Add(item); } else if (flag2) { list2.Add(item); } if (listAll) { list3.Add(item); } } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"[PSDUMP] {array.Length} ParticleSystem(s) in memory; {num} of the mesh-shaped ones are PLAYING.\n"); if (list.Count == 0) { stringBuilder.Append("[PSDUMP] no PROVABLY zero-area mesh emitter found.\n"); if (list2.Count == 0) { stringBuilder.Append("[PSDUMP] (If output_log.txt IS still spamming, the emitter's mesh is being invalidated at RUNTIME —\n re-run psdump WHILE the spam is flowing, and check the DDOL scene / a scene mid-unload.)\n"); } } else { stringBuilder.Append($"[PSDUMP] {list.Count} ZERO-AREA MESH EMITTER(S) — these are the spam. A PLAYING one logs every frame:\n"); foreach (string item2 in list) { stringBuilder.Append(item2).Append('\n'); } } if (list2.Count > 0) { stringBuilder.Append($"[PSDUMP] {list2.Count} SUSPECT(S) — playing, mesh-shaped, but the mesh is not readable so its area\n" + " cannot be verified from script. Do NOT read this as clean:\n"); foreach (string item3 in list2) { stringBuilder.Append(item3).Append('\n'); } } if (listAll && list3.Count > 0) { stringBuilder.Append($"[PSDUMP] all {list3.Count} mesh-shaped system(s):\n"); foreach (string item4 in list3) { stringBuilder.Append(item4).Append('\n'); } } log.LogMessage((object)stringBuilder.ToString()); } private unsafe static Mesh MeshOf(ShapeModule shape, ParticleSystemShapeType t, out string source, out bool baked) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown source = ((object)(*(ParticleSystemShapeType*)(&t))/*cast due to .constrained prefix*/).ToString(); baked = false; if ((int)t != 6) { if ((int)t != 13) { if ((int)t == 14) { SkinnedMeshRenderer skinnedMeshRenderer = ((ShapeModule)(ref shape)).skinnedMeshRenderer; if ((Object)(object)skinnedMeshRenderer == (Object)null) { source = "SkinnedMeshRenderer=NULL (shape was never bound to a renderer)"; return null; } int num = ((skinnedMeshRenderer.bones != null) ? skinnedMeshRenderer.bones.Length : 0); int num2 = 0; if (skinnedMeshRenderer.bones != null) { Transform[] bones = skinnedMeshRenderer.bones; foreach (Transform val in bones) { if ((Object)(object)val == (Object)null) { num2++; } } } source = $"SkinnedMeshRenderer '{((Object)skinnedMeshRenderer).name}' bones={num} dead={num2}"; try { Mesh val2 = new Mesh(); skinnedMeshRenderer.BakeMesh(val2); source += " [baked]"; baked = true; return val2; } catch { return skinnedMeshRenderer.sharedMesh; } } return null; } MeshRenderer meshRenderer = ((ShapeModule)(ref shape)).meshRenderer; if ((Object)(object)meshRenderer == (Object)null) { source = "MeshRenderer=NULL"; return null; } source = "MeshRenderer '" + ((Object)meshRenderer).name + "'"; MeshFilter component = ((Component)meshRenderer).GetComponent(); if (!((Object)(object)component == (Object)null)) { return component.sharedMesh; } return null; } return ((ShapeModule)(ref shape)).mesh; } private static float MeshArea(Mesh mesh) { //IL_0057: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mesh == (Object)null || mesh.vertexCount == 0) { return 0f; } Vector3[] vertices; int[] triangles; try { if (!mesh.isReadable) { return -1f; } vertices = mesh.vertices; triangles = mesh.triangles; } catch { return -1f; } float num = 0f; for (int i = 0; i + 2 < triangles.Length; i += 3) { float num2 = num; Vector3 val = Vector3.Cross(vertices[triangles[i + 1]] - vertices[triangles[i]], vertices[triangles[i + 2]] - vertices[triangles[i]]); num = num2 + ((Vector3)(ref val)).magnitude * 0.5f; } return num; } private static string Rate(ParticleSystem ps) { //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_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { EmissionModule emission = ps.emission; MinMaxCurve rateOverTime = ((EmissionModule)(ref emission)).rateOverTime; return ((MinMaxCurve)(ref rateOverTime)).constant.ToString("F1"); } catch { return "?"; } } private static string EmissionOn(ParticleSystem ps) { //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) try { EmissionModule emission = ps.emission; return ((EmissionModule)(ref emission)).enabled.ToString(); } catch { return "?"; } } private static string Path(Transform t) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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) StringBuilder stringBuilder = new StringBuilder(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Insert(0, ((Object)parent).name + "/"); parent = parent.parent; } Scene scene = ((Component)t).gameObject.scene; object obj; if (!((Scene)(ref scene)).IsValid()) { obj = "DDOL/none"; } else { scene = ((Component)t).gameObject.scene; obj = ((Scene)(ref scene)).name; } string arg = (string)obj; return $"[{arg}] {stringBuilder}"; } } public static class RagdollProbe { public static Func SpawnedUidClassifier; public static void Dump(string nameFilter, ManualLogSource log, float radius = 30f) { //IL_0078: 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) CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[RAGDOLL] no CharacterManager — nothing to probe."); return; } Character val = Lifecycle.FirstLocalCharacterOrNull(); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[RAGDOLL] no local player — nothing to probe."); return; } DictionaryExt characters = instance.Characters; StringBuilder stringBuilder = new StringBuilder(); int num = 0; for (int i = 0; i < characters.Count; i++) { Character val2 = characters.Values[i]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)val)) { float num2 = Vector3.Distance(((Component)val2).transform.position, ((Component)val).transform.position); if (!(num2 > radius) && (string.IsNullOrEmpty(nameFilter) || (val2.Name != null && val2.Name.IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0))) { stringBuilder.Append('\n').Append(Line(val2, num2)); num++; } } } log.LogMessage((object)((num == 0) ? string.Format("[RAGDOLL] no characters within {0:0}m{1}.", radius, string.IsNullOrEmpty(nameFilter) ? "" : (" matching '" + nameFilter + "'")) : string.Format("[RAGDOLL] {0} character(s) within {1:0}m{2}:{3}", num, radius, string.IsNullOrEmpty(nameFilter) ? "" : (" matching '" + nameFilter + "'"), stringBuilder))); } private static string Line(Character ch, float dist) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) string text = "?"; bool flag = false; bool flag2 = false; bool flag3 = false; Transform val = null; try { text = ((object)ch.UID/*cast due to .constrained prefix*/).ToString(); } catch { } try { flag = ch.Alive; } catch { } try { flag2 = ch.UseDeathAnim; } catch { } try { flag3 = ch.RagdollActive; } catch { } try { val = ch.RagdollRoot; } catch { } bool flag4 = SpawnedUidClassifier != null && SpawnedUidClassifier(text); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; try { Rigidbody[] componentsInChildren = ((Component)ch).GetComponentsInChildren(true); foreach (Rigidbody val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && ((Component)val2).CompareTag("Ragdoll")) { num++; } } num2 = ((Component)ch).GetComponentsInChildren(true).Length; CharacterJointManager[] componentsInChildren2 = ((Component)ch).GetComponentsInChildren(true); foreach (CharacterJointManager val3 in componentsInChildren2) { if (!((Object)(object)val3 == (Object)null)) { num3++; if (val3.m_hasJoint) { num4++; } } } } catch (Exception ex) { return " '" + ch.Name + "' uid=" + text + " — component census threw: " + ex.GetType().Name + ": " + ex.Message; } return string.Format(" '{0}' uid={1} spawned={2} dist={3:0.0}m alive={4} | ", ch.Name, text, flag4 ? "Y" : "N", dist, flag) + "deathAnim=" + (flag2 ? "Y" : "N") + " ragdollRoot=" + (((Object)(object)val != (Object)null) ? "Y" : "N") + " " + $"ragdollRB={num} joints={num2} managers={num3} mgrJoint={num4} ragdollActive={flag3}"; } } public static class Runner { private sealed class RunnerBehaviour : MonoBehaviour { } private static RunnerBehaviour _instance; public static MonoBehaviour Instance { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("ForgeKit.Runner") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)val); _instance = val.AddComponent(); } return (MonoBehaviour)(object)_instance; } } } public sealed class ScriptedBody : MonoBehaviour { public string Owner; public string Reason; } public static class ScriptedBodies { public static ScriptedBody Mark(GameObject go, string owner, string reason) { if ((Object)(object)go == (Object)null) { return null; } ScriptedBody scriptedBody = go.GetComponent(); if ((Object)(object)scriptedBody == (Object)null) { scriptedBody = go.AddComponent(); } scriptedBody.Owner = owner; scriptedBody.Reason = reason; return scriptedBody; } public static bool IsScripted(GameObject go) { if ((Object)(object)go != (Object)null) { return (Object)(object)go.GetComponent() != (Object)null; } return false; } public static bool IsScripted(Component c) { if ((Object)(object)c != (Object)null) { return IsScripted(c.gameObject); } return false; } public static string Describe(Component c) { if ((Object)(object)c == (Object)null) { return null; } ScriptedBody component = c.GetComponent(); if ((Object)(object)component == (Object)null) { return null; } string text = (string.IsNullOrEmpty(component.Owner) ? "(unnamed script)" : component.Owner); if (!string.IsNullOrEmpty(component.Reason)) { return text + " — " + component.Reason; } return text; } } internal sealed class ScriptRunner { private const float PumpGapWarnSeconds = 5f; private const float LoadSettleSeconds = 0.5f; private readonly ManualLogSource _log; private readonly Action _dispatch; private List _steps; private int _next; private int _current; private bool _live; private float _startedAt; private float _lastPump; private int _dispatched; private int _runTotal; private bool _waiting; private DevScript.StepKind _waitKind; private float _waitDeadline; private float _waitStartedAt; private bool _sawNotLoaded; private int _waitFrames; public bool IsRunning => _live; public ScriptRunner(ManualLogSource log, Action dispatch) { _log = log; _dispatch = dispatch; } public void Start(string tail) { DevScript.ScriptProgram scriptProgram = DevScript.Parse(tail); if (scriptProgram.Error != null) { string text = scriptProgram.Error; if (text.IndexOf("usage:", StringComparison.OrdinalIgnoreCase) < 0) { text += " — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; } _log.LogWarning((object)("[SCRIPT] parse error: " + text)); return; } if (_live) { _log.LogMessage((object)("[SCRIPT] ABORTED superseded at step " + _current + "/" + _steps.Count + " by a new script (n=" + scriptProgram.Steps.Count + ").")); } _steps = scriptProgram.Steps; _next = 0; _current = 0; _live = true; _startedAt = Time.unscaledTime; _lastPump = _startedAt; _dispatched = 0; _runTotal = 0; foreach (DevScript.ScriptStep step in _steps) { if (step.Kind == DevScript.StepKind.Run) { _runTotal++; } } _waiting = false; _log.LogMessage((object)("[SCRIPT] start steps=" + _steps.Count + " declared-wait=" + Fmt(scriptProgram.DeclaredWaitSeconds) + "s src='" + (tail ?? "").Trim() + "'")); Advance(); } public void Pump() { if (!_live) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastPump > 5f) { bool flag = _waiting && _waitKind == DevScript.StepKind.WaitLoaded; _log.LogWarning((object)("[SCRIPT] pump gap " + Fmt(unscaledTime - _lastPump) + "s at step " + _current + "/" + _steps.Count + (flag ? " — long frame or level load; resuming." : " — the consumer stopped ticking this channel (config gate? plugin disabled?); resuming."))); } _lastPump = unscaledTime; if (_waiting) { switch (_waitKind) { case DevScript.StepKind.Wait: if (unscaledTime < _waitDeadline) { return; } break; case DevScript.StepKind.WaitFrames: if (--_waitFrames > 0) { return; } break; case DevScript.StepKind.WaitLoaded: if (!LoadingDone()) { _sawNotLoaded = true; } if (!LoadingDone() || (!_sawNotLoaded && !(unscaledTime - _waitStartedAt >= 0.5f))) { if (!(unscaledTime < _waitDeadline)) { _log.LogWarning((object)("[SCRIPT] ABORTED waitloaded timed out after " + Fmt(unscaledTime - _waitStartedAt) + "s at step " + _current + "/" + _steps.Count + ".")); Stop(); } return; } break; } _waiting = false; } Advance(); } public void Cancel() { if (!_live) { _log.LogMessage((object)"[SCRIPT] idle — nothing to cancel."); return; } _log.LogMessage((object)("[SCRIPT] ABORTED cancelled at step " + _current + "/" + _steps.Count + ".")); Stop(); } public void Status() { if (!_live) { _log.LogMessage((object)"[SCRIPT] idle."); return; } float unscaledTime = Time.unscaledTime; string text = ((!_waiting) ? "no wait" : ((_waitKind != DevScript.StepKind.WaitFrames) ? (Fmt(Math.Max(0f, _waitDeadline - unscaledTime)) + "s") : (_waitFrames + " frames"))); _log.LogMessage((object)("[SCRIPT] running step " + _current + "/" + _steps.Count + ", " + text + " left on wait, elapsed " + Fmt(unscaledTime - _startedAt) + "s.")); } private void Advance() { if (_next >= _steps.Count) { _log.LogMessage((object)("[SCRIPT] DONE dispatched=" + _dispatched + "/" + _runTotal + " elapsed=" + Fmt(Time.unscaledTime - _startedAt) + "s (dispatch only — step outcomes are each verb's own log lines)")); Stop(); return; } DevScript.ScriptStep scriptStep = _steps[_next]; _next++; _current = _next; switch (scriptStep.Kind) { case DevScript.StepKind.Run: _log.LogMessage((object)("[SCRIPT] step " + _current + "/" + _steps.Count + " run: " + scriptStep.Command)); _dispatched++; try { _dispatch(scriptStep.Command); break; } catch (Exception ex) { _log.LogWarning((object)("[SCRIPT] step " + _current + "/" + _steps.Count + " threw: " + ex.Message + " (continuing)")); break; } case DevScript.StepKind.Wait: _waiting = true; _waitKind = DevScript.StepKind.Wait; _waitDeadline = Time.unscaledTime + (float)scriptStep.Seconds; _log.LogMessage((object)("[SCRIPT] step " + _current + "/" + _steps.Count + " wait " + Fmt(scriptStep.Seconds) + "s (unscaled)")); break; case DevScript.StepKind.WaitFrames: _waiting = true; _waitKind = DevScript.StepKind.WaitFrames; _waitFrames = scriptStep.Frames; _log.LogMessage((object)("[SCRIPT] step " + _current + "/" + _steps.Count + " waitframes " + scriptStep.Frames)); break; case DevScript.StepKind.WaitLoaded: _waiting = true; _waitKind = DevScript.StepKind.WaitLoaded; _waitStartedAt = Time.unscaledTime; _waitDeadline = _waitStartedAt + (float)scriptStep.Seconds; _sawNotLoaded = false; _log.LogMessage((object)("[SCRIPT] step " + _current + "/" + _steps.Count + " waitloaded (timeout " + Fmt(scriptStep.Seconds) + "s)")); break; } } private static bool LoadingDone() { NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance != (Object)null && instance.IsOverallLoadingDone) { return !instance.m_prepingLoadLevel; } return false; } private void Stop() { _live = false; _waiting = false; _steps = null; _next = 0; _current = 0; } private static string Fmt(double v) { return v.ToString("0.00", CultureInfo.InvariantCulture); } } public sealed class SelfTestHarness { private readonly ManualLogSource _log; private int _pass; private int _fail; private int _skip; public SelfTestHarness(ManualLogSource log) { _log = log; } public void Begin(string tag) { _pass = (_fail = (_skip = 0)); _log.LogMessage((object)("[SELFTEST] BEGIN (" + tag + ")")); } public void Check(string name, bool ok) { if (ok) { _pass++; _log.LogMessage((object)("[SELFTEST] PASS: " + name)); } else { _fail++; _log.LogError((object)("[SELFTEST] FAIL: " + name)); } } public void Skip(string name, string why) { _skip++; _log.LogMessage((object)("[SELFTEST] SKIP: " + name + " — " + why)); } public void CheckIf(bool canRun, string name, Func ok, string why) { if (canRun) { Check(name, ok()); } else { Skip(name, why); } } public void Exception(Exception e) { _fail++; _log.LogError((object)("[SELFTEST] EXCEPTION: " + e)); } public void Done() { _log.LogMessage((object)$"[SELFTEST] DONE pass={_pass} fail={_fail} skip={_skip}"); } } public static class SkySnapshot { public static void Log(string tag, ManualLogSource log = null) { ManualLogSource val = log ?? Plugin.Log; try { LogInner(tag, val); } catch (Exception ex) { val.LogWarning((object)("[SKY] (" + tag + ") snapshot failed: " + ex.Message)); } } private static void LogInner(string tag, ManualLogSource log) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) Material skybox = RenderSettings.skybox; EnvironmentConditions instance = EnvironmentConditions.Instance; Material val = (((Object)(object)instance != (Object)null) ? instance.m_skyboxMat : null); string text = Describe(skybox); string text2 = (((Object)(object)skybox == (Object)null || (Object)(object)instance == (Object)null) ? "n/a" : ((skybox == val) ? "ecClone" : "todAsset")); TOD_Sky instance2 = TOD_Sky.Instance; Light sun = RenderSettings.sun; log.LogMessage((object)("[SKY] (" + tag + ") skybox=" + text + " ecSkyMat=" + Describe(val) + " (" + text2 + ") | EC=" + SceneOf((Component)(object)instance) + " TOD_Sky=" + SceneOf((Component)(object)instance2) + " | sun=" + DescribeLight(sun))); Camera main = Camera.main; string arg = ((!((Object)(object)main == (Object)null)) ? $"'{((Object)main).name}' on={((Behaviour)main).enabled} clear={main.clearFlags} mask=0x{main.cullingMask:X8} far={main.farClipPlane:F0}" : ((main != null) ? "DESTROYED" : "NULL — no enabled MainCamera-tagged camera")); log.LogMessage((object)($"[SKY] ({tag}) ambient={RenderSettings.ambientMode} int={RenderSettings.ambientIntensity:F2} " + $"light={Rgb(RenderSettings.ambientLight)} | fog={RenderSettings.fog} color={Rgb(RenderSettings.fogColor)} " + $"density={RenderSettings.fogDensity:F4} | cam={arg}")); log.LogMessage((object)("[SKY] (" + tag + ") display: " + DescribeDisplayChain())); } public static bool TryGetDisplayDesync(out Camera cam, out CameraQuality quality, out string detail) { cam = null; quality = null; detail = null; GameDisplayInUI instance = GameDisplayInUI.Instance; SplitScreenManager instance2 = SplitScreenManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || !instance2.RenderInImage) { detail = "direct-to-screen mode (no RT display chain)"; return false; } if (instance.Screens == null || instance.Screens.Length == 0 || (Object)(object)instance.Screens[0] == (Object)null) { detail = "no display RawImage"; return false; } cam = Camera.main; if ((Object)(object)cam == (Object)null) { detail = "no main camera"; return false; } quality = ((Component)cam).GetComponent(); Texture texture = instance.Screens[0].texture; RenderTexture targetTexture = cam.targetTexture; string text = (((Object)(object)texture == (Object)null) ? "null" : ("#" + ((Object)texture).GetInstanceID())); string text2 = (((Object)(object)targetTexture == (Object)null) ? "null" : ("#" + ((Object)targetTexture).GetInstanceID())); bool flag = (object)texture != targetTexture; detail = "imageTex=" + text + " camTargetTex=" + text2 + " " + ((!flag) ? "in sync" : (((Object)(object)targetTexture == (Object)null) ? "not linked yet (camera target null — normal during early load init; repaired post-harvest if it persists)" : "DESYNC — the screen is showing a texture the camera is not rendering into (black-world signature)")); return flag; } private static string DescribeDisplayChain() { try { TryGetDisplayDesync(out var _, out var _, out var detail); return detail; } catch (Exception ex) { return "unreadable (" + ex.Message + ")"; } } private static string Describe(Material m) { if (m == null) { return "null"; } if ((Object)(object)m == (Object)null) { return $"DESTROYED#{((Object)m).GetInstanceID()}"; } return $"'{((Object)m).name}'#{((Object)m).GetInstanceID()}"; } private static string DescribeLight(Light l) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (l == null) { return "null"; } if ((Object)(object)l == (Object)null) { return "DESTROYED — RenderSettings.sun points at a dead light (donor leak?)"; } object[] obj = new object[4] { ((Object)l).name, ((Behaviour)l).enabled && ((Component)l).gameObject.activeInHierarchy, l.intensity, null }; Scene scene = ((Component)l).gameObject.scene; obj[3] = ((Scene)(ref scene)).name; return string.Format("'{0}' on={1} int={2:F2} scene='{3}'", obj); } private static string SceneOf(Component c) { //IL_0023: 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) if (c == null) { return "null"; } if ((Object)(object)c == (Object)null) { return "DESTROYED"; } Scene scene = c.gameObject.scene; return "'" + ((Scene)(ref scene)).name + "'"; } private static string Rgb(Color c) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({c.r:F2},{c.g:F2},{c.b:F2})"; } } public static class StatusApplyGate { public enum Gate { Unexplained, NoPrefab, NotLocal, DeadTarget, PetrificationNoOwner, QuestImmunity, TagImmunity, StatusResist, RequiredStatusMissing, DiseaseAlreadyCured, FamilyComplication } public sealed class PreState { public bool RequiredStatusCarried; public string ComplicationFrom; public string ComplicationTo; public static PreState Capture(Character target, string identifier) { PreState preState = new PreState(); try { StatusEffect val = Prefab(identifier); StatusEffectManager val2 = (((Object)(object)target != (Object)null) ? target.StatusEffectMngr : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return preState; } if (target.HasShamanicResonance && (Object)(object)val.AmplifiedStatus != (Object)null) { val = val.AmplifiedStatus; } if ((Object)(object)val.RequiredStatus != (Object)null) { preState.RequiredStatusCarried = val2.HasStatusEffect(val.RequiredStatus.IdentifierName); } StatusEffect val3 = FamilyMember(val2, val.EffectFamily); if ((Object)(object)val3 != (Object)null && (Object)(object)val3.ComplicationStatus != (Object)null) { preState.ComplicationFrom = val3.IdentifierName; preState.ComplicationTo = val3.ComplicationStatus.IdentifierName; } } catch { } return preState; } } public sealed class Bypass { private const string SourceId = "FK_StatusForce"; private const float Points = 100000f; private readonly CharacterStats _st; private readonly string _identifier; private TagSourceSelector[] _savedNatural; private Dictionary> _savedGranted; private bool _open; private Bypass(CharacterStats st, string identifier) { _st = st; _identifier = identifier; } public static Bypass Open(Character target, string identifier) { if ((Object)(object)target == (Object)null || (Object)(object)target.Stats == (Object)null) { return null; } Bypass bypass = new Bypass(target.Stats, identifier); try { bypass.Apply(); return bypass; } catch { bypass.Close(); throw; } } private void Apply() { _open = true; _savedNatural = _st.m_statusEffectsNaturalImmunity; _st.m_statusEffectsNaturalImmunity = (TagSourceSelector[])(object)new TagSourceSelector[0]; if (_st.m_statusEffectsImmunity != null && _st.m_statusEffectsImmunity.Count > 0) { _savedGranted = new Dictionary>(_st.m_statusEffectsImmunity); _st.m_statusEffectsImmunity.Clear(); } Push(_st.m_allStatusEffectBuildUpResistance); Stat s = default(Stat); if (_st.m_statusEffectsBuildUpResistances != null && _st.m_statusEffectsBuildUpResistances.TryGetValue(_identifier, ref s)) { Push(s); } } public void Close() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (!_open) { return; } _open = false; _st.m_statusEffectsNaturalImmunity = _savedNatural; if (_savedGranted != null && _st.m_statusEffectsImmunity != null) { foreach (KeyValuePair> item in _savedGranted) { if (!_st.m_statusEffectsImmunity.TryGetValue(item.Key, out var value) || value == null) { _st.m_statusEffectsImmunity[item.Key] = item.Value; } else { if (item.Value == null) { continue; } for (int i = 0; i < item.Value.Count; i++) { if (!value.Contains(item.Value[i])) { value.Add(item.Value[i]); } } } } _savedGranted = null; } Pop(_st.m_allStatusEffectBuildUpResistance); Stat s = default(Stat); if (_st.m_statusEffectsBuildUpResistances != null && _st.m_statusEffectsBuildUpResistances.TryGetValue(_identifier, ref s)) { Pop(s); } } private static void Push(Stat s) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown if (s != null) { s.AddStack(new StatStack("FK_StatusForce", -100000f, (Tag[])null), false); s.Update(); } } private static void Pop(Stat s) { if (s != null) { s.RemoveStack("FK_StatusForce", false); s.Update(); } } } public static bool IsForceable(Gate gate) { if (gate != Gate.TagImmunity) { return gate == Gate.StatusResist; } return true; } public static StatusEffect Prefab(string identifier) { Dictionary sTATUSEFFECT_PREFABS = ResourcesPrefabManager.STATUSEFFECT_PREFABS; if (sTATUSEFFECT_PREFABS == null || identifier == null) { return null; } if (!sTATUSEFFECT_PREFABS.TryGetValue(identifier, out var value)) { return null; } return value; } public static Gate Diagnose(Character target, string identifier, PreState pre, out string detail) { detail = ""; try { return Walk(target, identifier, pre, out detail); } catch (Exception ex) { detail = "the refusal-reason walk itself threw (" + ex.GetType().Name + ": " + ex.Message + ")"; return Gate.Unexplained; } } private static Gate Walk(Character target, string identifier, PreState pre, out string detail) { //IL_00d8: 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) detail = ""; StatusEffect val = Prefab(identifier); if ((Object)(object)val == (Object)null) { detail = "'" + identifier + "' is not in STATUSEFFECT_PREFABS"; return Gate.NoPrefab; } CharacterStats stats = target.Stats; StatusEffectManager statusEffectMngr = target.StatusEffectMngr; if (!target.IsPhotonPlayerLocal) { detail = "this box does not own the target (IsPhotonPlayerLocal=false) — statuses apply only on the owning client"; return Gate.NotLocal; } if ((!target.Alive || target.IsUndyingDead) && val.Purgeable) { detail = "target is dead/undying-dead and '" + identifier + "' is Purgeable"; return Gate.DeadTarget; } if (identifier == "Petrification" && (Object)(object)target.OwnerPlayerSys == (Object)null) { detail = "Petrification only applies to a character with an OwnerPlayerSys (a player)"; return Gate.PetrificationNoOwner; } if (target.QuestStatusImmunity) { detail = "Character.QuestStatusImmunity is set on the target"; return Gate.QuestImmunity; } if ((Object)(object)stats != (Object)null && !val.IgnoreStatusResist) { IList inheritedTags = val.InheritedTags; if (inheritedTags != null) { for (int i = 0; i < inheritedTags.Count; i++) { if (stats.HasStatusImmunity(inheritedTags[i])) { detail = "the target has STATUS IMMUNITY to the tag '" + TagName(inheritedTags[i]) + "' that '" + identifier + "' inherits (CharacterStats natural or granted immunity)"; return Gate.TagImmunity; } } } if (stats.GetStatusBuildUpModifier(identifier) == 0f) { detail = "build-up resistance is 100%+ for '" + identifier + "' so the engine's status-resist modifier is exactly 0 = TOTAL refusal (" + ResistNumbers(stats, identifier) + ")"; return Gate.StatusResist; } } if (target.HasShamanicResonance && (Object)(object)val.AmplifiedStatus != (Object)null) { val = val.AmplifiedStatus; } if (!((Object)(object)val.RequiredStatus == (Object)null)) { bool num; if (pre == null) { if (!((Object)(object)statusEffectMngr != (Object)null)) { goto IL_01ce; } num = statusEffectMngr.HasStatusEffect(val.RequiredStatus.IdentifierName); } else { num = pre.RequiredStatusCarried; } if (!num) { goto IL_01ce; } } Disease val2 = (Disease)(object)((val is Disease) ? val : null); if ((Object)(object)val2 != (Object)null && val2.HasStatusCure && (Object)(object)statusEffectMngr != (Object)null) { for (int j = 0; j < val2.StatusCure.Length; j++) { if (val2.StatusCure[j] != null && statusEffectMngr.HasStatus(val2.StatusCure[j])) { detail = "the target already carries a StatusCure for this Disease"; return Gate.DiseaseAlreadyCured; } } } string text = pre?.ComplicationFrom; string text2 = pre?.ComplicationTo; if (text == null && pre == null && (Object)(object)statusEffectMngr != (Object)null) { StatusEffect val3 = FamilyMember(statusEffectMngr, val.EffectFamily); if ((Object)(object)val3 != (Object)null && (Object)(object)val3.ComplicationStatus != (Object)null) { text = val3.IdentifierName; text2 = val3.ComplicationStatus.IdentifierName; } } if (text != null) { detail = "a family member ('" + text + "') carried a ComplicationStatus, so the engine REPLACED it with '" + text2 + "' and returned null — something DID apply; check statusdump"; return Gate.FamilyComplication; } detail = "none of AddStatusEffect's known refusal paths matches the target's current state"; return Gate.Unexplained; IL_01ce: detail = "'" + identifier + "' has RequiredStatus '" + val.RequiredStatus.IdentifierName + "', which the target was not carrying — grant that first"; return Gate.RequiredStatusMissing; } private static string ResistNumbers(CharacterStats st, string identifier) { float num = ((st.m_allStatusEffectBuildUpResistance != null) ? st.m_allStatusEffectBuildUpResistance.CurrentValue : 0f); string text = "none"; Stat val = default(Stat); if (st.m_statusEffectsBuildUpResistances != null && st.m_statusEffectsBuildUpResistances.TryGetValue(identifier, ref val) && val != null) { text = val.CurrentValue.ToString("0.#"); } return "statusRes=" + num.ToString("0.#") + " (all-status) + per-status[" + identifier + "]=" + text; } private static StatusEffect FamilyMember(StatusEffectManager mgr, StatusEffectFamily family) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mgr == (Object)null || family == (StatusEffectFamily)null) { return null; } string text = StatusEffectFamily.op_Implicit(family); if (text == null || mgr.m_statusPerFamily == null || mgr.m_statuses == null) { return null; } if (!mgr.m_statusPerFamily.TryGetValue(text, out var value) || value == null || value.Count == 0) { return null; } StatusEffect result = default(StatusEffect); if (!mgr.m_statuses.TryGetValue(value[0], ref result)) { return null; } return result; } private unsafe static string TagName(Tag tag) { //IL_0000: 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 (!string.IsNullOrEmpty(tag.TagName)) { return tag.TagName; } return ((object)(*(Tag*)(&tag))/*cast due to .constrained prefix*/).ToString(); } } public static class StatusNameIndex { public static bool TryResolve(string key, out string identifier, out string problem) { identifier = null; problem = null; string text = key?.Trim(); if (string.IsNullOrEmpty(text)) { problem = "no status name given."; return false; } Dictionary sTATUSEFFECT_PREFABS = ResourcesPrefabManager.STATUSEFFECT_PREFABS; if (sTATUSEFFECT_PREFABS == null || sTATUSEFFECT_PREFABS.Count == 0) { problem = "status registry not loaded yet (main menu too early?)."; return false; } foreach (string key2 in sTATUSEFFECT_PREFABS.Keys) { if (string.Equals(key2, text, StringComparison.OrdinalIgnoreCase)) { identifier = key2; return true; } } string text2 = null; foreach (KeyValuePair item in sTATUSEFFECT_PREFABS) { string text3 = DisplayName(item.Value); if (text3 != null && string.Equals(text3, text, StringComparison.OrdinalIgnoreCase) && (text2 == null || string.Compare(item.Key, text2, StringComparison.OrdinalIgnoreCase) < 0)) { text2 = item.Key; } } if (text2 != null) { identifier = text2; return true; } List list = new List(); foreach (string item2 in Suggest(text)) { list.Add("'" + item2 + "'"); } problem = "'" + text + "' matched no status — " + ((list.Count > 0) ? ("did you mean: " + string.Join(", ", list.ToArray())) : "no similar status names") + "."; return false; } public static List Suggest(string fragment, int max = 8) { List list = new List(); if (string.IsNullOrEmpty(fragment)) { return list; } string needle = fragment.Trim(); if (needle.Length == 0) { return list; } Dictionary sTATUSEFFECT_PREFABS = ResourcesPrefabManager.STATUSEFFECT_PREFABS; if (sTATUSEFFECT_PREFABS == null || sTATUSEFFECT_PREFABS.Count == 0) { return list; } List> list2 = new List>(); foreach (KeyValuePair item in sTATUSEFFECT_PREFABS) { list2.Add(new KeyValuePair(item.Key, item.Key)); string text = DisplayName(item.Value); if (text != null && !string.Equals(text, item.Key, StringComparison.OrdinalIgnoreCase)) { list2.Add(new KeyValuePair(text + " (" + item.Key + ")", item.Key)); } } List> list3 = ForgeKit.Suggest.TwoPass(new IEnumerable>[1] { list2 }, (KeyValuePair row, Suggest.Pass pass) => ForgeKit.Suggest.Matches(row.Key, needle, pass)); foreach (KeyValuePair item2 in list3) { list.Add(item2.Key); } list.Sort(StringComparer.OrdinalIgnoreCase); if (list.Count > max) { list.RemoveRange(max, list.Count - max); } return list; } private static string DisplayName(StatusEffect prefab) { if ((Object)(object)prefab == (Object)null) { return null; } try { string statusName = prefab.StatusName; return string.IsNullOrEmpty(statusName) ? null : statusName.Trim(); } catch { return null; } } } public static class Suggest { public enum Pass { Substring, Fuzzy } public static readonly Pass[] Passes = new Pass[2] { Pass.Substring, Pass.Fuzzy }; private static readonly char[] Space = new char[2] { ' ', '\t' }; public static bool Matches(string candidate, string want) { if (string.IsNullOrEmpty(candidate) || string.IsNullOrEmpty(want)) { return false; } if (candidate.IndexOf(want, StringComparison.OrdinalIgnoreCase) < 0) { return want.IndexOf(candidate, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } public static bool Matches(string candidate, string want, Pass pass) { if (pass != Pass.Fuzzy) { return Matches(candidate, want); } return MatchesFuzzy(candidate, want); } public static List TwoPass(IEnumerable> sources, Func matches, int max = int.MaxValue) { List list = new List(); if (sources == null || matches == null || max <= 0) { return list; } Pass[] passes = Passes; foreach (Pass arg in passes) { foreach (IEnumerable source in sources) { if (source == null) { continue; } foreach (T item in source) { if (list.Count >= max) { break; } if (matches(item, arg)) { list.Add(item); } } if (list.Count >= max) { break; } } if (list.Count > 0) { break; } } return list; } public static List Bidirectional(IEnumerable candidates, string want, int max = 8) { List list = new List(); if (candidates == null || string.IsNullOrWhiteSpace(want)) { return list; } List list2 = ((candidates is ICollection) ? null : new List()); foreach (string candidate in candidates) { list2?.Add(candidate); if (list.Count >= max) { if (list2 == null) { break; } } else if (Matches(candidate, want)) { list.Add(candidate); } } if (list.Count > 0) { return list; } IEnumerable enumerable = list2; foreach (string item in enumerable ?? candidates) { if (list.Count >= max) { break; } if (MatchesFuzzy(item, want)) { list.Add(item); } } return list; } public static bool MatchesFuzzy(string candidate, string want) { if (string.IsNullOrEmpty(candidate) || string.IsNullOrWhiteSpace(want)) { return false; } if (Matches(candidate, want)) { return true; } string[] array = want.Split(Space, StringSplitOptions.RemoveEmptyEntries); string[] array2 = candidate.Split(Space, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0 && array2.Length != 0) { bool flag = true; string[] array3 = array; foreach (string text in array3) { bool flag2 = false; string[] array4 = array2; foreach (string text2 in array4) { if (Matches(text2, text)) { flag2 = true; break; } if (text.Length >= 3 && Distance(text2, text) <= Budget(text)) { flag2 = true; break; } } if (!flag2) { flag = false; break; } } if (flag) { return true; } } if (want.Length >= 3) { return Distance(candidate, want) <= Budget(want); } return false; } private static int Budget(string s) { return Math.Max(1, (s?.Length ?? 0) / 5); } public static int Distance(string a, string b) { if (string.IsNullOrEmpty(a)) { return b?.Length ?? 0; } if (string.IsNullOrEmpty(b)) { return a.Length; } a = a.ToLowerInvariant(); b = b.ToLowerInvariant(); int[] array = new int[b.Length + 1]; int[] array2 = new int[b.Length + 1]; for (int i = 0; i <= b.Length; i++) { array[i] = i; } for (int j = 1; j <= a.Length; j++) { array2[0] = j; for (int k = 1; k <= b.Length; k++) { int num = ((a[j - 1] != b[k - 1]) ? 1 : 0); int num2 = array[k] + 1; int num3 = array2[k - 1] + 1; int num4 = array[k - 1] + num; array2[k] = ((num2 < num3) ? ((num2 < num4) ? num2 : num4) : ((num3 < num4) ? num3 : num4)); } int[] array3 = array; array = array2; array2 = array3; } return array[b.Length]; } } public class TableLoader : ITableSource> { private readonly Assembly _embeddedSource; private readonly ManualLogSource _log; private readonly string _fileName; private readonly string _logTag; private readonly string _singular; private readonly string _plural; private readonly string _mergeNote; private readonly string _reloadSuffix; private readonly Func, Dictionary> _parse; private readonly Func, Dictionary, Dictionary> _merge; private readonly Func, Action, Dictionary> _postLoad; private readonly Func, string> _builtInSuffix; private Dictionary _table; private ManualLogSource Log => _log ?? Plugin.Log; public Dictionary Table { get { if (_table == null) { _table = Load(); } return _table; } } public TableLoader(Assembly embeddedSource, ManualLogSource log, string fileName, string logTag, string singularNoun, string pluralNoun, Func, Dictionary> parse, Func, Dictionary, Dictionary> merge, string mergeNote = "replaces per-species", Func, Action, Dictionary> postLoad = null, Func, string> builtInSuffix = null, string reloadSuffix = "") { _embeddedSource = embeddedSource; _log = log; _fileName = fileName; _logTag = logTag; _singular = singularNoun; _plural = pluralNoun; _parse = parse; _merge = merge; _mergeNote = mergeNote; _postLoad = postLoad; _builtInSuffix = builtInSuffix; _reloadSuffix = reloadSuffix; } public Dictionary Reload() { _table = Load(); Log.LogMessage((object)$"{_logTag} reloaded {_fileName} ({_table.Count} {Noun(_table.Count)}).{_reloadSuffix}"); return _table; } private string Noun(int n) { if (n != 1) { return _plural; } return _singular; } private Dictionary Load() { Action arg = delegate(string m) { Log.LogWarning((object)(_logTag + " " + _fileName + ": " + m)); }; Dictionary dictionary = _parse(EmbeddedRes.Text(_embeddedSource, _fileName, _logTag, _log), arg); Log.LogMessage((object)string.Format("{0} loaded {1} built-in {2}{3}.", _logTag, dictionary.Count, Noun(dictionary.Count), _builtInSuffix?.Invoke(dictionary) ?? "")); try { string path = Path.Combine(Paths.ConfigPath, _fileName); if (File.Exists(path)) { Dictionary dictionary2 = _parse(File.ReadAllText(path), arg); dictionary = _merge(dictionary, dictionary2); Log.LogMessage((object)$"{_logTag} merged {dictionary2.Count} {Noun(dictionary2.Count)} from config override ({_mergeNote})."); } } catch (Exception ex) { Log.LogWarning((object)(_logTag + " " + _fileName + " override load failed: " + ex.Message)); } if (_postLoad != null) { dictionary = _postLoad(dictionary, arg); } return dictionary; } } public static class TableValidator { public sealed class ItemRef { public int? ItemId; public string NameKey; public string IdSubject; public string NameSubject; public ItemRef(int? itemId, string nameKey, string idSubject, string nameSubject) { ItemId = itemId; NameKey = nameKey; IdSubject = idSubject; NameSubject = nameSubject; } } public static bool RegistryReady(string tag, ManualLogSource log = null) { if (ResourcesPrefabManager.Instance == null || ResourcesPrefabManager.ITEM_PREFABS == null || ResourcesPrefabManager.ITEM_PREFABS.Count == 0) { (log ?? Plugin.Log).LogMessage((object)(tag + " boot check skipped — item registry not loaded yet.")); return false; } return true; } public static void CheckItemKey(string tag, string fileName, string noun, int? itemId, string nameKey, string idSubject, string nameSubject, ref int misses, ManualLogSource log = null) { ManualLogSource val = log ?? Plugin.Log; int itemId2; if (itemId.HasValue) { if ((Object)(object)ResourcesPrefabManager.Instance.GetItemPrefab(itemId.Value) == (Object)null) { misses++; val.LogWarning((object)(tag + " " + idSubject + " is not a known item — that " + noun + " can never match.")); } } else if (ItemNameIndex.TryResolve(nameKey, out itemId2)) { val.LogMessage((object)$"{tag} {nameSubject} resolved to ItemID {itemId2} (put the number in {fileName} to make it locale-proof)."); } else { misses++; val.LogWarning((object)(tag + " " + nameSubject + " matches no item display name on this locale — that " + noun + " can never match (fine if it's an unverified spelling candidate).")); } } public static void CheckItemTable(string tag, string fileName, string noun, IEnumerable refs, out int checkedRefs, out int misses, ManualLogSource log = null) { checkedRefs = 0; misses = 0; foreach (ItemRef @ref in refs) { checkedRefs++; CheckItemKey(tag, fileName, noun, @ref.ItemId, @ref.NameKey, @ref.IdSubject, @ref.NameSubject, ref misses, log); } } public static void CheckHexName(string tag, string fileName, string reloadVerb, HashSet checkedNames, string hexId, string subject, ref int misses, ManualLogSource log = null) { if (checkedNames.Add(hexId) && !((Object)(object)ResourcesPrefabManager.Instance.GetStatusEffectPrefab(hexId) != (Object)null)) { misses++; (log ?? Plugin.Log).LogWarning((object)(tag + " " + subject + ": hex '" + hexId + "' is not a loadable StatusEffect prefab name — run `statusdump` for the real names, fix " + fileName + " (config override) and `" + reloadVerb + "`.")); } } } public sealed class VerbContext { public Character Player; public string[] Parts; public string Verb { get { if (Parts == null || Parts.Length == 0) { return ""; } return Parts[0]; } } public string Arg(int i) { if (Parts == null || i < 0 || i >= Parts.Length) { return null; } return Parts[i]; } public string Tail(int from = 1) { if (Parts == null || Parts.Length <= from) { return null; } return string.Join(" ", Parts, from, Parts.Length - from); } public string Require(int i, string usage, ManualLogSource log) { string text = Arg(i); if (!string.IsNullOrEmpty(text)) { return text; } if (log != null) { log.LogWarning((object)usage); } return null; } } public sealed class VerbHost { private readonly CommandRegistry _commands; private readonly ManualLogSource _log; private readonly Func _player; public VerbHost(CommandRegistry commands, ManualLogSource log, Func player) { _commands = commands; _log = log; _player = player; } public void Register(string verb, string help, Action body, string tag = "[CMD]", bool needsPlayer = true, bool warnOnNoPlayer = true, bool masterOnly = false, string noPlayerMsg = null, ArgSpec[] args = null) { Register(new string[1] { verb }, help, body, tag, needsPlayer, warnOnNoPlayer, masterOnly, noPlayerMsg, args); } public void Register(string verb, string help, Action body, string tag, bool needsPlayer, bool warnOnNoPlayer, bool masterOnly, string noPlayerMsg) { Register(verb, help, body, tag, needsPlayer, warnOnNoPlayer, masterOnly, noPlayerMsg, null); } public void Register(string[] verbs, string help, Action body, string tag, bool needsPlayer, bool warnOnNoPlayer, bool masterOnly, string noPlayerMsg) { Register(verbs, help, body, tag, needsPlayer, warnOnNoPlayer, masterOnly, noPlayerMsg, null); } public void Register(string[] verbs, string help, Action body, string tag = "[CMD]", bool needsPlayer = true, bool warnOnNoPlayer = true, bool masterOnly = false, string noPlayerMsg = null, ArgSpec[] args = null) { VerbSpec spec = new VerbSpec { Verbs = verbs, Help = help, Args = args, Tag = tag, NeedsPlayer = needsPlayer, MasterOnly = masterOnly }; _commands.Register(spec, delegate(string[] parts) { Character val = _player(); if ((Object)(object)val == (Object)null && needsPlayer) { string text = noPlayerMsg ?? (tag + " no local player."); if (warnOnNoPlayer) { _log.LogWarning((object)text); } else { _log.LogMessage((object)text); } } else { if (!masterOnly || !PhotonNetwork.isNonMasterClientInRoom) { VerbContext verbContext = new VerbContext { Player = val, Parts = parts }; try { body(verbContext); return; } catch (Exception arg) { _log.LogError((object)$"{tag} '{verbContext.Verb}' failed: {arg}"); return; } } _log.LogWarning((object)(tag + " non-master client in room — master-only; skipped.")); } }); } } public static class CheatVerbs { private const string Tag = "[CHEAT]"; private static string DebugFilePath => Application.dataPath + "/DEBUG.txt"; public static void CheatsVerb(Character player, string[] parts, ManualLogSource log) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) string text = ((parts.Length > 1) ? parts[1].ToLowerInvariant() : null); switch (text) { case "on": case "off": { bool flag = text == "on"; bool cheatsEnabled = Global.CheatsEnabled; Global.CheatsEnabled = flag; log.LogMessage((object)(string.Format("{0} CheatsEnabled: {1} -> {2}", "[CHEAT]", cheatsEnabled, flag) + (flag ? " (session-only — 'debugfile on' to persist across relaunches; F1-F4 windows + debug hotkeys now live)." : "."))); break; } default: log.LogWarning((object)"[CHEAT] usage: cheats [on|off] — bare 'cheats' reports the full cheat state."); return; case null: break; } log.LogMessage((object)string.Format("{0} gate: CheatsEnabled={1} debugfile={2} ({3})", "[CHEAT]", Global.CheatsEnabled, File.Exists(DebugFilePath) ? "present" : "absent", DebugFilePath)); CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance != (Object)null) { log.LogMessage((object)string.Format("{0} enemyInvincible={1} (local-only, not persisted)", "[CHEAT]", instance.IsEnemyInvincible)); } if ((Object)(object)player == (Object)null) { log.LogMessage((object)"[CHEAT] no player yet — per-character cheat flags unavailable (main menu?)."); return; } CharacterCheats cheats = player.Cheats; CharacterControl characterControl = player.CharacterControl; LocalCharacterControl val = (LocalCharacterControl)(object)((characterControl is LocalCharacterControl) ? characterControl : null); float num = (((Object)(object)val != (Object)null) ? val.MovementMultiplier : 1f); log.LogMessage((object)(string.Format("{0} player: invincible={1} indestructibleEquip={2} ", "[CHEAT]", cheats.Invincible, cheats.IndestructibleEquipment) + $"needsEnabled={cheats.NeedsEnabled} noWeightPenalty={cheats.NotAffectedByWeightPenalties} " + $"ownsQuestItems={cheats.OwnsQuestItems} showTrajectory={cheats.ShowTrajectory} showTOD={cheats.ShowTOD} " + $"autoSave={cheats.AutoSave} speedMult={num:0.##}")); int rid = RewiredId(player); if (rid >= 0) { log.LogMessage((object)(string.Format("{0} persisted (PlayerPrefs, player {1}; '-' = never set): ", "[CHEAT]", rid) + "invincible=" + Pref((CheatType)0) + " indestructibleEquip=" + Pref((CheatType)1) + " needs=" + Pref((CheatType)2) + " weight=" + Pref((CheatType)6) + " ownsQuestItems=" + Pref((CheatType)9) + " trajectory=" + Pref((CheatType)3) + " mana=" + Pref((CheatType)8) + " speed=" + Pref((CheatType)4) + " autoSave=" + Pref((CheatType)10))); } string Pref(CheatType t) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 string cheatPrefKey = CharacterCheats.GetCheatPrefKey(t, rid); if (!PlayerPrefs.HasKey(cheatPrefKey)) { return "-"; } if ((int)t != 4) { return PlayerPrefs.GetInt(cheatPrefKey, 0).ToString(); } return PlayerPrefs.GetFloat(cheatPrefKey, 1f).ToString("0.##"); } } public static void CheatMenuVerb(Character player, string[] parts, ManualLogSource log) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) string text = ((parts.Length > 1) ? parts[1].ToLowerInvariant() : null); CharacterUI characterUI = player.CharacterUI; DeveloperToolManager val = (((Object)(object)characterUI != (Object)null) ? characterUI.m_developerToolManager : null); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[CHEAT] cheatmenu: no DeveloperToolManager on this character's UI."); return; } DeveloperTools val2; switch (text) { case "hide": val.HideCurrentDisplayedTool(); log.LogMessage((object)"[CHEAT] cheatmenu: hidden."); return; case "items": val2 = (DeveloperTools)0; break; case "cheats": val2 = (DeveloperTools)1; break; case "skills": val2 = (DeveloperTools)2; break; case "quests": val2 = (DeveloperTools)3; break; default: log.LogWarning((object)"[CHEAT] usage: cheatmenu "); return; } ArmGate(log); val.ToggleDevelopperTool(val2); log.LogMessage((object)("[CHEAT] cheatmenu: toggled " + text + " window (now " + (val.ToolDisplayed ? "shown" : "hidden") + ").")); } public static void GodmodeVerb(Character player, string[] parts, ManualLogSource log) { if (!TryOnOff(parts, player.Cheats.Invincible, out var on, out var err)) { log.LogWarning((object)("[CHEAT] " + err + " usage: godmode [on|off] (bare = toggle).")); return; } ArmGate(log); CharacterManager.Instance.SendCharacterInvincible(player, on); PersistInt((CheatType)0, player, on ? 1 : 0); log.LogMessage((object)(string.Format("{0} godmode: player invincible={1}", "[CHEAT]", on) + RoomNote() + " (persists via PlayerPrefs).")); } public static void EnemyGodVerb(string[] parts, ManualLogSource log) { CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[CHEAT] enemygod: no CharacterManager."); return; } if (!TryOnOff(parts, instance.IsEnemyInvincible, out var on, out var err)) { log.LogWarning((object)("[CHEAT] " + err + " usage: enemygod [on|off] (bare = toggle).")); return; } instance.IsEnemyInvincible = on; log.LogMessage((object)string.Format("{0} enemygod: enemies invincible={1} (local-only, resets at relaunch).", "[CHEAT]", on)); } public static void NeedsVerb(Character player, string[] parts, ManualLogSource log) { if (!TryOnOff(parts, player.Cheats.NeedsEnabled, out var on, out var err)) { log.LogWarning((object)("[CHEAT] " + err + " usage: needs [on|off] ('off' stops need drain; bare = toggle).")); return; } if (!on) { ArmGate(log); } player.Cheats.NeedsEnabled = on; PersistInt((CheatType)2, player, on ? 1 : 0); log.LogMessage((object)(string.Format("{0} needs: needsEnabled={1}", "[CHEAT]", on) + (on ? "" : " (drain stopped)") + " (persists via PlayerPrefs).")); } public static void SpeedMultVerb(Character player, string[] parts, ManualLogSource log) { CharacterControl characterControl = player.CharacterControl; LocalCharacterControl val = (LocalCharacterControl)(object)((characterControl is LocalCharacterControl) ? characterControl : null); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[CHEAT] speedmult: player has no LocalCharacterControl."); return; } if (parts.Length < 2 || !float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { log.LogMessage((object)string.Format("{0} speedmult: current={1:0.##} — usage: speedmult .", "[CHEAT]", val.MovementMultiplier)); return; } if (result <= 0f || result > 50f) { log.LogWarning((object)string.Format("{0} speedmult: {1} out of range (0 < mult <= 50).", "[CHEAT]", result)); return; } ArmGate(log); float movementMultiplier = val.MovementMultiplier; val.MovementMultiplier = result; PersistFloat((CheatType)4, player, result); log.LogMessage((object)string.Format("{0} speedmult: {1:0.##} -> {2:0.##} (persists via PlayerPrefs).", "[CHEAT]", movementMultiplier, result)); } public static void TimeJumpVerb(string[] parts, ManualLogSource log) { EnvironmentConditions instance = EnvironmentConditions.Instance; if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[CHEAT] timejump: no EnvironmentConditions (not in a world?)."); return; } if (parts.Length < 2 || !float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result <= 0f || result > 336f) { log.LogWarning((object)"[CHEAT] usage: timejump — advances game time through the vanilla skip-time path."); return; } instance.TimeJump(result); log.LogMessage((object)string.Format("{0} timejump: advanced {1:0.##}h (TOD now {2}).", "[CHEAT]", result, ((Object)(object)TOD_Sky.Instance != (Object)null) ? TOD_Sky.Instance.Cycle.Hour.ToString("0.##") : "?")); } public static void DebugFileVerb(string[] parts, ManualLogSource log) { bool flag = File.Exists(DebugFilePath); string text = ((parts.Length > 1) ? parts[1].ToLowerInvariant() : null); if (text == null) { log.LogMessage((object)("[CHEAT] debugfile: " + (flag ? "present" : "absent") + " — " + DebugFilePath + " (read once at boot; 'cheats on' is the live arm).")); return; } try { if (text == "on") { if (!flag) { File.WriteAllText(DebugFilePath, ""); } Global.CheatsEnabled = true; log.LogMessage((object)("[CHEAT] debugfile: " + (flag ? "already present" : "created") + " — debug mode persists across relaunches (and is armed now).")); } else if (text == "off") { if (flag) { File.Delete(DebugFilePath); } log.LogMessage((object)("[CHEAT] debugfile: " + (flag ? "deleted" : "already absent") + " — next boot starts with cheats off (this session's gate untouched; 'cheats off' for now).")); } else { log.LogWarning((object)"[CHEAT] usage: debugfile [on|off] (bare = report)."); } } catch (Exception ex) { log.LogWarning((object)("[CHEAT] debugfile: " + ex.GetType().Name + ": " + ex.Message)); } } private static void ArmGate(ManualLogSource log) { if (!Global.CheatsEnabled) { Global.CheatsEnabled = true; log.LogMessage((object)"[CHEAT] (armed Global.CheatsEnabled — session-only; 'debugfile on' to persist.)"); } } private static bool TryOnOff(string[] parts, bool current, out bool on, out string err) { err = null; if (parts.Length < 2) { on = !current; return true; } string text = parts[1].ToLowerInvariant(); if (!(text == "on")) { if (text == "off") { on = false; return true; } on = current; err = "'" + parts[1] + "'?"; return false; } on = true; return true; } private static int RewiredId(Character player) { CharacterUI val = (((Object)(object)player != (Object)null) ? player.CharacterUI : null); if (!((Object)(object)val != (Object)null)) { return -1; } return val.RewiredID; } private static void PersistInt(CheatType type, Character player, int value) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) int num = RewiredId(player); if (num >= 0) { PlayerPrefs.SetInt(CharacterCheats.GetCheatPrefKey(type, num), value); PlayerPrefs.Save(); } } private static void PersistFloat(CheatType type, Character player, float value) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) int num = RewiredId(player); if (num >= 0) { PlayerPrefs.SetFloat(CharacterCheats.GetCheatPrefKey(type, num), value); PlayerPrefs.Save(); } } private static string RoomNote() { if (PhotonNetwork.offlineMode || !PhotonNetwork.inRoom || PhotonNetwork.room == null || PhotonNetwork.room.PlayerCount <= 1) { return ""; } return " — MP NOTE: this RPCs to ALL (room-visible on this character)"; } } public sealed class CommonVerbsOptions { public bool Items = true; public bool World = true; public bool Combat = true; public bool Skills = true; public bool Status = true; public bool EngineDiag = true; public bool Containers = true; public bool Resilience = true; public bool Saves = true; public bool Cheats = true; public readonly HashSet Exclude = new HashSet(StringComparer.OrdinalIgnoreCase); public Func PetTarget; public bool Config = true; public Func ConfigSource; public Action OnConfigReloaded; } public static class CommonVerbs { public static void RegisterAll(VerbHost host, ManualLogSource log, CommonVerbsOptions opts = null) { opts = opts ?? new CommonVerbsOptions(); HashSet offered = new HashSet(StringComparer.OrdinalIgnoreCase); List honored = new List(); if (opts.Items) { if (On("give")) { host.Register("give", "Spawn any item ('give [pouch|bag|ground] [qty] ', default pouch x1).", delegate(VerbContext ctx) { GiveVerbs.GiveItem(ctx.Player, ctx.Parts, forceGround: false, log); }, "[GIVE]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[3] { new ArgSpec("dest", "enum", optional: true, new string[3] { "pouch", "bag", "ground" }, "pouch"), new ArgSpec("qty", "int", optional: true, null, "1"), new ArgSpec("item", "item") }); } if (On("drop")) { host.Register("drop", "Spawn any item on the ground ahead ('drop [qty] ' = give ground).", delegate(VerbContext ctx) { GiveVerbs.GiveItem(ctx.Player, ctx.Parts, forceGround: true, log); }, "[GIVE]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[2] { new ArgSpec("qty", "int", optional: true, null, "1"), new ArgSpec("item", "item") }); } if (On("removeitem")) { host.Register("removeitem", "Destroy matching pouch/bag items ('removeitem [qty 1-999|all] '; refuses ambiguous names; equipped gear untouched — unequip first).", delegate(VerbContext ctx) { GiveVerbs.RemoveItemVerb(ctx.Player, ctx.Parts, log); }, "[REMOVE]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[2] { new ArgSpec("qty", "string", optional: true, null, "1"), new ArgSpec("item", "item") }); } if (On("useitem")) { host.Register("useitem", "TryUse the first matching pouch/bag item ('useitem ').", delegate(VerbContext ctx) { GiveVerbs.UseItemVerb(ctx.Player, ctx.Parts, log); }, "[USEITEM]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("item", "item") }); } if (On("givewater")) { host.Register("givewater", "Spawn a filled Waterskin ('givewater [clean|river|salt|rancid|leyline|sparkling|healing]').", delegate(VerbContext ctx) { GiveVerbs.GiveWater(ctx.Player, ctx.Parts, log); }, "[GIVEWATER]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("kind", "enum", optional: true, new string[7] { "clean", "river", "salt", "rancid", "leyline", "sparkling", "healing" }, "clean") }); } if (On("equip")) { host.Register("equip", "Equip a matching inventory item, spawning it if absent ('equip ').", delegate(VerbContext ctx) { GiveVerbs.EquipVerb(ctx.Player, ctx.Parts, log); }, "[DEV]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("item", "item") }); } if (On("unequip")) { host.Register("unequip", "Unequip a slot ('unequip ').", delegate(VerbContext ctx) { GiveVerbs.UnequipVerb(ctx.Player, ctx.Parts, log); }, "[DEV]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("slot", "enum", optional: false, new string[9] { "helmet", "chest", "legs", "boots", "hands", "weapon", "offhand", "backpack", "quiver" }) }); } } if (opts.World) { if (On("teleport")) { host.Register("teleport", "Teleport the player to a RAW coordinate ('teleport '; coords via diag/posdump; the height is written as given and only WARNED about — for a ground-safe hop use walkto or standoff).", delegate(VerbContext ctx) { StageVerbs.TeleportVerb(ctx.Player, ctx.Parts, log); }, "[DEV]"); } if (On("goto")) { host.Register("goto", "Switch areas via the vanilla loader ('goto [spawnPoint] [nowatch]'; names via scenedump; saves on exit; host-only; a press-any-key watchdog is armed unless 'nowatch').", delegate(VerbContext ctx) { StageVerbs.GotoVerb(ctx.Parts, log); }, "[DEV]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: true, null, new ArgSpec[3] { new ArgSpec("scene", "scene"), new ArgSpec("spawnPoint", "int", optional: true), new ArgSpec("nowatch", "enum", optional: true, new string[1] { "nowatch" }) }); } if (On("settime")) { host.Register("settime", "Set the game time of day ('settime '; host-only).", delegate(VerbContext ctx) { StageVerbs.SetTimeVerb(ctx.Parts, log); }, "[DEV]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: true); } if (On("firecamp")) { host.Register("firecamp", "Place and LIGHT a real campfire ahead of the player ('firecamp [on|off|remove|status] [distance 0.5-20]'; 'off' extinguishes so recovery can be watched, 'status' prints the live TemperatureSource bands; host-only).", delegate(VerbContext ctx) { FireVerbs.FirecampVerb(ctx.Player, ctx.Parts, log); }, "[FIRECAMP]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: true, null, new ArgSpec[2] { new ArgSpec("action", "enum", optional: true, new string[4] { "on", "off", "remove", "status" }, "on"), new ArgSpec("distance", "float", optional: true, null, "2") }); } if (On("givemoney")) { host.Register("givemoney", "Add silver to the pouch ('givemoney <1-1000000>').", delegate(VerbContext ctx) { StageVerbs.GiveMoneyVerb(ctx.Player, ctx.Parts, log); }, "[DEV]"); } if (On("face")) { host.Register("face", "Point the player, in place ('face '; warns when a lock/zoom/camera mode would erase the facing).", delegate(VerbContext ctx) { MoveVerbs.FaceVerb(ctx.Player, ctx.Parts, log); }, "[FACE]"); } if (On("moveto")) { host.Register("moveto", "Place the player near a wild creature, on the ground, FACING it ('moveto [gap-meters]', default 2m — stages a melee swing in one command).", delegate(VerbContext ctx) { MoveVerbs.MoveToVerb(ctx.Player, ctx.Parts, log); }, "[MOVETO]"); } if (On("standoff")) { host.Register("standoff", "Place the player a measured distance from the pet (or a creature), on connected ground, FACING it ('standoff [bearing=] [target=pet|nearest|]'; REFUSES rather than dropping you in the air — the leash-distance primitive).", delegate(VerbContext ctx) { MoveVerbs.StandoffVerb(ctx.Player, opts.PetTarget, ctx.Parts, log); }, "[STANDOFF]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[3] { new ArgSpec("metres", "float"), new ArgSpec("bearing", "string", optional: true), new ArgSpec("target", "string", optional: true) }); } if (On("walkto")) { host.Register("walkto", "Teleport to x/z with the HEIGHT TAKEN FROM THE NAVMESH ('walkto ' — no y argument on purpose; refuses outright if there is no navmesh in that column, and never places you above where you stand unless the mesh says so).", delegate(VerbContext ctx) { MoveVerbs.WalkToVerb(ctx.Player, ctx.Parts, log); }, "[WALKTO]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[2] { new ArgSpec("x", "float"), new ArgSpec("z", "float") }); } } if (opts.Combat) { if (On("sethp")) { host.Register("sethp", "Set player HP ('sethp '; floor 1 — death needs real damage).", delegate(VerbContext ctx) { StageVerbs.SetHpPlayerVerb(ctx.Player, ctx.Parts, log); }, "[DEV]"); } if (On("combatclear")) { host.Register("combatclear", "ResetCombat() on the player.", delegate(VerbContext ctx) { StageVerbs.CombatClearVerb(ctx.Player, log); }, "[COMBAT]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, "[COMBAT] no player.", null); } if (On("killnearest")) { host.Register("killnearest", "Overkill the nearest wild creature via the vanilla pipeline ('killnearest [species] [radius]', default 40m; skips Faction.NONE dialogue NPCs like Maren).", delegate(VerbContext ctx) { StageVerbs.KillNearest(ctx.Player, ctx.Parts, log); }, "[KILL]"); } if (On("swing")) { host.Register("swing", "One real attack with the equipped weapon via the real input entry (auto-draws if sheathed; bows press+release ~0.3s apart; stage with 'moveto'/'face', or 'lockon' for bows).", delegate(VerbContext ctx) { StageVerbs.SwingVerb(ctx.Player, ctx.Parts, log); }, "[SWING]"); } if (On("lockon")) { host.Register("lockon", "Lock the game's own targeting onto a wild creature ('lockon [nearest|species]' — auto-faces the body and aims bow shots; warns if beyond TrueRange).", delegate(VerbContext ctx) { MoveVerbs.LockOnVerb(ctx.Player, ctx.Parts, log); }, "[LOCK]"); } if (On("lockoff")) { host.Register("lockoff", "Release the lock (and clear the camera-facing flag so a set facing sticks again).", delegate(VerbContext ctx) { MoveVerbs.LockOffVerb(ctx.Player, log); }, "[LOCK]"); } } if (opts.Skills) { if (On("learnskill")) { host.Register("learnskill", "Teach the player any skill ('learnskill '; did-you-mean lists skills only).", delegate(VerbContext ctx) { SkillVerbs.LearnSkillVerb(ctx.Player, ctx.Parts, log); }, "[SKILL]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("skill", "skill") }); } if (On("unlearnskill")) { host.Register("unlearnskill", "Forget a learned skill ('unlearnskill ') — the gate tests need skills ABSENT.", delegate(VerbContext ctx) { SkillVerbs.UnlearnSkillVerb(ctx.Player, ctx.Parts, log); }, "[SKILL]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("skill", "skill") }); } if (On("resetcooldowns")) { host.Register("resetcooldowns", "Clear every learned skill's cooldown.", delegate(VerbContext ctx) { SkillVerbs.ResetCooldownsVerb(ctx.Player, log); }, "[DEV]"); } if (On("castspell")) { host.Register("castspell", "Cast a LEARNED skill via the real quickslot pipeline ('castspell '; requirements/cooldown/mana apply for real — learnskill it first).", delegate(VerbContext ctx) { SkillVerbs.CastSpellVerb(ctx.Player, ctx.Parts, log); }, "[CASTSPELL]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("skill", "skill") }); } } if (opts.Status) { if (On("grantstatus")) { host.Register("grantstatus", "Apply a status effect the vanilla way ('grantstatus [target=player|pet] [force]'; names via statusdump; prefab duration — no override; a refusal NAMES the engine gate, and `force` suspends a resistance/immunity gate for that one grant).", delegate(VerbContext ctx) { StatusVerbs.GrantStatusVerb(ctx.Player, opts.PetTarget, ctx.Parts, log); }, "[STATUS]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[3] { new ArgSpec("status", "status"), new ArgSpec("target", "enum", optional: true, new string[2] { "target=player", "target=pet" }), new ArgSpec("force", "enum", optional: true, new string[1] { "force" }) }); } if (On("removestatus")) { host.Register("removestatus", "Clear a running status early ('removestatus [target=player|pet]').", delegate(VerbContext ctx) { StatusVerbs.RemoveStatusVerb(ctx.Player, opts.PetTarget, ctx.Parts, log); }, "[STATUS]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[2] { new ArgSpec("status", "status"), new ArgSpec("target", "enum", optional: true, new string[2] { "target=player", "target=pet" }) }); } } if (opts.EngineDiag) { if (On("statusdump")) { host.Register("statusdump", "Every loadable StatusEffect prefab name.", delegate { DiagVerbs.StatusDump(log); }, "[CMD]", needsPlayer: false); } if (On("invdump")) { host.Register("invdump", "List the player's pouch+bag contents ('invdump [name-or-ItemID filter]'); filtered, reports a TOTAL QTY. Equipped gear is not listed.", delegate(VerbContext ctx) { DiagVerbs.InvDump(ctx.Player, ctx.Parts, log); }, "[INVDUMP]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("filter", "item", optional: true) }); } if (On("pos")) { host.Register("pos", "Player placement + facing-stickiness state: pos/rotY/sheathed/weapon/controlMode/faceLikeCamera/zoom/lock, plus nearest-wild dist + facing-angle.", delegate(VerbContext ctx) { MoveVerbs.PosVerb(ctx.Player, log); }, "[PPOS]"); } if (On("scenedump")) { host.Register("scenedump", "Dump the build-settings scene list.", delegate { BuildScenes.Dump(log); }, "[CMD]", needsPlayer: false); } if (On("skydump")) { host.Register("skydump", "RenderSettings/camera/sky snapshot (Bug 22 black screen).", delegate { SkySnapshot.Log("manual skydump", log); }, "[CMD]", needsPlayer: false); } if (On("groundprobe")) { host.Register("groundprobe", "Measure the navmesh at the player (nearest-poly hits by radius).", delegate(VerbContext ctx) { DiagVerbs.GroundProbe(ctx.Player, log); }, "[PROBE]", needsPlayer: true, warnOnNoPlayer: false, masterOnly: false, "[PROBE] no player.", null); } if (On("combatmgrdump")) { host.Register("combatmgrdump", "Global.CombatManager identity/liveness (Bug 12).", delegate { DiagVerbs.CombatMgrDump(log); }, "[CMD]", needsPlayer: false); } if (On("keybinds")) { host.Register("keybinds", "Every key claimed by every mod in the family, grouped by key (conflicts flagged).", delegate { log.LogMessage((object)Keybinds.Report()); }, "[CMD]", needsPlayer: false); } if (On("ragdolldump")) { host.Register("ragdolldump", "ragdolldump [name] [radius] — death-pose machinery census for characters within radius (default 30m; a flown-apart corpse needs a bigger one): deathAnim/ragdollRoot/ragdollRB/joints/managers/mgrJoint per character. A humanoid with mgrJoint=0 will gumby on death; joints>0 on a corpse = the ragdoll assembled.", delegate(VerbContext ctx) { string text = ctx.Tail(); float radius = 30f; if (!string.IsNullOrEmpty(text)) { int num = text.LastIndexOf(' '); string s = ((num < 0) ? text : text.Substring(num + 1)); if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && result > 0f) { radius = result; text = ((num < 0) ? "" : text.Substring(0, num).Trim()); } } RagdollProbe.Dump(text, log, radius); }, "[CMD]", needsPlayer: false); } if (On("psdump")) { host.Register("psdump", "psdump [all] — NAME the source of the 'Particle System is trying to spawn on a mesh with zero surface area' storm (Bug 27): every mesh-shaped ParticleSystem whose mesh is null/empty/degenerate, with its full hierarchy path, scene, playing state and where the mesh came from. 'all' also lists the healthy mesh-shaped systems. Run it WHILE the spam is flowing.", delegate(VerbContext ctx) { ParticleProbe.Dump(ctx.Parts.Length > 1 && string.Equals(ctx.Parts[1], "all", StringComparison.OrdinalIgnoreCase), log); }, "[CMD]", needsPlayer: false); } if (On("screenshot")) { host.Register("screenshot", "Capture the current frame to BepInEx/screenshots/ as PNG ('screenshot [supersize 1-4] [label]'; async — grep '[SHOT] saved' for the path). Full frame incl. UI.", delegate(VerbContext ctx) { DiagVerbs.Screenshot(ctx.Parts, log); }, "[SHOT]", needsPlayer: false); } } if (opts.Containers) { if (On("containerdump")) { host.Register("containerdump", "containerdump [radius] — census of every world container within radius (default 15m): name/id/uid/dist, CONCRETE TYPE, drop-table counts, items, value, and the generated/canGather freshness flag. The type column decides whether a TreasureChest patch can reach them at all.", delegate(VerbContext ctx) { ContainerVerbs.Dump(ctx.Player, ctx.Parts, log); }, "[CONTAINER]"); } if (On("containerroll")) { host.Register("containerroll", "containerroll [name-filter] [radius] [fresh|reopen] — re-roll the nearest matching container's loot (default: nearest, 15m, fresh). 'fresh' resets the fill gate + drop tables first; 'reopen' re-calls the fill with no reset and asserts the once-per-container gate held. Host/SP only. Fill path only — the loot panel and open-time hold timing are NOT exercised, and each roll fires the container's gather quest event.", delegate(VerbContext ctx) { ContainerVerbs.Roll(ctx.Player, ctx.Parts, log); }, "[CONTAINER]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: true); } } if (opts.Cheats) { if (On("cheats")) { host.Register("cheats", "Report the FULL cheat state (gate, DEBUG.txt, every per-character cheat flag, persisted values) — the pre-debugging confounder audit; 'cheats on|off' flips Global.CheatsEnabled live (session-only).", delegate(VerbContext ctx) { CheatVerbs.CheatsVerb(ctx.Player, ctx.Parts, log); }, "[CHEAT]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("state", "enum", optional: true, new string[2] { "on", "off" }) }); } if (On("cheatmenu")) { host.Register("cheatmenu", "Toggle a vanilla debug window without its F-key ('cheatmenu '; arms the gate if needed).", delegate(VerbContext ctx) { CheatVerbs.CheatMenuVerb(ctx.Player, ctx.Parts, log); }, "[CHEAT]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("window", "enum", optional: false, new string[5] { "items", "cheats", "skills", "quests", "hide" }) }); } if (On("godmode")) { host.Register("godmode", "Vanilla player invincibility ('godmode [on|off]', bare = toggle). MP: RPCs to ALL — room-visible.", delegate(VerbContext ctx) { CheatVerbs.GodmodeVerb(ctx.Player, ctx.Parts, log); }, "[CHEAT]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("state", "enum", optional: true, new string[2] { "on", "off" }) }); } if (On("enemygod")) { host.Register("enemygod", "Enemy invincibility ('enemygod [on|off]', bare = toggle; local-only, resets at relaunch).", delegate(VerbContext ctx) { CheatVerbs.EnemyGodVerb(ctx.Parts, log); }, "[CHEAT]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("state", "enum", optional: true, new string[2] { "on", "off" }) }); } if (On("needs")) { host.Register("needs", "Hunger/thirst/sleep drain ('needs off' stops it — the vanilla needs cheat; bare = toggle).", delegate(VerbContext ctx) { CheatVerbs.NeedsVerb(ctx.Player, ctx.Parts, log); }, "[CHEAT]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("state", "enum", optional: true, new string[2] { "on", "off" }) }); } if (On("speedmult")) { host.Register("speedmult", "Movement speed multiplier, the F2 slider ('speedmult ', 1 = normal; bare = report).", delegate(VerbContext ctx) { CheatVerbs.SpeedMultVerb(ctx.Player, ctx.Parts, log); }, "[CHEAT]", needsPlayer: true, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("mult", "float", optional: true) }); } if (On("timejump")) { host.Register("timejump", "Advance game time via the vanilla skip-time path ('timejump '; runs the elapsed-time sim — needs drain, respawns; host-only. settime SETS the clock instead).", delegate(VerbContext ctx) { CheatVerbs.TimeJumpVerb(ctx.Parts, log); }, "[CHEAT]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: true, null, new ArgSpec[1] { new ArgSpec("hours", "float") }); } if (On("debugfile")) { host.Register("debugfile", "Create/delete this install's DEBUG.txt so debug mode persists across relaunches ('debugfile [on|off]', bare = report).", delegate(VerbContext ctx) { CheatVerbs.DebugFileVerb(ctx.Parts, log); }, "[CHEAT]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("state", "enum", optional: true, new string[2] { "on", "off" }) }); } } if (opts.Config && opts.ConfigSource != null) { if (On("reloadcfg")) { host.Register("reloadcfg", "Re-read this mod's .cfg from disk (BepInEx 5 has no file-watcher — a hand edit does nothing until this).", delegate { ReloadCfg(opts, log); }, "[CFG]", needsPlayer: false); } if (On("set")) { host.Register("set", "Set a config value live ('set [transient]'; keys via cfgdump; saved to the .cfg unless 'transient').", delegate(VerbContext ctx) { SetCfgVerb(opts, ctx.Parts, log); }, "[CFG]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[3] { new ArgSpec("key", "configkey"), new ArgSpec("value", "rest"), new ArgSpec("transient", "enum", optional: true, new string[1] { "transient" }) }); } if (On("cfgdump")) { host.Register("cfgdump", "List this mod's config ('cfgdump [section]'): Section.Key = value (Type), * marks non-default.", delegate(VerbContext ctx) { CfgDumpVerb(opts, ctx.Arg(1), log); }, "[CFG]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("section", "string", optional: true) }); } if (On("cfgdrift")) { host.Register("cfgdrift", "List only the config entries that differ from the shipped defaults ('cfgdrift [section]') — the same drift the [CFGSKEW] boot line counts.", delegate(VerbContext ctx) { CfgDriftVerb(opts, ctx.Arg(1), log); }, "[CFG]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("section", "string", optional: true) }); } } if (opts.Saves) { if (On("savelist")) { host.Register("savelist", "List this install's character saves ('savelist'; main menu).", delegate { SaveVerbs.SaveListVerb(log); }, "[SAVE]", needsPlayer: false); } if (On("loadsave")) { host.Register("loadsave", "Load a character save from the MAIN MENU, no keyboard ('loadsave '; ids via savelist; one selection per process).", delegate(VerbContext ctx) { SaveVerbs.LoadSaveVerb(ctx.Parts, log); }, "[SAVE]", needsPlayer: false); } } if (opts.Resilience && On("unstick")) { host.Register(new string[2] { "unstick", "unwedge" }, "Diagnose and recover a wedged session ('unstick' dumps; 'unstick fix' applies one rung; 'unstick [fix|force] ').", delegate(VerbContext ctx) { ResilienceVerbs.UnstickVerb(ctx.Parts, log); }, "[UNSTICK]", needsPlayer: false); } List list = new List(); foreach (string item in opts.Exclude) { if (!offered.Contains(item)) { list.Add(item); } } log.LogMessage((object)("[FORGEKIT] CommonVerbs registered: " + $"items={opts.Items} world={opts.World} combat={opts.Combat} skills={opts.Skills} " + $"status={opts.Status} enginediag={opts.EngineDiag} containers={opts.Containers} " + $"cheats={opts.Cheats} " + $"resilience={opts.Resilience} saves={opts.Saves} config={opts.ConfigSource != null && opts.Config}" + ((honored.Count > 0) ? ("; excluded: " + string.Join(", ", honored.ToArray())) : "; excluded: none"))); if (list.Count > 0) { log.LogWarning((object)("[FORGEKIT] CommonVerbs: exclusion(s) matched no verb in an enabled domain: " + string.Join(", ", list.ToArray()) + " — typo, or the whole domain is already off? (An exclusion that misses leaves the pack's copy registered.)")); } bool On(string verb) { offered.Add(verb); if (!opts.Exclude.Contains(verb)) { return true; } honored.Add(verb); return false; } } public static void RegisterConfigVerbs(CommandRegistry commands, ManualLogSource log, Func configSource, Action onConfigReloaded = null, bool includeReloadCfg = true) { CommonVerbsOptions opts = new CommonVerbsOptions { ConfigSource = configSource, OnConfigReloaded = onConfigReloaded }; VerbHost verbHost = new VerbHost(commands, log, () => (Character)null); if (includeReloadCfg) { verbHost.Register("reloadcfg", "Re-read this mod's .cfg from disk (BepInEx 5 has no file-watcher — a hand edit does nothing until this).", delegate { ReloadCfg(opts, log); }, "[CFG]", needsPlayer: false); } verbHost.Register("set", "Set a config value live ('set [transient]'; keys via cfgdump; saved to the .cfg unless 'transient').", delegate(VerbContext ctx) { SetCfgVerb(opts, ctx.Parts, log); }, "[CFG]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[3] { new ArgSpec("key", "configkey"), new ArgSpec("value", "rest"), new ArgSpec("transient", "enum", optional: true, new string[1] { "transient" }) }); verbHost.Register("cfgdump", "List this mod's config ('cfgdump [section]'): Section.Key = value (Type), * marks non-default.", delegate(VerbContext ctx) { CfgDumpVerb(opts, ctx.Arg(1), log); }, "[CFG]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("section", "string", optional: true) }); verbHost.Register("cfgdrift", "List only the config entries that differ from the shipped defaults ('cfgdrift [section]') — the same drift the [CFGSKEW] boot line counts.", delegate(VerbContext ctx) { CfgDriftVerb(opts, ctx.Arg(1), log); }, "[CFG]", needsPlayer: false, warnOnNoPlayer: true, masterOnly: false, null, new ArgSpec[1] { new ArgSpec("section", "string", optional: true) }); } private static void SetCfgVerb(CommonVerbsOptions opts, string[] parts, ManualLogSource log) { DevCfg.SetArgs setArgs = DevCfg.ParseSet(parts); if (setArgs.Problem != null) { log.LogWarning((object)("[CFG] " + setArgs.Problem)); return; } ConfigFile val = opts.ConfigSource(); if (val == null) { log.LogWarning((object)"[CFG] set: no ConfigFile on this channel."); return; } List list = new List(); foreach (ConfigDefinition item in new List(val.Keys)) { if (DevCfg.MatchKey(setArgs.Key, item.Section, item.Key)) { list.Add(val[item]); } } if (list.Count == 0) { log.LogWarning((object)("[CFG] set: '" + setArgs.Key + "' matches no config key on this mod — keys via cfgdump.")); return; } if (list.Count > 1) { List list2 = new List(); foreach (ConfigEntryBase item2 in list) { list2.Add(DevCfg.Display(item2.Definition.Section, item2.Definition.Key)); } log.LogWarning((object)("[CFG] set: '" + setArgs.Key + "' is ambiguous — use the full form: " + string.Join(", ", list2.ToArray()))); return; } ConfigEntryBase val2 = list[0]; object obj; try { obj = TomlTypeConverter.ConvertToValue(setArgs.Value, val2.SettingType); } catch (Exception ex) { log.LogWarning((object)("[CFG] set: '" + setArgs.Value + "' is not a valid " + val2.SettingType.Name + " for " + DevCfg.Display(val2.Definition.Section, val2.Definition.Key) + " (" + ex.Message + ").")); return; } ConfigDescription description = val2.Description; if (((description != null) ? description.AcceptableValues : null) != null && !val2.Description.AcceptableValues.IsValid(obj)) { log.LogWarning((object)("[CFG] set: " + setArgs.Value + " is outside the acceptable range — " + val2.Description.AcceptableValues.ToDescriptionString().TrimStart('#', ' '))); return; } string serializedValue = val2.GetSerializedValue(); bool saveOnConfigSet = val.SaveOnConfigSet; try { val.SaveOnConfigSet = false; val2.BoxedValue = obj; } finally { val.SaveOnConfigSet = saveOnConfigSet; } if (!setArgs.Transient) { val.Save(); } opts.OnConfigReloaded?.Invoke(); CatalogDump.RefreshAll(); log.LogMessage((object)("[CFG] set " + DevCfg.Display(val2.Definition.Section, val2.Definition.Key) + ": " + serializedValue + " -> " + val2.GetSerializedValue() + (setArgs.Transient ? " (transient — reverts at relaunch)" : " (saved)") + ((opts.OnConfigReloaded != null) ? " + re-applied." : "."))); } private static void CfgDriftVerb(CommonVerbsOptions opts, string sectionFilter, ManualLogSource log) { ConfigFile val = opts.ConfigSource(); if (val == null) { log.LogWarning((object)"[CFG] cfgdrift: no ConfigFile on this channel."); return; } int total; List list = CfgSkew.Drifted(val, sectionFilter, out total); foreach (CfgSkewRules.Row item in list) { log.LogMessage((object)("[CFG] " + CfgSkewRules.Describe(item))); } log.LogMessage((object)(string.Format("[CFG] cfgdrift: {0} of {1} entr{2} differ from shipped defaults", list.Count, total, (total == 1) ? "y" : "ies") + (string.IsNullOrEmpty(sectionFilter) ? "" : (" in [" + sectionFilter + "]")) + " — " + val.ConfigFilePath)); } private static void CfgDumpVerb(CommonVerbsOptions opts, string sectionFilter, ManualLogSource log) { ConfigFile val = opts.ConfigSource(); if (val == null) { log.LogWarning((object)"[CFG] cfgdump: no ConfigFile on this channel."); return; } int num = 0; foreach (ConfigDefinition item in new List(val.Keys)) { if (DevCfg.SectionMatches(sectionFilter, item.Section) || DevCfg.MatchKey(sectionFilter, item.Section, item.Key)) { ConfigEntryBase val2 = val[item]; string serializedValue = val2.GetSerializedValue(); string shippedDefault; bool flag = DevCfg.Skew(val2, out shippedDefault); log.LogMessage((object)("[CFG] " + DevCfg.Display(item.Section, item.Key) + " = " + serializedValue + " (" + val2.SettingType.Name + ")" + (flag ? (" *default " + shippedDefault) : ""))); num++; } } log.LogMessage((object)(string.Format("[CFG] cfgdump: {0} entr{1}", num, (num == 1) ? "y" : "ies") + (string.IsNullOrEmpty(sectionFilter) ? "" : (" in [" + sectionFilter + "]")) + " — " + val.ConfigFilePath)); } private static void ReloadCfg(CommonVerbsOptions opts, ManualLogSource log) { ConfigFile val = opts.ConfigSource(); if (val == null) { log.LogWarning((object)"[CFG] reloadcfg: no ConfigFile — nothing to reload."); return; } val.Reload(); opts.OnConfigReloaded?.Invoke(); log.LogMessage((object)($"[CFG] reloadcfg: re-read {val.ConfigFilePath} ({val.Count} entries)" + ((opts.OnConfigReloaded != null) ? " + re-applied." : "."))); } public static void RegisterAll(CommandRegistry commands, ManualLogSource log, Func player, CommonVerbsOptions opts = null) { RegisterAll(new VerbHost(commands, log, player), log, opts); } } internal static class ContainerVerbs { internal const string Tag = "[CONTAINER]"; internal static void Dump(Character player, string[] parts, ManualLogSource log) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) DevContainer.DumpArgs dumpArgs = DevContainer.ParseDump(parts); if (dumpArgs.Error != null) { log.LogWarning((object)("[CONTAINER] parse: " + dumpArgs.Error)); return; } if (dumpArgs.Clamped) { log.LogMessage((object)(string.Format("{0} radius clamped to {1:F0}m ", "[CONTAINER]", dumpArgs.Radius) + $"(range {1.0:F0}-{60.0:F0}).")); } float num = (float)dumpArgs.Radius; List list = Containers.Scan(((Component)player).transform.position, num, log); bool flag = !PhotonNetwork.isNonMasterClientInRoom; log.LogMessage((object)(string.Format("{0} ── {1} container(s) within {2:F0}m ", "[CONTAINER]", list.Count, num) + $"(WorldItems layer, master={flag}) ──")); int num2 = 0; foreach (ItemContainer item in list) { if (item is TreasureChest) { num2++; } log.LogMessage((object)("[CONTAINER] " + Describe(item, ((Component)player).transform.position))); } log.LogMessage((object)(string.Format("{0} type reach: {1}/{2} are TreasureChest — a Harmony patch on ", "[CONTAINER]", num2, list.Count) + "TreasureChest.ProcessGenerateContent runs for those only.")); log.LogMessage((object)"[CONTAINER] Gatherable is a SIBLING of TreasureChest under SelfFilledItemContainer, not a subclass: it overrides ProcessGenerateContent and calls base, so a TreasureChest patch never sees it (and it has no m_hasGeneratedContent — freshness is CanGather/IsEmpty + seasons)."); } private static string Describe(ItemContainer c, Vector3 from) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(string.Format("'{0}' id={1} uid={2} ", (((Item)c).Name ?? "").Trim(), ((Item)c).ItemID, ((Item)c).UID)).Append($"dist={Vector3.Distance(((Component)c).transform.position, from):F1}m ").Append($"type={((object)c).GetType().Name} special={c.SpecialType} "); SelfFilledItemContainer val = (SelfFilledItemContainer)(object)((c is SelfFilledItemContainer) ? c : null); if ((Object)(object)val != (Object)null) { CountTables(val, out var main, out var cond); stringBuilder.Append($"tables=main:{main}/cond:{cond} "); } else { stringBuilder.Append("tables=n/a "); } stringBuilder.Append($"items={c.ItemCount} value={c.ContentValue} "); TreasureChest val2 = (TreasureChest)(object)((c is TreasureChest) ? c : null); if ((Object)(object)val2 != (Object)null) { stringBuilder.Append($"generated={val2.HasGeneratedContent} (raw m_hasGeneratedContent={val2.m_hasGeneratedContent}, " + $"ignore={val2.m_ignoreHasGeneratedContent})"); } else { Gatherable val3 = (Gatherable)(object)((c is Gatherable) ? c : null); stringBuilder.Append(string.Format("canGather={0} empty={1}", ((Object)(object)val3 != (Object)null) ? val3.CanGather.ToString() : "n/a", c.IsEmpty)); if ((Object)(object)val3 != (Object)null) { stringBuilder.Append($" mult={val3.GatherMultiplier}"); } } return stringBuilder.ToString(); } internal static void CountTables(SelfFilledItemContainer self, out int main, out int cond) { main = 0; cond = 0; List drops = self.m_drops; if (drops == null) { return; } foreach (Dropable item in drops) { if (!((Object)(object)item == (Object)null)) { if (item.m_mainDropTables != null) { main += item.m_mainDropTables.Count; } if (item.m_conditionalDropTables != null) { cond += item.m_conditionalDropTables.Count; } } } } internal static void Roll(Character player, string[] parts, ManualLogSource log) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) DevContainer.RollArgs rollArgs = DevContainer.ParseRoll(parts); if (rollArgs.Error != null) { log.LogWarning((object)("[CONTAINER] parse: " + rollArgs.Error)); return; } if (rollArgs.Clamped) { log.LogMessage((object)(string.Format("{0} radius clamped to {1:F0}m ", "[CONTAINER]", rollArgs.Radius) + $"(range {1.0:F0}-{60.0:F0}).")); } Vector3 position = ((Component)player).transform.position; float num = (float)rollArgs.Radius; List list = Containers.Scan(position, num, log); ItemContainer val = Containers.Nearest(list, position, rollArgs.NameFilter); if ((Object)(object)val == (Object)null) { log.LogWarning((object)string.Format("{0} no container matching '{1}' within {2:F0}m.", "[CONTAINER]", rollArgs.NameFilter ?? "", num)); if (list.Count == 0) { log.LogMessage((object)(string.Format("{0} nothing on the WorldItems layer within {1:F0}m at all — ", "[CONTAINER]", num) + "widen the radius, or you are not standing near a container.")); return; } log.LogMessage((object)string.Format("{0} {1} container(s) WERE in range:", "[CONTAINER]", list.Count)); { foreach (ItemContainer item in list) { log.LogMessage((object)("[CONTAINER] '" + (((Item)item).Name ?? "").Trim() + "' (" + ((object)item).GetType().Name + ", " + $"{Vector3.Distance(((Component)item).transform.position, position):F1}m)")); } return; } } SelfFilledItemContainer val2 = (SelfFilledItemContainer)(object)((val is SelfFilledItemContainer) ? val : null); if ((Object)(object)val2 == (Object)null) { log.LogWarning((object)("[CONTAINER] '" + (((Item)val).Name ?? "").Trim() + "' is a " + ((object)val).GetType().Name + ", not a SelfFilledItemContainer — it has no loot-generation path to roll.")); return; } string text = (((Item)val).Name ?? "").Trim(); TreasureChest val3 = (TreasureChest)(object)((val is TreasureChest) ? val : null); int itemCount = val.ItemCount; int contentValue = val.ContentValue; bool flag = (Object)(object)val3 != (Object)null && val3.HasGeneratedContent; string arg = (((Object)(object)val3 != (Object)null) ? flag.ToString() : "n/a"); log.LogMessage((object)("[CONTAINER] " + rollArgs.Mode.ToString().ToLowerInvariant() + " roll on '" + text + "' (" + ((object)val).GetType().Name + ", " + $"{Vector3.Distance(((Component)val).transform.position, position):F1}m) — before: items={itemCount} " + $"value={contentValue} generated={arg}")); if (rollArgs.Mode == DevContainer.RollMode.Fresh) { if ((Object)(object)val3 != (Object)null) { val3.ResetDrop(); } else { ((ItemContainer)val2).ClearPouch(); } if ((Object)(object)val3 == (Object)null || flag) { val2.ResetMainDrops(false); val2.ResetConditionalMainDrops(false); } } val2.GenerateContents(); int itemCount2 = val.ItemCount; int contentValue2 = val.ContentValue; string text2 = (((Object)(object)val3 != (Object)null) ? val3.HasGeneratedContent.ToString() : "n/a"); log.LogMessage((object)(string.Format("{0} after: items={1} value={2} generated={3} ", "[CONTAINER]", itemCount2, contentValue2, text2) + "[" + StackList(val) + "]")); log.LogMessage((object)(string.Format("{0} delta: items {1:+#;-#;0} -> {2} ", "[CONTAINER]", itemCount, itemCount2) + $"({itemCount2 - itemCount:+#;-#;0}), value {contentValue} -> {contentValue2} " + $"({contentValue2 - contentValue:+#;-#;0})")); if (rollArgs.Mode == DevContainer.RollMode.Reopen) { bool flag2 = itemCount2 != itemCount || contentValue2 != contentValue; if ((Object)(object)val3 == (Object)null) { log.LogMessage((object)("[CONTAINER] no fill gate exists on this " + ((object)val).GetType().Name + " — an unchanged result is a coincidence, not a gate. Only TreasureChest carries m_hasGeneratedContent. (delta this run: " + (flag2 ? "CHANGED" : "unchanged") + ")")); } else if (!flag2) { log.LogMessage((object)"[CONTAINER] UNCHANGED (the once-per-container fill gate held)."); } else { log.LogWarning((object)"[CONTAINER] CHANGED on a reopen — the once-per-container fill gate did NOT hold. That is a GATE VIOLATION on a TreasureChest: m_hasGeneratedContent was already true, so ProcessGenerateContent should have been a no-op. Record it."); } } else if (itemCount2 == itemCount && contentValue2 == contentValue) { log.LogMessage((object)"[CONTAINER] note: a fresh roll that lands identical is a legitimate outcome — every die can honestly roll empty. Repeat the verb to see the spread."); } log.LogMessage((object)("[CONTAINER] limits: this is the FILL path only — InteractionOpenChest's loot panel and hold-to-open timing are NOT exercised, so a human still opens one container by hand for that half. But this roll DID add the container's gather quest event (" + (((Object)(object)val3 == (Object)null) ? "twice — a Gatherable adds it again from its own override" : "once") + "), even in reopen mode where the fill no-ops, and quest events are save-persisted. Do not repeat-roll a quest-bearing container.")); if (rollArgs.Mode == DevContainer.RollMode.Fresh) { log.LogMessage((object)("[CONTAINER] limits: fresh mode also WRITES real, save-persisted state — the pouch, the gen flag" + (((Object)(object)val3 == (Object)null || flag) ? ", and the ISavable drop-table gather counts" : "") + ". Roll on containers you are willing to lose.")); } } private static string StackList(ItemContainer c) { List containedItems = c.GetContainedItems(); if (containedItems == null || containedItems.Count == 0) { return "empty"; } List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (Item item in containedItems) { if (!((Object)(object)item == (Object)null)) { string text = (item.Name ?? ((object)item).GetType().Name).Trim(); int num = Mathf.Max(1, item.RemainingAmount); if (dictionary.ContainsKey(text)) { dictionary[text] += num; continue; } dictionary[text] = num; list.Add(text); } } StringBuilder stringBuilder = new StringBuilder(); foreach (string item2 in list) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append($"'{item2}' x{dictionary[item2]}"); } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "empty"; } } public static class Containers { private static readonly Collider[] _hits = (Collider[])(object)new Collider[256]; private static bool _truncationWarned; public static bool TruncationSeen => _truncationWarned; public static List Scan(Vector3 pos, float radius, ManualLogSource log) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); int num = Physics.OverlapSphereNonAlloc(pos, radius, _hits, Global.WorldItemsMask); if (num >= _hits.Length && log != null) { _truncationWarned = true; log.LogWarning((object)(string.Format("{0} overlap buffer FULL ({1}/{2} colliders within ", "[CONTAINER]", num, _hits.Length) + $"{radius:F0}m) — this listing is TRUNCATED and a nearer container may be missing from it. " + "Narrow the radius and re-run.")); } for (int i = 0; i < num; i++) { Collider val = _hits[i]; _hits[i] = null; if ((Object)(object)val == (Object)null) { continue; } ItemContainer componentInParent = ((Component)val).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null) && ((Item)componentInParent).IsInWorld) { string item = ((!string.IsNullOrEmpty(((Item)componentInParent).UID)) ? ((Item)componentInParent).UID : $"{((Item)componentInParent).Name}@{((Component)componentInParent).transform.position.x:F0},{((Component)componentInParent).transform.position.z:F0}"); if (hashSet.Add(item) && !(Vector3.Distance(((Component)componentInParent).transform.position, pos) > radius)) { list.Add(componentInParent); } } } list.Sort((ItemContainer x, ItemContainer y) => Vector3.Distance(((Component)x).transform.position, pos).CompareTo(Vector3.Distance(((Component)y).transform.position, pos))); return list; } public static ItemContainer Nearest(List found, Vector3 pos, string filter) { if (found == null) { return null; } foreach (ItemContainer item in found) { if (DevContainer.NameMatches(((Item)item).Name, filter)) { return item; } } return null; } } public static class DevCfg { public sealed class SetArgs { public string Key; public string Value; public bool Transient; public string Problem; } public static SetArgs ParseSet(string[] parts) { SetArgs setArgs = new SetArgs(); if (parts == null || parts.Length < 3) { setArgs.Problem = "usage: set [transient] (keys via cfgdump)"; return setArgs; } setArgs.Key = parts[1]; int num = parts.Length; if (string.Equals(parts[num - 1], "transient", StringComparison.OrdinalIgnoreCase)) { setArgs.Transient = true; num--; } if (num < 3) { setArgs.Problem = "usage: set [transient] — missing value."; return setArgs; } setArgs.Value = string.Join(" ", parts, 2, num - 2); return setArgs; } public static bool MatchKey(string token, string section, string key) { if (string.IsNullOrEmpty(token) || section == null || key == null) { return false; } for (int num = token.IndexOf('.'); num >= 0; num = token.IndexOf('.', num + 1)) { string text = token.Substring(0, num); string text2 = token.Substring(num + 1); if (text.Equals(section, StringComparison.OrdinalIgnoreCase) && text2.Equals(key, StringComparison.OrdinalIgnoreCase)) { return true; } } return token.Equals(key, StringComparison.OrdinalIgnoreCase); } public static string Display(string section, string key) { return section + "." + key; } public static bool SectionMatches(string filter, string section) { if (!string.IsNullOrEmpty(filter)) { return string.Equals(filter, section, StringComparison.OrdinalIgnoreCase); } return true; } public static bool Skew(ConfigEntryBase e, out string shippedDefault) { shippedDefault = null; if (e == null) { return false; } string serializedValue; try { serializedValue = e.GetSerializedValue(); } catch { return false; } string text = null; try { text = TomlTypeConverter.ConvertToString(e.DefaultValue, e.SettingType); } catch { } if (!CfgSkewRules.Differs(serializedValue, text)) { return false; } shippedDefault = text; return true; } } public static class DevContainer { public enum RollMode { Fresh, Reopen } public sealed class DumpArgs { public double Radius = 15.0; public bool Clamped; public string Error; } public sealed class RollArgs { public string NameFilter; public double Radius = 15.0; public bool Clamped; public RollMode Mode; public string Error; } public const double RadiusDefault = 15.0; public const double RadiusMin = 1.0; public const double RadiusMax = 60.0; public const string DumpUsage = "usage: containerdump [radius-meters]"; public const string RollUsage = "usage: containerroll [name-filter] [radius-meters] [fresh|reopen] (default: nearest, 15m, fresh)"; public static DumpArgs ParseDump(string[] parts) { DumpArgs dumpArgs = new DumpArgs(); int num = ((parts != null) ? parts.Length : 0); if (num <= 1) { return dumpArgs; } if (num > 2) { dumpArgs.Error = "usage: containerdump [radius-meters]"; return dumpArgs; } if (!TryNum(parts[1], out var value)) { dumpArgs.Error = "usage: containerdump [radius-meters]"; return dumpArgs; } dumpArgs.Radius = Clamp(value, out var clamped); dumpArgs.Clamped = clamped; return dumpArgs; } public static RollArgs ParseRoll(string[] parts) { RollArgs rollArgs = new RollArgs(); int num = ((parts != null) ? parts.Length : 0); if (num <= 1) { return rollArgs; } int num2 = num; bool flag = false; bool flag2 = false; for (int i = 0; i < 2; i++) { if (num2 <= 1) { break; } string token = (parts[num2 - 1] ?? "").Trim(); if (!flag && TryMode(token, out var mode)) { rollArgs.Mode = mode; flag = true; num2--; continue; } if (flag2 || !TryNum(token, out var value)) { break; } rollArgs.Radius = Clamp(value, out var clamped); rollArgs.Clamped = clamped; flag2 = true; num2--; } if (num2 <= 1) { return rollArgs; } string text = string.Join(" ", parts, 1, num2 - 1).Trim(); rollArgs.NameFilter = ((text.Length == 0) ? null : text); return rollArgs; } public static bool NameMatches(string liveName, string filter) { if (filter == null) { return true; } string text = filter.Trim(); if (text.Length == 0) { return true; } string text2 = (liveName ?? "").Trim(); return text2.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0; } private static bool TryMode(string token, out RollMode mode) { if (string.Equals(token, "fresh", StringComparison.OrdinalIgnoreCase)) { mode = RollMode.Fresh; return true; } if (string.Equals(token, "reopen", StringComparison.OrdinalIgnoreCase)) { mode = RollMode.Reopen; return true; } mode = RollMode.Fresh; return false; } private static double Clamp(double r, out bool clamped) { double num = ((r < 1.0) ? 1.0 : ((r > 60.0) ? 60.0 : r)); clamped = num != r; return num; } private static bool TryNum(string token, out double value) { return DevNum.TryFinite(token, out value); } } public enum FireAction { On, Off, Remove, Status } public sealed class FireArgs { public FireAction Action; public float Distance = 2f; public string Error; } public static class DevFire { public const float DefaultDistance = 2f; public const float MinDistance = 0.5f; public const float MaxDistance = 20f; public const string Usage = "usage: firecamp [on|off|remove|status] [distance 0.5-20]"; public static FireArgs Parse(string[] parts) { FireArgs fireArgs = new FireArgs(); if (parts == null) { return fireArgs; } for (int i = 1; i < parts.Length; i++) { string text = parts[i]; if (string.IsNullOrEmpty(text)) { continue; } FireAction? fireAction = ActionFor(text); if (fireAction.HasValue) { fireArgs.Action = fireAction.Value; continue; } if (DevNum.TryFinite(text, out var value)) { if (value < 0.5 || value > 20.0) { fireArgs.Error = $"distance must be {0.5f:0.#}-{20f:0.#} metres (got '{text}'). " + "usage: firecamp [on|off|remove|status] [distance 0.5-20]"; return fireArgs; } fireArgs.Distance = (float)value; continue; } fireArgs.Error = "unknown argument '" + text + "'. usage: firecamp [on|off|remove|status] [distance 0.5-20]"; return fireArgs; } return fireArgs; } public static FireAction? ActionFor(string token) { if (string.IsNullOrEmpty(token)) { return null; } switch (token.Trim().ToLowerInvariant()) { case "light": case "kindle": case "on": return FireAction.On; case "douse": case "off": case "out": case "extinguish": return FireAction.Off; case "clear": case "remove": case "delete": return FireAction.Remove; case "status": case "info": return FireAction.Status; default: return null; } } public static int StepAt(int[] perIncrement, float[] rangeMin, float[] rangeMax, float distance) { if (perIncrement == null || rangeMin == null || rangeMax == null) { return 0; } int num = Math.Min(perIncrement.Length, Math.Min(rangeMin.Length, rangeMax.Length)); for (int i = 0; i < num; i++) { if (distance >= rangeMin[i] && distance <= rangeMax[i]) { return perIncrement[i]; } } return 0; } } public enum GiveDest { Pouch, Bag, Ground } public sealed class GiveArgs { public GiveDest Dest; public int Qty = 1; public string Name = ""; public string Error; } public sealed class ItemQuery { public bool ById { get; private set; } public int Id { get; private set; } public string Needle { get; private set; } public static ItemQuery Parse(string key) { string text = key?.Trim(); if (string.IsNullOrEmpty(text)) { return null; } ItemQuery itemQuery = new ItemQuery { Needle = text }; if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { itemQuery.ById = true; itemQuery.Id = result; } return itemQuery; } public bool Matches(string itemName, int itemId) { if (ById) { return itemId == Id; } if (itemName != null) { return itemName.IndexOf(Needle, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } public static class DevGive { public const int MaxQty = 99; public const string Usage = "usage: give [pouch|bag|ground] [qty 1-99] "; public static GiveArgs Parse(string[] parts, bool forceGround) { GiveArgs giveArgs = new GiveArgs(); int num = 1; if (parts != null && num < parts.Length && TryParseDest(parts[num], out var dest)) { giveArgs.Dest = dest; num++; } if (parts != null && num < parts.Length && num + 1 < parts.Length && int.TryParse(parts[num], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { if (result < 1 || result > 99) { giveArgs.Error = string.Format("qty {0} is out of range (1-{1}). {2}", result, 99, "usage: give [pouch|bag|ground] [qty 1-99] "); return giveArgs; } giveArgs.Qty = result; num++; } giveArgs.Name = Tail(parts, num); if (giveArgs.Name.Length == 0) { giveArgs.Error = "usage: give [pouch|bag|ground] [qty 1-99] "; } if (forceGround) { giveArgs.Dest = GiveDest.Ground; } return giveArgs; } public static List SplitStacks(int qty, int maxStack) { List list = new List(); if (qty < 1) { return list; } if (maxStack < 1) { maxStack = 1; } for (int num = qty; num > 0; num -= maxStack) { list.Add(Math.Min(num, maxStack)); } return list; } public static int ReportedStackCount(int qty, int maxStack, bool stackable) { if (!stackable || qty < 1) { return 0; } if (maxStack < 1) { maxStack = 1; } return (qty + maxStack - 1) / maxStack; } public static string WaterTypeNameFor(string key) { switch ((key ?? "").Trim().ToLowerInvariant()) { case "clean": case "": return "Clean"; case "river": case "fresh": return "Fresh"; case "salt": return "Salt"; case "rancid": return "Rancid"; case "magic": case "leyline": return "Magic"; case "pure": case "sparkling": return "Pure"; case "healing": return "Healing"; default: return null; } } private static bool TryParseDest(string token, out GiveDest dest) { if (string.Equals(token, "pouch", StringComparison.OrdinalIgnoreCase)) { dest = GiveDest.Pouch; return true; } if (string.Equals(token, "bag", StringComparison.OrdinalIgnoreCase)) { dest = GiveDest.Bag; return true; } if (string.Equals(token, "ground", StringComparison.OrdinalIgnoreCase)) { dest = GiveDest.Ground; return true; } dest = GiveDest.Pouch; return false; } private static string Tail(string[] parts, int from) { if (parts == null || from >= parts.Length) { return ""; } return string.Join(" ", parts, from, parts.Length - from).Trim(); } } public static class DevGoto { public sealed class GotoArgs { public string SceneArg; public int SpawnPoint; public bool ArmWatchdog = true; public string Error; } public const string Usage = "goto [spawnPoint] [nowatch] (names: scenedump)"; public const string SpawnUsage = "goto [spawnPoint] — spawnPoint must be an integer."; public const string NoWatchToken = "nowatch"; public static GotoArgs Parse(string[] parts) { GotoArgs gotoArgs = new GotoArgs(); int num = ((parts != null) ? parts.Length : 0); if (num < 2) { gotoArgs.Error = "goto [spawnPoint] [nowatch] (names: scenedump)"; return gotoArgs; } gotoArgs.SceneArg = parts[1]; string text = null; for (int i = 2; i < num; i++) { string text2 = (parts[i] ?? "").Trim(); if (text2.Length == 0) { continue; } if (string.Equals(text2, "nowatch", StringComparison.OrdinalIgnoreCase)) { gotoArgs.ArmWatchdog = false; continue; } if (text != null) { gotoArgs.Error = "goto [spawnPoint] — spawnPoint must be an integer."; return gotoArgs; } text = text2; } if (text != null) { if (!DevNum.TryInt(text, out var value)) { gotoArgs.Error = "goto [spawnPoint] — spawnPoint must be an integer."; return gotoArgs; } gotoArgs.SpawnPoint = value; } return gotoArgs; } } public static class DevMove { public enum FaceMode { Nearest, Species, Yaw, Point } public sealed class FaceArgs { public FaceMode Mode; public string Species; public double Yaw; public double X; public double Z; public string Error; } public sealed class MoveToArgs { public bool Nearest; public string Species; public double Gap; public bool Clamped; public string Error; } public sealed class LockOnArgs { public bool Nearest; public string Species; public string Error; } public const string FaceUsage = "usage: face "; public const string MoveToUsage = "usage: moveto [gap-meters]"; public const double GapDefault = 2.0; public const double GapMin = 0.5; public const double GapMax = 20.0; public const string LockOnUsage = "usage: lockon [nearest|species]"; public static FaceArgs ParseFace(string[] parts) { FaceArgs faceArgs = new FaceArgs(); int num = ((parts != null) ? parts.Length : 0); if (num < 2) { faceArgs.Error = "usage: face "; return faceArgs; } if (num == 3 && TryNum(parts[1], out var value) && TryNum(parts[2], out var value2)) { faceArgs.Mode = FaceMode.Point; faceArgs.X = value; faceArgs.Z = value2; return faceArgs; } if (num == 2) { if (TryNum(parts[1], out var value3)) { faceArgs.Mode = FaceMode.Yaw; faceArgs.Yaw = value3; return faceArgs; } if (string.Equals(parts[1].Trim(), "nearest", StringComparison.OrdinalIgnoreCase)) { faceArgs.Mode = FaceMode.Nearest; return faceArgs; } } string text = string.Join(" ", parts, 1, num - 1).Trim(); if (text.Length == 0) { faceArgs.Error = "usage: face "; return faceArgs; } faceArgs.Mode = FaceMode.Species; faceArgs.Species = text; return faceArgs; } public static MoveToArgs ParseMoveTo(string[] parts) { MoveToArgs moveToArgs = new MoveToArgs { Gap = 2.0 }; int num = ((parts != null) ? parts.Length : 0); if (num < 2) { moveToArgs.Error = "usage: moveto [gap-meters]"; return moveToArgs; } int num2 = num; if (TryNum(parts[num2 - 1], out var value)) { double num3 = ((value < 0.5) ? 0.5 : ((value > 20.0) ? 20.0 : value)); moveToArgs.Clamped = num3 != value; moveToArgs.Gap = num3; num2--; } if (num2 < 2) { moveToArgs.Error = "usage: moveto [gap-meters]"; return moveToArgs; } string text = string.Join(" ", parts, 1, num2 - 1).Trim(); if (text.Length == 0) { moveToArgs.Error = "usage: moveto [gap-meters]"; return moveToArgs; } if (string.Equals(text, "nearest", StringComparison.OrdinalIgnoreCase)) { moveToArgs.Nearest = true; return moveToArgs; } moveToArgs.Species = text; return moveToArgs; } public static LockOnArgs ParseLockOn(string[] parts) { LockOnArgs lockOnArgs = new LockOnArgs(); int num = ((parts != null) ? parts.Length : 0); if (num < 2) { lockOnArgs.Nearest = true; return lockOnArgs; } string text = string.Join(" ", parts, 1, num - 1).Trim(); if (text.Length == 0 || string.Equals(text, "nearest", StringComparison.OrdinalIgnoreCase)) { lockOnArgs.Nearest = true; return lockOnArgs; } lockOnArgs.Species = text; return lockOnArgs; } private static bool TryNum(string token, out double value) { return DevNum.TryFinite(token, out value); } } public static class DevNum { public static bool TryFinite(string token, out double value) { if (double.TryParse((token ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out value) && !double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } public static bool TryInt(string token, out int value) { return int.TryParse((token ?? "").Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out value); } } public sealed class RemoveArgs { public bool All; public int Qty = 1; public string Name = ""; public string Error; } public static class DevRemove { public const int MaxQty = 999; public const string Usage = "usage: removeitem [qty 1-999|all] "; public static RemoveArgs Parse(string[] parts) { RemoveArgs removeArgs = new RemoveArgs(); int num = 1; if (parts != null && num < parts.Length && num + 1 < parts.Length) { int result; if (string.Equals(parts[num], "all", StringComparison.OrdinalIgnoreCase)) { removeArgs.All = true; num++; } else if (int.TryParse(parts[num], NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) { if (result < 1 || result > 999) { removeArgs.Error = string.Format("qty {0} is out of range (1-{1}). {2}", result, 999, "usage: removeitem [qty 1-999|all] "); return removeArgs; } removeArgs.Qty = result; num++; } } removeArgs.Name = Tail(parts, num); if (removeArgs.Name.Length == 0) { removeArgs.Error = "usage: removeitem [qty 1-999|all] "; } return removeArgs; } public static List<(int StackIndex, int Take)> PlanTakes(int wanted, IList stackAmounts) { List<(int, int)> list = new List<(int, int)>(); if (wanted < 1 || stackAmounts == null) { return list; } int num = wanted; for (int i = 0; i < stackAmounts.Count; i++) { if (num <= 0) { break; } int num2 = stackAmounts[i]; if (num2 >= 1) { int num3 = Math.Min(num, num2); list.Add((i, num3)); num -= num3; } } return list; } private static string Tail(string[] parts, int from) { if (parts == null || from >= parts.Length) { return ""; } return string.Join(" ", parts, from, parts.Length - from).Trim(); } } public static class DevScript { public enum StepKind { Run, Wait, WaitFrames, WaitLoaded } public sealed class ScriptStep { public StepKind Kind; public string Command; public double Seconds; public int Frames; } public sealed class ScriptProgram { public List Steps = new List(); public string Error; public double DeclaredWaitSeconds; } public const string Usage = "usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; public const int MaxSteps = 32; public const double MaxWaitSeconds = 120.0; public const double MaxTotalWaitSeconds = 300.0; public const int MaxWaitFrames = 600; public const double WaitLoadedDefaultTimeout = 60.0; public static ScriptProgram Parse(string tail) { ScriptProgram scriptProgram = new ScriptProgram(); if (string.IsNullOrEmpty(tail) || tail.Trim().Length == 0) { scriptProgram.Error = "no steps — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return scriptProgram; } string[] array = tail.Split(new char[1] { ';' }); string[] array2 = array; foreach (string text in array2) { string text2 = (text ?? "").Trim(); if (text2.Length == 0 || text2[0] == '#') { continue; } string err; ScriptStep scriptStep = Classify(text2, out err); if (err != null) { scriptProgram.Error = err; return scriptProgram; } if (scriptProgram.Steps.Count >= 32) { scriptProgram.Error = "too many steps (max " + 32 + ") — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return scriptProgram; } scriptProgram.Steps.Add(scriptStep); if (scriptStep.Kind == StepKind.Wait) { scriptProgram.DeclaredWaitSeconds += scriptStep.Seconds; if (scriptProgram.DeclaredWaitSeconds > 300.0) { scriptProgram.Error = "total declared wait exceeds " + Fmt(300.0) + "s (max total wait) — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return scriptProgram; } } } if (scriptProgram.Steps.Count == 0) { scriptProgram.Error = "no steps — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return scriptProgram; } return scriptProgram; } private static ScriptStep Classify(string piece, out string err) { err = null; string[] array = piece.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); string a = ((array.Length != 0) ? array[0] : ""); if (Is(a, "script")) { err = "nested 'script' is not allowed"; return null; } if (Is(a, "wait")) { if (array.Length != 2) { err = "wait needs exactly one number of seconds — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (!TryNum(array[1], out var value)) { err = "wait: '" + array[1] + "' is not a number — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (value <= 0.0) { err = "wait must be greater than 0 seconds — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (value > 120.0) { err = "wait exceeds " + Fmt(120.0) + "s (max single wait) — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } return new ScriptStep { Kind = StepKind.Wait, Seconds = value }; } if (Is(a, "waitframes")) { if (array.Length != 2) { err = "waitframes needs exactly one frame count — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (!DevNum.TryInt(array[1], out var value2)) { err = "waitframes: '" + array[1] + "' is not a whole number — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (value2 < 1) { err = "waitframes must be at least 1 frame — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (value2 > 600) { err = "waitframes exceeds " + 600 + " (max wait frames) — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } return new ScriptStep { Kind = StepKind.WaitFrames, Frames = value2 }; } if (Is(a, "waitloaded")) { double value3 = 60.0; if (array.Length > 2) { err = "waitloaded takes at most one timeout in seconds — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (array.Length == 2) { if (!TryNum(array[1], out value3)) { err = "waitloaded: '" + array[1] + "' is not a number — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } if (value3 <= 0.0) { err = "waitloaded timeout must be greater than 0 seconds — usage: script ; ... (timing: wait | waitframes | waitloaded [s])"; return null; } } return new ScriptStep { Kind = StepKind.WaitLoaded, Seconds = value3 }; } return new ScriptStep { Kind = StepKind.Run, Command = piece }; } private static bool Is(string a, string b) { return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } private static bool TryNum(string token, out double value) { return DevNum.TryFinite(token, out value); } private static string Fmt(double v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } } public sealed class ShotArgs { public int Supersize = 1; public string Label = ""; public string Error; } public static class DevShot { public const int MaxSupersize = 4; public const string Usage = "usage: screenshot [supersize 1-4] [label]"; public static ShotArgs Parse(string[] parts) { ShotArgs shotArgs = new ShotArgs(); int num = 1; if (parts != null && num < parts.Length && int.TryParse(parts[num], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { if (result < 1 || result > 4) { shotArgs.Error = string.Format("supersize {0} is out of range (1-{1}). {2}", result, 4, "usage: screenshot [supersize 1-4] [label]"); return shotArgs; } shotArgs.Supersize = result; num++; } shotArgs.Label = SanitizeLabel(Tail(parts, num)); return shotArgs; } public static string SanitizeLabel(string raw) { if (string.IsNullOrEmpty(raw)) { return ""; } StringBuilder stringBuilder = new StringBuilder(raw.Length); bool flag = true; string text = raw.Trim().ToLowerInvariant(); foreach (char c in text) { if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { stringBuilder.Append(c); flag = false; } else if ((c == '-' || c == '_' || c == ' ') && !flag) { stringBuilder.Append('-'); flag = true; } } string text2 = stringBuilder.ToString().TrimEnd(new char[1] { '-' }); if (text2.Length <= 40) { return text2; } return text2.Substring(0, 40).TrimEnd(new char[1] { '-' }); } public static string FileName(DateTime now, string label) { string text = SanitizeLabel(label); return "shot_" + now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture) + ((text.Length > 0) ? ("_" + text) : "") + ".png"; } private static string Tail(string[] parts, int from) { if (parts == null || from >= parts.Length) { return ""; } return string.Join(" ", parts, from, parts.Length - from).Trim(); } } public static class DevSkill { public sealed class SkillArgs { public string Name = ""; public bool All; public string Error; } public const string AllToken = "all"; public const string LearnUsage = "usage: learnskill "; public const string UnlearnUsage = "usage: unlearnskill "; public const string CastUsage = "usage: castspell "; public static SkillArgs ParseLearn(string[] parts) { return Parse(parts, allowAll: false, "usage: learnskill "); } public static SkillArgs ParseUnlearn(string[] parts) { return Parse(parts, allowAll: true, "usage: unlearnskill "); } public static SkillArgs ParseCast(string[] parts) { return Parse(parts, allowAll: false, "usage: castspell "); } private static SkillArgs Parse(string[] parts, bool allowAll, string usage) { SkillArgs skillArgs = new SkillArgs(); string text = Tail(parts, 1); if (text.Length == 0) { skillArgs.Error = usage; return skillArgs; } if (allowAll && string.Equals(text, "all", StringComparison.OrdinalIgnoreCase)) { skillArgs.All = true; return skillArgs; } skillArgs.Name = text; return skillArgs; } private static string Tail(string[] parts, int from) { if (parts == null || from >= parts.Length) { return ""; } return string.Join(" ", parts, from, parts.Length - from).Trim(); } } public static class DevStandoff { public sealed class StandoffArgs { public double Metres; public bool Clamped; public bool HasBearing; public double Bearing; public string Target; public string Error; } public sealed class WalkToArgs { public double X; public double Z; public string Error; } public struct Candidate { public double BearingDeg; public double Metres; } public const string StandoffUsage = "usage: standoff [bearing=] [target=pet|nearest|]"; public const double MetresMin = 1.0; public const double MetresMax = 200.0; public const string TargetPet = "pet"; public const string TargetNearest = "nearest"; public const string WalkToUsage = "usage: walkto (ground-safe; no y — the ground decides it)"; public static readonly double[] WalkToColumnOffsets = new double[8] { 0.0, -8.0, -25.0, -60.0, -150.0, -400.0, 8.0, 25.0 }; public const double WalkToMaxRiseMeters = 3.0; public static readonly double[] BearingFan = new double[12] { 0.0, -20.0, 20.0, -40.0, 40.0, -60.0, 60.0, -90.0, 90.0, -135.0, 135.0, 180.0 }; public static readonly double[] RadiusFactors = new double[6] { 1.0, 0.95, 1.05, 0.9, 1.1, 0.8 }; public const double RoofSanityMeters = 12.0; public static int MaxColumnProbes => WalkToColumnOffsets.Length; public static int MaxCandidates => BearingFan.Length * RadiusFactors.Length; public static StandoffArgs ParseStandoff(string[] parts) { StandoffArgs standoffArgs = new StandoffArgs { Target = "pet" }; int num = ((parts != null) ? parts.Length : 0); if (num < 2) { standoffArgs.Error = "usage: standoff [bearing=] [target=pet|nearest|]"; return standoffArgs; } if (!DevNum.TryFinite(parts[1], out var value)) { standoffArgs.Error = "usage: standoff [bearing=] [target=pet|nearest|]"; return standoffArgs; } double num2 = ((value < 1.0) ? 1.0 : ((value > 200.0) ? 200.0 : value)); standoffArgs.Clamped = num2 != value; standoffArgs.Metres = num2; for (int i = 2; i < num; i++) { string text = parts[i]; if (string.IsNullOrEmpty(text)) { continue; } if (StartsWith(text, "bearing=")) { string token = text.Substring("bearing=".Length); if (!DevNum.TryFinite(token, out var value2)) { standoffArgs.Error = "usage: standoff [bearing=] [target=pet|nearest|]"; return standoffArgs; } standoffArgs.HasBearing = true; standoffArgs.Bearing = Normalize(value2); continue; } if (StartsWith(text, "target=")) { string text2 = string.Join(" ", parts, i, num - i).Substring("target=".Length).Trim(); if (text2.Length == 0) { standoffArgs.Error = "usage: standoff [bearing=] [target=pet|nearest|]"; return standoffArgs; } standoffArgs.Target = text2; break; } standoffArgs.Error = "usage: standoff [bearing=] [target=pet|nearest|]"; return standoffArgs; } return standoffArgs; } public static bool IsPetTarget(string target) { return string.Equals(target, "pet", StringComparison.OrdinalIgnoreCase); } public static bool IsNearestTarget(string target) { return string.Equals(target, "nearest", StringComparison.OrdinalIgnoreCase); } public static WalkToArgs ParseWalkTo(string[] parts) { WalkToArgs walkToArgs = new WalkToArgs(); int num = ((parts != null) ? parts.Length : 0); if (num < 3 || !DevNum.TryFinite(parts[1], out var value) || !DevNum.TryFinite(parts[2], out var value2)) { walkToArgs.Error = "usage: walkto (ground-safe; no y — the ground decides it)"; return walkToArgs; } walkToArgs.X = value; walkToArgs.Z = value2; return walkToArgs; } public static int PlanColumn(double currentY, double[] buf) { if (buf == null) { return 0; } int num = 0; for (int i = 0; i < WalkToColumnOffsets.Length; i++) { if (num >= buf.Length) { break; } buf[num++] = currentY + WalkToColumnOffsets[i]; } return num; } public static bool RiseOk(double landingY, double currentY) { return landingY - currentY <= 3.0; } public static int Plan(double wantMetres, double baseBearingDeg, Candidate[] buf) { if (buf == null) { return 0; } int num = 0; for (int i = 0; i < RadiusFactors.Length; i++) { for (int j = 0; j < BearingFan.Length; j++) { if (num >= buf.Length) { return num; } buf[num].BearingDeg = Normalize(baseBearingDeg + BearingFan[j]); buf[num].Metres = wantMetres * RadiusFactors[i]; num++; } } return num; } public static double Tolerance(double wantMetres) { double num = 0.12 * ((wantMetres < 0.0) ? (0.0 - wantMetres) : wantMetres); if (!(num < 2.0)) { return num; } return 2.0; } public static bool DistanceOk(double achievedMetres, double wantMetres) { double num = achievedMetres - wantMetres; if (num < 0.0) { num = 0.0 - num; } return num <= Tolerance(wantMetres); } public static bool HeightOk(double landingY, double referenceY) { double num = landingY - referenceY; if (num < 0.0) { num = 0.0 - num; } return num <= 12.0; } public static double Normalize(double deg) { deg %= 360.0; if (!(deg < 0.0)) { return deg; } return deg + 360.0; } private static bool StartsWith(string token, string prefix) { if (token.Length >= prefix.Length) { return string.Equals(token.Substring(0, prefix.Length), prefix, StringComparison.OrdinalIgnoreCase); } return false; } } public static class DevState { public enum HpTarget { Pet, Player } public sealed class HpArgs { public HpTarget Target; public double Value; public bool IsPercent; public string Error; } public const string HpUsage = "usage: sethp "; public const string SlotUsage = "usage: unequip "; public static HpArgs ParseSetHp(string[] parts) { HpArgs hpArgs = new HpArgs(); if (parts == null || parts.Length < 3) { hpArgs.Error = "usage: sethp "; return hpArgs; } if (string.Equals(parts[1], "pet", StringComparison.OrdinalIgnoreCase)) { hpArgs.Target = HpTarget.Pet; } else { if (!string.Equals(parts[1], "player", StringComparison.OrdinalIgnoreCase)) { hpArgs.Error = "unknown target '" + parts[1] + "'. usage: sethp "; return hpArgs; } hpArgs.Target = HpTarget.Player; } string text = parts[2].Trim(); if (text.EndsWith("%", StringComparison.Ordinal)) { hpArgs.IsPercent = true; text = text.Substring(0, text.Length - 1); } if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 0.0 || (hpArgs.IsPercent && result > 100.0)) { hpArgs.Error = "bad value '" + parts[2] + "'. usage: sethp "; return hpArgs; } hpArgs.Value = result; return hpArgs; } public static int? SlotIndexFor(string name) { switch ((name ?? "").Trim().ToLowerInvariant()) { case "helmet": case "head": return 0; case "body": case "chest": case "armor": return 1; case "legs": case "pants": return 2; case "foot": case "feet": case "boots": return 3; case "gloves": case "hands": return 4; case "weapon": case "right": case "righthand": return 5; case "left": case "lefthand": case "offhand": return 6; case "back": case "backpack": case "bag": return 7; case "quiver": return 8; default: return null; } } } public static class DevStatus { public enum StatusTarget { Player, Pet } public sealed class StatusArgs { public string Name = ""; public StatusTarget Target; public bool Force; public string LiteralName = ""; public string Error; } public const string TargetKey = "target="; public const string ForceKey = "force"; public const string GrantUsage = "usage: grantstatus [target=player|pet] [force]"; public const string RemoveUsage = "usage: removestatus [target=player|pet]"; private static readonly char[] Space = new char[2] { ' ', '\t' }; public static StatusArgs ParseGrant(string[] parts) { return Parse(parts, "usage: grantstatus [target=player|pet] [force]", allowForce: true); } public static StatusArgs ParseRemove(string[] parts) { return Parse(parts, "usage: removestatus [target=player|pet]", allowForce: false); } private static StatusArgs Parse(string[] parts, string usage, bool allowForce) { StatusArgs statusArgs = new StatusArgs(); string text = Tail(parts, 1); if (text.Length == 0) { statusArgs.Error = usage; return statusArgs; } for (int i = 0; i < 2; i++) { int num = text.LastIndexOfAny(Space); string text2 = ((num < 0) ? text : text.Substring(num + 1)); string text3 = ((num < 0) ? "" : text.Substring(0, num).Trim()); if (text2.StartsWith("target=", StringComparison.OrdinalIgnoreCase)) { string text4 = text2.Substring("target=".Length); if (string.Equals(text4, "player", StringComparison.OrdinalIgnoreCase)) { statusArgs.Target = StatusTarget.Player; } else { if (!string.Equals(text4, "pet", StringComparison.OrdinalIgnoreCase)) { statusArgs.Error = "unknown target '" + text4 + "' — valid values are player, pet. " + usage; return statusArgs; } statusArgs.Target = StatusTarget.Pet; } text = text3; } else { if (!allowForce || statusArgs.Force || !string.Equals(text2, "force", StringComparison.OrdinalIgnoreCase)) { break; } statusArgs.Force = true; text = text3; } if (text.Length == 0) { statusArgs.Error = usage; return statusArgs; } } statusArgs.Name = text; statusArgs.LiteralName = (statusArgs.Force ? (text + " force") : text); return statusArgs; } private static string Tail(string[] parts, int from) { if (parts == null || from >= parts.Length) { return ""; } return string.Join(" ", parts, from, parts.Length - from).Trim(); } } public static class DevSwing { public struct GateFacts { public bool IsPhotonPlayerLocal; public bool InLocomotion; public bool NextIsLocomotion; public bool Blocking; public bool LocomotionAction; public bool Sheathing; public bool InChargeCancelCooldown; public bool CancelChargingSent; public int NextAttackAllowed; public bool AttackOnRelease; } public static bool GateOpen(in GateFacts f) { if (f.IsPhotonPlayerLocal && f.InLocomotion && f.NextIsLocomotion && !f.Blocking && !f.LocomotionAction && !f.Sheathing && !f.InChargeCancelCooldown) { return !f.CancelChargingSent; } return false; } public static bool Queued(in GateFacts f) { if (!GateOpen(in f)) { return f.NextAttackAllowed > 0; } return false; } public static List FailingConditions(in GateFacts f) { List list = new List(); if (!f.IsPhotonPlayerLocal) { list.Add("IsPhotonPlayerLocal=False — AttackInput is a no-op for a remote/AI character (not the local Photon player)"); } if (!f.InLocomotion) { list.Add("InLocomotion=False"); } if (!f.NextIsLocomotion) { list.Add("NextIsLocomotion=False"); } if (f.Blocking) { list.Add("Blocking=True"); } if (f.LocomotionAction) { list.Add("LocomotionAction=True"); } if (f.Sheathing) { list.Add("Sheathing=True"); } if (f.InChargeCancelCooldown) { list.Add("InChargeCancelCooldown=True"); } if (f.CancelChargingSent) { list.Add("CancelChargingSent=True"); } return list; } public static string Explain(in GateFacts f) { if (Queued(in f)) { return "not a refusal: the press was QUEUED as the next combo/charge step " + $"(NextAtkAllowed={f.NextAttackAllowed}" + (f.AttackOnRelease ? ", charge weapon — the shot fires on release" : "") + "). Vanilla returns false for a queued input too. Watch for the hit, not the bool."; } List list = FailingConditions(in f); if (list.Count == 0) { return "the gate conditions all read OPEN, so the refusal came from the charge-timing half (a charge weapon released less than 0.5s ago — the press was banked as m_chargeWanted). Re-issue after ~1s."; } string text = string.Join(", ", list.ToArray()); string text2 = ((!f.InLocomotion || !f.NextIsLocomotion) ? " 'Locomotion' is the animator's neutral stand/walk/run TAG, NOT movement — a character mid-animation (item use, stagger, knockback, the tail of a dodge, sitting/sleeping) is out of it while standing still, so this is usually TRANSIENT: swing waits for it and retries." : (f.Blocking ? " Release the block first." : (f.Sheathing ? " A draw/sheathe animation is still playing — swing waits for it." : ""))); return "refused by vanilla's AttackInput gate (Character.cs:5749): " + text + "." + text2; } } public static class DevUnstick { public enum UnstickMode { Dump, Fix, Force } public enum UnstickStep { Auto, Prologue, Gate, PauseMenu, TimeScale, ForceUnpause, ForceReady } public sealed class UnstickArgs { public UnstickMode Mode; public UnstickStep Step; public string Error; } public struct AutoState { public bool Paused; public bool ProloguePanelUp; public bool GateGuardHolds; public bool PauseMenuClaimed; public bool TimeScaleZero; } public const string Usage = "usage: unstick [fix|force] [prologue|gate|pausemenu|timescale|forceunpause|forceready] (bare 'unstick' dumps state and changes nothing)"; public const string Held = "HELD"; public const string Reverted = "REVERTED"; public const string NoChange = "NO-CHANGE"; public static UnstickArgs Parse(string[] parts) { UnstickArgs unstickArgs = new UnstickArgs { Mode = UnstickMode.Dump, Step = UnstickStep.Auto }; int num = ((parts != null) ? parts.Length : 0); if (num < 2) { return unstickArgs; } string text = (parts[1] ?? "").Trim(); bool flag = string.Equals(text, "fix", StringComparison.OrdinalIgnoreCase); bool flag2 = string.Equals(text, "force", StringComparison.OrdinalIgnoreCase); if (flag || flag2) { unstickArgs.Mode = ((!flag2) ? UnstickMode.Fix : UnstickMode.Force); if (num == 2) { return unstickArgs; } if (num > 3) { unstickArgs.Error = "unknown extra argument '" + parts[3] + "'. usage: unstick [fix|force] [prologue|gate|pausemenu|timescale|forceunpause|forceready] (bare 'unstick' dumps state and changes nothing)"; return unstickArgs; } if (!TryStep(parts[2], out var step)) { unstickArgs.Error = "unknown step '" + parts[2] + "'. usage: unstick [fix|force] [prologue|gate|pausemenu|timescale|forceunpause|forceready] (bare 'unstick' dumps state and changes nothing)"; return unstickArgs; } unstickArgs.Step = step; return unstickArgs; } if (num > 2) { unstickArgs.Error = "unknown extra argument '" + parts[2] + "'. usage: unstick [fix|force] [prologue|gate|pausemenu|timescale|forceunpause|forceready] (bare 'unstick' dumps state and changes nothing)"; return unstickArgs; } if (!TryStep(text, out var step2)) { unstickArgs.Error = "unknown argument '" + text + "'. usage: unstick [fix|force] [prologue|gate|pausemenu|timescale|forceunpause|forceready] (bare 'unstick' dumps state and changes nothing)"; return unstickArgs; } unstickArgs.Mode = UnstickMode.Fix; unstickArgs.Step = step2; return unstickArgs; } private static bool TryStep(string token, out UnstickStep step) { switch ((token ?? "").Trim().ToLowerInvariant()) { case "auto": step = UnstickStep.Auto; return true; case "prologue": step = UnstickStep.Prologue; return true; case "gate": step = UnstickStep.Gate; return true; case "pausemenu": step = UnstickStep.PauseMenu; return true; case "timescale": step = UnstickStep.TimeScale; return true; case "forceunpause": step = UnstickStep.ForceUnpause; return true; case "forceready": step = UnstickStep.ForceReady; return true; default: step = UnstickStep.Auto; return false; } } public static string Name(UnstickStep step) { return step switch { UnstickStep.Prologue => "prologue", UnstickStep.Gate => "gate", UnstickStep.PauseMenu => "pausemenu", UnstickStep.TimeScale => "timescale", UnstickStep.ForceUnpause => "forceunpause", UnstickStep.ForceReady => "forceready", _ => "auto", }; } public static UnstickStep ChooseAuto(AutoState s) { if (s.ProloguePanelUp) { return UnstickStep.Prologue; } if (s.GateGuardHolds) { return UnstickStep.Gate; } if (s.PauseMenuClaimed) { return UnstickStep.PauseMenu; } if (s.TimeScaleZero && !s.PauseMenuClaimed) { return UnstickStep.TimeScale; } if (s.Paused) { return UnstickStep.ForceUnpause; } return UnstickStep.Auto; } public static string Verdict(bool pausedBefore, bool pausedAfter) { if (!pausedAfter) { return "HELD"; } if (!pausedBefore) { return "NO-CHANGE"; } return "REVERTED"; } } internal static class DiagVerbs { internal static void InvDump(Character player, string[] parts, ManualLogSource log) { string text = ((parts != null && parts.Length > 1) ? string.Join(" ", parts, 1, parts.Length - 1).Trim() : null); int result = 0; string text2 = "name substring"; if (!string.IsNullOrEmpty(text)) { string via; if (int.TryParse(text, out result)) { text2 = "literal ItemID"; } else if (ItemNameIndex.TryResolveCatalog(text, out result, out via)) { text2 = "catalog name"; } else { result = 0; } } List list = new List(); int num = 0; int num2 = 0; long num3 = 0L; float num4 = 0f; float num5 = 0f; foreach (var (val, text3) in Inventories.AllByContainer(player)) { if ((Object)(object)val == (Object)null) { continue; } num++; num4 += val.Weight; if (string.IsNullOrEmpty(text) || (result != 0 && val.ItemID == result) || (val.Name != null && val.Name.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)) { num2++; num3 += val.RemainingAmount; num5 += val.Weight; if (list.Count < 200) { list.Add($"[INVDUMP] {text3,-5} '{val.Name}' (id {val.ItemID}, uid {val.UID}) " + $"x{val.RemainingAmount}, {val.Weight:0.##} wt"); } } } if ((Object)(object)player == (Object)null || (Object)(object)player.Inventory == (Object)null) { log.LogWarning((object)"[INVDUMP] no player inventory (not in a world yet?)."); return; } string arg = (string.IsNullOrEmpty(text) ? "" : (" matching '" + text + "'" + ((result != 0) ? $" (id {result} via {text2})" : (" (" + text2 + ")")))); log.LogMessage((object)($"[INVDUMP] pouch+bag{arg}: {num2} stack(s), TOTAL QTY {num3}, " + $"{num5:0.##} wt — of {num} stack(s) / {num4:0.##} wt carried. " + "(Equipped gear is NOT listed — it lives in equipment slots, not the pouch/bag.)")); foreach (string item in list) { log.LogMessage((object)item); } if (num2 > list.Count) { log.LogMessage((object)($"[INVDUMP] … {num2 - list.Count} more stack(s) not printed " + "(200-line cap; the totals above still count every one).")); } if (num2 == 0 && !string.IsNullOrEmpty(text)) { log.LogMessage((object)("[INVDUMP] nothing matches '" + text + "'. TOTAL QTY 0 — this is a real zero, not a lookup failure (the sweep ran and found the other " + $"{num} stack(s)).")); } } internal static void StatusDump(ManualLogSource log) { List list = new List(ResourcesPrefabManager.STATUSEFFECT_PREFABS.Keys); list.Sort(StringComparer.OrdinalIgnoreCase); log.LogMessage((object)$"[STATUSDUMP] {list.Count} loadable StatusEffect prefab name(s):"); foreach (string item in list) { log.LogMessage((object)("[STATUSDUMP] " + item)); } } internal static void CombatMgrDump(ManualLogSource log) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) GlobalCombatManager combatManager = Global.CombatManager; if ((Object)(object)combatManager == (Object)null) { log.LogMessage((object)"[COMBAT-FIX] Global.CombatManager is null/destroyed."); return; } object[] obj = new object[4] { ((Object)combatManager).GetInstanceID(), null, null, null }; Scene scene = ((Component)combatManager).gameObject.scene; obj[1] = ((Scene)(ref scene)).name; obj[2] = combatManager.PlayersInCombat.Count; obj[3] = combatManager.LocalPlayersInCombat.Count; log.LogMessage((object)string.Format("[COMBAT-FIX] Global.CombatManager id={0} scene='{1}' playersInCombat={2} localPlayersInCombat={3}", obj)); } internal static void Screenshot(string[] parts, ManualLogSource log) { ShotArgs shotArgs = DevShot.Parse(parts); if (shotArgs.Error != null) { log.LogWarning((object)("[SHOT] parse: " + shotArgs.Error)); return; } string text = Path.Combine(Paths.BepInExRootPath, "screenshots"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, DevShot.FileName(DateTime.Now, shotArgs.Label)); ScreenCapture.CaptureScreenshot(text2, shotArgs.Supersize); log.LogMessage((object)$"[SHOT] capturing {text2} supersize={shotArgs.Supersize}"); Runner.Instance.StartCoroutine(WaitForShot(text2, log)); } private static IEnumerator WaitForShot(string path, ManualLogSource log) { float start = Time.unscaledTime; long lastLen = -1L; while (Time.unscaledTime - start < 10f) { yield return null; long num; try { FileInfo fileInfo = new FileInfo(path); num = (fileInfo.Exists ? fileInfo.Length : (-1)); } catch (Exception ex) { log.LogWarning((object)("[SHOT] poll failed for " + path + ": " + ex.Message)); yield break; } if (num > 0 && num == lastLen) { log.LogMessage((object)$"[SHOT] saved {path} {num} bytes"); yield break; } lastLen = num; } log.LogWarning((object)($"[SHOT] TIMEOUT after {10f:0}s — no stable file at {path} " + "(the capture may still land later; check the directory).")); } internal static void GroundProbe(Character player, ManualLogSource log) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)player).transform.position; log.LogMessage((object)("[PROBE] player pos=" + ((Vector3)(ref position)).ToString("F2"))); float[] array = new float[6] { 0.5f, 1f, 1.5f, 2.5f, 5f, 10f }; NavMeshHit val = default(NavMeshHit); foreach (float num in array) { string text; if (!NavMesh.SamplePosition(position, ref val, num, -1)) { text = $"[PROBE] r={num:F1}: nothing"; } else { object[] obj = new object[4] { num, ((NavMeshHit)(ref val)).position.y - position.y, Vector3.Distance(position, ((NavMeshHit)(ref val)).position), null }; Vector3 position2 = ((NavMeshHit)(ref val)).position; obj[3] = ((Vector3)(ref position2)).ToString("F2"); text = string.Format("[PROBE] r={0:F1}: hit dy={1:+0.00;-0.00} dist={2:F2} at {3}", obj); } log.LogMessage((object)text); } } } internal static class FireVerbs { private const int CampfireItemId = 5000100; private const int CampfireKitItemId = 5000101; private static readonly List s_placed = new List(); internal static void FirecampVerb(Character player, string[] parts, ManualLogSource log) { FireArgs fireArgs = DevFire.Parse(parts); if (fireArgs.Error != null) { log.LogWarning((object)("[FIRECAMP] " + fireArgs.Error)); return; } if ((Object)(object)player == (Object)null) { log.LogWarning((object)"[FIRECAMP] no local player."); return; } switch (fireArgs.Action) { case FireAction.On: Light(player, fireArgs.Distance, log); break; case FireAction.Off: Douse(player, log, destroy: false); break; case FireAction.Remove: Douse(player, log, destroy: true); break; default: Status(player, log); break; } } private static void Light(Character player, float distance, ManualLogSource log) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) Item val = Newest(); if ((Object)(object)val != (Object)null) { if (Kindle(val, log)) { log.LogMessage((object)("[FIRECAMP] re-lit the campfire already placed at " + Where(val) + " " + $"({Vector3.Distance(((Component)val).transform.position, ((Component)player).transform.position):0.0}m from you). " + "Use 'firecamp remove' first if you wanted a fresh one.")); Report(val, player, log); } return; } Item val2 = ((ResourcesPrefabManager.Instance != null) ? ResourcesPrefabManager.Instance.GetItemPrefab(5000100) : null); if ((Object)(object)val2 == (Object)null) { log.LogWarning((object)($"[FIRECAMP] Campfire (ItemID {5000100}) is not a known item prefab — " + "wrong game build or the prefab manager is not ready yet.")); return; } string how; Vector3 val3 = Ground(player, distance, out how); Item val4 = (((Object)(object)ItemManager.Instance != (Object)null) ? ItemManager.Instance.GenerateItemNetwork(5000100) : null); if ((Object)(object)val4 == (Object)null) { log.LogWarning((object)$"[FIRECAMP] GenerateItemNetwork({5000100}) returned null."); return; } val4.ChangeParent((Transform)null, val3, Quaternion.LookRotation(Flat(((Component)player).transform.forward))); val4.SetForceSyncPos(); Deployable component = ((Component)val4).GetComponent(); if ((Object)(object)component != (Object)null) { component.StartDeployAnimation(); } s_placed.Add(val4); log.LogMessage((object)($"[FIRECAMP] placed a Campfire (ItemID {5000100}) at {Where(val4)} — " + $"{distance:0.0}m ahead, ground via {how}.")); if (Kindle(val4, log)) { Report(val4, player, log); } } private static bool Kindle(Item fire, ManualLogSource log) { FueledContainer val = (((Object)(object)fire != (Object)null) ? ((Component)fire).GetComponent() : null); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[FIRECAMP] the spawned campfire has no FueledContainer — cannot light it. (No TemperatureSource will be enabled, so this fire gives NO warmth.)"); return false; } val.TryKindle = true; val.Kindle(); log.LogMessage((object)($"[FIRECAMP] kindled — lit={val.IsLit}, fuelTime={val.FuelTime:0.##} game-hours. " + "(Refuels itself from any Fuel item put in the campfire's own container.)")); return true; } private static void Douse(Character player, ManualLogSource log, bool destroy) { Item val = Newest(); if ((Object)(object)val == (Object)null) { val = NearestFire(player, 30f); } if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[FIRECAMP] no campfire placed by this verb and none within 30m — nothing to put out."); return; } FueledContainer component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { component.TurnOff(); log.LogMessage((object)($"[FIRECAMP] put out the campfire at {Where(val)} — lit={component.IsLit}. " + "Ambient at the pet should fall back over the next samples (TempRecoverSeconds).")); } if (destroy) { s_placed.Remove(val); val.SetDestroyWanted(); if ((Object)(object)ItemManager.Instance != (Object)null) { ItemManager.Instance.DestroyItem(val.UID); } log.LogMessage((object)"[FIRECAMP] campfire destroyed — staging cleaned up."); } } private static void Status(Character player, ManualLogSource log) { Item val = Newest(); if ((Object)(object)val == (Object)null) { val = NearestFire(player, 30f); } if ((Object)(object)val == (Object)null) { log.LogMessage((object)"[FIRECAMP] no campfire placed by this verb and none within 30m."); } else { Report(val, player, log); } } private static void Report(Item fire, Character player, ManualLogSource log) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) FueledContainer component = ((Component)fire).GetComponent(); TemperatureSource componentInChildren = ((Component)fire).GetComponentInChildren(true); float num = (((Object)(object)player != (Object)null) ? Vector3.Distance(((Component)fire).transform.position, ((Component)player).transform.position) : (-1f)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[FIRECAMP] campfire at " + Where(fire) + " — lit=" + (((Object)(object)component != (Object)null) ? component.IsLit.ToString() : "no FueledContainer")); if ((Object)(object)component != (Object)null) { stringBuilder.Append($", fuelTime={component.FuelTime:0.##}h"); } stringBuilder.Append($", {num:0.0}m from you."); log.LogMessage((object)stringBuilder.ToString()); if ((Object)(object)componentInChildren == (Object)null) { log.LogWarning((object)"[FIRECAMP] no TemperatureSource on this campfire — it will never change ambient."); return; } int[] array = ((componentInChildren.TemperaturePerIncrement != null) ? componentInChildren.TemperaturePerIncrement.ToArray() : new int[0]); List list = new List(); List list2 = new List(); if (componentInChildren.DistanceRanges != null) { foreach (Vector2 distanceRange in componentInChildren.DistanceRanges) { list.Add(distanceRange.x); list2.Add(distanceRange.y); } } StringBuilder stringBuilder2 = new StringBuilder(); for (int i = 0; i < array.Length && i < list.Count; i++) { if (i > 0) { stringBuilder2.Append(", "); } stringBuilder2.Append($"({list[i]:0.#}-{list2[i]:0.#}m -> {array[i]:+#;-#;0})"); } log.LogMessage((object)($"[FIRECAMP] TemperatureSource enabled={((Behaviour)componentInChildren).isActiveAndEnabled} " + "bands: " + ((stringBuilder2.Length > 0) ? stringBuilder2.ToString() : "(none)") + "; " + $"predicted step at your {num:0.0}m = {DevFire.StepAt(array, list.ToArray(), list2.ToArray(), num)}. " + $"BiggestRangeInScene={TemperatureSource.BiggestRangeInScene:0.#}m. " + "Confirm with 'tempdump' — that samples EnvironmentConditions at the PET, which is the real path.")); } private static Item Newest() { for (int num = s_placed.Count - 1; num >= 0; num--) { if ((Object)(object)s_placed[num] != (Object)null) { return s_placed[num]; } s_placed.RemoveAt(num); } return null; } private static Item NearestFire(Character player, float radius) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return null; } Item result = null; float num = float.MaxValue; FueledContainer[] array = Object.FindObjectsOfType(); foreach (FueledContainer val in array) { if ((Object)(object)val == (Object)null) { continue; } Item component = ((Component)val).GetComponent(); if (!((Object)(object)component == (Object)null) && component.ItemID == 5000100) { float num2 = Vector3.Distance(((Component)component).transform.position, ((Component)player).transform.position); if (num2 < num && num2 <= radius) { result = component; num = num2; } } } return result; } private static Vector3 Ground(Character player, float distance, out string how) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) //IL_0026: 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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)player).transform.position + Flat(((Component)player).transform.forward) * distance; RaycastHit val2 = default(RaycastHit); if (Physics.Raycast(val + Vector3.up * 2f, Vector3.down, ref val2, 12f, -1, (QueryTriggerInteraction)1)) { how = "collider raycast"; return ((RaycastHit)(ref val2)).point; } how = "NO ground found — placed at the player's own height"; return val; } private static Vector3 Flat(Vector3 v) { //IL_0022: 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) v.y = 0f; if (!(((Vector3)(ref v)).sqrMagnitude > 0.0001f)) { return Vector3.forward; } return ((Vector3)(ref v)).normalized; } private static string Where(Item item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) Vector3 position = ((Component)item).transform.position; return $"({position.x:F1}, {position.y:F1}, {position.z:F1})"; } } internal static class GiveVerbs { internal static string Tail(string[] parts) { if (parts == null || parts.Length <= 1) { return null; } return string.Join(" ", parts, 1, parts.Length - 1); } internal static void GiveItem(Character player, string[] parts, bool forceGround, ManualLogSource log) { //IL_0088: 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_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: 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_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) GiveArgs giveArgs = DevGive.Parse(parts, forceGround); if (giveArgs.Error != null) { log.LogWarning((object)("[GIVE] parse: " + giveArgs.Error)); return; } if (!ItemNameIndex.TryResolveArg(giveArgs.Name, null, null, out var itemId, out var via, out var problem)) { log.LogWarning((object)("[GIVE] resolve: " + problem)); return; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(itemId) : null); if ((Object)(object)val == (Object)null) { log.LogWarning((object)$"[GIVE] prefab: ItemID {itemId} is not a known item prefab."); return; } Transform val2 = null; Vector3 val3 = default(Vector3); string text = "pouch"; switch (giveArgs.Dest) { case GiveDest.Bag: { Bag equippedBag = player.Inventory.EquippedBag; if ((Object)(object)equippedBag == (Object)null || (Object)(object)equippedBag.Container == (Object)null) { log.LogWarning((object)"[GIVE] bag: no equipped bag — equip a backpack or target pouch/ground."); return; } val2 = ((Component)equippedBag.Container).transform; text = "bag"; break; } case GiveDest.Ground: { val3 = player.CenterPosition + ((Component)player).transform.forward; RaycastHit val5 = default(RaycastHit); if (Physics.Raycast(new Ray(player.CenterPosition, ((Component)player).transform.forward), ref val5, 1f, Global.FullEnvironmentMask)) { val3 = player.CenterPosition + (((RaycastHit)(ref val5)).point - player.CenterPosition) * 0.5f; } text = "ground"; break; } default: { CharacterInventory inventory = player.Inventory; ItemContainer val4 = (((Object)(object)inventory != (Object)null) ? inventory.Pouch : null); if ((Object)(object)val4 == (Object)null) { log.LogWarning((object)"[GIVE] pouch: no player pouch yet (inventory still initialising) — retry in a moment."); return; } val2 = ((Component)val4).transform; break; } } MultipleUsage component = ((Component)val).GetComponent(); bool flag = (Object)(object)component != (Object)null; int num = ((!flag || component.MaxStackAmount <= 0) ? 1 : component.MaxStackAmount); List list = DevGive.SplitStacks(giveArgs.Qty, (!flag) ? 1 : num); Item val6 = null; int num2 = 0; foreach (int item in list) { Item val7 = ItemManager.Instance.GenerateItemNetwork(itemId); if ((Object)(object)val7 == (Object)null) { log.LogWarning((object)$"[GIVE] spawn: GenerateItemNetwork({itemId}) returned null after {num2}/{giveArgs.Qty} — stopping."); break; } if (flag) { val7.RemainingAmount = item; } if ((Object)(object)val2 != (Object)null) { val7.ChangeParent(val2); } else { val7.ChangeParent((Transform)null, val3); val7.ResetTargetMove(); } if ((Object)(object)val6 == (Object)null) { val6 = val7; } num2 += ((!flag) ? 1 : item); } if (!((Object)(object)val6 == (Object)null)) { int num3 = DevGive.ReportedStackCount(num2, num, flag); string text2 = ((num3 > 1) ? $" in {num3} stack(s)" : ""); string text3 = ((giveArgs.Dest == GiveDest.Ground) ? $" ({val3.x:F1}, {val3.y:F1}, {val3.z:F1})" : ""); log.LogMessage((object)$"[GIVE] gave '{val6.Name}' (ItemID={itemId}) x{num2}{text2} -> {text}{text3} (via {via})."); Notify.Player(player, $"+{num2} {val6.Name} → {text}"); } } internal static void RemoveItemVerb(Character player, string[] parts, ManualLogSource log) { RemoveArgs removeArgs = DevRemove.Parse(parts); if (removeArgs.Error != null) { log.LogWarning((object)("[REMOVE] parse: " + removeArgs.Error)); return; } int num = 0; int itemId; string via; if (int.TryParse(removeArgs.Name, out var result)) { num = result; } else if (ItemNameIndex.TryResolveCatalog(removeArgs.Name, out itemId, out via)) { num = itemId; } List list = new List(); HashSet hashSet = new HashSet(); int num2 = 0; long num3 = 0L; foreach (Item item3 in Inventories.All(player)) { num2++; bool num4; if (num == 0) { if (item3.Name == null) { continue; } num4 = item3.Name.IndexOf(removeArgs.Name, StringComparison.OrdinalIgnoreCase) >= 0; } else { num4 = item3.ItemID == num; } if (num4) { list.Add(item3); hashSet.Add(item3.ItemID); num3 += item3.RemainingAmount; } } if (list.Count == 0) { log.LogMessage((object)("[REMOVE] nothing matches '" + removeArgs.Name + "' — TOTAL QTY 0, nothing removed " + $"(the sweep ran and found the other {num2} stack(s); equipped " + "gear is NOT swept — it lives in equipment slots, 'unequip' first).")); return; } if (hashSet.Count > 1) { log.LogWarning((object)($"[REMOVE] ambiguous: '{removeArgs.Name}' matches {hashSet.Count} distinct " + "items — a destructive verb does not guess; use the ItemID. Matches:")); HashSet hashSet2 = new HashSet(); { foreach (Item item4 in list) { if (hashSet2.Add(item4.ItemID)) { log.LogWarning((object)$"[REMOVE] '{item4.Name}' (id {item4.ItemID})"); } } return; } } int itemID = list[0].ItemID; string name = list[0].Name; long num5 = (removeArgs.All ? num3 : removeArgs.Qty); List list2 = new List(list.Count); foreach (Item item5 in list) { list2.Add(item5.RemainingAmount); } long num6 = 0L; foreach (var item6 in DevRemove.PlanTakes((int)Math.Min(num5, 2147483647L), list2)) { int item = item6.StackIndex; int item2 = item6.Take; Inventories.ConsumeResult consumeResult = Inventories.ConsumeOne(list[item], item2); num6 += consumeResult.Requested - consumeResult.Shortfall; if (!consumeResult.Consumed) { log.LogWarning((object)("[REMOVE] stack uid " + list[item].UID + ": consume did not fully take — " + consumeResult.Describe())); } } Notify.Player(player, $"-{num6} {name}"); Runner.Instance.StartCoroutine(DeferredReport.Wrap(ReportRemoval(player, itemID, name, num3, num6, num5, log), log, "removeitem")); } private static IEnumerator ReportRemoval(Character player, int resolvedId, string itemName, long beforeQty, long removed, long wanted, ManualLogSource log) { yield return null; if ((Object)(object)player == (Object)null) { log.LogWarning((object)($"[REMOVE] '{itemName}' (id {resolvedId}): removed {removed} of {wanted} " + "requested, but the player was gone a frame later (scene change or death) — no post-total. Re-read with invdump.")); yield break; } long num = 0L; foreach (Item item in Inventories.All(player)) { if (item.ItemID == resolvedId) { num += item.RemainingAmount; } } string arg = ((removed < wanted) ? $" (SHORTFALL: {wanted - removed} of the requested {wanted} were not there to remove)" : ""); string text = $"[REMOVE] '{itemName}' (id {resolvedId}): removed {removed} of {wanted} requested " + $"— TOTAL QTY {beforeQty} -> {num}{arg}. (Equipped gear is NOT touched " + "— it lives in equipment slots; 'unequip' first.)"; if (removed < wanted) { log.LogWarning((object)text); } else { log.LogMessage((object)text); } } internal static Item FindInventoryItem(Character player, string key) { ItemQuery itemQuery = ItemQuery.Parse(key); if (itemQuery == null) { return null; } foreach (Item item in Inventories.All(player)) { if (itemQuery.Matches(item.Name, item.ItemID)) { return item; } } return null; } internal static void UseItemVerb(Character player, string[] parts, ManualLogSource log) { if (parts.Length < 2) { log.LogWarning((object)"[USEITEM] usage: useitem "); return; } string text = Tail(parts); Item val = FindInventoryItem(player, text); if ((Object)(object)val == (Object)null) { log.LogWarning((object)("[USEITEM] no inventory item matching '" + text + "'.")); return; } log.LogMessage((object)$"[USEITEM] using '{val.Name}' (ItemID={val.ItemID}, qty {val.RemainingAmount})..."); bool flag = val.TryUse(player); log.LogMessage((object)$"[USEITEM] TryUse -> {flag}; qty now {val.RemainingAmount}. (Cast-anim effects consume AFTER the anim — re-run 'useitem' or check the [TAMEFOOD] consume line for the post-anim count.)"); } internal static void GiveWater(Character player, string[] parts, ManualLogSource log) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) string text = ((parts.Length > 1) ? parts[1] : null); string text2 = DevGive.WaterTypeNameFor(text); if (text2 == null) { log.LogWarning((object)("[GIVEWATER] unknown water type '" + text + "'. usage: givewater [clean|river|salt|rancid|leyline|sparkling|healing]")); return; } WaterType val = (WaterType)Enum.Parse(typeof(WaterType), text2); ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val2 = ((instance != null) ? instance.GetItemPrefab(4200040) : null); if ((Object)(object)val2 == (Object)null) { log.LogWarning((object)$"[GIVEWATER] Waterskin (ItemID {4200040}) is not a known item prefab."); return; } Item val3 = ItemManager.Instance.GenerateItemNetwork(4200040); if ((Object)(object)val3 == (Object)null) { log.LogWarning((object)$"[GIVEWATER] GenerateItemNetwork({4200040}) returned null."); return; } val3.ChangeParent(((Component)player.Inventory.Pouch).transform); WaterContainer val4 = (WaterContainer)(object)((val3 is WaterContainer) ? val3 : null); if ((Object)(object)val4 == (Object)null) { log.LogWarning((object)"[GIVEWATER] spawned item is not a WaterContainer."); return; } val4.Fill(val, 9999); log.LogMessage((object)$"[GIVEWATER] gave a Waterskin filled with '{val4.ContainedWaterDisplay}' ({val}) -> pouch."); Notify.Player(player, "+1 Waterskin (" + val4.ContainedWaterDisplay + ") → pouch"); } internal static void EquipVerb(Character player, string[] parts, ManualLogSource log) { //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Expected O, but got Unknown if (parts.Length < 2) { log.LogWarning((object)"[DEV] usage: equip (equips from inventory, spawning if absent)"); return; } string key = Tail(parts); ItemQuery itemQuery = ItemQuery.Parse(key); if (itemQuery == null) { log.LogWarning((object)"[DEV] usage: equip "); return; } foreach (Item item in Inventories.All(player)) { Equipment val = (Equipment)(object)((item is Equipment) ? item : null); if (val != null && itemQuery.Matches(item.Name, item.ItemID)) { player.Inventory.EquipItem(val, false); log.LogMessage((object)$"[DEV] equipped '{((Item)val).Name}' (ItemID={((Item)val).ItemID}) from inventory."); return; } } for (int i = 0; i <= 8; i++) { CharacterInventory inventory = player.Inventory; CharacterEquipment val2 = (((Object)(object)inventory != (Object)null) ? inventory.Equipment : null); Item val3 = (((Object)(object)val2 != (Object)null) ? val2.GetEquippedItem((EquipmentSlotIDs)i) : null); if ((Object)(object)val3 != (Object)null && itemQuery.Matches(val3.Name, val3.ItemID)) { log.LogMessage((object)$"[DEV] equip: '{val3.Name}' (ItemID={val3.ItemID}) is already equipped in {(object)(EquipmentSlotIDs)i} — nothing to do."); return; } } if (!ItemNameIndex.TryResolveArg(key, null, null, out var itemId, out var via, out var problem)) { log.LogWarning((object)("[DEV] equip: " + problem)); return; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val4 = ((instance != null) ? instance.GetItemPrefab(itemId) : null); if ((Object)(object)val4 == (Object)null) { log.LogWarning((object)$"[DEV] equip: ItemID {itemId} is not a known item prefab."); return; } if (!(val4 is Equipment)) { log.LogWarning((object)$"[DEV] equip: '{val4.Name}' (ItemID={itemId}) is not equipment."); return; } Item val5 = ItemManager.Instance.GenerateItemNetwork(itemId); if ((Object)(object)val5 == (Object)null) { log.LogWarning((object)$"[DEV] equip: GenerateItemNetwork({itemId}) returned null."); return; } val5.ChangeParent(((Component)player.Inventory.Pouch).transform); player.Inventory.EquipItem((Equipment)val5, false); log.LogMessage((object)$"[DEV] spawned + equipped '{val5.Name}' (ItemID={itemId}) (via {via})."); } internal static void UnequipVerb(Character player, string[] parts, ManualLogSource log) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) int? num = ((parts.Length > 1) ? DevState.SlotIndexFor(parts[1]) : ((int?)null)); if (!num.HasValue) { log.LogWarning((object)"[DEV] usage: unequip "); return; } EquipmentSlotIDs val = (EquipmentSlotIDs)num.Value; CharacterInventory inventory = player.Inventory; CharacterEquipment val2 = (((Object)(object)inventory != (Object)null) ? inventory.Equipment : null); Item val3 = (((Object)(object)val2 != (Object)null) ? val2.GetEquippedItem(val) : null); Equipment val4 = (Equipment)(object)((val3 is Equipment) ? val3 : null); if (val4 == null) { log.LogMessage((object)$"[DEV] nothing equipped in {val}."); return; } string name = ((Item)val4).Name; int itemID = ((Item)val4).ItemID; string uID = ((Item)val4).UID; player.Inventory.UnequipItem(val4); Runner.Instance.StartCoroutine(DeferredReport.Wrap(ReportUnequip(player, val, name, itemID, uID, log), log, "unequip")); } private static IEnumerator ReportUnequip(Character player, EquipmentSlotIDs slotId, string name, int id, string uid, ManualLogSource log) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) yield return null; if ((Object)(object)player == (Object)null) { log.LogWarning((object)($"[DEV] unequip: '{name}' (ItemID={id}) was unequipped from {slotId}, but " + "the player was gone a frame later (scene change or death) — the slot was not re-read, so this is not a confirmation.")); yield break; } CharacterInventory inventory = player.Inventory; CharacterEquipment val = (((Object)(object)inventory != (Object)null) ? inventory.Equipment : null); Item val2 = (((Object)(object)val != (Object)null) ? val.GetEquippedItem(slotId) : null); if ((Object)(object)val2 != (Object)null && val2.UID == uid) { string text = (((int)slotId == 7) ? " (a bag needs free inventory space to land; make room or drop it manually)" : ""); log.LogWarning((object)$"[DEV] unequip: '{name}' (ItemID={id}) is STILL in {slotId} — the slot did not clear{text}."); } else { log.LogMessage((object)$"[DEV] unequipped '{name}' (ItemID={id}) from {slotId} -> inventory."); } } } internal static class MoveVerbs { private const float Range = 40f; private static readonly DevStandoff.Candidate[] s_plan = new DevStandoff.Candidate[DevStandoff.MaxCandidates]; private static readonly double[] s_column = new double[DevStandoff.MaxColumnProbes]; private const float RefFeetRadius = 2.5f; private const float CandidateRadius = 4f; private const float WalkToNavRadius = 12f; private const float WalkToProbeUp = 200f; private const float WalkToProbeDown = 400f; private const float SettleUp = 2f; private const float SettleDown = 6f; private static readonly NavMeshPath s_path = new NavMeshPath(); internal static void FaceVerb(Character player, string[] parts, ManualLogSource log) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) DevMove.FaceArgs faceArgs = DevMove.ParseFace(parts); if (faceArgs.Error != null) { log.LogWarning((object)("[FACE] parse: " + faceArgs.Error)); return; } Vector3 position = ((Component)player).transform.position; Character target = null; float yaw; switch (faceArgs.Mode) { case DevMove.FaceMode.Yaw: yaw = (float)faceArgs.Yaw; break; case DevMove.FaceMode.Point: { Vector3 to = default(Vector3); ((Vector3)(ref to))..ctor((float)faceArgs.X, position.y, (float)faceArgs.Z); if (!TryBearing(position, to, out yaw)) { log.LogWarning((object)"[FACE] already at target position; cannot derive a bearing."); return; } break; } default: { string want = ((faceArgs.Mode == DevMove.FaceMode.Species) ? faceArgs.Species : null); if (!TryTarget(player, want, "[FACE]", log, out target)) { return; } if (!TryBearing(position, ((Component)target).transform.position, out yaw)) { log.LogWarning((object)"[FACE] already at target position; cannot derive a bearing."); return; } break; } } player.Teleport(position, Quaternion.Euler(0f, yaw, 0f)); if ((Object)(object)target != (Object)null) { log.LogMessage((object)($"[FACE] facing '{target.Name}' — yaw {Normalize(yaw):0}° " + $"(dist {Vector3.Distance(position, ((Component)target).transform.position):0.0}m, " + $"facing-angle {FacingAngle(player, ((Component)target).transform.position):0}°).")); } else { log.LogMessage((object)$"[FACE] yaw set to {Normalize(yaw):0}°."); } WarnIfFacingMayNotStick(player, "FACE", log); } internal static void MoveToVerb(Character player, string[] parts, ManualLogSource log) { //IL_008f: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) DevMove.MoveToArgs moveToArgs = DevMove.ParseMoveTo(parts); if (moveToArgs.Error != null) { log.LogWarning((object)("[MOVETO] parse: " + moveToArgs.Error)); return; } if (moveToArgs.Clamped) { log.LogMessage((object)$"[MOVETO] note: gap clamped to {moveToArgs.Gap:0.#}m (allowed {0.5:0.#}–{20.0:0.#})."); } string want = (moveToArgs.Nearest ? null : moveToArgs.Species); if (TryTarget(player, want, "[MOVETO]", log, out var target)) { Vector3 position = ((Component)target).transform.position; Vector3 val = position - ((Component)player).transform.position; val.y = 0f; Vector3 val2 = ((((Vector3)(ref val)).sqrMagnitude > 1E-06f) ? ((Vector3)(ref val)).normalized : (-Flatten(((Component)target).transform.forward))); if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f) { val2 = Vector3.forward; log.LogWarning((object)("[MOVETO] note: standing on '" + target.Name + "' and its facing is degenerate — no bearing to derive; placing on an arbitrary (world-forward) side.")); } Vector3 val3 = SnapToGround(position - val2 * (float)moveToArgs.Gap, "[MOVETO]", log); player.Teleport(val3, Quaternion.LookRotation(val2, Vector3.up)); log.LogMessage((object)($"[MOVETO] placed {moveToArgs.Gap:0.#}m from '{target.Name}' " + $"(dist {Vector3.Distance(((Component)player).transform.position, position):0.0}m, " + $"facing-angle {FacingAngle(player, position):0}°).")); WarnIfFacingMayNotStick(player, "MOVETO", log); } } internal static void LockOnVerb(Character player, string[] parts, ManualLogSource log) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) DevMove.LockOnArgs lockOnArgs = DevMove.ParseLockOn(parts); if (lockOnArgs.Error != null) { log.LogWarning((object)("[LOCK] parse: " + lockOnArgs.Error)); return; } TargetingSystem targetingSystem = player.TargetingSystem; if ((Object)(object)targetingSystem == (Object)null) { log.LogWarning((object)"[LOCK] player has no TargetingSystem."); return; } string want = (lockOnArgs.Nearest ? null : lockOnArgs.Species); if (!TryTarget(player, want, "[LOCK]", log, out var target)) { return; } LockingPoint lockingPoint = target.LockingPoint; if ((Object)(object)lockingPoint == (Object)null) { log.LogWarning((object)("[LOCK] '" + target.Name + "' has no LockingPoint — it cannot be locked (nothing changed).")); return; } targetingSystem.SetLockingPoint(lockingPoint); if ((Object)(object)player.CharacterCamera != (Object)null) { player.CharacterCamera.LookAtTransform = targetingSystem.LockingPointTrans; } else { log.LogWarning((object)"[LOCK] no CharacterCamera — lock set, but the camera was not pointed at it."); } float num = Vector3.Distance(((Component)player).transform.position, ((Component)target).transform.position); float trueRange = targetingSystem.TrueRange; log.LogMessage((object)($"[LOCK] locked '{target.Name}' — dist {num:0.0}m, TrueRange {trueRange:0.#}m " + "(20 melee / 40 bow / 80 with Hunter's Eye).")); if (num > trueRange + 2f) { log.LogWarning((object)($"[LOCK] {num:0.0}m is beyond TrueRange+2 ({trueRange + 2f:0.#}m) — the vanilla babysitter " + "will release this lock within a frame. Close the distance ('moveto') first.")); } } internal static void LockOffVerb(Character player, ManualLogSource log) { TargetingSystem targetingSystem = player.TargetingSystem; if ((Object)(object)targetingSystem == (Object)null) { log.LogWarning((object)"[LOCK] player has no TargetingSystem."); return; } if (!targetingSystem.Locked) { log.LogMessage((object)"[LOCK] nothing locked."); return; } Character lockedCharacter = targetingSystem.LockedCharacter; string text = (((Object)(object)lockedCharacter != (Object)null) ? lockedCharacter.Name : "(unnamed locking point)"); targetingSystem.ReleaseTarget(); CharacterControl characterControl = player.CharacterControl; LocalCharacterControl val = (LocalCharacterControl)(object)((characterControl is LocalCharacterControl) ? characterControl : null); if (val != null) { val.FaceLikeCamera = false; } if ((Object)(object)player.CharacterCamera != (Object)null) { player.CharacterCamera.LookAtTransform = null; } log.LogMessage((object)("[LOCK] released '" + text + "'.")); } internal static void PosVerb(Character player, ManualLogSource log) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)player).transform.position; CharacterControl characterControl = player.CharacterControl; LocalCharacterControl val = (LocalCharacterControl)(object)((characterControl is LocalCharacterControl) ? characterControl : null); TargetingSystem targetingSystem = player.TargetingSystem; string arg = (((Object)(object)player.CurrentWeapon != (Object)null) ? ("'" + ((Item)player.CurrentWeapon).Name + "'") : "none"); string arg2 = (((Object)(object)val != (Object)null) ? ((object)Unsafe.As(ref val.ControlMode)/*cast due to .constrained prefix*/).ToString() : "n/a (not a LocalCharacterControl)"); string text = (((Object)(object)val != (Object)null) ? val.FaceLikeCamera.ToString() : "n/a"); string text2 = (((Object)(object)player.CharacterCamera != (Object)null) ? player.CharacterCamera.InZoomMode.ToString() : "n/a"); string text3 = "no"; if ((Object)(object)targetingSystem != (Object)null && targetingSystem.Locked) { Character lockedCharacter = targetingSystem.LockedCharacter; text3 = (((Object)(object)lockedCharacter != (Object)null) ? ("'" + lockedCharacter.Name + "'") : "yes (non-character locking point)"); } log.LogMessage((object)($"[PPOS] pos=({position.x:0.0},{position.y:0.0},{position.z:0.0}) rotY={((Component)player).transform.eulerAngles.y:F0} " + $"sheathed={player.Sheathed} weapon={arg} controlMode={arg2} " + "faceLikeCamera=" + text + " zoom=" + text2 + " locked=" + text3)); Character val2 = StageVerbs.FindNearestWild(player, 40f, null); if ((Object)(object)val2 != (Object)null) { log.LogMessage((object)($"[PPOS] nearest='{val2.Name}' dist={Vector3.Distance(position, ((Component)val2).transform.position):0.0}m " + $"facing-angle={FacingAngle(player, ((Component)val2).transform.position):0}°")); } else { log.LogMessage((object)$"[PPOS] no living wild creature within {40f:0.#}m."); } } internal static void StandoffVerb(Character player, Func petTarget, string[] parts, ManualLogSource log) { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) DevStandoff.StandoffArgs standoffArgs = DevStandoff.ParseStandoff(parts); if (standoffArgs.Error != null) { log.LogWarning((object)("[STANDOFF] parse: " + standoffArgs.Error)); return; } if (standoffArgs.Clamped) { log.LogMessage((object)$"[STANDOFF] note: distance clamped to {standoffArgs.Metres:0.#}m (allowed {1.0:0.#}–{200.0:0.#})."); } Character target; string text; if (DevStandoff.IsPetTarget(standoffArgs.Target)) { if (petTarget == null) { log.LogWarning((object)"[STANDOFF] no pet-target provider on this channel — use 'target=nearest' or 'target=', or run this on a pet-owning mod's channel."); return; } target = petTarget(); if ((Object)(object)target == (Object)null) { log.LogWarning((object)"[STANDOFF] no live pet body to stand off from (target=pet)."); return; } text = "pet '" + target.Name + "'"; } else { string want = (DevStandoff.IsNearestTarget(standoffArgs.Target) ? null : standoffArgs.Target); if (!TryTarget(player, want, "[STANDOFF]", log, out target)) { return; } text = "'" + target.Name + "'"; } Vector3 position = ((Component)target).transform.position; double baseBearingDeg; float yaw; if (standoffArgs.HasBearing) { baseBearingDeg = standoffArgs.Bearing; } else if (TryBearing(position, ((Component)player).transform.position, out yaw)) { baseBearingDeg = yaw; } else { baseBearingDeg = 0.0; log.LogMessage((object)"[STANDOFF] note: standing exactly on the reference body — no current bearing to keep; searching from world-north."); } NavMeshHit val = default(NavMeshHit); bool flag = NavMesh.SamplePosition(position, ref val, 2.5f, -1); Vector3 val2 = (flag ? ((NavMeshHit)(ref val)).position : position); if (!flag) { log.LogWarning((object)$"[STANDOFF] note: no navmesh within {2.5f:0.#}m of {text} — landings below are ground-checked but NOT connectivity-checked."); } int num = DevStandoff.Plan(standoffArgs.Metres, baseBearingDeg, s_plan); Vector3 landing = default(Vector3); string text2 = null; NavMeshHit val5 = default(NavMeshHit); for (int i = 0; i < num; i++) { Vector3 val3 = Quaternion.Euler(0f, (float)s_plan[i].BearingDeg, 0f) * Vector3.forward; Vector3 val4 = val2 + val3 * (float)s_plan[i].Metres; if (!NavMesh.SamplePosition(val4, ref val5, 4f, -1)) { continue; } Vector3 val6 = Settle(((NavMeshHit)(ref val5)).position); float num2 = FlatDistance(position, val6); if (DevStandoff.DistanceOk(num2, standoffArgs.Metres) && DevStandoff.HeightOk(val6.y, position.y)) { string text3 = $"bearing={s_plan[i].BearingDeg:0}° dist={num2:0.0}m dy={val6.y - position.y:+0.0;-0.0}m"; if (!flag || (NavMesh.CalculatePath(val2, val6, -1, s_path) && (int)s_path.status == 0)) { Place(player, val6, position, text, text3 + (flag ? " path=complete" : " path=unchecked"), log); return; } if (text2 == null) { landing = val6; text2 = text3; } } } if (text2 != null) { log.LogWarning((object)($"[STANDOFF] no path-connected landing at {standoffArgs.Metres:0.#}m — using a DISCONNECTED one " + "(" + text2 + "). The reference body cannot walk back from here; a leash reading taken from this spot measures an island, not a leash. Re-run somewhere open, or pass an explicit bearing=.")); Place(player, landing, position, text, text2 + " path=DISCONNECTED", log); } else { log.LogWarning((object)($"[STANDOFF] REFUSED — no ground found at {standoffArgs.Metres:0.#}m from {text} " + $"(tried {num} candidates: {DevStandoff.BearingFan.Length} bearings × {DevStandoff.RadiusFactors.Length} ranges, " + $"navmesh search {4f:0.#}m, tolerance ±{DevStandoff.Tolerance(standoffArgs.Metres):0.#}m, " + $"roof sanity {12.0:0.#}m). NOTHING MOVED. Try a shorter distance, or move to open ground first.")); Notify.Player(player, $"standoff {standoffArgs.Metres:0.#}m refused — no ground found"); } } internal static void WalkToVerb(Character player, string[] parts, ManualLogSource log) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_0222: 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_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) DevStandoff.WalkToArgs walkToArgs = DevStandoff.ParseWalkTo(parts); if (walkToArgs.Error != null) { log.LogWarning((object)("[WALKTO] parse: " + walkToArgs.Error)); return; } Vector3 position = ((Component)player).transform.position; int num = DevStandoff.PlanColumn(position.y, s_column); Vector3 val = default(Vector3); string text = null; Vector3 val2 = default(Vector3); string text2 = null; Vector3 val3 = default(Vector3); NavMeshHit val4 = default(NavMeshHit); for (int i = 0; i < num; i++) { ((Vector3)(ref val3))..ctor((float)walkToArgs.X, (float)s_column[i], (float)walkToArgs.Z); if (NavMesh.SamplePosition(val3, ref val4, 12f, -1)) { Vector3 val5 = Settle(((NavMeshHit)(ref val4)).position); string text3 = $"navmesh (probe y={s_column[i]:F1}, r<={12f:0.#}m) dy={val5.y - position.y:+0.0;-0.0}m"; if (DevStandoff.RiseOk(val5.y, position.y)) { val = val5; text = text3; break; } if (text2 == null) { val2 = val5; text2 = text3; } } } if (text == null && text2 != null) { log.LogWarning((object)($"[WALKTO] the ONLY navmesh in the column at ({walkToArgs.X:F1}, {walkToArgs.Z:F1}) is " + $"{val2.y - position.y:0.0}m ABOVE you ({text2}) — more than the {3.0:0.#}m " + "rise sanity. Placing anyway because it is genuine navmesh, but if the next reading looks off-mesh this is why: check 'pos' before trusting any distance measured from here.")); val = val2; text = text2 + " RISE-OVERRIDE"; } if (text == null) { log.LogWarning((object)($"[WALKTO] REFUSED — no navmesh within {12f:0.#}m of ({walkToArgs.X:F1}, {walkToArgs.Z:F1}) " + $"at any of {num} probe heights from y={position.y:F1} (offsets " + $"{DevStandoff.WalkToColumnOffsets[0]:+0.#;-0.#}…{DevStandoff.WalkToColumnOffsets[DevStandoff.WalkToColumnOffsets.Length - 1]:+0.#;-0.#}m). " + "NOTHING MOVED. " + ColliderColumnReport(walkToArgs.X, walkToArgs.Z, position.y) + " A collider is NOT a floor — it is as likely to be a roof or a bounding volume, which is how 0.4.5 lifted a character 23.9m into the air. Use 'goto' to reach the area and walk, or 'teleport' only if you truly mean a raw height.")); Notify.Player(player, $"walkto ({walkToArgs.X:0},{walkToArgs.Z:0}) refused — no navmesh there"); } else { player.Teleport(val, ((Component)player).transform.rotation); log.LogMessage((object)($"[WALKTO] placed at ({val.x:F1}, {val.y:F1}, {val.z:F1}) via {text} — " + $"moved {Vector3.Distance(position, val):0.0}m from ({position.x:F1}, {position.y:F1}, {position.z:F1}).")); } } private static string ColliderColumnReport(double x, double z, float fromY) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor((float)x, fromY + 200f, (float)z); RaycastHit val2 = default(RaycastHit); if (!Physics.Raycast(val, Vector3.down, ref val2, 600f, -1, (QueryTriggerInteraction)1)) { return $"No collider either in the {600f:0}m column."; } return $"(Nearest collider under the column top: '{((Object)((RaycastHit)(ref val2)).collider).name}' at y={((RaycastHit)(ref val2)).point.y:F1}, " + $"{((RaycastHit)(ref val2)).point.y - fromY:+0.0;-0.0}m from you — NOT used.)"; } private static Vector3 Settle(Vector3 p) { //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) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) RaycastHit val = default(RaycastHit); if (!Physics.Raycast(p + Vector3.up * 2f, Vector3.down, ref val, 8f, -1, (QueryTriggerInteraction)1)) { return p; } return new Vector3(p.x, ((RaycastHit)(ref val)).point.y, p.z); } private static float FlatDistance(Vector3 a, Vector3 b) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) a.y = 0f; b.y = 0f; return Vector3.Distance(a, b); } private static void Place(Character player, Vector3 landing, Vector3 refPos, string refWhat, string why, ManualLogSource log) { //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) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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) //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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0088: Unknown result type (might be due to invalid IL or missing references) Vector3 val = Flatten(refPos - landing); Quaternion val2 = ((((Vector3)(ref val)).sqrMagnitude > 1E-06f) ? Quaternion.LookRotation(val, Vector3.up) : ((Component)player).transform.rotation); player.Teleport(landing, val2); log.LogMessage((object)($"[STANDOFF] placed {FlatDistance(((Component)player).transform.position, refPos):0.0}m from {refWhat} " + $"at ({landing.x:F1}, {landing.y:F1}, {landing.z:F1}) — {why}.")); WarnIfFacingMayNotStick(player, "STANDOFF", log); } private static void WarnIfFacingMayNotStick(Character player, string tag, ManualLogSource log) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Invalid comparison between Unknown and I4 TargetingSystem targetingSystem = player.TargetingSystem; if ((Object)(object)targetingSystem != (Object)null && targetingSystem.Locked) { Character lockedCharacter = targetingSystem.LockedCharacter; log.LogWarning((object)("[" + tag + "] facing may not stick: a lock is active" + (((Object)(object)lockedCharacter != (Object)null) ? (" on '" + lockedCharacter.Name + "'") : "") + " and the body is being steered onto it every frame — run 'lockoff' first if you want a fixed bearing.")); } if ((Object)(object)player.CharacterCamera != (Object)null && player.CharacterCamera.InZoomMode) { log.LogWarning((object)("[" + tag + "] facing may not stick: bow ZOOM mode is on — aim overrides body yaw while zoomed.")); } CharacterControl characterControl = player.CharacterControl; LocalCharacterControl val = (LocalCharacterControl)(object)((characterControl is LocalCharacterControl) ? characterControl : null); if ((Object)(object)val != (Object)null && (int)val.ControlMode == 1 && !player.Sheathed && (Object)(object)player.CurrentWeapon != (Object)null) { log.LogWarning((object)("[" + tag + "] facing may not stick: ControlMode=HalfCameraControl with a DRAWN weapon makes the body track camera yaw at ~500°/s (LocalCharacterControl.cs:72-98) — this facing may be erased within frames. Sheathe, or move the camera onto the target, or use 'lockon'.")); } } private static bool TryBearing(Vector3 from, Vector3 to, out float yaw) { //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) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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) Vector3 val = to - from; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 1E-06f) { yaw = 0f; return false; } Quaternion val2 = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); yaw = ((Quaternion)(ref val2)).eulerAngles.y; return true; } private static float FacingAngle(Character player, Vector3 targetPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) Vector3 val = targetPos - ((Component)player).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 1E-06f) { return 0f; } return Vector3.Angle(Flatten(((Component)player).transform.forward), val); } private static Vector3 Flatten(Vector3 v) { //IL_0022: 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) v.y = 0f; if (!(((Vector3)(ref v)).sqrMagnitude < 1E-06f)) { return ((Vector3)(ref v)).normalized; } return Vector3.zero; } private static Vector3 SnapToGround(Vector3 p, string tag, ManualLogSource log) { //IL_0000: 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_002f: 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) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(p, ref val, 6f, -1)) { return new Vector3(p.x, ((NavMeshHit)(ref val)).position.y, p.z); } RaycastHit val2 = default(RaycastHit); if (Physics.Raycast(p + Vector3.up * 1.2f, Vector3.down, ref val2, 30f, -1, (QueryTriggerInteraction)1)) { return new Vector3(p.x, ((RaycastHit)(ref val2)).point.y, p.z); } if (log != null) { log.LogWarning((object)(tag + " note: no navmesh within 6m and no ground within 30m below " + $"({p.x:F1}, {p.y:F1}, {p.z:F1}) — placed at the raw height; expect a drop or a wall.")); } return p; } private static bool TryTarget(Character player, string want, string tag, ManualLogSource log, out Character target) { target = StageVerbs.FindNearestWild(player, 40f, want); if ((Object)(object)target != (Object)null) { return true; } log.LogWarning((object)string.Format("{0} no living wild creature matching '{1}' within {2:0.#}m.", tag, want ?? "nearest", 40f)); return false; } private static float Normalize(float yaw) { yaw %= 360f; if (!(yaw < 0f)) { return yaw; } return yaw + 360f; } } internal static class ResilienceVerbs { internal const string Tag = "[UNSTICK]"; private const int MaxProloguePages = 64; private const float VerifyDelaySeconds = 0.75f; private const int PrologueReassertAttempts = 3; internal static void UnstickVerb(string[] parts, ManualLogSource log) { DevUnstick.UnstickArgs unstickArgs = DevUnstick.Parse(parts); if (unstickArgs.Error != null) { log.LogWarning((object)("[UNSTICK] " + unstickArgs.Error)); return; } Dump(log); if (unstickArgs.Mode == DevUnstick.UnstickMode.Dump) { log.LogMessage((object)"[UNSTICK] mode=dump — nothing applied ('unstick fix' to act)."); return; } bool force = unstickArgs.Mode == DevUnstick.UnstickMode.Force; bool pausedBefore = ForceUnpausePredicate(log); DevUnstick.UnstickStep step = ((unstickArgs.Step == DevUnstick.UnstickStep.Auto) ? ChooseAuto() : unstickArgs.Step); if ((unstickArgs.Step == DevUnstick.UnstickStep.Auto) ? ApplyAuto(force, log) : ApplyStep(unstickArgs.Step, force, log)) { Dump(log); StartVerify(step, pausedBefore, log); } } private static void Dump(ManualLogSource log) { NetworkLevelLoader nll = NetworkLevelLoader.Instance; log.LogMessage((object)("[UNSTICK] paused=" + Safe(() => (Object)(object)nll != (Object)null && nll.IsGameplayPaused) + " pausedBy=[" + PausedByKeys(nll) + "] otherPlayerPaused=" + Safe(() => (Object)(object)nll != (Object)null && nll.m_otherPlayerPaused) + " returningToMainMenu=" + Safe(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsReturningToMainMenu) + " appClosing=" + Safe(() => Global.IsApplicationClosing))); log.LogMessage((object)(string.Format("{0} time: timeScale={1:F3} fixedDelta={2:F4} ", "[UNSTICK]", Time.timeScale, Time.fixedDeltaTime) + $"gamePausedByPlayer={Global.GamePausedByPlayer} " + "pauseScreenOpen=" + Safe(() => (Object)(object)MenuManager.Instance != (Object)null && (Object)(object)MenuManager.Instance.GamePausedScreen != (Object)null && ((UIElement)MenuManager.Instance.GamePausedScreen).IsDisplayed))); log.LogMessage((object)("[UNSTICK] gate: " + LoadGate.FormatGateState(nll))); log.LogMessage((object)("[UNSTICK] prologue: panelUp=" + Safe(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsProloguePanelDisplayed) + " (up ⇒ the load coroutine is parked BEFORE the continue gate; 'unstick fix' walks it off)")); log.LogMessage((object)("[UNSTICK] net: msgQueue=" + Safe(() => PhotonNetwork.isMessageQueueRunning) + " inRoom=" + Safe(() => PhotonNetwork.inRoom) + " offline=" + Safe(() => PhotonNetwork.offlineMode) + " " + $"master={Safe(() => PhotonNetwork.isMasterClient)} peers={PeerCount(log)}")); } private static DevUnstick.UnstickStep ChooseAuto() { DevUnstick.AutoState s = new DevUnstick.AutoState { Paused = ForceUnpausePredicate(), ProloguePanelUp = ProloguePredicate(), GateGuardHolds = GatePredicate(), PauseMenuClaimed = PauseMenuPredicate(), TimeScaleZero = (Time.timeScale < 0.01f) }; return DevUnstick.ChooseAuto(s); } private static bool ApplyAuto(bool force, ManualLogSource log) { DevUnstick.UnstickStep unstickStep = ChooseAuto(); if (unstickStep != DevUnstick.UnstickStep.Auto) { return ApplyStep(unstickStep, force, log); } log.LogMessage((object)"[UNSTICK] no applicable fix — nothing this verb models is wedged."); return false; } private static bool ApplyStep(DevUnstick.UnstickStep step, bool force, ManualLogSource log) { NetworkLevelLoader instance = NetworkLevelLoader.Instance; switch (step) { case DevUnstick.UnstickStep.Prologue: { if (!ProloguePredicate() && !force) { log.LogMessage((object)"[UNSTICK] prologue: nothing to do — the prologue/context panel is not displayed."); return false; } int num2 = WalkProloguePages(log); bool flag3 = ProloguePredicate(); log.LogMessage((object)(string.Format("{0} applied prologue: GoToNextProloguePage() x{1} — ", "[UNSTICK]", num2) + (flag3 ? "panel is STILL displayed; the pause stack keeps its 'Prologue' key and the load stays parked." : "panel hidden, so ProloguePanel.OnHide runs UnPauseGameplay(\"Prologue\") and FinishLoadLevel resumes."))); return true; } case DevUnstick.UnstickStep.Gate: if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[UNSTICK] gate: no NetworkLevelLoader (main menu?)."); return false; } if (!GatePredicate() && !force) { log.LogMessage((object)"[UNSTICK] gate: nothing to do — the four gate conditions do not hold (see the 'gate:' line above; 'unstick force gate' sets it anyway)."); return false; } instance.SetContinueAfterLoading(); log.LogMessage((object)"[UNSTICK] applied gate: SetContinueAfterLoading() — passed the retail 'press any key' gate."); return true; case DevUnstick.UnstickStep.PauseMenu: { if (!PauseMenuPredicate() && !force) { log.LogMessage((object)"[UNSTICK] pausemenu: nothing to do — Global.GamePausedByPlayer is -1 (nobody claims the pause menu)."); return false; } float timeScale = Time.timeScale; int gamePausedByPlayer = Global.GamePausedByPlayer; PauseMenu.Pause(false); log.LogMessage((object)(string.Format("{0} applied pausemenu: PauseMenu.Pause(false) — timeScale {1:F3} -> 1, ", "[UNSTICK]", timeScale) + $"gamePausedByPlayer {gamePausedByPlayer} -> -1.")); return true; } case DevUnstick.UnstickStep.TimeScale: { if (!TimeScalePredicate() && !force) { log.LogMessage((object)string.Format("{0} timescale: nothing to do — timeScale is {1:F3} (or the pause menu claims it; use pausemenu).", "[UNSTICK]", Time.timeScale)); return false; } float timeScale2 = Time.timeScale; Time.timeScale = 1f; Time.fixedDeltaTime = 0.022f; log.LogMessage((object)string.Format("{0} applied timescale: {1:F3} -> 1 (no pause menu claimed it).", "[UNSTICK]", timeScale2)); return true; } case DevUnstick.UnstickStep.ForceUnpause: { if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[UNSTICK] forceunpause: no NetworkLevelLoader (main menu?)."); return false; } if (!ForceUnpausePredicate(log) && !force) { log.LogMessage((object)"[UNSTICK] forceunpause: nothing to do — gameplay is not paused."); return false; } if (!force && !OfflineOrAlone(log)) { log.LogWarning((object)(string.Format("{0} refused forceunpause: in a room with {1} peer(s) — ", "[UNSTICK]", PeerCount(log)) + "ForceUnpauseGameplay() clears the LOCAL pause set (NetworkLevelLoader.cs:1670-1674) WITHOUT the SendPauseStatus(false) RPC that SendUnpauseGameplay sends (:2004-2018), so every peer stays paused and the party desyncs. Fix the actual cause, or type 'unstick force forceunpause' to override.")); return false; } string arg = PausedByKeys(instance); int num = -1; try { num = ((instance.m_playerCurrentlyInPause != null) ? instance.m_playerCurrentlyInPause.Count : 0); } catch (Exception ex) { log.LogWarning((object)("[UNSTICK] forceunpause: could not read the paused-player set (" + ex.GetType().Name + ": " + ex.Message + ").")); } bool flag = false; bool flag2 = false; try { if (instance.m_playerCurrentlyInPause != null) { instance.m_playerCurrentlyInPause.Clear(); } flag = true; } catch (Exception ex2) { log.LogWarning((object)("[UNSTICK] forceunpause: clearing the paused-player set threw (" + ex2.GetType().Name + ": " + ex2.Message + ").")); } try { if (instance.m_gameplayPausedBy != null) { instance.m_gameplayPausedBy.Clear(); } flag2 = true; } catch (Exception ex3) { log.LogWarning((object)("[UNSTICK] forceunpause: clearing pausedBy threw (" + ex3.GetType().Name + ": " + ex3.Message + ").")); } log.LogMessage((object)(string.Format("{0} applied forceunpause: cleared pausedBy [{1}] + {2} paused player(s) — ", "[UNSTICK]", arg, num) + "NB the load coroutine is NOT advanced by this." + ((flag && flag2) ? "" : $" PARTIAL: pausedPlayers={flag} pausedBy={flag2}."))); return true; } case DevUnstick.UnstickStep.ForceReady: if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[UNSTICK] forceready: no NetworkLevelLoader (main menu?)."); return false; } if (!force && !OfflineOrAlone(log)) { log.LogWarning((object)("[UNSTICK] refused forceready: online — ForceAllPlayersReady() (NetworkLevelLoader.cs:1686) sets m_allPlayerReadyToContinue with NO handshake from the players it speaks for, so the host resumes " + $"while {PeerCount(log)} peer(s) are still loading. Offline only; 'unstick force forceready' overrides.")); return false; } instance.ForceAllPlayersReady(); log.LogWarning((object)"[UNSTICK] applied forceready: ForceAllPlayersReady() — FORGED the ready handshake; any player who had not checked in is now assumed ready."); return true; default: log.LogWarning((object)string.Format("{0} step '{1}' has no implementation — nothing applied (a rung was added to DevUnstick.UnstickStep without a case here).", "[UNSTICK]", step)); return false; } } private static bool GatePredicate() { NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { return false; } return LoadPhase.GateGuardHolds(LoadGate.Snapshot(instance)); } private static bool ProloguePredicate() { try { return (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsProloguePanelDisplayed; } catch { return false; } } private static int WalkProloguePages(ManualLogSource log) { int num = 0; for (int i = 0; i < 64; i++) { if (!ProloguePredicate()) { break; } try { MenuManager.Instance.GoToNextProloguePage(); num++; } catch (Exception e) { Note(log, $"GoToNextProloguePage threw after {num} page(s) — the panel is left as it is", e); break; } } if (num >= 64) { log.LogWarning((object)(string.Format("{0} prologue: stopped after {1} pages without the panel hiding — ", "[UNSTICK]", 64) + "this is a bound, not a repair; treat the panel as still up.")); } return num; } private static bool PauseMenuPredicate() { return Global.GamePausedByPlayer != -1; } private static bool TimeScalePredicate() { if (Time.timeScale < 0.01f) { return Global.GamePausedByPlayer == -1; } return false; } private static bool ForceUnpausePredicate(ManualLogSource log = null) { NetworkLevelLoader instance = NetworkLevelLoader.Instance; try { return (Object)(object)instance != (Object)null && instance.IsGameplayPaused; } catch (Exception e) { Note(log, "IsGameplayPaused threw", e); return false; } } private static bool OfflineOrAlone(ManualLogSource log = null) { try { if (PhotonNetwork.offlineMode) { return true; } if (!PhotonNetwork.inRoom) { return true; } return PeerCount(log) == 0; } catch (Exception e) { Note(log, "the Photon room read threw — assuming peers are present", e); return false; } } private static void StartVerify(DevUnstick.UnstickStep step, bool pausedBefore, ManualLogSource log) { try { Runner.Instance.StartCoroutine(VerifyRoutine(step, pausedBefore, log)); } catch (Exception e) { Note(log, "could not schedule the " + DevUnstick.Name(step) + " verify — the 'applied' line above is UNVERIFIED", e); } } private static IEnumerator VerifyRoutine(DevUnstick.UnstickStep step, bool pausedBefore, ManualLogSource log) { yield return (object)new WaitForSecondsRealtime(0.75f); if (step == DevUnstick.UnstickStep.Prologue) { for (int i = 1; i < 3; i++) { if (!ProloguePredicate()) { break; } int num = WalkProloguePages(log); log.LogMessage((object)(string.Format("{0} prologue re-assert {1}/{2}: the panel came back up ", "[UNSTICK]", i, 2) + $"(a further context screen) — walked {num} more page(s).")); yield return (object)new WaitForSecondsRealtime(0.75f); } } bool flag = ForceUnpausePredicate(log); string text = DevUnstick.Verdict(pausedBefore, flag); log.LogMessage((object)(string.Format("{0} verify step={1} after={2:F2}s(real) ", "[UNSTICK]", DevUnstick.Name(step), 0.75f) + $"pausedBefore={pausedBefore} pausedNow={flag} pausedBy=[{PausedByKeys(NetworkLevelLoader.Instance)}] " + $"prologuePanelUp={ProloguePredicate()} -> {text}" + ((text == "REVERTED") ? (" — the 'applied' line above did NOT stick; something still holds the pause (read pausedBy above). NEXT RUNG: '" + DevUnstick.Name(ChooseAuto()) + "' — the ladder applies ONE rung per invocation by design, so fire 'unstick fix' again NOW rather than waiting out a poll interval.") : ((text == "NO-CHANGE") ? " — gameplay was not paused before this rung either, so nothing here proves a repair." : " — gameplay is genuinely running again.")))); Dump(log); } private static int PeerCount(ManualLogSource log = null) { try { return (PhotonNetwork.otherPlayers != null) ? PhotonNetwork.otherPlayers.Length : 0; } catch (Exception e) { Note(log, "the peer count read threw — the -1 below is a sentinel, not a count", e); return -1; } } private static void Note(ManualLogSource log, string what, Exception e) { if (log != null) { log.LogWarning((object)("[UNSTICK] " + what + " (" + e.GetType().Name + ": " + e.Message + ").")); } } private static string PausedByKeys(NetworkLevelLoader nll) { if ((Object)(object)nll == (Object)null) { return ""; } try { List list = ((nll.m_gameplayPausedBy != null) ? nll.m_gameplayPausedBy.Keys : null); if (list == null || list.Count == 0) { return ""; } return string.Join(",", list.ToArray()); } catch (Exception ex) { return "threw:" + ex.GetType().Name; } } private static string Safe(Func f) { try { object obj = f(); return (obj == null) ? "null" : obj.ToString(); } catch (Exception ex) { return "threw:" + ex.GetType().Name; } } } internal static class SaveVerbs { private static bool _latched; internal static void SaveListVerb(ManualLogSource log) { SaveManager instance = SaveManager.Instance; if (instance == null || !instance.SaveRetrieved) { log.LogWarning((object)(_latched ? "[SAVE] save list unavailable: it was consumed by this session's loadsave (the select clears the holder's temp list) — restart the game to list again." : "[SAVE] save list not retrieved yet (SaveManager still initialising) — try again in a second.")); return; } IList characterSaves = instance.CharacterSaves; if (characterSaves == null || characterSaves.Count == 0) { log.LogWarning((object)(_latched ? "[SAVE] save list unavailable: it was consumed by this session's loadsave (the select clears the holder's temp list) — restart the game to list again." : "[SAVE] no character saves found under SaveGames/.")); return; } log.LogMessage((object)$"[SAVE] {characterSaves.Count} character save(s) — 'loadsave ' (prefer uid/name; index is THIS list's order, not the menu's):"); for (int i = 0; i < characterSaves.Count; i++) { CharacterSaveInstanceHolder val = characterSaves[i]; string text = ((val != null) ? val.CharacterUID : null) ?? "?"; PlayerSaveData val2 = ((val == null) ? null : val.MostRecentInstance?.CharSave?.PSave); if (val2 == null) { log.LogMessage((object)$"[SAVE] [{i}] uid={text} (no readable instance)"); continue; } log.LogMessage((object)$"[SAVE] [{i}] uid={text} name='{val2.Name}' area='{val2.AreaName}' level={val2.LegacyLevel} hardcore={val2.HardcoreMode} saved={val2.DateTime}"); } } internal static void LoadSaveVerb(string[] parts, ManualLogSource log) { if (parts == null || parts.Length < 2) { log.LogWarning((object)"[SAVE] usage: loadsave (list them with 'savelist')"); return; } MenuManager instance = MenuManager.Instance; if ((Object)(object)instance == (Object)null || !instance.IsInMainMenuScene) { log.LogWarning((object)"[SAVE] REFUSED: loadsave is a MAIN MENU verb — a world is already loaded. Use 'goto ' to move within a session."); return; } if (_latched) { log.LogWarning((object)"[SAVE] REFUSED: a loadsave is already in flight this session. Selecting twice corrupts the save-instance holder (it clears its temp list as it selects) and only a restart recovers."); return; } string arg = parts[1]; _latched = true; Runner.Instance.StartCoroutine(LoadSaveRoutine(arg, log)); } private static IEnumerator LoadSaveRoutine(string arg, ManualLogSource log) { float t0 = Time.unscaledTime; while (SaveManager.Instance == null || !SaveManager.Instance.SaveRetrieved) { if (Time.unscaledTime - t0 > 60f) { log.LogError((object)"[SAVE] gave up: SaveManager never reported SaveRetrieved within 60s."); _latched = false; yield break; } yield return null; } while ((Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsStartupVideoActive) { yield return null; } SplitScreenManager ssm = SplitScreenManager.Instance; while ((Object)(object)ssm == (Object)null || ssm.LocalPlayerCount <= 0) { if (Time.unscaledTime - t0 > 60f) { log.LogError((object)"[SAVE] gave up: no local SplitPlayer exists after 60s — the menu never finished standing up."); _latched = false; yield break; } ssm = SplitScreenManager.Instance; yield return null; } SplitPlayer val = ssm.LocalPlayers[0]; if (val != null && (val.IsSaveSelectionPending || val.IsSaveSelctionConfirmed)) { log.LogWarning((object)"[SAVE] REFUSED: a save selection is already pending/confirmed — something else (a click?) got there first."); _latched = false; yield break; } SaveManager instance = SaveManager.Instance; IList list = ((instance != null) ? instance.CharacterSaves : null); if (list == null || list.Count == 0) { log.LogWarning((object)"[SAVE] REFUSED: no character saves available yet (SaveManager still initialising, or SaveGames/ is empty) — run 'savelist' to check, then retry."); _latched = false; yield break; } CharacterSaveInstanceHolder val2 = null; if (int.TryParse(arg, out var result) && result >= 0 && result < list.Count) { val2 = list[result]; } if (val2 == null) { foreach (CharacterSaveInstanceHolder item in list) { if (item != null && string.Equals(item.CharacterUID, arg, StringComparison.OrdinalIgnoreCase)) { val2 = item; break; } } } if (val2 == null) { foreach (CharacterSaveInstanceHolder item2 in list) { if (item2 != null && item2.CharacterUID != null && item2.CharacterUID.StartsWith(arg, StringComparison.OrdinalIgnoreCase)) { val2 = item2; break; } } } if (val2 == null) { foreach (CharacterSaveInstanceHolder item3 in list) { PlayerSaveData val3 = ((item3 == null) ? null : item3.MostRecentInstance?.CharSave?.PSave); if (val3 != null && string.Equals(val3.Name, arg, StringComparison.OrdinalIgnoreCase)) { val2 = item3; break; } } } if (val2 == null) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < list.Count; i++) { StringBuilder stringBuilder2 = stringBuilder.Append((i == 0) ? "" : ", ").Append('[').Append(i) .Append("] "); CharacterSaveInstanceHolder obj = list[i]; stringBuilder2.Append((obj != null) ? obj.CharacterUID : null); } log.LogWarning((object)$"[SAVE] no save matches '{arg}'. Known: {stringBuilder} (full detail via 'savelist')"); _latched = false; yield break; } NetworkLevelLoader instance2 = NetworkLevelLoader.Instance; if ((Object)(object)instance2 != (Object)null) { log.LogMessage((object)$"[SAVE] AutoLoadFirstNextScene was {instance2.AutoLoadFirstNextScene} — forcing true so ConnectionCoroutine takes its world-load branch."); instance2.AutoLoadFirstNextScene = true; } LoadGate.ArmForVerb(log, "loadsave " + val2.CharacterUID); PlayerSaveData val4 = val2.MostRecentInstance?.CharSave?.PSave; log.LogMessage((object)("[SAVE] selecting uid=" + val2.CharacterUID + " name='" + val4?.Name + "' area='" + val4?.AreaName + "' — vanilla load follows (this is the PS5-resume path, PS5ActivityData.cs:326-339).")); CharacterSave val5 = SaveManager.Instance.ChooseCharacterSaveInstance(UID.op_Implicit(val2.CharacterUID), 0); if (val5 == null) { log.LogError((object)"[SAVE] ChooseCharacterSaveInstance returned null — nothing selected; the menu is unchanged."); _latched = false; yield break; } CharacterUI cachedUI = ssm.GetCachedUI(0); if ((Object)(object)cachedUI == (Object)null) { log.LogError((object)"[SAVE] no cached CharacterUI for player 0 — cannot deliver the selection."); _latched = false; } else { cachedUI.OnSaveSelected(val5); log.LogMessage((object)"[SAVE] OnSaveSelected dispatched — watch [LOADGATE] for the load, then 'petstatus'/'pos' once it settles."); } } } internal static class SkillVerbs { private const int ShamanicResonanceItemId = 8205150; internal static void LearnSkillVerb(Character player, string[] parts, ManualLogSource log) { DevSkill.SkillArgs skillArgs = DevSkill.ParseLearn(parts); if (skillArgs.Error != null) { log.LogWarning((object)("[SKILL] parse: " + skillArgs.Error)); return; } if (!ItemNameIndex.TryResolveArg(skillArgs.Name, IsSkill, "skill", out var itemId, out var via, out var problem)) { log.LogWarning((object)("[SKILL] resolve: " + problem)); return; } ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(itemId) : null); if ((Object)(object)val == (Object)null) { log.LogWarning((object)$"[SKILL] prefab: ItemID {itemId} is not a known item prefab."); return; } if (!(val is Skill)) { log.LogWarning((object)$"[SKILL] prefab: '{val.Name}' (ItemID={itemId}) is not a Skill — learnskill takes skills only."); return; } CharacterSkillKnowledge val2 = KnowledgeOf(player); if ((Object)(object)val2 == (Object)null) { log.LogWarning((object)"[SKILL] no SkillKnowledge."); return; } if (((CharacterKnowledge)val2).IsItemLearned(itemId)) { log.LogMessage((object)$"[SKILL] learn: '{val.Name}' (ItemID={itemId}) already learned — nothing to do."); return; } ItemManager instance2 = ItemManager.Instance; if ((Object)(object)instance2 == (Object)null) { log.LogWarning((object)$"[SKILL] learn: no ItemManager (ItemID {itemId} not spawned)."); return; } Item obj = instance2.GenerateItemNetwork(itemId); Skill val3 = (Skill)(object)((obj is Skill) ? obj : null); if (val3 == null) { log.LogWarning((object)$"[SKILL] learn: GenerateItemNetwork({itemId}) didn't return a Skill."); return; } player.Inventory.TryUnlockSkill(val3); if (!((CharacterKnowledge)val2).IsItemLearned(itemId)) { log.LogWarning((object)$"[SKILL] learn: '{((Item)val3).Name}' (ItemID={itemId}) did not stick (TryUnlockSkill is local-player only)."); return; } log.LogMessage((object)$"[SKILL] learned '{((Item)val3).Name}' (ItemID={itemId}) (via {via})."); Notify.Player(player, "Learned " + ((Item)val3).Name); } internal static void UnlearnSkillVerb(Character player, string[] parts, ManualLogSource log) { DevSkill.SkillArgs skillArgs = DevSkill.ParseUnlearn(parts); if (skillArgs.Error != null) { log.LogWarning((object)("[SKILL] parse: " + skillArgs.Error)); return; } CharacterSkillKnowledge val = KnowledgeOf(player); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[SKILL] no SkillKnowledge."); return; } if (skillArgs.All) { List list = new List(); IList learnedItems = ((CharacterKnowledge)val).GetLearnedItems(); if (learnedItems != null) { for (int i = 0; i < learnedItems.Count; i++) { if (learnedItems[i] is Skill) { list.Add(learnedItems[i]); } } } int num = 0; foreach (Item item in list) { if (Destroy(player, val, item, log)) { num++; } } int num2 = list.Count - num; log.LogMessage((object)$"[SKILL] unlearned {num} skill(s) (all)."); if (num2 > 0) { log.LogWarning((object)$"[SKILL] unlearn all: {num2} skill(s) were in knowledge but not destroyable this frame (freshly learned?) — re-run unlearnskill next batch."); } if (num > 0) { Notify.Player(player, $"Unlearned {num} skill(s)"); } return; } if (!ItemNameIndex.TryResolveArg(skillArgs.Name, IsSkill, "skill", out var itemId, out var via, out var problem)) { log.LogWarning((object)("[SKILL] resolve: " + problem)); return; } Item itemFromItemID = ((CharacterKnowledge)val).GetItemFromItemID(itemId); if ((Object)(object)itemFromItemID == (Object)null) { string arg = Label(itemId); log.LogMessage((object)$"[SKILL] unlearn: '{arg}' (ItemID={itemId}) is not learned — nothing to do."); return; } string name = itemFromItemID.Name; if (!Destroy(player, val, itemFromItemID, log)) { log.LogWarning((object)$"[SKILL] unlearn: '{name}' (ItemID={itemId}) is in knowledge but was not destroyable this frame (freshly learned?) — re-run unlearnskill next batch."); return; } log.LogMessage((object)$"[SKILL] unlearned '{name}' (ItemID={itemId}) (via {via})."); Notify.Player(player, "Unlearned " + name); } private static bool Destroy(Character player, CharacterSkillKnowledge knowledge, Item item, ManualLogSource log) { if ((Object)(object)item == (Object)null) { return false; } int itemID = item.ItemID; string uID = item.UID; ItemManager instance = ItemManager.Instance; if ((Object)(object)instance == (Object)null) { log.LogWarning((object)$"[SKILL] unlearn: no ItemManager — '{item.Name}' (ItemID={itemID}) left learned."); return false; } instance.DestroyItem(uID); if ((Object)(object)knowledge != (Object)null && ((CharacterKnowledge)knowledge).IsItemLearned(itemID)) { return false; } if (itemID == 8205150 && (Object)(object)player != (Object)null) { player.HasShamanicResonance = false; log.LogMessage((object)"[SKILL] cleared HasShamanicResonance (vanilla RemoveItem leaves it set)."); } return true; } internal static void CastSpellVerb(Character player, string[] parts, ManualLogSource log) { DevSkill.SkillArgs skillArgs = DevSkill.ParseCast(parts); if (skillArgs.Error != null) { log.LogWarning((object)("[CASTSPELL] parse: " + skillArgs.Error)); return; } CharacterSkillKnowledge val = KnowledgeOf(player); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[CASTSPELL] no SkillKnowledge."); return; } if (!ItemNameIndex.TryResolveArg(skillArgs.Name, IsSkill, "skill", out var itemId, out var via, out var problem)) { log.LogWarning((object)("[CASTSPELL] resolve: " + problem)); return; } Item itemFromItemID = ((CharacterKnowledge)val).GetItemFromItemID(itemId); if ((Object)(object)itemFromItemID == (Object)null) { string arg = Label(itemId); log.LogMessage((object)$"[CASTSPELL] cast: '{arg}' (ItemID={itemId}) is not learned — nothing to cast."); return; } Skill val2 = (Skill)(object)((itemFromItemID is Skill) ? itemFromItemID : null); if (val2 == null) { log.LogWarning((object)$"[CASTSPELL] cast: '{itemFromItemID.Name}' (ItemID={itemId}) is learned but is not a Skill — castspell takes skills only."); return; } string name = ((Item)val2).Name; if (player.IsCasting) { log.LogWarning((object)"[CASTSPELL] refuse: already casting."); return; } if (!player.InLocomotion) { log.LogWarning((object)"[CASTSPELL] refuse: not in locomotion (movement/animation state blocks casts)."); return; } if (!player.NextIsLocomotion) { log.LogWarning((object)"[CASTSPELL] refuse: leaving locomotion (an action/animation is queued)."); return; } if (player.PreparingToSleep) { log.LogWarning((object)"[CASTSPELL] refuse: preparing to sleep."); return; } if (!val2.HasAllRequirements(false)) { log.LogWarning((object)("[CASTSPELL] refuse: '" + name + "' — " + WhyNotReady(player, val2))); return; } log.LogMessage((object)$"[CASTSPELL] casting '{name}' (ItemID={itemId}, via {via})..."); ((Item)val2).TryQuickSlotUse(); if (player.IsCasting || val2.InCooldown()) { log.LogMessage((object)"[CASTSPELL] cast started."); } else { log.LogWarning((object)"[CASTSPELL] call made but no cast observed (CastSpell can discard silently; see state above — re-check via statusdump next poll)."); } } private static string WhyNotReady(Character player, Skill skill) { if (skill.InCooldown()) { float num = skill.RealCooldown * (1f - skill.CoolDownProgress); if (num > 0f) { return $"cooldown {num:F1}s remaining."; } return "cooldown still running (a cast is in progress)."; } float num2 = (((Object)(object)player.Stats != (Object)null) ? player.Stats.GetFinalManaConsumption((Tag[])null, skill.ManaCost) : skill.ManaCost); if (num2 > 0f && player.Mana < num2) { return $"mana {num2:F0} needed, {player.Mana:F0} available."; } float num3 = (((Object)(object)player.Stats != (Object)null) ? player.Stats.GetFinalStaminaConsumption((Tag[])null, skill.GetStaminaCost()) : skill.GetStaminaCost()); if (num3 > 0f && player.Stamina < num3) { return $"stamina {num3:F0} needed, {player.Stamina:F0} available."; } if (skill.HealthCost > 0f && player.Health < skill.HealthCost + 1f) { return $"health {skill.HealthCost:F0} needed, {player.Health:F0} available."; } return "requirements not met (weapon/item requirement?)."; } private static bool IsSkill(Item prefab) { return prefab is Skill; } private static CharacterSkillKnowledge KnowledgeOf(Character player) { CharacterInventory val = (((Object)(object)player != (Object)null) ? player.Inventory : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.SkillKnowledge; } private static string Label(int id) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; Item val = ((instance != null) ? instance.GetItemPrefab(id) : null); if (!((Object)(object)val != (Object)null)) { return id.ToString(); } return val.Name; } internal static void ResetCooldownsVerb(Character player, ManualLogSource log) { CharacterSkillKnowledge val = KnowledgeOf(player); IList list = (((Object)(object)val != (Object)null) ? ((CharacterKnowledge)val).GetLearnedItems() : null); if (list == null) { log.LogWarning((object)"[DEV] no SkillKnowledge."); return; } int num = 0; for (int i = 0; i < list.Count; i++) { Item obj = list[i]; Skill val2 = (Skill)(object)((obj is Skill) ? obj : null); if (val2 != null) { val2.ResetCoolDown(); num++; } } log.LogMessage((object)$"[DEV] reset the cooldown on {num} learned skill(s)."); } } internal static class StageVerbs { private const float FreefallWarnMeters = 3f; private const float FreefallProbeMeters = 500f; internal static void TeleportVerb(Character player, string[] parts, ManualLogSource log) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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_006c: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) if (parts.Length < 4 || !DevNum.TryFinite(parts[1], out var value) || !DevNum.TryFinite(parts[2], out var value2) || !DevNum.TryFinite(parts[3], out var value3)) { log.LogWarning((object)"[DEV] usage: teleport (world coords — see diag/posdump for the current ones)"); return; } Vector3 position = ((Component)player).transform.position; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor((float)value, (float)value2, (float)value3); string text = null; NavMeshHit val2 = default(NavMeshHit); RaycastHit val3 = default(RaycastHit); if (NavMesh.SamplePosition(val, ref val2, 5f, -1)) { if (val.y - ((NavMeshHit)(ref val2)).position.y > 3f) { text = $"{val.y - ((NavMeshHit)(ref val2)).position.y:0}m above the navmesh"; } } else if (Physics.Raycast(val, Vector3.down, ref val3, 500f, -1, (QueryTriggerInteraction)1)) { if (((RaycastHit)(ref val3)).distance > 3f) { text = $"{((RaycastHit)(ref val3)).distance:0}m above the nearest collider"; } } else { text = $"no navmesh within 5m and no collider within {500f:0}m below"; } if (text != null) { log.LogWarning((object)($"[DEV] teleport: ({value:F1}, {value2:F1}, {value3:F1}) is {text} — expect a FALL. " + "teleport writes the height you give it and never probes; use 'walkto ' (ground-snapped) or 'standoff ' (measured distance from the pet) when you do not know the ground height.")); } player.Teleport(val, ((Component)player).transform.rotation); log.LogMessage((object)$"[DEV] teleported ({position.x:F1}, {position.y:F1}, {position.z:F1}) -> ({value:F1}, {value2:F1}, {value3:F1})."); } internal static void GotoVerb(string[] parts, ManualLogSource log) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) DevGoto.GotoArgs gotoArgs = DevGoto.Parse(parts); if (gotoArgs.Error != null) { log.LogWarning((object)("[DEV] usage: " + gotoArgs.Error)); return; } string sceneArg = gotoArgs.SceneArg; int spawnPoint = gotoArgs.SpawnPoint; List similar; string text = BuildScenes.Resolve(sceneArg, out similar); if (text == null) { log.LogWarning((object)("[DEV] goto: '" + sceneArg + "' is not a build-settings scene — " + ((similar.Count > 0) ? ("did you mean: " + string.Join(", ", similar.ToArray())) : "see scenedump") + ".")); return; } NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[DEV] goto: no NetworkLevelLoader yet."); return; } if (LoadGate.LoadInFlight(instance, out var why)) { string[] obj = new string[5] { "[DEV] goto: refused — a load is already in flight (", why, "; scene='", null, null }; Scene activeScene = SceneManager.GetActiveScene(); obj[3] = ((Scene)(ref activeScene)).name; obj[4] = "'). Re-issue after '[LOADGATE] phase=done'."; log.LogWarning((object)string.Concat(obj)); return; } if (Time.timeScale < 0.01f) { float timeScale = Time.timeScale; int gamePausedByPlayer = Global.GamePausedByPlayer; if (gamePausedByPlayer != -1) { PauseMenu.Pause(false); log.LogWarning((object)($"[DEV] goto: timeScale was {timeScale:F3} (pause menu, player {gamePausedByPlayer}) — closed the pause menu " + "before loading; a scaled-time load never advances (BlackFade/Invoke are scaled).")); } else { Time.timeScale = 1f; Time.fixedDeltaTime = 0.022f; log.LogWarning((object)($"[DEV] goto: timeScale was {timeScale:F3} (unclaimed) — restored to 1 before loading; " + "a scaled-time load never advances (BlackFade/Invoke are scaled).")); } } LoadGate.LatchGoto(); if (gotoArgs.ArmWatchdog) { LoadGate.ArmForVerb(log, "goto " + text); } log.LogMessage((object)($"[DEV] goto '{text}' spawnPoint {spawnPoint} — loading (the game saves on an area change)." + (gotoArgs.ArmWatchdog ? " watchdog armed." : " watchdog OFF (nowatch)."))); instance.LoadLevel(text, spawnPoint, 1.5f, true); } internal static void SetTimeVerb(string[] parts, ManualLogSource log) { if (parts.Length < 2 || !DevNum.TryFinite(parts[1], out var value) || value < 0.0 || value >= 24.0) { log.LogWarning((object)"[DEV] usage: settime "); return; } EnvironmentConditions instance = EnvironmentConditions.Instance; if ((Object)(object)instance == (Object)null) { log.LogWarning((object)"[DEV] settime: no EnvironmentConditions in this scene."); return; } float timeOfDay = instance.TimeOfDay; instance.SetTimeOfDay((float)value, false); log.LogMessage((object)$"[DEV] time of day {timeOfDay:F2} -> {instance.TimeOfDay:F2}."); } internal static void GiveMoneyVerb(Character player, string[] parts, ManualLogSource log) { if (parts.Length < 2 || !DevNum.TryInt(parts[1], out var value) || value < 1 || value > 1000000) { log.LogWarning((object)"[DEV] usage: givemoney <1-1000000>"); return; } int containedSilver = player.Inventory.ContainedSilver; player.Inventory.AddMoney(value); log.LogMessage((object)(PhotonNetwork.isNonMasterClientInRoom ? $"[DEV] +{value} silver sent via master RPC — balance updates on sync." : $"[DEV] silver {containedSilver} -> {player.Inventory.ContainedSilver} (+{value}).")); } internal static void SetHpPlayerVerb(Character player, string[] parts, ManualLogSource log) { if (parts.Length == 2) { parts = new string[3] { parts[0], "player", parts[1] }; } DevState.HpArgs hpArgs = DevState.ParseSetHp(parts); if (hpArgs.Error != null) { log.LogWarning((object)("[DEV] sethp: " + hpArgs.Error)); return; } if (hpArgs.Target == DevState.HpTarget.Pet) { log.LogWarning((object)"[DEV] sethp: no pet system on this channel — this sethp sets PLAYER hp ('sethp ')."); return; } CharacterStats stats = player.Stats; if ((Object)(object)stats == (Object)null) { log.LogWarning((object)"[DEV] sethp: no player stats."); return; } float activeMaxHealth = stats.ActiveMaxHealth; float currentHealth = stats.CurrentHealth; stats.SetHealth(Mathf.Clamp((float)(hpArgs.IsPercent ? ((double)activeMaxHealth * hpArgs.Value / 100.0) : hpArgs.Value), 1f, activeMaxHealth)); log.LogMessage((object)$"[DEV] player hp {currentHealth:F0} -> {stats.CurrentHealth:F0}/{activeMaxHealth:F0}."); } internal static void CombatClearVerb(Character player, ManualLogSource log) { log.LogMessage((object)$"[COMBAT] before: InCombat={player.InCombat} engaged={Count(player.EngagedCharacters)} {CheckerState(player)}"); try { player.ClearHostility(); } catch (Exception ex) { log.LogWarning((object)("[COMBAT] ClearHostility threw (" + ex.GetType().Name + ": " + ex.Message + ") — clearing by hand.")); try { player.m_engagedCharacters?.Clear(); } catch { } try { player.m_hostilesCurrentlyChecking?.Clear(); } catch { } try { player.m_timeUnawarePerHostile?.Clear(); } catch { } try { player.m_HostilesWereAware?.Clear(); } catch { } try { player.m_lastDealers?.Clear(); } catch { } if (player.m_hostilityCheckCoroutine != null) { try { ((MonoBehaviour)player).StopCoroutine(player.m_hostilityCheckCoroutine); } catch { } player.m_hostilityCheckCoroutine = null; } try { if ((Object)(object)Global.CombatManager != (Object)null) { Global.CombatManager.RemoveCombatCharacter(player); } } catch { } } log.LogMessage((object)$"[COMBAT] ClearHostility() -> InCombat={player.InCombat} engaged={Count(player.EngagedCharacters)} {CheckerState(player)}."); } private static int Count(List list) { return list?.Count ?? (-1); } private static string CheckerState(Character player) { try { int num = Count(player.m_hostilesCurrentlyChecking); string arg = ((player.m_hostilityCheckCoroutine == null) ? "null" : ((num == 0) ? "non-null with nothing being checked — this handle can only be DEAD" : "non-null (liveness not observable from here — see combatcheck)")); return $"coroutine={arg} checking={num}"; } catch { return "coroutine= checking="; } } internal static void KillNearest(Character player, string[] parts, ManualLogSource log) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) float num = 40f; int num2 = parts.Length; if (num2 > 1 && DevNum.TryFinite(parts[num2 - 1], out var value)) { num = Mathf.Clamp((float)value, 1f, 200f); num2--; } string text = ((num2 > 1) ? string.Join(" ", parts, 1, num2 - 1).Trim() : null); if (string.IsNullOrEmpty(text)) { text = null; } Character val = FindNearestWild(player, num, text); if ((Object)(object)val == (Object)null) { log.LogWarning((object)string.Format("[KILL] no wild '{0}' within {1:0.#}.", text ?? "creature", num)); return; } float num3 = (((Object)(object)val.Stats != (Object)null) ? val.Stats.CurrentHealth : (-1f)); log.LogMessage((object)$"[KILL] overkilling '{val.Name}' (UID {val.UID}, health {num3:0.#}) via ReceiveHit — watch for [TAMEDROP]."); Vector3 val2 = val.CenterPosition - ((Component)player).transform.position; Vector3 normalized = ((Vector3)(ref val2)).normalized; val.ReceiveHit((Weapon)null, 999999f, normalized, val.CenterPosition, 45f, 1f, (Character)null, 0f); if (val.Alive) { log.LogWarning((object)$"[KILL] '{val.Name}' is STILL Alive after the overkill hit (health {(((Object)(object)val.Stats != (Object)null) ? val.Stats.CurrentHealth : (-1f)):0.#}) — resistant/invulnerable? Kill it in ordinary combat instead."); } } internal static void SwingVerb(Character player, string[] parts, ManualLogSource log) { if (parts != null && parts.Length > 1) { log.LogMessage((object)("[SWING] note: v1 takes no arguments — ignoring '" + string.Join(" ", parts, 1, parts.Length - 1) + "'. Target selection is out of scope: position yourself with 'teleport' and face the target.")); } LogState(player, log); DevSwing.GateFacts f = Gate(player); if (player.Sheathed && (Object)(object)player.CurrentWeapon != (Object)null) { log.LogMessage((object)"[SWING] drawing weapon first..."); player.SheatheInput(); ((MonoBehaviour)player).StartCoroutine(DrawThenSwing(player, log)); } else if (!DevSwing.GateOpen(in f)) { ((MonoBehaviour)player).StartCoroutine(WaitForGateThenSwing(player, log)); } else { Strike(player, log); } } private static IEnumerator WaitForGateThenSwing(Character player, ManualLogSource log) { log.LogMessage((object)("[SWING] gate CLOSED — waiting up to 2s for it to open (" + DevSwing.Explain(Gate(player)) + ")")); float deadline = Time.unscaledTime + 2f; while ((Object)(object)player != (Object)null && Time.unscaledTime < deadline) { if (DevSwing.GateOpen(Gate(player))) { Strike(player, log); yield break; } yield return null; } if (!((Object)(object)player == (Object)null)) { log.LogWarning((object)("[SWING] gate never opened within 2s — NOT attacking. " + DevSwing.Explain(Gate(player)) + " A gate that stays closed for a full 2s usually means the WORLD SIM is wedged/paused (the command channel answers anyway — it polls unscaled time): check with 'unstick'.")); } } private static DevSwing.GateFacts Gate(Character player) { return new DevSwing.GateFacts { IsPhotonPlayerLocal = player.IsPhotonPlayerLocal, InLocomotion = player.InLocomotion, NextIsLocomotion = player.NextIsLocomotion, Blocking = player.Blocking, LocomotionAction = player.LocomotionAction, Sheathing = player.Sheathing, InChargeCancelCooldown = player.InChargeCancelCooldown, CancelChargingSent = player.CancelChargingSent, NextAttackAllowed = player.NextAtkAllowed, AttackOnRelease = ((Object)(object)player.CurrentWeapon != (Object)null && player.CurrentWeapon.AttackOnRelease) }; } private static void LogState(Character player, ManualLogSource log) { Weapon currentWeapon = player.CurrentWeapon; CharacterStats stats = player.Stats; string arg = (((Object)(object)currentWeapon != (Object)null) ? (((Item)currentWeapon).Name ?? "?") : "none (unarmed — needs UnarmedHitDetector + 5 stamina)"); string arg2 = (((Object)(object)currentWeapon != (Object)null) ? ((object)Unsafe.As(ref currentWeapon.Type)/*cast due to .constrained prefix*/).ToString() : "-"); DevSwing.GateFacts f = Gate(player); log.LogMessage((object)($"[SWING] state: weapon='{arg}' type={arg2} attackOnRelease={(Object)(object)currentWeapon != (Object)null && currentWeapon.AttackOnRelease} " + $"sheathed={player.Sheathed} inLocomotion={player.InLocomotion} blocking={player.Blocking} " + $"stamina={(((Object)(object)stats != (Object)null) ? stats.CurrentStamina : (-1f)):F0}/{(((Object)(object)stats != (Object)null) ? stats.ActiveMaxStamina : (-1f)):F0} " + string.Format("| gate={0} nextIsLocomotion={1} ", DevSwing.GateOpen(in f) ? "OPEN" : "CLOSED", player.NextIsLocomotion) + $"locomotionAction={player.LocomotionAction} sheathing={player.Sheathing} " + $"chargeCancelCd={player.InChargeCancelCooldown} cancelChargingSent={player.CancelChargingSent} " + $"nextAtkAllowed={player.NextAtkAllowed}.")); } private static void Strike(Character player, ManualLogSource log) { Weapon currentWeapon = player.CurrentWeapon; DevSwing.GateFacts f = Gate(player); bool flag = player.AttackInput(0, 0); if (flag) { log.LogMessage((object)"[SWING] AttackInput(0,0) -> True."); } else { string text = DevSwing.Explain(in f); if (DevSwing.Queued(in f)) { log.LogMessage((object)("[SWING] AttackInput(0,0) -> False — " + text)); } else { log.LogWarning((object)("[SWING] AttackInput(0,0) -> False — " + text)); } } if ((Object)(object)currentWeapon != (Object)null && currentWeapon.AttackOnRelease) { ((MonoBehaviour)player).StartCoroutine(ReleaseCharge(player, flag, log)); } } private static IEnumerator DrawThenSwing(Character player, ManualLogSource log) { float deadline = Time.unscaledTime + 2f; while ((Object)(object)player != (Object)null && (player.Sheathed || player.Sheathing) && Time.unscaledTime < deadline) { yield return null; } if (!((Object)(object)player == (Object)null)) { if (player.Sheathed || player.Sheathing) { log.LogWarning((object)"[SWING] draw refused or timed out — weapon still sheathed; re-run swing next poll."); } else { Strike(player, log); } } } private static IEnumerator ReleaseCharge(Character player, bool pressOk, ManualLogSource log) { yield return (object)new WaitForSecondsRealtime(0.3f); if (!((Object)(object)player == (Object)null)) { player.AttackReleased(0); log.LogMessage((object)("[SWING] AttackReleased(0) fired (charge weapon)." + (pressOk ? "" : " NB the press had returned False — if that was a real precondition failure (not a queued charge), this release may no-op."))); } } internal static Character FindNearestWild(Character player, float range, string speciesFilter) { //IL_0011: 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_0072: Invalid comparison between Unknown and I4 //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) List list = new List(); CharacterManager.Instance.FindCharactersInRange(((Component)player).transform.position, range, ref list); Character result = null; float num = float.MaxValue; foreach (Character item in list) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)player) && item.IsAI && item.Alive && (int)item.Faction != 1 && !((Object)(object)item.OwnerPlayerSys != (Object)null) && ((int)item.Faction != 0 || !IsDialogueNpc(item)) && (string.IsNullOrEmpty(speciesFilter) || string.Equals((item.Name ?? "").Trim(), speciesFilter, StringComparison.OrdinalIgnoreCase))) { float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)item).transform.position); if (num2 < num) { num = num2; result = item; } } } return result; } private static bool IsDialogueNpc(Character c) { if ((Object)(object)((Component)c).GetComponentInChildren(true) != (Object)null) { return true; } if ((Object)(object)((Component)c).GetComponentInChildren(true) != (Object)null) { return true; } return false; } } internal static class StatusVerbs { internal static void GrantStatusVerb(Character player, Func petTarget, string[] parts, ManualLogSource log) { DevStatus.StatusArgs statusArgs = DevStatus.ParseGrant(parts); if (statusArgs.Error != null) { log.LogWarning((object)("[STATUS] parse: " + statusArgs.Error)); } else { if (!PickTarget(player, petTarget, statusArgs.Target, log, out var target, out var label)) { return; } bool flag = statusArgs.Force; if (!StatusNameIndex.TryResolve(statusArgs.Name, out var identifier, out var problem)) { if (!flag || !StatusNameIndex.TryResolve(statusArgs.LiteralName, out var identifier2, out var _)) { log.LogWarning((object)("[STATUS] resolve: " + problem)); return; } identifier = identifier2; flag = false; log.LogMessage((object)("[STATUS] '" + statusArgs.LiteralName + "' is itself a status name — treating the trailing 'force' as part of the name, NOT as the force flag.")); } StatusEffectManager statusEffectMngr = target.StatusEffectMngr; if ((Object)(object)statusEffectMngr == (Object)null) { log.LogWarning((object)("[STATUS] grant: " + label + " has no StatusEffectMngr.")); return; } StatusApplyGate.PreState pre = StatusApplyGate.PreState.Capture(target, identifier); StatusEffect val = statusEffectMngr.AddStatusEffect(identifier); if ((Object)(object)val == (Object)null) { string detail; StatusApplyGate.Gate gate = StatusApplyGate.Diagnose(target, identifier, pre, out detail); bool flag2 = StatusApplyGate.IsForceable(gate); if (!flag) { log.LogWarning((object)($"[STATUS] grant blocked ({gate}): {detail} — target {label}, IsDead={target.IsDead}." + (flag2 ? " This is a DEFENCE, not a broken verb: re-run with a trailing `force` to suspend it for one grant." : " `force` cannot step around this one — it is a property of the target or the status, not a defence.") + " (check statusdump)")); return; } if (!flag2) { log.LogWarning((object)($"[STATUS] grant blocked ({gate}) and `force` REFUSED: {detail} — target {label}. " + "force only suspends status resistance/immunity; forcing past this gate would manufacture a state the game itself cannot produce, which is worse than a failed grant.")); return; } StatusApplyGate.PreState pre2 = StatusApplyGate.PreState.Capture(target, identifier); StatusApplyGate.Bypass bypass = StatusApplyGate.Bypass.Open(target, identifier); try { val = statusEffectMngr.AddStatusEffect(identifier); } finally { bypass?.Close(); } if ((Object)(object)val == (Object)null) { string detail2; StatusApplyGate.Gate gate2 = StatusApplyGate.Diagnose(target, identifier, pre2, out detail2); log.LogWarning((object)($"[STATUS] grant STILL blocked after force ({gate2}): {detail2} — target {label}. " + $"The pre-force reason was {gate}: {detail}.")); return; } log.LogWarning((object)($"[STATUS] FORCED past {gate} on {label} — {detail}. The suspension covered this one grant " + "and has been reverted; the target's real resistance is unchanged, so anything that re-evaluates it (a second application, a build-up tick) will be refused again.")); } string text = val.IdentifierName ?? identifier; StatusEffect statusEffectOfName = statusEffectMngr.GetStatusEffectOfName(text); if ((Object)(object)statusEffectOfName == (Object)null) { log.LogWarning((object)("[STATUS] grant: AddStatusEffect('" + identifier + "') returned an instance but " + label + " does not carry it on readback (applied-then-removed, or absent-until-init) — re-check with statusdump.")); return; } string text2 = Lifespan(statusEffectOfName); string text3 = ((text != identifier) ? (" (requested '" + identifier + "' — engine substituted)") : ""); log.LogMessage((object)("[STATUS] granted '" + text + "'" + text3 + " to " + label + " — " + text2 + ".")); } } internal static void RemoveStatusVerb(Character player, Func petTarget, string[] parts, ManualLogSource log) { DevStatus.StatusArgs statusArgs = DevStatus.ParseRemove(parts); if (statusArgs.Error != null) { log.LogWarning((object)("[STATUS] parse: " + statusArgs.Error)); } else { if (!PickTarget(player, petTarget, statusArgs.Target, log, out var target, out var label)) { return; } if (!StatusNameIndex.TryResolve(statusArgs.Name, out var identifier, out var problem)) { log.LogWarning((object)("[STATUS] resolve: " + problem)); return; } StatusEffectManager statusEffectMngr = target.StatusEffectMngr; if ((Object)(object)statusEffectMngr == (Object)null) { log.LogWarning((object)("[STATUS] remove: " + label + " has no StatusEffectMngr.")); return; } if ((Object)(object)statusEffectMngr.GetStatusEffectOfName(identifier) == (Object)null) { log.LogMessage((object)("[STATUS] '" + identifier + "' is not currently on " + label + " — nothing to remove.")); return; } statusEffectMngr.RemoveStatusWithIdentifierName(identifier); if ((Object)(object)statusEffectMngr.GetStatusEffectOfName(identifier) != (Object)null) { log.LogWarning((object)("[STATUS] remove: '" + identifier + "' is STILL on " + label + " after RemoveStatusWithIdentifierName — it did not take (stacked instance? non-local target on MP?).")); } else { log.LogMessage((object)("[STATUS] removed '" + identifier + "' from " + label + ".")); } } } private static string Lifespan(StatusEffect carried) { if (carried.Permanent) { return "permanent"; } if (carried.RemainingLifespan > 0f) { return $"{carried.RemainingLifespan:0.#}s remaining"; } if (carried.StatusData != null && carried.StatusData.LifeSpan > 0f) { return $"{carried.StatusData.LifeSpan:0.#}s (prefab lifespan; instance still activating)"; } return "0s remaining (no baked lifespan — instant/one-shot effect?)"; } private static bool PickTarget(Character player, Func petTarget, DevStatus.StatusTarget which, ManualLogSource log, out Character target, out string label) { target = null; label = null; if (which == DevStatus.StatusTarget.Player) { target = player; label = "player"; return true; } if (petTarget == null) { log.LogWarning((object)"[STATUS] target=pet: no pet-target provider on this channel."); return false; } Character val = petTarget(); if ((Object)(object)val == (Object)null) { log.LogWarning((object)"[STATUS] target=pet: no active pet."); return false; } target = val; label = "pet '" + val.Name + "'"; return true; } } public static class UsageSpec { public static ArgSpec[] Derive(string[] verbNames, string help) { if (help == null || verbNames == null || verbNames.Length == 0) { return null; } string text = FindUsageClause(verbNames, help); if (text == null) { return null; } List<(string, bool)> list = SplitTokens(text); if (list.Count == 0) { return null; } List list2 = new List(); foreach (var item3 in list) { string item = item3.Item1; bool item2 = item3.Item2; ArgSpec argSpec = TokenToSpec(item, item2); if (argSpec == null) { return null; } list2.Add(argSpec); } if (list2.Count <= 0) { return null; } return list2.ToArray(); } private static string FindUsageClause(string[] verbNames, string help) { foreach (string text in verbNames) { if (string.IsNullOrEmpty(text)) { continue; } int startIndex = 0; while (true) { int num = help.IndexOf("'" + text, startIndex, StringComparison.Ordinal); if (num < 0) { break; } startIndex = num + 1; int num2 = num + 1 + text.Length; if (num2 < help.Length && help[num2] == ' ') { int num3 = help.IndexOf('\'', num2); string text2 = ((num3 < 0) ? help.Substring(num + 1) : help.Substring(num + 1, num3 - num - 1)).Trim(); string text3 = StripVerb(verbNames, text2); if (text3 != null) { return text3; } } } } for (int j = 0; j < help.Length; j++) { if (help[j] == '\'') { int num4 = help.IndexOf('\'', j + 1); if (num4 < 0) { break; } string text4 = help.Substring(j + 1, num4 - j - 1).Trim(); string text5 = StripVerb(verbNames, text4); if (text5 != null) { return text5; } j = num4; } } string text6 = StripVerb(verbNames, help); if (text6 != null) { int num5 = text6.IndexOfAny(new char[3] { ';', '—', '(' }); string text7 = ((num5 < 0) ? text6 : text6.Substring(0, num5)).Trim(); if (text7.IndexOf('<') >= 0 || text7.IndexOf('[') >= 0) { return text7; } } return null; } private static string StripVerb(string[] verbNames, string text) { foreach (string text2 in verbNames) { if (text.Length <= text2.Length || !text.StartsWith(text2, StringComparison.Ordinal)) { continue; } char c = text[text2.Length]; if (c == ' ') { string text3 = text.Substring(text2.Length + 1).Trim(); if (text3.Length > 0 && (text3[0] == '<' || text3[0] == '[')) { return text3; } } } return null; } private static List<(string Body, bool Optional)> SplitTokens(string usage) { List<(string, bool)> list = new List<(string, bool)>(); int num = 0; while (num < usage.Length) { char c = usage[num]; if (c == ' ') { num++; continue; } char c2 = c; if (c2 != '<' && c2 != '[') { int num2 = usage.IndexOf(' ', num); string text = ((num2 < 0) ? usage.Substring(num) : usage.Substring(num, num2 - num)).Trim(); if (text.IndexOf('|') <= 0 || text.IndexOf('<') >= 0 || text.IndexOf('[') >= 0) { break; } list.Add((text, false)); num = ((num2 < 0) ? usage.Length : num2); continue; } char c3 = ((c2 == '<') ? '>' : ']'); int num3 = 1; int i; for (i = num + 1; i < usage.Length; i++) { if (num3 <= 0) { break; } if (usage[i] == c2) { num3++; } else if (usage[i] == c3) { num3--; } } if (num3 != 0) { break; } list.Add((usage.Substring(num + 1, i - num - 2).Trim(), c2 == '[')); num = i; } return list; } private static ArgSpec TokenToSpec(string body, bool optional) { if (body.Length == 0) { return null; } if ((body[0] == '<' && body[body.Length - 1] == '>') || (body[0] == '[' && body[body.Length - 1] == ']')) { body = body.Substring(1, body.Length - 2).Trim(); } if (body.IndexOf('|') >= 0) { string[] array = body.Split(new char[1] { '|' }); string text = null; int num = array[0].IndexOf('='); if (num >= 0) { text = array[0].Substring(0, num + 1).Trim(); } List list = new List(); List list2 = new List(); string[] array2 = array; foreach (string text2 in array2) { string text3 = text2.Trim(); if (text3.Length == 0 || text3.IndexOf(' ') >= 0) { return null; } string type; if (text != null) { list.Add((text3.IndexOf('=') < 0) ? (text + text3) : text3); } else if (!optional && IsExactTypeWord(text3, out type)) { if (!list2.Contains(type)) { list2.Add(type); } } else if (!IsGenericPlaceholder(text3)) { list.Add(text3); } } if (list.Count == 0 && list2.Count == 0) { return new ArgSpec("value", "string", optional); } return new ArgSpec("choice", (list2.Count > 0) ? string.Join("|", list2) : "enum", optional, (list.Count > 0) ? list.ToArray() : null); } string text4 = body; int num2 = text4.IndexOf('='); if (num2 >= 0) { return new ArgSpec(text4.Substring(0, num2), "enum", optional, new string[1] { text4.Substring(0, num2 + 1) }); } bool flag = text4.EndsWith("...") || text4.EndsWith("…"); if (flag) { text4 = text4.TrimEnd('.', '…').Trim(); } if (text4.IndexOf(' ') >= 0) { string text5 = InferType(text4); if (text5 == "string") { return null; } return new ArgSpec(text4.Replace(' ', '-'), text5, optional); } string text6 = (flag ? "rest" : InferType(text4)); if (text4.IndexOf("args", StringComparison.OrdinalIgnoreCase) >= 0) { text6 = "rest"; } if (optional && text6 == "string" && !IsGenericPlaceholder(text4)) { return new ArgSpec(text4, "enum", optional: true, new string[1] { text4 }); } return new ArgSpec(text4, text6, optional); } private static bool IsGenericPlaceholder(string word) { string text = word.ToLowerInvariant().TrimEnd(new char[1] { '%' }); if (text != null) { switch (text.Length) { case 3: switch (text[0]) { case 'u': break; case 't': goto IL_0198; case 'k': goto IL_01ad; case 'q': goto IL_01c2; default: goto end_IL_0027; } if (!(text == "uid")) { break; } goto IL_032e; case 8: { char c = text[0]; if (c != 'o') { if (c != 's' || !(text == "spellkey")) { break; } } else if (!(text == "owneruid")) { break; } goto IL_032e; } case 5: { char c = text[0]; if ((uint)c <= 105u) { if (c != 'c') { if (c != 'i' || !(text == "index")) { break; } } else if (!(text == "count")) { break; } } else if (c != 'l') { if (c != 'n') { if (c != 'v' || !(text == "value")) { break; } } else if (!(text == "npcid")) { break; } } else if (!(text == "label")) { break; } goto IL_032e; } case 6: { char c = text[0]; if ((uint)c <= 102u) { if (c != 'a') { if (c != 'f' || !(text == "filter")) { break; } } else if (!(text == "amount")) { break; } } else if (c != 'm') { if (c != 'r' || !(text == "reason")) { break; } } else if (!(text == "meters")) { break; } goto IL_032e; } case 4: { char c = text[0]; if ((uint)c <= 110u) { if (c != 'h') { if (c != 'n' || !(text == "name")) { break; } } else if (!(text == "hour")) { break; } } else if (c != 's') { if (c != 't' || !(text == "text")) { break; } } else if (!(text == "slot")) { break; } goto IL_032e; } case 1: switch (text[0]) { case 'n': case 'x': case 'y': case 'z': break; default: goto end_IL_0027; } goto IL_032e; case 11: if (!(text == "name-filter")) { break; } goto IL_032e; case 2: if (!(text == "id")) { break; } goto IL_032e; case 10: if (!(text == "gap-meters")) { break; } goto IL_032e; case 7: { if (!(text == "seconds")) { break; } goto IL_032e; } IL_0198: if (!(text == "tag")) { break; } goto IL_032e; IL_01c2: if (!(text == "qty")) { break; } goto IL_032e; IL_01ad: if (!(text == "key")) { break; } goto IL_032e; IL_032e: return true; end_IL_0027: break; } } return false; } private static bool IsExactTypeWord(string word, out string type) { switch (word.ToLowerInvariant()) { case "species": type = "species"; return true; case "scene": type = "scene"; return true; case "skill": type = "skill"; return true; case "status": type = "status"; return true; case "item": case "itemid": type = "item"; return true; default: type = null; return false; } } private static string InferType(string rawName) { string text = rawName.ToLowerInvariant(); if (text.Length > 0 && char.IsDigit(text[0]) && text.IndexOf('-') > 0) { return "int"; } if (text.Contains("species")) { return "species"; } if (text.Contains("scene")) { return "scene"; } if (text.Contains("skill")) { return "skill"; } if (text.Contains("status")) { return "status"; } if (text.Contains("itemid") || text.Contains("item") || text.Contains("name-or")) { return "item"; } if (text != null) { switch (text.Length) { case 5: { char c = text[0]; if (c != 'c') { if (c != 'd') { if (c != 'i' || !(text == "index")) { break; } } else if (!(text == "depth")) { break; } } else if (!(text == "count")) { break; } goto IL_01ae; } case 1: switch (text[0]) { case 'n': case 'x': case 'y': case 'z': break; default: goto end_IL_00b8; } goto IL_01ae; case 6: { char c = text[0]; if (c != 'm') { if (c != 'r' || !(text == "radius")) { break; } } else if (!(text == "meters")) { break; } goto IL_01ae; } case 3: if (!(text == "qty")) { break; } goto IL_01ae; case 7: if (!(text == "seconds")) { break; } goto IL_01ae; case 2: { if (!(text == "id")) { break; } goto IL_01ae; } IL_01ae: return "int"; end_IL_00b8: break; } } if (text.StartsWith("qty ") || text.StartsWith("hour") || text.Contains("radius") || text.Contains("seconds") || text.Contains("count") || text.Contains("degrees") || text.Contains("1-")) { return "int"; } return "string"; } } public sealed class ArgSpec { public string Name; public string Type = "string"; public bool Optional; public string[] Choices; public string Default; public ArgSpec() { } public ArgSpec(string name, string type, bool optional = false, string[] choices = null, string @default = null) { Name = name; Type = type; Optional = optional; Choices = choices; Default = @default; } } public sealed class VerbSpec { public string[] Verbs; public string Help; public ArgSpec[] Args; public string Tag; public bool NeedsPlayer; public bool MasterOnly; public string JoinedVerbs { get { if (Verbs != null) { return string.Join("/", Verbs); } return ""; } } }