using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using IShowSeed;
using IShowSeed.Prediction;
using IShowSeed.Random;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("IShowSeed")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Seeded runs! Now with leaderboards!")]
[assembly: AssemblyFileVersion("1.8.0.0")]
[assembly: AssemblyInformationalVersion("1.8.0+73043635dd784c9ffc6d3ed804654270a4cbce63")]
[assembly: AssemblyProduct("IShowSeed")]
[assembly: AssemblyTitle("IShowSeed")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.8.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.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 MasterServer
{
public record LeaderboardEntry(string steamid, int rank, int score, int time, int seed, bool hardmode, bool iron, bool finished);
internal static HttpClient HttpClient;
internal static bool Initialized;
public static async Task InitializeHttpClient()
{
HttpClient = new HttpClient();
HttpClient.Timeout = TimeSpan.FromSeconds(Plugin.TimeoutSeconds.Value);
string[] uris = new string[3]
{
Plugin.LeaderboardUri.Value,
"http://128.199.54.23:80",
"http://158.160.65.211:5252"
};
string[] array = uris;
foreach (string uri in array)
{
try
{
Plugin.Beep.LogInfo((object)("Checking seeded runs leaderboard uri: " + uri));
HttpClient.BaseAddress = new Uri(uri);
(await HttpClient.GetAsync("/health")).EnsureSuccessStatusCode();
Initialized = true;
}
catch (Exception)
{
Plugin.Beep.LogInfo((object)("Uri `" + uri + "` failed"));
continue;
}
break;
}
if (Initialized)
{
Plugin.Beep.LogInfo((object)$"Connected to {HttpClient.BaseAddress}");
Plugin.LeaderboardUri.Value = HttpClient.BaseAddress.ToString();
}
}
public static async Task<List<LeaderboardEntry>> FetchLeaderboards(string gamemode, ScoreType scoreType, int limit = 10, string steamid = "")
{
//IL_0019: 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)
if (!Initialized)
{
return new List<LeaderboardEntry>();
}
string endpoint = string.Format($"getstats?gamemode={gamemode}&limit={limit}&sortby={scoreType}&steamid={steamid}");
Plugin.Beep.LogWarning((object)("Fetching Leaderboards: " + endpoint));
using HttpResponseMessage response = await HttpClient.GetAsync(endpoint);
if (!response.IsSuccessStatusCode)
{
Plugin.Beep.LogError((object)response);
response.EnsureSuccessStatusCode();
}
return JsonConvert.DeserializeObject<List<LeaderboardEntry>>(await response.Content.ReadAsStringAsync());
}
public static async void UploadScore(string gamemode, int seed, float score, float time, bool iron, bool hardmode, bool finished)
{
if (!Initialized)
{
return;
}
Plugin.Beep.LogWarning((object)$"Uploading run info: gamemode={gamemode}, seed={seed}, score={score}, time={time}, iron={iron}, hardmode={hardmode}, finished={finished}");
using StringContent jsonContent = new StringContent(JsonConvert.SerializeObject((object)new Dictionary<string, object>
{
{ "gamemode", gamemode },
{
"steamid",
SteamClient.SteamId.Value.ToString()
},
{ "seed", seed },
{
"time",
Mathf.FloorToInt(Mathf.Clamp(time, 0f, 2.1474836E+09f))
},
{
"score",
Mathf.FloorToInt(Mathf.Clamp(score, 0f, 2.1474836E+09f))
},
{ "iron", iron },
{ "hardmode", hardmode },
{ "finished", finished }
}, (Formatting)0), Encoding.UTF8, "application/json");
using HttpResponseMessage response = await HttpClient.PostAsync("uploadscore", jsonContent);
if (!response.IsSuccessStatusCode)
{
Plugin.Beep.LogError((object)response);
response.EnsureSuccessStatusCode();
}
}
}
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit
{
}
}
namespace IShowSeed
{
public static class Helpers
{
public static readonly FieldRef<CL_AssetManager, List<WKDatabaseHolder>> databasesRef = (FieldRef<CL_AssetManager, List<WKDatabaseHolder>>)(object)AccessTools.FieldRefAccess<List<WKDatabaseHolder>>(typeof(CL_AssetManager), "databases");
internal static List<string> BaseGamemodes = new List<string>(8) { "Campaign", "Endless Superstructure", "Endless Substructure", "Endless Underworks", "Endless Silos", "Endless Pipeworks", "Endless Habitation", "Endless Abyss" };
public static string LevelOf(Transform tr)
{
if ((Object)(object)tr == (Object)null)
{
return "could_not_get_level_of_" + ((Object)((Component)tr).gameObject).name;
}
return ((Component)tr).GetComponentInParent<M_Level>(true).levelName;
}
public static List<Perk> GetAllPerks()
{
List<Perk> list = new List<Perk>();
foreach (WKDatabaseHolder item in databasesRef.Invoke(CL_AssetManager.instance))
{
list.AddRange(item.database.perkAssets);
}
foreach (Perk item2 in list)
{
item2.id = item2.id.ToLower();
}
return list;
}
internal static void PatchAllWithAttribute<T>(Harmony harmony) where T : Attribute
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
IEnumerable<Type> enumerable = from t in executingAssembly.GetTypes()
where t.GetCustomAttribute<T>() != null
select t;
foreach (Type item in enumerable)
{
harmony.PatchAll(item);
}
}
internal static List<string> GetAllGamemodes(bool withOptions)
{
List<string> list = new List<string>();
foreach (string baseGamemode in BaseGamemodes)
{
list.Add(baseGamemode);
if (withOptions)
{
list.Add(baseGamemode + "-Hardmode");
list.Add(baseGamemode + "-Iron");
list.Add(baseGamemode + "-Hardmode-Iron");
}
}
return list;
}
internal static string GetAllGamemodesStr(bool withOptions, string delimiter)
{
return string.Join(delimiter, GetAllGamemodes(withOptions));
}
}
[AttributeUsage(AttributeTargets.Class)]
public class OnlyForSeededRunsPatchAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Class)]
public class PermanentPatchAttribute : Attribute
{
}
[PermanentPatch]
[HarmonyPatch(typeof(Leaderboard_Panel), "UpdateScore")]
public static class SpawnSettings_UpdateScore_Patcher
{
public static bool FilterBySeed = false;
private static readonly FieldRef<Leaderboard_Panel, List<Leaderboard_Score>> scoreObjectsRef = (FieldRef<Leaderboard_Panel, List<Leaderboard_Score>>)(object)AccessTools.FieldRefAccess<List<Leaderboard_Score>>(typeof(Leaderboard_Panel), "scoreObjects");
public static bool Prefix(Leaderboard_Panel __instance)
{
if (Plugin.IsRandomRun() || !MasterServer.Initialized)
{
return true;
}
try
{
OverrideLeaderboardsAsync(__instance);
}
catch (Exception ex)
{
Plugin.Beep.LogError((object)"An error occurred when trying to connect to leaderboards server! Make sure you have the latest version of the mod and default URI setting in mod's config");
Plugin.Beep.LogError((object)ex);
}
return false;
}
public static async Task OverrideLeaderboardsAsync(Leaderboard_Panel __instance)
{
List<Leaderboard_Score> scoreObjects = scoreObjectsRef.Invoke(__instance);
string leaderboardsName = CL_GameManager.GetGamemodeName(true, false);
for (int j = 0; j < scoreObjects.Count; j++)
{
((Component)scoreObjects[j]).gameObject.SetActive(false);
}
List<MasterServer.LeaderboardEntry> lbScores = null;
if ((int)__instance.defaultType == 0)
{
lbScores = await MasterServer.FetchLeaderboards(leaderboardsName, __instance.scoreType, __instance.scoresToPull);
}
else if ((int)__instance.defaultType == 1 || (int)__instance.defaultType == 2)
{
lbScores = await MasterServer.FetchLeaderboards(leaderboardsName, __instance.scoreType, __instance.scoresToPull, SteamClient.SteamId.Value.ToString());
List<MasterServer.LeaderboardEntry> list = lbScores;
List<MasterServer.LeaderboardEntry> list2 = list;
if (list2 == null)
{
List<MasterServer.LeaderboardEntry> list3;
lbScores = (list3 = await MasterServer.FetchLeaderboards(leaderboardsName, __instance.scoreType, __instance.scoresToPull));
list2 = list3;
}
_ = list2;
}
for (int i = 0; i < scoreObjects.Count; i++)
{
if (i >= lbScores.Count)
{
((Component)scoreObjects[i]).gameObject.SetActive(false);
continue;
}
SteamId steamId = new SteamId
{
Value = Convert.ToUInt64(lbScores[i].steamid)
};
Friend steamUser = new Friend(steamId);
((Behaviour)scoreObjects[i].hmIcon).enabled = lbScores[i].hardmode;
((Behaviour)scoreObjects[i].ikIcon).enabled = lbScores[i].iron;
((Component)scoreObjects[i]).gameObject.SetActive(true);
scoreObjects[i].positionText.text = $"<mspace=''>{lbScores[i].rank}</mspace>";
scoreObjects[i].nameText.text = ((Friend)(ref steamUser)).Name ?? "";
if ((int)__instance.scoreType == 0)
{
scoreObjects[i].scoreText.text = $"{lbScores[i].score}\n<color=grey>seed: {lbScores[i].seed}</color>";
}
else
{
if ((int)__instance.scoreType != 1)
{
Plugin.Beep.LogWarning((object)$"Unsupported scoreType {__instance.scoreType}");
break;
}
TimeSpan timeSpan = TimeSpan.FromSeconds(lbScores[i].time);
scoreObjects[i].scoreText.text = $"{timeSpan:hh\\:mm\\:ss} <color=grey>seed: {lbScores[i].seed}</color>";
}
Texture2D texture = SteamManager.ConvertSteamIcon((await ((Friend)(ref steamUser)).GetMediumAvatarAsync()).Value);
scoreObjects[i].profile.texture = (Texture)(object)texture;
if (SteamClient.SteamId.Value == steamId.Value)
{
((Graphic)scoreObjects[i].positionText).color = __instance.isMeColor;
((Graphic)scoreObjects[i].nameText).color = __instance.isMeColor;
((Graphic)scoreObjects[i].scoreText).color = __instance.isMeColor;
}
else
{
((Graphic)scoreObjects[i].positionText).color = Color.white;
((Graphic)scoreObjects[i].nameText).color = Color.white;
((Graphic)scoreObjects[i].scoreText).color = Color.white;
}
}
}
}
[PermanentPatch]
[HarmonyPatch(typeof(M_Gamemode), "Finish")]
public static class M_Gamemode_Finish_Patcher
{
public static bool Prefix(M_Gamemode __instance, float time, bool hasFinished)
{
if (Plugin.IsRandomRun())
{
return true;
}
__instance.gamemodeModule.OnFinish(hasFinished);
bool flag = (!__instance.allowCheatedScores && CommandConsole.hasCheated) || (!__instance.allowCheatedScores && !CL_GameManager.AreAchievementsAllowed());
try
{
MasterServer.UploadScore(__instance.GetGamemodeName(true), Plugin.SeedForRandom, flag ? 0f : __instance.GetPlayerScore(hasFinished), time, __instance.IsIronKnuckle(), __instance.allowHardmode && CL_GameManager.IsHardmode(), hasFinished);
}
catch (Exception ex)
{
Plugin.Beep.LogError((object)"An error occurred when trying to connect to leaderboards server! Make sure you have the latest version of the mod and default URI setting in mod's config");
Plugin.Beep.LogError((object)ex);
}
return false;
}
}
[BepInPlugin("shishyando.WK.IShowSeed", "IShowSeed", "1.8.0")]
public class Plugin : BaseUnityPlugin
{
internal static Plugin Instance;
internal static ManualLogSource Beep;
internal static Harmony TogglableHarmony = new Harmony("shishyando.WK.IShowSeed.togglable");
internal static Harmony PermanentHarmony = new Harmony("shishyando.WK.IShowSeed.permanent");
internal static int SeedForRandom = 0;
private static bool OnStartupDone = false;
internal static ConfigEntry<int> ConfigPresetSeed;
internal static ConfigEntry<bool> PersistBetweenGameRestarts;
internal static ConfigEntry<string> LeaderboardUri;
internal static ConfigEntry<int> TimeoutSeconds;
internal static ConfigEntry<string> EnabledGamemodesStr;
internal static List<string> EnabledGamemodes;
internal static ConfigEntry<string> DesiredRouteDescription;
internal static ConfigEntry<int> SeedSearchMin;
internal static ConfigEntry<int> SeedSearchMax;
internal static ConfigEntry<bool> EnableRandomSeedReplayability;
private void Awake()
{
Instance = this;
Beep = ((BaseUnityPlugin)this).Logger;
ConfigPresetSeed = ((BaseUnityPlugin)this).Config.Bind<int>("General", "PresetSeed", 0, "Preset seed, `0` to keep the default behaviour");
PersistBetweenGameRestarts = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "PersistBetweenGameRestarts", true, "If true, the seed will stay the same even after game restart");
EnableRandomSeedReplayability = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableRandomSeedReplayability", false, "If true, random-seeded runs can be replayed in a seeded run by entering the seed you had");
EnabledGamemodesStr = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Gamemodes", Helpers.GetAllGamemodesStr(withOptions: true, "|") ?? "", "Gamemodes which should be affected by IShowSeed, separated by `|`. Available values (can append \"-Hardmode\", \"-Iron\" or \"-Hardmode-Iron\"):\n" + Helpers.GetAllGamemodesStr(withOptions: false, "\n"));
EnabledGamemodes = EnabledGamemodesStr.Value.Split('|').ToList();
LeaderboardUri = ((BaseUnityPlugin)this).Config.Bind<string>("Leaderboards", "Uri", "http://128.199.54.23:80", "If you encounter any network problems, make sure you have the latest version of the mod and reset this to default");
TimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Leaderboards", "TimeoutSeconds", 10, "If this is not enough, either the server is down or you have network issues");
DesiredRouteDescription = ((BaseUnityPlugin)this).Config.Bind<string>("SeedSearch", "DesiredRouteDescription", "shortcut_burner: perk_u_t2_adoptionday, perk_metabolicstasis, perk_rabbitdna", "Format: `{route name}: {unstable_perk1}, {perk2}, {perk3}`, available route names: `default`, `shortcut_sink`, `shortcut_burner`");
SeedSearchMin = ((BaseUnityPlugin)this).Config.Bind<int>("SeedSearch", "MinSeed", 3665, "Min seed for desired route search");
SeedSearchMax = ((BaseUnityPlugin)this).Config.Bind<int>("SeedSearch", "MaxSeed", 3665, "Max seed for desired route search, do not make this range too big or your game will load forever");
SceneManager.sceneLoaded += OnGameStartup;
Beep.LogInfo((object)"shishyando.WK.IShowSeed is loaded");
}
public void OnGameStartup(Scene scene, LoadSceneMode loadSceneMode)
{
if (!(((Scene)(ref scene)).name != "Main-Menu") && !OnStartupDone)
{
OnStartupDone = true;
MasterServer.InitializeHttpClient();
ClearSeedOnRestartIfNeeded();
Vanga.DoSeedSearch();
Helpers.PatchAllWithAttribute<PermanentPatchAttribute>(PermanentHarmony);
}
}
public void ClearSeedOnRestartIfNeeded()
{
if (!PersistBetweenGameRestarts.Value)
{
ConfigPresetSeed.Value = 0;
Beep.LogInfo((object)"PersistBetweenGameRestarts is false, clearing the preset seed");
}
}
public static bool IsSeededRun()
{
return ConfigPresetSeed.Value != 0;
}
public static bool IsRandomRun()
{
return ConfigPresetSeed.Value == 0;
}
public static bool ShouldEnableRod()
{
return (IsSeededRun() || EnableRandomSeedReplayability.Value) && EnabledGamemodes.Contains(CL_GameManager.GetGamemodeName(true, false));
}
}
[PermanentPatch]
[HarmonyPatch(typeof(SceneManager), "LoadScene", new Type[] { typeof(string) })]
public static class SceneManager_LoadScene_Patcher
{
public static void Prefix(string sceneName)
{
if (sceneName == "Main-Menu")
{
Plugin.TogglableHarmony.UnpatchSelf();
Rod.SwitchToMode(Rod.ERandomMode.Disabled);
}
if (!(sceneName == "Game-Main"))
{
return;
}
if (Plugin.ShouldEnableRod())
{
if (Rod.GetMode() == Rod.ERandomMode.Enabled)
{
Plugin.Beep.LogInfo((object)"Only resetting random on restart, no double patching");
Rod.Reset();
}
else
{
Plugin.Beep.LogInfo((object)"Applying patches for random for the first time");
Rod.SwitchToMode(Rod.ERandomMode.Enabled);
Helpers.PatchAllWithAttribute<OnlyForSeededRunsPatchAttribute>(Plugin.TogglableHarmony);
}
}
else
{
Plugin.Beep.LogInfo((object)("Gamemode `" + CL_GameManager.GetGamemodeName(true, false) + "` is not enabled in IShowSeed's configs, not doing anything"));
}
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "shishyando.WK.IShowSeed";
public const string PLUGIN_NAME = "IShowSeed";
public const string PLUGIN_VERSION = "1.8.0";
}
}
namespace IShowSeed.UI
{
[PermanentPatch]
[HarmonyPatch(typeof(UT_SeededEnable), "OnEnable")]
public static class UT_SeededEnable_OnEnable_Patcher
{
public static bool Prefix(UT_SeededEnable __instance)
{
TextMeshProUGUI component = ((Component)__instance).gameObject.GetComponent<TextMeshProUGUI>();
((TMP_Text)component).text = $"SEED: {Plugin.SeedForRandom}";
if (Plugin.IsSeededRun())
{
((TMP_Text)component).text = "PRESET " + ((TMP_Text)component).text;
}
return false;
}
}
}
namespace IShowSeed.Random
{
public static class Rod
{
public enum ERandomMode
{
Disabled,
Enabled,
Prediction
}
public struct Context
{
public State PrevRandomState;
public int BaseSeed;
public bool Valid;
public uint CallNumber;
}
public static ERandomMode Mode = ERandomMode.Disabled;
private static readonly object _lock = new object();
private static readonly Dictionary<int, State> _stateBySiteSeed = new Dictionary<int, State>();
private static readonly Dictionary<int, uint> _cntBySiteSeed = new Dictionary<int, uint>();
internal static void Enter(ref Context ctx, string customCallSite)
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
if (Mode != 0)
{
ctx = default(Context);
ctx.BaseSeed = DeriveSeed(customCallSite ?? ComputeSiteKey());
Monitor.Enter(_lock);
ctx.PrevRandomState = Random.state;
if (!_stateBySiteSeed.ContainsKey(ctx.BaseSeed))
{
Random.InitState(ctx.BaseSeed);
_stateBySiteSeed[ctx.BaseSeed] = Random.state;
_cntBySiteSeed[ctx.BaseSeed] = 1u;
ctx.CallNumber = 1u;
}
else
{
Random.state = _stateBySiteSeed[ctx.BaseSeed];
ctx.CallNumber = ++_cntBySiteSeed[ctx.BaseSeed];
}
ctx.Valid = true;
}
}
internal static void Exit(in Context ctx)
{
//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_0021: 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)
if (ctx.Valid)
{
State state = Random.state;
_stateBySiteSeed[ctx.BaseSeed] = state;
Random.state = ctx.PrevRandomState;
Monitor.Exit(_lock);
}
}
private static string ComputeSiteKey()
{
return GetStackTraceStr(2);
}
private static int DeriveSeed(string siteKey)
{
return Animator.StringToHash(siteKey) ^ Plugin.SeedForRandom;
}
internal static ERandomMode GetMode()
{
return Mode;
}
internal static void SwitchToMode(ERandomMode mode)
{
if (Mode != ERandomMode.Prediction && mode != ERandomMode.Prediction)
{
Plugin.Beep.LogInfo((object)$"RandomGod switching to {mode}");
}
Monitor.Enter(_lock);
Mode = mode;
_stateBySiteSeed.Clear();
Monitor.Exit(_lock);
}
internal static void Reset()
{
Plugin.Beep.LogWarning((object)"RandomGod reset");
Monitor.Enter(_lock);
_stateBySiteSeed.Clear();
Monitor.Exit(_lock);
}
private static string GetStackTraceStr(int frames)
{
StackTrace stackTrace = new StackTrace(frames);
return string.Join(" ==> ", stackTrace.GetFrames().Select(delegate(StackFrame f)
{
MethodBase method = f.GetMethod();
Type declaringType = method.DeclaringType;
return (declaringType == null) ? "" : (declaringType.FullName + "." + method.Name);
}));
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(App_PerkPage), "GenerateCards", new Type[] { typeof(bool) })]
public static class App_PerkPage_GenerateCards_Patcher
{
private static readonly FieldRef<App_PerkPage, OS_Manager> osRef = (FieldRef<App_PerkPage, OS_Manager>)(object)AccessTools.FieldRefAccess<OS_Manager>(typeof(App_PerkPage), "os");
private static readonly FieldRef<OS_Computer_Interface, int> seedRef = (FieldRef<OS_Computer_Interface, int>)(object)AccessTools.FieldRefAccess<int>(typeof(OS_Computer_Interface), "seed");
public static void Prefix(ref Rod.Context __state, App_PerkPage __instance, bool refresh)
{
Rod.Enter(ref __state, GenerateCustomCallSite(__instance, refresh));
seedRef.Invoke(osRef.Invoke(__instance).worldInterface) = -1;
}
public static void Postfix(ref Rod.Context __state, App_PerkPage __instance)
{
seedRef.Invoke(osRef.Invoke(__instance).worldInterface) = (int)(__state.BaseSeed + __state.CallNumber);
Rod.Exit(in __state);
}
private static string GenerateCustomCallSite(App_PerkPage i, bool refresh)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
Transform transform = ((Component)osRef.Invoke(i).worldInterface).transform;
return $"perkpage_{i.perkPageType}_{Helpers.LevelOf(transform)}_{i.minCards}_{i.maxCards}_{refresh}";
}
}
[PermanentPatch]
[HarmonyPatch(typeof(CL_EventManager), "GetPossibleEvents")]
public static class CL_EventManager_GetPossibleEvents_Patcher
{
public static void Postfix(ref List<SessionEvent> __result)
{
if (Plugin.IsSeededRun())
{
__result?.RemoveAll((SessionEvent x) => (int)x.startCheck == 3);
}
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(ENV_ArtifactDevice), "Start")]
public static class ENV_ArtifactDevice_Start_Patcher
{
public static void Prefix(ref Rod.Context __state)
{
Rod.Enter(ref __state, "artifact_device");
}
public static void Finalizer(ref Rod.Context __state)
{
Rod.Exit(in __state);
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(ENV_VendingMachine), "GenerateOptions")]
public static class ENV_VendingMachine_GenerateOptions_Patcher
{
private static readonly FieldRef<ENV_VendingMachine, int> localSeedRef = AccessTools.FieldRefAccess<ENV_VendingMachine, int>("localSeed");
public static void Prefix(ref Rod.Context __state, ENV_VendingMachine __instance)
{
Rod.Enter(ref __state, GenerateCustomCallSite(__instance));
localSeedRef.Invoke(__instance) = (int)(__state.BaseSeed + __state.CallNumber);
}
public static void Finalizer(ref Rod.Context __state)
{
Rod.Exit(in __state);
}
private static string GenerateCustomCallSite(ENV_VendingMachine i)
{
//IL_001f: 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_0045: Unknown result type (might be due to invalid IL or missing references)
Transform transform = ((Component)i).transform;
return $"vendo_{Helpers.LevelOf(transform)}_{transform.position.x:F2}_{transform.position.y:F2}_{transform.position.z:F2}";
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(ENV_VendingMachine), "SetSeed")]
public static class ENV_VendingMachine_SetSeed_Patcher
{
public static bool Prefix()
{
return false;
}
}
[PermanentPatch]
[HarmonyPatch(typeof(M_Level), "Awake")]
public static class M_Level_Awake_Patcher
{
public static void Prefix(M_Level __instance)
{
if (Plugin.IsSeededRun())
{
__instance.canFlip = false;
}
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(OS_Computer_Interface), "SetSeed")]
public static class OS_Computer_Interface_SetSeed_Patcher
{
public static bool Prefix()
{
return false;
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(SpawnSettings), "RandomCheck")]
public static class SpawnSettings_RandomCheck_Patcher
{
public static bool Prefix(ref Rod.Context __state, ref bool __result, SpawnSettings __instance)
{
float effectiveSpawnChance = __instance.GetEffectiveSpawnChance();
if (effectiveSpawnChance == 0f)
{
__result = false;
return false;
}
if (effectiveSpawnChance == 1f)
{
__result = true;
return false;
}
Rod.Enter(ref __state, null);
return true;
}
public static void Finalizer(ref Rod.Context __state)
{
Rod.Exit(in __state);
}
}
[OnlyForSeededRunsPatch]
[HarmonyPatch(typeof(WorldLoader), "Awake")]
public static class WorldLoader_Awake_Patcher
{
public static void Prefix()
{
WorldLoader.SetPresetSeed(Plugin.ConfigPresetSeed.Value.ToString());
Plugin.Beep.LogInfo((object)$"custom preset seed: {Plugin.ConfigPresetSeed.Value}");
Plugin.SeedForRandom = Plugin.ConfigPresetSeed.Value;
}
}
[PermanentPatch]
[HarmonyPatch(typeof(WorldLoader), "Initialize")]
public static class WorldLoader_Initialize_Patcher
{
public static void Postfix()
{
if (Plugin.IsRandomRun())
{
Plugin.SeedForRandom = WorldLoader.instance.seed;
}
}
}
}
namespace IShowSeed.Random.UI
{
[PermanentPatch]
[HarmonyPatch(typeof(CL_UIManager), "Update")]
public static class CL_UIManager_Update_Patcher
{
public static void Postfix()
{
DebugMenu.UpdateDebugText("starting-seed", $"<color=blue>Starting seed: {Plugin.SeedForRandom}</color>");
}
}
[PermanentPatch]
[HarmonyPatch(typeof(MenuManager), "Start")]
public static class MenuManager_Start_Patcher
{
public static void Postfix(MenuManager __instance)
{
PatchSeedWindow(__instance);
AddSeedWindowButton(__instance);
}
private static void AddSeedWindowButton(MenuManager __instance)
{
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Expected O, but got Unknown
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Expected O, but got Unknown
Transform val = ((Component)__instance.menu).transform.Find("Main Menu Buttons/Logbook");
Transform val2 = Object.Instantiate<Transform>(val, ((Component)val).transform.parent);
((Object)val2).name = "Seeded runs";
TextMeshProUGUI component = ((Component)val2.GetChild(0)).GetComponent<TextMeshProUGUI>();
((TMP_Text)component).text = "seeded runs";
((Graphic)component).color = Color.grey;
((TMP_Text)component).fontSize = ((TMP_Text)component).fontSize / 2f;
Button component2 = ((Component)val2).GetComponent<Button>();
component2.onClick = new ButtonClickedEvent();
((UnityEvent)component2.onClick).AddListener((UnityAction)delegate
{
__instance.seedWindow.SetActive(!__instance.seedWindow.activeSelf);
});
}
private static void PatchSeedWindow(MenuManager __instance)
{
GameObject seedWindow = __instance.seedWindow;
TextMeshProUGUI component = ((Component)seedWindow.transform.Find("Overview Titles/Title Text")).gameObject.GetComponent<TextMeshProUGUI>();
TextMeshProUGUI buttonText = ((Component)seedWindow.transform.Find("Tab Selection Hor/Exit/Text (TMP)")).gameObject.GetComponent<TextMeshProUGUI>();
Button component2 = ((Component)seedWindow.transform.Find("Tab Selection Hor/Exit")).gameObject.GetComponent<Button>();
TMP_InputField seedPrompt = ((Component)seedWindow.transform.Find("Seed Input")).gameObject.GetComponent<TMP_InputField>();
TextMeshProUGUI component3 = ((Component)seedWindow.transform.Find("Seed Input/Text Area/Placeholder")).gameObject.GetComponent<TextMeshProUGUI>();
if ((Object)(object)buttonText == (Object)null || (Object)(object)seedPrompt == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component == (Object)null || (Object)(object)component3 == (Object)null)
{
Plugin.Beep.LogWarning((object)$"button: {component2}\nbuttonText: {buttonText}\nseedPrompt: {seedPrompt}\ntitle: {component}\nplaceholder {component3}");
}
else
{
PatchTitle(component);
PatchPrompt(seedPrompt, component3);
PatchButton(component2);
}
void PatchButton(Button button)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Expected O, but got Unknown
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Expected O, but got Unknown
((Graphic)buttonText).color = Color.grey;
((TMP_Text)buttonText).text = "Save to Config";
button.onClick = new ButtonClickedEvent();
((UnityEvent)button.onClick).AddListener((UnityAction)delegate
{
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
if (int.TryParse(seedPrompt.text, out var result) || seedPrompt.text == "")
{
Plugin.ConfigPresetSeed.Value = result;
((BaseUnityPlugin)Plugin.Instance).Config.Save();
Plugin.Beep.LogInfo((object)$"Set new seed to {result}");
Plugin.Beep.LogInfo((object)("Run preview: " + JsonConvert.SerializeObject((object)Vanga.GenerateRouteInfos(result), (Formatting)1)));
seedWindow.SetActive(false);
}
else
{
((TMP_Text)buttonText).text = "Invalid seed";
((Graphic)buttonText).color = Color.red;
}
});
}
void PatchPrompt(TMP_InputField prompt, TextMeshProUGUI placeholder)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Expected O, but got Unknown
prompt.onValueChanged = new OnChangeEvent();
((UnityEvent<string>)(object)prompt.onValueChanged).AddListener((UnityAction<string>)delegate
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
((Graphic)buttonText).color = Color.white;
((TMP_Text)buttonText).text = "Save to Config";
});
prompt.text = Plugin.ConfigPresetSeed.Value.ToString();
if (prompt.text == "0")
{
prompt.text = "";
}
((TMP_Text)placeholder).text = "KEEP RANDOM";
}
static void PatchTitle(TextMeshProUGUI title)
{
((TMP_Text)title).text = "ENTER SEED";
}
}
}
}
namespace IShowSeed.Prediction
{
public struct PredictedPerks
{
public List<string> PerkIds;
public List<string> RefreshedPerkIds;
}
public static class PerkMock
{
public static PredictedPerks Generate(PerkPageType perkPageType, string levelName, int minCards, int maxCards)
{
//IL_004b: 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)
Rod.SwitchToMode(Rod.ERandomMode.Prediction);
bool flag = false;
PredictedPerks predictedPerks = default(PredictedPerks);
predictedPerks.PerkIds = new List<string>();
predictedPerks.RefreshedPerkIds = new List<string>();
PredictedPerks result = predictedPerks;
int num = 0;
while (num <= 1)
{
Rod.Context ctx = default(Rod.Context);
Rod.Enter(ref ctx, $"perkpage_{perkPageType}_{levelName}_{minCards}_{maxCards}_{flag}");
List<string> source = GenerateInternal(perkPageType, minCards, maxCards, flag, result.PerkIds);
if (!flag)
{
result.PerkIds = source.ToList();
}
else
{
result.RefreshedPerkIds = source.ToList();
}
Rod.Exit(in ctx);
num++;
flag = true;
}
Rod.SwitchToMode(Rod.ERandomMode.Disabled);
return result;
}
private static List<string> GenerateInternal(PerkPageType perkPageType, int minCards, int maxCards, bool refresh, List<string> generatedPerks)
{
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Invalid comparison between Unknown and I4
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Invalid comparison between Unknown and I4
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Invalid comparison between Unknown and I4
//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
//IL_00c3: Invalid comparison between Unknown and I4
//IL_015e: Unknown result type (might be due to invalid IL or missing references)
//IL_0160: Invalid comparison between Unknown and I4
List<string> list = new List<string>();
List<string> list2 = new List<string>();
if (generatedPerks.Count > 0)
{
for (int num = generatedPerks.Count - 1; num >= 0; num--)
{
if (refresh)
{
list2.Add(generatedPerks[num]);
}
}
}
int num2 = Random.Range(minCards, maxCards + 1);
List<Perk> list3 = Helpers.GetAllPerks().ToList();
for (int num3 = list3.Count - 1; num3 >= 0; num3--)
{
if ((int)perkPageType == 0)
{
if ((int)list3[num3].spawnPool != 0)
{
list3.RemoveAt(num3);
continue;
}
}
else if ((int)perkPageType == 1 && (int)list3[num3].spawnPool != 1)
{
list3.RemoveAt(num3);
continue;
}
if (refresh && list3.Count > maxCards && list2 != null && list2.Contains(list3[num3].id))
{
list3.RemoveAt(num3);
}
}
for (int i = 0; i < num2; i++)
{
bool flag = false;
Perk val = null;
int num4 = 0;
while (!flag && num4 < 100)
{
num4++;
val = list3[Random.Range(0, list3.Count)];
if ((int)perkPageType == 1)
{
flag = true;
}
else if ((i < num2 - 1 && val.cost == 0) || (i == num2 - 1 && val.cost > 0))
{
flag = true;
}
}
list3.Remove(val);
list.Add(val.id);
}
return list;
}
}
public static class Vanga
{
public struct PerkMachinePred
{
public PerkPageType PerkPageType;
public string LevelName;
public PredictedPerks PredictedPerks;
public PerkMachinePred(PerkPageType type, string levelName, int minCards, int maxCards)
{
//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_0006: 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_0014: Unknown result type (might be due to invalid IL or missing references)
PerkPageType = type;
LevelName = levelName;
PredictedPerks = PerkMock.Generate(type, levelName, minCards, maxCards);
}
}
public struct RouteInfo
{
public string RouteName;
public List<PerkMachinePred> PerkMachines;
}
private static readonly List<string> _machines = new List<string>(6) { "perkpage_unstable_M1_Silos_SafeArea_01_2_2", "perkpage_regular_Campaign_Interlude_Silo_To_Pipeworks_01_3_3", "perkpage_regular_Campaign_Interlude_Sink_To_Pipeworks_01_2_2", "perkpage_unstable_Campaign_Interlude_Sink_To_Pipeworks_01_2_2", "perkpage_regular_M3_Habitation_Shaft_Intro_3_3", "perkpage_regular_Campaign_Interlude_Chute_To_Habitation_2_2" };
public static List<RouteInfo> GenerateRouteInfos(int s)
{
Plugin.SeedForRandom = s;
return new List<RouteInfo>(3)
{
new RouteInfo
{
RouteName = "default",
PerkMachines = new List<PerkMachinePred>(3)
{
new PerkMachinePred((PerkPageType)1, "M1_Silos_SafeArea_01", 2, 2),
new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Silo_To_Pipeworks_01", 3, 3),
new PerkMachinePred((PerkPageType)0, "M3_Habitation_Shaft_Intro", 3, 3)
}
},
new RouteInfo
{
RouteName = "shortcut_sink",
PerkMachines = new List<PerkMachinePred>(3)
{
new PerkMachinePred((PerkPageType)1, "Campaign_Interlude_Sink_To_Pipeworks_01", 2, 2),
new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Sink_To_Pipeworks_01", 2, 2),
new PerkMachinePred((PerkPageType)0, "M3_Habitation_Shaft_Intro", 3, 3)
}
},
new RouteInfo
{
RouteName = "shortcut_burner",
PerkMachines = new List<PerkMachinePred>(3)
{
new PerkMachinePred((PerkPageType)1, "M1_Silos_SafeArea_01", 2, 2),
new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Silo_To_Pipeworks_01", 3, 3),
new PerkMachinePred((PerkPageType)0, "Campaign_Interlude_Chute_To_Habitation", 2, 2)
}
}
};
}
public static void DoSeedSearch()
{
if (string.IsNullOrWhiteSpace(Plugin.DesiredRouteDescription.Value))
{
return;
}
HashSet<string> hashSet = new HashSet<string> { "default", "shortcut_sink", "shortcut_burner" };
string[] array = Plugin.DesiredRouteDescription.Value.Split(':');
if (array.Length == 0)
{
Plugin.Beep.LogWarning((object)("Invalid DesiredRouteDescription format: '" + Plugin.DesiredRouteDescription.Value + "'. Expected format: '{route}: {perk1}, {perk2}, {perk3}'"));
return;
}
string routeName = array[0].Trim().ToLower();
string[] array2 = ((array.Length >= 2) ? (from p in array[1].Split(',')
select p.Trim().ToLower()).ToArray() : Array.Empty<string>());
if (!hashSet.Contains(routeName))
{
Plugin.Beep.LogWarning((object)("Invalid desired route name: '" + routeName + "'. Valid routes are: " + string.Join(", ", hashSet)));
return;
}
if (array2.Length != 3)
{
Plugin.Beep.LogWarning((object)string.Format("Invalid desired perk count: {0}. Expected exactly {1} perks but got: {2}", array2.Length, 3, string.Join(", ", array2)));
return;
}
Plugin.Beep.LogInfo((object)("Searching desired route: " + routeName + ", perks: " + string.Join(", ", array2)));
for (int i = Plugin.SeedSearchMin.Value; i <= Plugin.SeedSearchMax.Value; i++)
{
RouteInfo routeInfo = GenerateRouteInfos(i).First((RouteInfo x) => x.RouteName == routeName);
if (routeInfo.PerkMachines[0].PredictedPerks.PerkIds.Contains(array2[0]) && routeInfo.PerkMachines[1].PredictedPerks.PerkIds.Contains(array2[1]) && routeInfo.PerkMachines[2].PredictedPerks.PerkIds.Contains(array2[2]))
{
Plugin.Beep.LogInfo((object)$"Found suitable seed: {i}");
}
}
}
}
}