using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CG.Client.UserData;
using CG.Ship.Object;
using CG.Space;
using Client.Player.Interactions;
using Gameplay.Mutators;
using Gameplay.Ship.VoidJump;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Photon.Pun;
using ResourceAssets;
using UI.MainHUD;
using UnityEngine;
using VoidManager;
using VoidManager.CustomGUI;
using VoidManager.MPModChecks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Void Tunnel Decay")]
[assembly: AssemblyDescription("Mutator: void-tunnel stability now decays on a fixed real-time clock while travelling, independent of the vanilla per-jump interdiction roll. Left alone long enough in the void, interdiction becomes certain rather than merely likely.")]
[assembly: AssemblyProduct("VoidTunnelDecay")]
[assembly: AssemblyCompany("Jack")]
[assembly: AssemblyFileVersion("0.4.1.0")]
[assembly: ComVisible(false)]
[assembly: AssemblyMetadata("ModCategories", "Mods, Disables Progression")]
[assembly: AssemblyMetadata("MPType", "Client")]
[assembly: AssemblyMetadata("ProgressionFlag", "enabled")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.4.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
internal static class MenuKit
{
private static readonly Dictionary<string, bool> foldouts = new Dictionary<string, bool>();
private static readonly HashSet<string> loggedFailures = new HashSet<string>();
public static void Header(string text)
{
GUILayout.Space(10f);
GUILayout.Label("<b>" + text + "</b>", Array.Empty<GUILayoutOption>());
}
public static void Rule()
{
//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)
//IL_002d: 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_0056: Unknown result type (might be due to invalid IL or missing references)
GUILayout.Space(6f);
Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
Color color = GUI.color;
GUI.color = new Color(1f, 1f, 1f, 0.25f);
GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
GUI.color = color;
GUILayout.Space(6f);
}
public static bool Foldout(string id, string label)
{
foldouts.TryGetValue(id, out var value);
if (GUILayout.Button((value ? "[-] " : "[+] ") + label, Array.Empty<GUILayoutOption>()))
{
value = !value;
foldouts[id] = value;
}
return value;
}
public static void DevZone(string id, Action body)
{
if (!Configs.IsDebugMode)
{
return;
}
GUILayout.Space(12f);
Rule();
if (!Foldout(id + ".dev", "Developer tools"))
{
return;
}
GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
try
{
body();
}
catch (Exception ex)
{
GUILayout.Label("Dev section failed: " + ex.Message + " (see log)", Array.Empty<GUILayoutOption>());
if (loggedFailures.Add(id))
{
Debug.LogError((object)("[MenuKit] dev zone '" + id + "' threw: " + ex));
}
}
finally
{
GUILayout.EndVertical();
}
}
}
internal static class ModFlagPolicy
{
internal const bool FlagsSession = true;
internal const MultiplayerType MPType = (MultiplayerType)20;
internal const SessionModificationEffect NetworkSpawnedEffect = (SessionModificationEffect)2;
internal const SessionModificationEffect RequiredEffect = (SessionModificationEffect)94;
internal const string BuildTag = "enabled";
}
internal static class MutatorSlots
{
private const string TargetType = "Gameplay.Hub.HubMutatorManager";
private const string TargetMethod = "GetMaxMutatorsCount";
internal static ConfigEntry<bool> ExtraSlot;
internal static bool Installed;
internal static string Status = "not installed";
private static ManualLogSource _log;
private static string _tag;
internal static int Bonus
{
get
{
try
{
return (ExtraSlot != null && ExtraSlot.Value) ? 1 : 0;
}
catch
{
return 0;
}
}
}
internal static void Install(ConfigFile config, Harmony harmony, ManualLogSource log, string tag)
{
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_00a3: Expected O, but got Unknown
_log = log;
_tag = tag;
try
{
ExtraSlot = config.Bind<bool>("Mutators", "ExtraMutatorSlot", true, "Raise the Mutators menu's selection limit by one, so this mod's mutator does not cost one of the vanilla slots. Every installed mod with this setting on adds its own +1. The limit is checked by the host, so the host's setting is the one that counts in a multiplayer room. Takes effect immediately.");
if (!Installed)
{
Type type = AccessTools.TypeByName("Gameplay.Hub.HubMutatorManager");
MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "GetMaxMutatorsCount", (Type[])null, (Type[])null));
if (methodInfo == null)
{
Status = "FAILED - Gameplay.Hub.HubMutatorManager.GetMaxMutatorsCount not found (game update?)";
log.LogError((object)(tag + ": extra mutator slot not installed: " + Status));
}
else
{
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(MutatorSlots), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Installed = true;
Status = "installed";
}
}
}
catch (Exception ex)
{
Status = "FAILED - " + ex.GetType().Name;
log.LogError((object)(tag + ": extra mutator slot not installed, vanilla limit stays: " + ex));
}
}
private static void Postfix(ref int __result)
{
try
{
__result += Bonus;
}
catch (Exception ex)
{
if (_log != null)
{
_log.LogWarning((object)(_tag + ": extra mutator slot postfix failed: " + ex));
}
}
}
internal static void Draw()
{
if (ExtraSlot == null)
{
GUILayout.Label("Extra slot : " + Status, Array.Empty<GUILayoutOption>());
return;
}
bool flag = GUILayout.Toggle(ExtraSlot.Value, " Extra mutator slot (+1 to the selection limit; host's setting counts)", Array.Empty<GUILayoutOption>());
if (flag != ExtraSlot.Value)
{
ExtraSlot.Value = flag;
}
if (!Installed)
{
GUILayout.Label("Extra slot : " + Status, Array.Empty<GUILayoutOption>());
}
}
}
internal static class StatContrib
{
internal enum Kind
{
Additive,
Multiplicative,
Override
}
private const string SlotKey = "jack.voidcrew.statcontrib.v1";
internal static ManualLogSource Log;
private static object[] Root()
{
object[] array = AppDomain.CurrentDomain.GetData("jack.voidcrew.statcontrib.v1") as object[];
if (array == null)
{
array = new object[2]
{
new object(),
new Dictionary<object, object>()
};
AppDomain.CurrentDomain.SetData("jack.voidcrew.statcontrib.v1", array);
}
return array;
}
internal static bool Set(object target, string field, string ownerId, Func<float> getter, Action<float> setter, Kind kind, float value, Func<bool> alive = null)
{
if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId) || getter == null || setter == null)
{
Error("StatContrib.Set called with a null argument (field '" + field + "', owner '" + ownerId + "') - ignored.");
return false;
}
if (float.IsNaN(value) || float.IsInfinity(value))
{
Error("StatContrib.Set: owner '" + ownerId + "' offered a non-finite value for '" + field + "' - ignored.");
return false;
}
try
{
object[] array = Root();
lock (array[0])
{
Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
object value2;
Dictionary<string, object> dictionary2 = (Dictionary<string, object>)(dictionary.TryGetValue(target, out value2) ? ((Dictionary<string, object>)value2) : (dictionary[target] = new Dictionary<string, object>()));
object[] array2;
if (dictionary2.TryGetValue(field, out value2))
{
array2 = (object[])value2;
}
else
{
array2 = (object[])(dictionary2[field] = new object[4]
{
getter(),
getter,
setter,
new List<object[]>()
});
Info("captured base " + ((float)array2[0]).ToString("0.####") + " for " + Describe(target, field));
}
List<object[]> list = (List<object[]>)array2[3];
object[] array3 = null;
for (int i = 0; i < list.Count; i++)
{
if ((string)list[i][0] == ownerId)
{
array3 = list[i];
break;
}
}
if (array3 == null)
{
array3 = new object[4] { ownerId, null, null, null };
list.Add(array3);
}
array3[1] = alive;
array3[2] = (int)kind;
array3[3] = value;
Recompute(dictionary2, field, array2);
SweepDeadOwners(dictionary, target, field);
}
return true;
}
catch (Exception ex)
{
Error("StatContrib.Set('" + field + "', '" + ownerId + "') threw: " + ex);
return false;
}
}
internal static void Remove(object target, string field, string ownerId)
{
if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId))
{
return;
}
try
{
object[] array = Root();
lock (array[0])
{
Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
if (!dictionary.TryGetValue(target, out var value))
{
return;
}
Dictionary<string, object> dictionary2 = (Dictionary<string, object>)value;
if (!dictionary2.TryGetValue(field, out value))
{
return;
}
object[] array2 = (object[])value;
List<object[]> list = (List<object[]>)array2[3];
bool flag = false;
for (int num = list.Count - 1; num >= 0; num--)
{
if ((string)list[num][0] == ownerId)
{
list.RemoveAt(num);
flag = true;
}
}
if (flag)
{
Recompute(dictionary2, field, array2);
if (!dictionary2.ContainsKey(field) && dictionary2.Count == 0)
{
dictionary.Remove(target);
}
SweepDeadOwners(dictionary, target, field);
}
}
}
catch (Exception ex)
{
Error("StatContrib.Remove('" + field + "', '" + ownerId + "') threw: " + ex);
}
}
internal static string DescribeField(object target, string field)
{
try
{
object[] array = Root();
lock (array[0])
{
Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
if (target == null || !dictionary.TryGetValue(target, out var value))
{
return null;
}
if (!((Dictionary<string, object>)value).TryGetValue(field, out value))
{
return null;
}
object[] array2 = (object[])value;
List<object[]> list = (List<object[]>)array2[3];
string text = field + ": base " + ((float)array2[0]).ToString("0.####");
for (int i = 0; i < list.Count; i++)
{
text = text + ", " + (string)list[i][0] + " " + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + " " + ((float)list[i][3]).ToString("0.####");
}
return text;
}
}
catch
{
return null;
}
}
private static void Recompute(Dictionary<string, object> byField, string field, object[] entry)
{
List<object[]> list = (List<object[]>)entry[3];
for (int num = list.Count - 1; num >= 0; num--)
{
Func<bool> func = list[num][1] as Func<bool>;
bool flag = true;
if (func != null)
{
try
{
flag = func();
}
catch
{
flag = false;
}
}
if (!flag)
{
Info("dropping contribution '" + (string)list[num][0] + "' to '" + field + "' - its owner is gone.");
list.RemoveAt(num);
}
}
float num2 = (float)entry[0];
Action<float> setter = (Action<float>)entry[2];
if (list.Count == 0)
{
byField.Remove(field);
TryWrite(setter, num2, field, "base restore");
return;
}
float num3 = num2;
float num4 = 1f;
object[] array = null;
for (int i = 0; i < list.Count; i++)
{
float num5 = (float)list[i][3];
switch ((Kind)(int)list[i][2])
{
case Kind.Additive:
num3 += num5;
break;
case Kind.Multiplicative:
num4 *= num5;
break;
case Kind.Override:
array = list[i];
break;
}
}
float num6;
if (array != null)
{
num6 = (float)array[3];
if (list.Count > 1)
{
Info("override '" + (string)array[0] + "' wins '" + field + "' over " + (list.Count - 1) + " other contribution(s).");
}
}
else
{
num6 = num3 * num4;
}
if (float.IsNaN(num6) || float.IsInfinity(num6))
{
Error("composed value for '" + field + "' is non-finite; writing the base back instead. " + DescribeEntry(field, entry));
num6 = num2;
}
TryWrite(setter, num6, field, "recompute");
}
private static void SweepDeadOwners(Dictionary<object, object> entries, object exceptTarget, string exceptField)
{
List<object> list = null;
foreach (KeyValuePair<object, object> entry in entries)
{
Dictionary<string, object> dictionary = (Dictionary<string, object>)entry.Value;
List<string> list2 = null;
foreach (KeyValuePair<string, object> item in dictionary)
{
if (entry.Key == exceptTarget && item.Key == exceptField)
{
continue;
}
List<object[]> list3 = (List<object[]>)((object[])item.Value)[3];
for (int i = 0; i < list3.Count; i++)
{
Func<bool> func = list3[i][1] as Func<bool>;
bool flag = true;
if (func != null)
{
try
{
flag = func();
}
catch
{
flag = false;
}
}
if (!flag)
{
List<string> obj2 = list2 ?? new List<string>();
list2 = obj2;
obj2.Add(item.Key);
break;
}
}
}
if (list2 != null)
{
for (int j = 0; j < list2.Count; j++)
{
Recompute(dictionary, list2[j], (object[])dictionary[list2[j]]);
}
}
if (dictionary.Count == 0)
{
List<object> obj3 = list ?? new List<object>();
list = obj3;
obj3.Add(entry.Key);
}
}
if (list != null)
{
for (int k = 0; k < list.Count; k++)
{
entries.Remove(list[k]);
}
}
}
private static void TryWrite(Action<float> setter, float value, string field, string why)
{
try
{
setter(value);
}
catch (Exception ex)
{
Info("write to '" + field + "' (" + why + ") failed: " + ex.Message);
}
}
private static string Describe(object target, string field)
{
string text;
try
{
text = target.GetType().Name;
}
catch
{
text = "<target>";
}
return text + "." + field;
}
private static string DescribeEntry(string field, object[] entry)
{
List<object[]> list = (List<object[]>)entry[3];
string text = "base " + ((float)entry[0]).ToString("0.####") + ";";
for (int i = 0; i < list.Count; i++)
{
text = text + " " + (string)list[i][0] + "=" + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + ":" + ((float)list[i][3]).ToString("0.####");
}
return text;
}
private static void Info(string m)
{
if (Log != null)
{
Log.LogInfo((object)("StatContrib: " + m));
}
}
private static void Error(string m)
{
if (Log != null)
{
Log.LogError((object)("StatContrib: " + m));
}
}
}
internal interface ITuningKnob
{
string Name { get; }
string Help { get; }
bool IsInert { get; }
void WriteJson(JObject o);
void ReadJson(JObject o);
string Describe();
void DrawMenuRow();
string BakeSnippet();
}
internal sealed class TuningFile
{
private readonly string _guid;
private readonly string _tag;
private readonly string _readme;
private readonly ManualLogSource _log;
private readonly List<ITuningKnob> _order = new List<ITuningKnob>();
private readonly Dictionary<string, ITuningKnob> _byName = new Dictionary<string, ITuningKnob>(StringComparer.OrdinalIgnoreCase);
private readonly List<Action> _reloadHandlers = new List<Action>();
private DateTime _lastWrite = DateTime.MinValue;
private JObject _baseline;
private JObject _previousBaseline;
internal string LastBakePath = "<bake not run>";
private string _menuSignature;
internal string Status { get; private set; } = "not loaded";
internal string Path { get; private set; }
internal string BaselinePath { get; private set; }
internal IList<ITuningKnob> Knobs => _order;
internal bool AllInert
{
get
{
for (int i = 0; i < _order.Count; i++)
{
if (!_order[i].IsInert)
{
return false;
}
}
return true;
}
}
internal TuningFile(string pluginGuid, string logTag, string readme, ManualLogSource log)
{
_guid = pluginGuid;
_tag = logTag;
_readme = readme;
_log = log;
}
internal T Add<T>(T knob) where T : ITuningKnob
{
if (knob == null)
{
return default(T);
}
if (_byName.ContainsKey(knob.Name))
{
Warn("duplicate tuning knob '" + knob.Name + "' ignored. Two declarations of one name means one of them is silently dead.");
return knob;
}
_byName[knob.Name] = knob;
_order.Add(knob);
return knob;
}
internal ITuningKnob Find(string name)
{
if (!_byName.TryGetValue(name, out var value))
{
return null;
}
return value;
}
internal void OnReload(Action handler)
{
if (handler != null && !_reloadHandlers.Contains(handler))
{
_reloadHandlers.Add(handler);
}
}
internal void Initialise(string configDir)
{
try
{
Path = System.IO.Path.Combine(configDir, _guid + ".tuning.json");
BaselinePath = System.IO.Path.Combine(configDir, _guid + ".tuning.baseline.json");
_baseline = BuildKnobObject();
_previousBaseline = ReadPreviousBaseline();
WriteBaseline();
if (!File.Exists(Path))
{
Save("created at the declared defaults");
}
else
{
Load();
}
}
catch (Exception e)
{
Status = "FAILED to initialise: " + Brief(e);
Error(Status);
}
}
internal bool PollForChanges()
{
if (string.IsNullOrEmpty(Path))
{
return false;
}
try
{
if (!File.Exists(Path))
{
return false;
}
if (File.GetLastWriteTimeUtc(Path) <= _lastWrite)
{
return false;
}
Load();
Info("tuning reloaded - " + Status);
FireReloadHandlers();
return true;
}
catch (Exception e)
{
Warn("tuning poll threw: " + Brief(e));
return false;
}
}
private void FireReloadHandlers()
{
for (int i = 0; i < _reloadHandlers.Count; i++)
{
try
{
_reloadHandlers[i]();
}
catch (Exception ex)
{
Error("a tuning re-apply handler threw: " + ex);
}
}
}
private void Load()
{
string text;
try
{
text = File.ReadAllText(Path);
}
catch (Exception e)
{
Status = "could not read the file (" + Brief(e) + "); previous values stand";
Error(Status);
return;
}
_lastWrite = File.GetLastWriteTimeUtc(Path);
JObject val;
try
{
val = JObject.Parse(text);
}
catch (Exception e2)
{
Status = "MALFORMED tuning file (" + Brief(e2) + "); previous values stand";
Error(Status + ". Fix " + Path + " - nothing was applied from it.");
return;
}
JToken obj = val["knobs"];
JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
if (val2 == null)
{
Status = "tuning file has no 'knobs' object; every knob left at its declared default";
Warn(Status);
return;
}
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
foreach (KeyValuePair<string, JToken> item in val2)
{
if (!_byName.TryGetValue(item.Key, out var value))
{
num2++;
Warn("tuning file names unknown knob '" + item.Key + "' - ignored. This is a typo or a rename, not a tuning value.");
continue;
}
JToken value2 = item.Value;
JObject val3 = (JObject)(object)((value2 is JObject) ? value2 : null);
if (val3 == null)
{
continue;
}
if (FollowsNewDeclaration(item.Key, val3))
{
num4++;
num++;
continue;
}
try
{
value.ReadJson(val3);
}
catch (Exception e3)
{
Warn("knob '" + item.Key + "' could not be read: " + Brief(e3));
}
num++;
if (!value.IsInert)
{
num3++;
}
}
Status = num + " knob(s) read, " + num3 + " active" + ((num2 > 0) ? (", " + num2 + " unknown name(s) ignored") : "") + ((num4 > 0) ? (", " + num4 + " untouched knob(s) moved to this build's new declared value") : "");
if (num3 == 0)
{
Status += " - every knob inert";
}
int count = DriftReport().Count;
if (count > 0)
{
Status = Status + "; " + count + " differ(s) from source";
}
if (num2 > 0 || num < _order.Count || num4 > 0)
{
string status = Status;
Save("rewritten to match this version's knobs (" + (_order.Count - num) + " added, " + num2 + " stale name(s) dropped)");
Status = status + "; file rewritten to match this version's knobs";
}
}
private JObject ReadPreviousBaseline()
{
try
{
if (!File.Exists(BaselinePath))
{
return null;
}
JToken obj = JObject.Parse(File.ReadAllText(BaselinePath))["knobs"];
return (JObject)(object)((obj is JObject) ? obj : null);
}
catch
{
return null;
}
}
private bool FollowsNewDeclaration(string name, JObject fromFile)
{
if (_previousBaseline == null || _baseline == null)
{
return false;
}
JToken obj = _previousBaseline[name];
JObject val = (JObject)(object)((obj is JObject) ? obj : null);
JToken obj2 = _baseline[name];
JObject val2 = (JObject)(object)((obj2 is JObject) ? obj2 : null);
if (val == null || val2 == null)
{
return false;
}
if (JToken.DeepEquals((JToken)(object)val, (JToken)(object)val2))
{
return false;
}
return JToken.DeepEquals((JToken)(object)val, (JToken)(object)fromFile);
}
internal void Save(string why)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
try
{
JObject val = new JObject();
val["_readme"] = JToken.op_Implicit(_readme + " The '_' field in each knob is a comment and is ignored. Saved changes are picked up within a second - no restart. This file is NOT shipped: values dialled in here reach nobody else until they are written back into the mod's source declarations.");
val["knobs"] = (JToken)(object)BuildKnobObject();
EnsureDir(Path);
File.WriteAllText(Path, ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>()));
_lastWrite = File.GetLastWriteTimeUtc(Path);
Status = why;
Info("tuning file " + why + " -> " + Path);
}
catch (Exception e)
{
Status = "could not write the tuning file: " + Brief(e);
Error(Status);
}
}
private JObject BuildKnobObject()
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Expected O, but got Unknown
JObject val = new JObject();
for (int i = 0; i < _order.Count; i++)
{
ITuningKnob tuningKnob = _order[i];
JObject val2 = new JObject();
val2["_"] = JToken.op_Implicit(tuningKnob.Help);
tuningKnob.WriteJson(val2);
val[tuningKnob.Name] = (JToken)(object)val2;
}
return val;
}
private void WriteBaseline()
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
try
{
JObject val = new JObject();
val["_readme"] = JToken.op_Implicit("Generated every launch. These are the knob values as DECLARED IN SOURCE, which is what a build handed to anyone else runs at. Diff the sibling .tuning.json against this to see which dialled-in numbers would be lost by shipping. Do not edit; edit the source declarations and relaunch.");
val["knobs"] = (JToken)(object)_baseline;
EnsureDir(BaselinePath);
File.WriteAllText(BaselinePath, ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>()));
}
catch (Exception e)
{
Warn("could not write the tuning baseline (" + Brief(e) + "). The release drift check has nothing to compare against for this mod.");
}
}
internal List<string> DriftReport()
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Expected O, but got Unknown
List<string> list = new List<string>();
if (_baseline == null)
{
return list;
}
for (int i = 0; i < _order.Count; i++)
{
ITuningKnob tuningKnob = _order[i];
JObject val = new JObject();
tuningKnob.WriteJson(val);
JToken obj = _baseline[tuningKnob.Name];
JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
if (val2 == null)
{
continue;
}
foreach (KeyValuePair<string, JToken> item in val)
{
JToken val3 = val2[item.Key];
if (val3 != null && !JToken.DeepEquals(val3, item.Value))
{
list.Add(tuningKnob.Name + "." + item.Key + ": source " + ((object)val3)?.ToString() + " -> live " + (object)item.Value);
}
}
}
return list;
}
internal string WriteBakeSnippet(string outputDir)
{
try
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("// " + _tag + " - tuning bake, taken from the live tuning file.");
stringBuilder.AppendLine("//");
stringBuilder.AppendLine("// Paste over the knob declarations. After this the declared defaults");
stringBuilder.AppendLine("// and the tuning file agree, the drift check is clean, and a build");
stringBuilder.AppendLine("// handed to anyone else plays at the numbers you dialled in.");
stringBuilder.AppendLine("//");
stringBuilder.AppendLine("// Source : " + (Path ?? "<none>"));
stringBuilder.AppendLine("// State : " + Status);
stringBuilder.AppendLine();
for (int i = 0; i < _order.Count; i++)
{
stringBuilder.AppendLine(_order[i].BakeSnippet());
}
List<string> list = DriftReport();
stringBuilder.AppendLine();
if (list.Count == 0)
{
stringBuilder.AppendLine("// Nothing has drifted - source already matches the tuning file.");
}
else
{
stringBuilder.AppendLine("// Differs from source (" + list.Count + "):");
for (int j = 0; j < list.Count; j++)
{
stringBuilder.AppendLine("// " + list[j]);
}
}
string text = System.IO.Path.Combine(outputDir, _guid + ".tuning.bake.cs.txt");
EnsureDir(text);
File.WriteAllText(text, stringBuilder.ToString());
Info("tuning bake snippet -> " + text);
return text;
}
catch (Exception ex)
{
Error("tuning bake snippet threw: " + ex);
return "<failed: " + Brief(ex) + ">";
}
}
internal void DrawMenu()
{
try
{
GUILayout.Label("Tuning : " + Status, Array.Empty<GUILayoutOption>());
GUILayout.Label(" " + (Path ?? "<not initialised>"), Array.Empty<GUILayoutOption>());
for (int i = 0; i < _order.Count; i++)
{
_order[i].DrawMenuRow();
}
string text = MenuSignature();
if (_menuSignature == null)
{
_menuSignature = text;
}
else if (text != _menuSignature)
{
_menuSignature = text;
FireReloadHandlers();
}
List<string> list = DriftReport();
GUILayout.Space(6f);
if (list.Count == 0)
{
GUILayout.Label("Source and tuning file agree - safe to package.", Array.Empty<GUILayoutOption>());
}
else
{
GUILayout.Label("NOT IN SOURCE (" + list.Count + ") - a build sent to anyone else", Array.Empty<GUILayoutOption>());
GUILayout.Label("would run at the source values, not these:", Array.Empty<GUILayoutOption>());
for (int j = 0; j < list.Count; j++)
{
GUILayout.Label(" " + list[j], Array.Empty<GUILayoutOption>());
}
}
GUILayout.Space(4f);
if (GUILayout.Button("Save these values to the tuning file", Array.Empty<GUILayoutOption>()))
{
Save("saved from the F5 menu");
}
if (GUILayout.Button("Reload the tuning file now", Array.Empty<GUILayoutOption>()))
{
_lastWrite = DateTime.MinValue;
PollForChanges();
}
if (GUILayout.Button("Write the bake snippet (the release step)", Array.Empty<GUILayoutOption>()))
{
LastBakePath = WriteBakeSnippet(Paths.ConfigPath);
}
GUILayout.Label(" " + LastBakePath, Array.Empty<GUILayoutOption>());
}
catch (Exception e)
{
GUILayout.Label("Tuning menu failed to draw: " + Brief(e), Array.Empty<GUILayoutOption>());
}
}
private string MenuSignature()
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Expected O, but got Unknown
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < _order.Count; i++)
{
JObject val = new JObject();
_order[i].WriteJson(val);
stringBuilder.Append(((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()));
}
return stringBuilder.ToString();
}
private static void EnsureDir(string file)
{
string directoryName = System.IO.Path.GetDirectoryName(file);
if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
}
private static string Brief(Exception e)
{
if (e != null)
{
return e.GetType().Name + ": " + e.Message;
}
return "<null>";
}
private void Info(string m)
{
if (_log != null)
{
_log.LogInfo((object)(_tag + ": " + m));
}
}
private void Warn(string m)
{
if (_log != null)
{
_log.LogWarning((object)(_tag + ": " + m));
}
}
private void Error(string m)
{
if (_log != null)
{
_log.LogError((object)(_tag + ": " + m));
}
}
internal static string F(float v)
{
return v.ToString("0.####", CultureInfo.InvariantCulture);
}
}
internal sealed class TuningScalar : ITuningKnob
{
public float Value;
public readonly float Declared;
public readonly float Min;
public readonly float Max;
public readonly float Sentinel;
private readonly NumericField _field = new NumericField();
public string Name { get; private set; }
public string Help { get; private set; }
public bool IsInert
{
get
{
if (!float.IsNaN(Sentinel) && Nearly(Value, Sentinel))
{
return true;
}
return Nearly(Value, Declared);
}
}
public bool IsSentinel
{
get
{
if (!float.IsNaN(Sentinel))
{
return Nearly(Value, Sentinel);
}
return false;
}
}
internal TuningScalar(string name, string help, float declared, float min, float max, float sentinel = float.NaN)
{
Name = name;
Help = help;
Value = declared;
Declared = declared;
Min = min;
Max = max;
Sentinel = sentinel;
}
public void WriteJson(JObject o)
{
o["value"] = JToken.op_Implicit(Value);
}
public void ReadJson(JObject o)
{
JToken val = o["value"];
if (val == null)
{
return;
}
try
{
float num = Extensions.Value<float>((IEnumerable<JToken>)val);
if (!float.IsNaN(num) && !float.IsInfinity(num))
{
Value = num;
}
}
catch
{
}
}
public string Describe()
{
if (!IsSentinel)
{
return TuningFile.F(Value);
}
return "keep the game's own value";
}
public void DrawMenuRow()
{
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label(Name + " " + Describe(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) });
Value = _field.Draw(Value);
Value = GUILayout.HorizontalSlider(Value, Min, Max, Array.Empty<GUILayoutOption>());
GUILayout.EndHorizontal();
}
public string BakeSnippet()
{
string text = (float.IsNaN(Sentinel) ? "" : (", " + TuningFile.F(Sentinel) + "f"));
return "Scalar(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + TuningFile.F(Value) + "f, " + TuningFile.F(Min) + "f, " + TuningFile.F(Max) + "f" + text + ");";
}
private static bool Nearly(float a, float b)
{
return Math.Abs(a - b) < 0.0001f;
}
}
internal sealed class TuningString : ITuningKnob
{
public string Value;
public readonly string Declared;
public string Name { get; private set; }
public string Help { get; private set; }
public bool HasValue => !string.IsNullOrEmpty(Value);
public bool IsInert => string.Equals(Value, Declared, StringComparison.Ordinal);
internal TuningString(string name, string help, string declared)
{
Name = name;
Help = help;
Value = declared ?? "";
Declared = declared ?? "";
}
public void WriteJson(JObject o)
{
o["text"] = JToken.op_Implicit(Value ?? "");
}
public void ReadJson(JObject o)
{
JToken val = o["text"];
if (val == null)
{
return;
}
try
{
string text = Extensions.Value<string>((IEnumerable<JToken>)val);
Value = (text ?? "").Trim();
}
catch
{
}
}
public string Describe()
{
if (!HasValue)
{
return "the game's own choice";
}
return Value;
}
public void DrawMenuRow()
{
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label(Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) });
Value = GUILayout.TextField(Value ?? "", Array.Empty<GUILayoutOption>()) ?? "";
GUILayout.EndHorizontal();
}
public string BakeSnippet()
{
return "Text(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", \"" + (Value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"") + "\");";
}
}
internal sealed class NumericField
{
private string _text;
private float _synced = float.NaN;
internal float Draw(float value, float width = 64f, bool integer = false)
{
if (_text == null || value != _synced)
{
_text = (integer ? ((int)Math.Round(value)).ToString(CultureInfo.InvariantCulture) : TuningFile.F(value));
_synced = value;
}
string text = GUILayout.TextField(_text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) });
if (text == _text)
{
return value;
}
_text = text;
if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return value;
}
if (float.IsNaN(result) || float.IsInfinity(result))
{
return value;
}
if (integer)
{
result = (float)Math.Round(result);
}
_synced = result;
return result;
}
}
internal sealed class TuningToggle : ITuningKnob
{
public bool Value;
public readonly bool Declared;
public string Name { get; private set; }
public string Help { get; private set; }
public bool IsInert => Value == Declared;
internal TuningToggle(string name, string help, bool declared)
{
Name = name;
Help = help;
Value = declared;
Declared = declared;
}
public void WriteJson(JObject o)
{
o["on"] = JToken.op_Implicit(Value);
}
public void ReadJson(JObject o)
{
JToken val = o["on"];
if (val == null)
{
return;
}
try
{
Value = Extensions.Value<bool>((IEnumerable<JToken>)val);
}
catch
{
}
}
public string Describe()
{
if (!Value)
{
return "off";
}
return "on";
}
public void DrawMenuRow()
{
Value = GUILayout.Toggle(Value, " " + Name + " - " + Help, Array.Empty<GUILayoutOption>());
}
public string BakeSnippet()
{
return "Toggle(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + (Value ? "true" : "false") + ");";
}
}
namespace VoidCrewVoidTunnelDecay
{
[BepInPlugin("jack.voidcrew.voidtunneldecay", "Void Tunnel Decay", "0.4.1")]
[BepInProcess("Void Crew.exe")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
internal static ManualLogSource Log;
internal static bool PatchesApplied;
private void Awake()
{
//IL_009d: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Expected O, but got Unknown
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
Log = ((BaseUnityPlugin)this).Logger;
VtdConfig.Bind(((BaseUnityPlugin)this).Config);
VtdTuning.Initialise(Paths.ConfigPath);
if (!VtdConfig.Enabled.Value)
{
Log.LogWarning((object)"VoidTunnelDecay: disabled via config ([General] Enabled = false). No patches applied, no mutator registered - vanilla tunnel-stability behaviour only.");
return;
}
Harmony harmony;
try
{
harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "jack.voidcrew.voidtunneldecay");
PatchesApplied = true;
}
catch (Exception ex)
{
PatchesApplied = false;
Log.LogError((object)("VoidTunnelDecay: patching failed, going inert - vanilla tunnel-stability behaviour only: " + ex));
return;
}
MutatorSlots.Install(((BaseUnityPlugin)this).Config, harmony, Log, "VoidTunnelDecay");
GameObject val = new GameObject("VoidTunnelDecay.Driver");
Object.DontDestroyOnLoad((Object)val);
((Object)val).hideFlags = (HideFlags)61;
val.AddComponent<VtdDriver>();
Log.LogInfo((object)("Void Tunnel Decay v0.4.1 loaded. Stability decays " + VtdConfig.PercentPerTick.Value + "%/" + VtdConfig.TickIntervalSeconds.Value + "s while travelling the void tunnel, once selected in the Mutators menu."));
}
}
internal class VtdDriver : MonoBehaviour
{
private float _nextRegister;
private float _nextTuningPoll;
private void Update()
{
try
{
float unscaledTime = Time.unscaledTime;
if (!VtdRegistration.Registered && unscaledTime >= _nextRegister)
{
_nextRegister = unscaledTime + 2f;
VtdRegistration.EnsureRegistered();
}
if (!(unscaledTime < _nextTuningPoll))
{
_nextTuningPoll = unscaledTime + 1f;
VtdTuning.PollForChanges();
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("VoidTunnelDecay: driver poll failed: " + ex));
}
}
}
public class VtdVoidPlugin : VoidPlugin
{
public override MultiplayerType MPType => (MultiplayerType)8;
public override string Author => "Jack";
public override string Description => "Mutator: void-tunnel stability now decays on a fixed real-time clock while travelling, independent of the vanilla per-jump interdiction roll. Left alone long enough in the void, interdiction becomes certain rather than merely likely.";
public override string ThunderstoreID => "Jack_Modding/Void_Tunnel_Decay";
public override SessionChangedReturn OnSessionChange(SessionChangedInput input)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
return new SessionChangedReturn
{
SetMod_Session = VtdMutator.Active
};
}
}
public class VtdSettingsMenu : ModSettingsMenu
{
public override string Name()
{
return "Void Tunnel Decay";
}
public override void Draw()
{
GUILayout.Label("Mutator : " + VtdRegistration.Status, Array.Empty<GUILayoutOption>());
GUILayout.Label("Active now : " + (VtdMutator.Active ? "yes" : "no") + (VtdMutator.ForcedOn ? " (testing override forced on)" : ""), Array.Empty<GUILayoutOption>());
GUILayout.Label("Enforcing : " + (VtdMutator.Enforcing ? "yes" : (VtdMutator.Active ? "no - quest disallows interdiction, immune" : "no")), Array.Empty<GUILayoutOption>());
GUILayout.Label("Rate : " + VtdConfig.PercentPerTick.Value + "% / " + VtdConfig.TickIntervalSeconds.Value + "s", Array.Empty<GUILayoutOption>());
GUILayout.Label("Full drain : " + 100f / VtdConfig.PercentPerTick.Value * VtdConfig.TickIntervalSeconds.Value + "s of uninterrupted travel per jump", Array.Empty<GUILayoutOption>());
GUILayout.Space(8f);
MutatorSlots.Draw();
GUILayout.Space(8f);
GUILayout.Label("Config: BepInEx/config/jack.voidcrew.voidtunneldecay.cfg", Array.Empty<GUILayoutOption>());
MenuKit.DevZone("void-tunnel-decay", DrawDevTools);
}
private static void DrawDevTools()
{
GUILayout.Label("Patches : " + (Plugin.PatchesApplied ? "applied" : "NOT APPLIED - inert"), Array.Empty<GUILayoutOption>());
GUILayout.Label("Last roll : gate=" + VtdMutator.LastGateValue.ToString("0.###") + " roll=" + VtdMutator.LastRollValue.ToString("0.###") + " (also LogDebug'd every tick as 'VoidTunnelDecay: tick' - enable Debug in BepInEx.cfg's LogLevels to capture it in LogOutput.log)", Array.Empty<GUILayoutOption>());
GUILayout.Space(10f);
GUILayout.Label("TESTING ONLY - never shipped. Bake into source before packaging.", Array.Empty<GUILayoutOption>());
VtdTuning.File?.DrawMenu();
}
}
internal static class VtdPluginInfo
{
public const string PLUGIN_GUID = "jack.voidcrew.voidtunneldecay";
public const string PLUGIN_NAME = "VoidTunnelDecay";
public const string USERS_PLUGIN_NAME = "Void Tunnel Decay";
public const string PLUGIN_VERSION = "0.4.1";
public const string PLUGIN_DESCRIPTION = "Mutator: void-tunnel stability now decays on a fixed real-time clock while travelling, independent of the vanilla per-jump interdiction roll. Left alone long enough in the void, interdiction becomes certain rather than merely likely.";
public const string PLUGIN_AUTHORS = "Jack";
public const string PLUGIN_THUNDERSTORE_ID = "Jack_Modding/Void_Tunnel_Decay";
public const float RUN_XP_BONUS = 0.25f;
public const string MUTATOR_GUID = "7cb9e33adfb6443888d7fe719c27e65d";
public const string MUTATOR_PATH = "Mutators/Mutator_InterdictionDecay";
public const string MUTATOR_NAME = "Interdiction Decay";
public const string MUTATOR_DESC = "Void-tunnel stability decays on a fixed clock while travelling, independent of the normal chance of interdiction. Left alone in the void long enough, interdiction stops being a matter of luck.";
public const string MUTATOR_ICON_SPRITE_NAME = "icon_voidsanction";
}
internal static class Refl
{
private const BindingFlags ALL = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
public static FieldInfo Field(Type t, string name)
{
while (t != null)
{
FieldInfo field = t.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field;
}
t = t.BaseType;
}
return null;
}
public static object GetField(object target, string name)
{
if (target == null)
{
return null;
}
FieldInfo fieldInfo = Field(target.GetType(), name);
if (!(fieldInfo == null))
{
return fieldInfo.GetValue(target);
}
return null;
}
public static bool SetField(object target, string name, object value)
{
if (target == null)
{
return false;
}
FieldInfo fieldInfo = Field(target.GetType(), name);
if (fieldInfo == null)
{
return false;
}
fieldInfo.SetValue(target, value);
return true;
}
public static string Brief(Exception e)
{
if (e != null)
{
return e.GetType().Name + ": " + e.Message;
}
return "<null>";
}
}
internal static class VtdConfig
{
internal static ConfigEntry<bool> Enabled;
internal static ConfigEntry<float> PercentPerTick;
internal static ConfigEntry<float> TickIntervalSeconds;
internal static void Bind(ConfigFile config)
{
Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. Off means this mod applies no patches and registers no assets - the void tunnel behaves exactly like vanilla, decay clock included.");
PercentPerTick = config.Bind<float>("Gameplay", "PercentPerTick", 1f, "Stability lost per tick. Read with TickIntervalSeconds: 1% per 1s empties a fresh 100% clock in 100s (~1.7 min) of uninterrupted void travel.");
TickIntervalSeconds = config.Bind<float>("Gameplay", "TickIntervalSeconds", 1f, "Seconds between ticks. See PercentPerTick.");
}
}
internal static class VtdMutator
{
private class StabilityClock
{
public int StartTimestamp;
public int LastTickProcessed;
public float InitialValue;
}
internal static bool ForcedOn;
internal static float ForcedPercentPerTick;
internal static float ForcedTickIntervalSeconds;
private static readonly ConditionalWeakTable<VoidJumpSystem, StabilityClock> _clocks = new ConditionalWeakTable<VoidJumpSystem, StabilityClock>();
internal static float LastGateValue;
internal static float LastRollValue;
internal static float LastPercentRemaining = 100f;
internal static bool Active
{
get
{
//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)
if (ForcedOn)
{
return true;
}
GameSession activeSession = GameSessionManager.ActiveSession;
if (activeSession == null)
{
return false;
}
foreach (Mutator activeMutator in activeSession.ActiveMutators)
{
if ((Object)(object)activeMutator != (Object)null && ((SelfReferencingScriptableObject)activeMutator).ContainerGuid == VtdRegistration.OurGuid)
{
return true;
}
}
return false;
}
}
internal static bool Enforcing
{
get
{
if (!Active || !PhotonNetwork.IsMasterClient)
{
return false;
}
GameSession activeSession = GameSessionManager.ActiveSession;
if (activeSession != null && activeSession.ActiveQuest != null)
{
return activeSession.ActiveQuest.AllowsInterdiction;
}
return false;
}
}
private static float PercentPerTick
{
get
{
if (!ForcedOn)
{
return VtdConfig.PercentPerTick.Value;
}
return ForcedPercentPerTick;
}
}
private static float TickIntervalSeconds
{
get
{
if (!ForcedOn)
{
return VtdConfig.TickIntervalSeconds.Value;
}
return ForcedTickIntervalSeconds;
}
}
internal static void SubscribeForceOn(float percentPerTick, float tickIntervalSeconds)
{
ForcedOn = true;
ForcedPercentPerTick = percentPerTick;
ForcedTickIntervalSeconds = tickIntervalSeconds;
}
private static float GetInitialValue(VoidJumpSystem system)
{
PlayerControlledShip val = (((Object)(object)system != (Object)null) ? system.PlayerShip : null);
float num = 1f;
if ((Object)(object)val != (Object)null)
{
float maxHitPointsValue = ((OrbitObject)val).MaxHitPointsValue;
if (maxHitPointsValue > 0f)
{
num = Mathf.Clamp01(((OrbitObject)val).HitPoints / maxHitPointsValue);
}
}
float value = VtdTuning.GateExponentBase.Value;
return Mathf.Clamp01((value - 1f + num) / value);
}
internal static void ResetClock(VoidJumpSystem system)
{
float initialValue = GetInitialValue(system);
_clocks.Remove(system);
_clocks.Add(system, new StabilityClock
{
StartTimestamp = PhotonNetwork.ServerTimestamp,
LastTickProcessed = -1,
InitialValue = initialValue
});
LastPercentRemaining = initialValue * 100f;
}
internal static bool RollForInterdiction(VoidJumpSystem system)
{
if (!_clocks.TryGetValue(system, out var value))
{
return false;
}
int num = Mathf.Max(1, Mathf.RoundToInt(TickIntervalSeconds * 1000f));
int num2 = (PhotonNetwork.ServerTimestamp - value.StartTimestamp) / num;
for (int i = value.LastTickProcessed + 1; i <= num2; i++)
{
float num3 = Mathf.Clamp01(value.InitialValue - (float)i * (PercentPerTick / 100f));
float num4 = InterdictionGate(num3, value.InitialValue);
float value2 = Random.value;
bool flag = value2 < num4;
Plugin.Log.LogDebug((object)("VoidTunnelDecay: tick " + i + " v=" + num3.ToString("0.###") + " gate=" + num4.ToString("0.###") + " roll=" + value2.ToString("0.###") + (flag ? " => INTERDICT" : " => survives")));
LastGateValue = num4;
LastRollValue = value2;
if (flag)
{
value.LastTickProcessed = i;
LastPercentRemaining = num3 * 100f;
return true;
}
}
value.LastTickProcessed = num2;
LastPercentRemaining = PercentRemaining(system);
return false;
}
private static float InterdictionGate(float v, float initialValue)
{
float num = 1f / (VtdTuning.GateExponentBase.Value + Mathf.Sqrt(initialValue));
float num2 = Mathf.Max(0f, Mathf.Cos((float)Math.PI * (1f - v) / 2f));
return 1f - Mathf.Pow(num2, num);
}
internal static float PercentRemaining(VoidJumpSystem system)
{
if (!_clocks.TryGetValue(system, out var value))
{
return 100f;
}
float num = (float)(PhotonNetwork.ServerTimestamp - value.StartTimestamp) / (TickIntervalSeconds * 1000f);
return Mathf.Clamp(value.InitialValue * 100f - num * PercentPerTick, 0f, 100f);
}
}
internal static class VtdReflection
{
internal static readonly FieldInfo VoidJumpSystemField = AccessTools.Field(typeof(VoidJumpState), "voidJumpSystem");
}
[HarmonyPatch(typeof(VoidJumpTravellingStable), "OnEnter")]
internal static class Patch_Stable_OnEnter
{
private static void Postfix(VoidJumpTravellingStable __instance)
{
if (!VtdMutator.Enforcing)
{
return;
}
try
{
object? value = VtdReflection.VoidJumpSystemField.GetValue(__instance);
VoidJumpSystem val = (VoidJumpSystem)((value is VoidJumpSystem) ? value : null);
if (val != null)
{
VtdMutator.ResetClock(val);
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("VoidTunnelDecay: OnEnter hook failed, clock not started for this jump: " + ex));
}
}
}
[HarmonyPatch(typeof(VoidJumpTravellingStable), "UpdateState")]
internal static class Patch_Stable_UpdateState
{
private static void Postfix(VoidJumpTravellingStable __instance)
{
if (!VtdMutator.Enforcing)
{
return;
}
try
{
object? value = VtdReflection.VoidJumpSystemField.GetValue(__instance);
VoidJumpSystem val = (VoidJumpSystem)((value is VoidJumpSystem) ? value : null);
if (val != null && (object)val.ActiveState == __instance && VtdMutator.RollForInterdiction(val))
{
val.ChangeActiveState<VoidJumpTravellingUnstable>();
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("VoidTunnelDecay: UpdateState hook failed for this tick: " + ex));
}
}
}
[HarmonyPatch(typeof(TunnelStabilityTextController), "UpdateVoidTunnelStability", new Type[] { typeof(int) })]
internal static class Patch_HudStability
{
private static void Prefix(ref int stabilityValue)
{
if (!VtdMutator.Enforcing)
{
return;
}
try
{
stabilityValue = Mathf.RoundToInt((float)stabilityValue * (VtdMutator.LastPercentRemaining / 100f));
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("VoidTunnelDecay: HUD stability scale failed, showing vanilla's raw number: " + ex));
}
}
}
[HarmonyPatch(typeof(UnlockContainer), "IsUnlocked")]
internal static class Patch_IsUnlocked
{
private static bool Prefix(GUIDUnion guid, ref bool __result)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
try
{
if (!VtdRegistration.Registered || !VtdRegistration.IsOurs(guid))
{
return true;
}
__result = true;
return false;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("VoidTunnelDecay: IsUnlocked override failed, falling back to vanilla check: " + ex));
return true;
}
}
}
internal static class VtdRegistration
{
public static string Status = "not registered yet";
public static string LastError = "";
private static string _stage = "<none>";
private static GUIDUnion _ourGuid;
private static bool _guidParsed;
private static Mutator _mutator;
private static MutatorDef _def;
private static ContextInfo _ctx;
public static bool Registered { get; private set; }
public static GUIDUnion OurGuid
{
get
{
//IL_001c: 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_0017: Unknown result type (might be due to invalid IL or missing references)
if (!_guidParsed)
{
_guidParsed = true;
_ourGuid = new GUIDUnion("7cb9e33adfb6443888d7fe719c27e65d");
}
return _ourGuid;
}
}
public unsafe static bool IsOurs(GUIDUnion guid)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
return ((object)(*(GUIDUnion*)(&guid))/*cast due to .constrained prefix*/).Equals((object?)OurGuid);
}
public static bool ContainersReady()
{
try
{
return (Object)(object)ResourceAssetContainerRegister.Instance != (Object)null && RuntimeAssetsRegister.Instance != null && (Object)(object)ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>.Instance != (Object)null && (Object)(object)ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>.Instance != (Object)null;
}
catch
{
return false;
}
}
public static bool EnsureRegistered()
{
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_017f: 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_0136: Expected O, but got Unknown
//IL_013b: 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_014f: Expected O, but got Unknown
//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
//IL_018b: Unknown result type (might be due to invalid IL or missing references)
//IL_0192: Expected O, but got Unknown
//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
if (Registered)
{
return true;
}
if (!ContainersReady())
{
Status = "waiting for asset containers";
return false;
}
try
{
_stage = "find icon sprite";
Sprite val = FindLoadedSprite("icon_voidsanction");
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"VoidTunnelDecay: no loaded sprite named 'icon_voidsanction' yet - the mutator will register with a blank icon until something else loads it this session.");
}
_stage = "create Mutator";
if ((Object)(object)_mutator == (Object)null)
{
_mutator = ScriptableObject.CreateInstance<Mutator>();
((Object)_mutator).name = "Mutator_InterdictionDecay";
Keep((Object)(object)_mutator);
}
((SelfReferencingScriptableObject)_mutator).ContainerGuid = OurGuid;
if (_mutator.Effects == null)
{
_mutator.Effects = new List<MutatorEffect>();
}
_mutator.RunXPBonus = 0.25f;
_stage = "create ContextInfo";
if ((Object)(object)_ctx == (Object)null)
{
_ctx = ContextInfo.Create(val, "Interdiction Decay", "Void-tunnel stability decays on a fixed clock while travelling, independent of the normal chance of interdiction. Left alone in the void long enough, interdiction stops being a matter of luck.");
Keep((Object)(object)_ctx);
}
else if ((Object)(object)val != (Object)null && (Object)(object)_ctx.Icon == (Object)null)
{
_ctx.Icon = val;
}
_stage = "create def";
if (_def == null)
{
_def = new MutatorDef();
((ResourceAssetDef<Mutator>)(object)_def).Ref = new ResourceAssetRef(OurGuid, "Mutators/Mutator_InterdictionDecay");
((ResourceAssetDef<Mutator>)(object)_def).Ref.IsRuntime = true;
}
_def.ContextInfo = (IResourceAssetContextInfo)(object)_ctx;
_stage = "RuntimeAssetsRegister";
RuntimeAssetsRegister instance = RuntimeAssetsRegister.Instance;
if (!instance.HasAsset(OurGuid))
{
RuntimeAssetInfo val2 = new RuntimeAssetInfo();
val2.Name = ((Object)_mutator).name;
val2.DisplayName = "Interdiction Decay";
instance.RegisterAsset(OurGuid, (Object)(object)_mutator, (SessionModificationEffect)0, val2);
}
_stage = "MutatorContainer";
MutatorContainer instance2 = ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>.Instance;
if (!((ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>)(object)instance2).HasItem(OurGuid))
{
((ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>)(object)instance2).RegisterRuntimeAsset(OurGuid, _def);
}
_stage = "AssetDescriptions";
AppendToAssetDescriptions(instance2, _def);
_stage = "UnlockContainer";
RegisterUnlock();
_stage = "verify";
string text = Verify();
if (text != null)
{
Fail("verification failed: " + text);
return false;
}
Registered = true;
LastError = "";
Status = "registered as Interdiction Decay (7cb9e33adfb6443888d7fe719c27e65d)";
Plugin.Log.LogInfo((object)("VoidTunnelDecay: " + Status));
return true;
}
catch (Exception ex)
{
Fail("threw at stage '" + _stage + "': " + ex);
return false;
}
}
private static void Fail(string msg)
{
LastError = msg;
Status = "FAILED - " + msg;
Plugin.Log.LogError((object)("VoidTunnelDecay: " + msg));
}
private static Sprite FindLoadedSprite(string name)
{
if (string.IsNullOrEmpty(name))
{
return null;
}
Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>();
foreach (Sprite val in array)
{
if ((Object)(object)val != (Object)null && ((Object)val).name == name)
{
return val;
}
}
return null;
}
private static void RegisterUnlock()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Expected O, but got Unknown
//IL_0024: 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_0038: Expected O, but got Unknown
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: 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)
UnlockContainer instance = ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>.Instance;
if (!((Object)(object)instance == (Object)null) && !((ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>)(object)instance).HasItem(OurGuid))
{
UnlockItemDef val = new UnlockItemDef();
((ResourceAssetDef<Object>)(object)val).Ref = new ResourceAssetRef(OurGuid, "Mutators/Mutator_InterdictionDecay");
((ResourceAssetDef<Object>)(object)val).Ref.IsRuntime = true;
val.rarity = (RarityType)3;
object obj = (object)default(UnlockOptions);
Refl.SetField(obj, "UnlockCriteria", (object)(UnlockCriteriaType)8);
Refl.SetField(obj, "RankRequirement", 0);
if (!Refl.SetField(val, "unlockOptions", obj))
{
Plugin.Log.LogWarning((object)"VoidTunnelDecay: could not set UnlockItemDef.unlockOptions");
}
((ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>)(object)instance).RegisterRuntimeAsset(OurGuid, val);
}
}
private static void AppendToAssetDescriptions(MutatorContainer container, MutatorDef def)
{
try
{
if (Refl.GetField(container, "AssetDescriptions") is List<MutatorDef> list && !list.Contains(def))
{
list.Add(def);
}
}
catch (Exception e)
{
Plugin.Log.LogWarning((object)("VoidTunnelDecay: appending to AssetDescriptions threw: " + Refl.Brief(e)));
}
}
private static string Verify()
{
//IL_0005: 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)
MutatorDef val = default(MutatorDef);
if (!((ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>)(object)ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>.Instance).TryGetByGuid(OurGuid, ref val) || val == null)
{
return "container does not resolve our GUID";
}
Mutator asset = ((ResourceAssetDef<Mutator>)(object)val).Asset;
if ((Object)(object)asset == (Object)null)
{
return "def.Asset is null (RuntimeAssetsRegister lookup or IsRuntime flag)";
}
if (asset.Effects == null)
{
return "Mutator.Effects is null - Mutator.Activate iterates it unguarded";
}
if (val.ContextInfo == null)
{
return "ContextInfo is null - the selection menu tooltip dereferences it unguarded";
}
if (string.IsNullOrEmpty(val.ContextInfo.HeaderText))
{
return "ContextInfo.HeaderText is empty";
}
if (!((ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>)(object)ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>.Instance).HasItem(OurGuid))
{
return "no UnlockContainer entry - UpdateMutatorStates dereferences it unguarded";
}
return null;
}
private static void Keep(Object o)
{
if (!(o == (Object)null))
{
o.hideFlags = (HideFlags)61;
Object.DontDestroyOnLoad(o);
}
}
}
internal static class VtdTuning
{
internal static TuningFile File;
internal static TuningScalar GateExponentBase;
internal static void Initialise(string configDir)
{
File = new TuningFile("jack.voidcrew.voidtunneldecay", "VoidTunnelDecay", "Testing-only knob for the interdiction gate: gate(v) = 1 - cos(pi*(1-v)/2) ^ (1 / (GateExponentBase + sqrt(initial_value))), rolled once per decay tick against the fraction of stability remaining (v). Lower GateExponentBase makes the odds climb faster as stability drops. Never shipped - bake into source before packaging a build for anyone else.", Plugin.Log);
GateExponentBase = File.Add(new TuningScalar("GateExponentBase", "Denominator term in the interdiction-roll exponent. Current default is 3 (was 31).", 3f, 1f, 100f));
File.Initialise(configDir);
}
internal static bool PollForChanges()
{
if (File != null)
{
return File.PollForChanges();
}
return false;
}
}
}