#define DEBUG
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Xml.Linq;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using OutwardModsCommunicator.EventBus;
using OutwardModsCommunicator.Managers;
using OutwardSoftcoreMode.BepInEx.Configs;
using OutwardSoftcoreMode.Events;
using OutwardSoftcoreMode.Services;
using SideLoader;
using UnityEngine;
using UnityEngine.Events;
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: AssemblyTitle("OutwardSoftcoreMode")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OutwardSoftcoreMode")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("c5450fe0-edcf-483f-b9ea-4b1ef9d36da7")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace OutwardSoftcoreMode
{
[BepInPlugin("gymmed.softcore_mode", "Softcore Mode", "0.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class OutwardSoftcoreMode : BaseUnityPlugin
{
public const string GUID = "gymmed.softcore_mode";
public const string NAME = "Softcore Mode";
public const string VERSION = "0.1.0";
public static string prefix = "[Softcore-Mode]";
public const string EVENTS_LISTENER_GUID = "gymmed.softcore_mode_*";
internal static ManualLogSource Log;
public static bool IsCurrentGameSoftcore;
internal static int PendingSoftcoreCount;
internal static HashSet<string> PendingManualBackupUIDs = new HashSet<string>();
internal static HashSet<string> PendingCooldownUIDs = new HashSet<string>();
public static ConfigEntry<int> MaxBackups => BackupsConfigs.MaxBackups;
public static ConfigEntry<float> SaveCooldownHours => SaveCooldownConfigs.SaveCooldownHours;
public static ConfigEntry<int> DeathChance => DeathChanceConfigs.DeathChance;
public static ConfigEntry<bool> CooldownRandomizerEnabled => CooldownRandomizerConfigs.CooldownRandomizerEnabled;
public static ConfigEntry<float> CooldownRandomizerMinHours => CooldownRandomizerConfigs.CooldownRandomizerMinHours;
public static ConfigEntry<float> CooldownRandomizerMaxHours => CooldownRandomizerConfigs.CooldownRandomizerMaxHours;
internal void Awake()
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
Log = ((BaseUnityPlugin)this).Logger;
LogMessage("Hello world from Softcore Mode 0.1.0!");
BackupsConfigs.Init((BaseUnityPlugin)(object)this);
SaveCooldownConfigs.Init((BaseUnityPlugin)(object)this);
DeathChanceConfigs.Init((BaseUnityPlugin)(object)this);
CooldownRandomizerConfigs.Init((BaseUnityPlugin)(object)this);
new Harmony("gymmed.softcore_mode").PatchAll();
EventBusRegister.RegisterEvents();
}
internal void Update()
{
if (PendingCooldownUIDs.Count <= 0)
{
return;
}
float gameTimeF = EnvironmentConditions.GameTimeF;
if (!(gameTimeF > 0f))
{
return;
}
foreach (string pendingCooldownUID in PendingCooldownUIDs)
{
SoftcoreSaveManager.SetLastBackupGameTime(pendingCooldownUID, gameTimeF);
SoftcoreSaveManager.ClearRestoredFlag(pendingCooldownUID);
DebugLog($"Cooldown set to {gameTimeF:F2} for restored {pendingCooldownUID} (deferred)");
}
PendingCooldownUIDs.Clear();
}
public static void LogMessage(string message)
{
Log.LogMessage((object)(prefix + " " + message));
}
[Conditional("DEBUG")]
public static void DebugLog(string message)
{
Log.LogMessage((object)(prefix + " [DEBUG] " + message));
}
public static void LogSL(string message)
{
SL.Log(prefix + " " + message);
}
public static string GetProjectLocation()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
}
}
namespace OutwardSoftcoreMode.Services
{
public static class BackupMetadataStore
{
private const string FILENAME = "SoftcoreSaveData.xml";
private static string GetFilePath(string uid, string instanceTimestamp)
{
return Path.Combine(SoftcorePaths.GetBackupsDir(uid), instanceTimestamp, "SoftcoreSaveData.xml");
}
public static void WriteGameTime(string uid, string instanceTimestamp, float gameTime)
{
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Expected O, but got Unknown
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Expected O, but got Unknown
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Expected O, but got Unknown
string filePath = GetFilePath(uid, instanceTimestamp);
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
XDocument val = new XDocument(new object[1] { (object)new XElement(XName.op_Implicit("SoftcoreBackupData"), (object)new XElement(XName.op_Implicit("GameTime"), (object)gameTime.ToString("F2"))) });
val.Save(filePath);
}
private static bool TryReadGameTime(string uid, string instanceTimestamp, out float gameTime)
{
string filePath = GetFilePath(uid, instanceTimestamp);
if (!File.Exists(filePath))
{
gameTime = -1f;
return false;
}
try
{
XDocument val = XDocument.Load(filePath);
XElement root = val.Root;
XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("GameTime")) : null);
if (val2 != null && float.TryParse(val2.Value, out gameTime))
{
return true;
}
}
catch
{
}
gameTime = -1f;
return false;
}
public static float GetLatestBackupGameTime(string uid)
{
string backupsDir = SoftcorePaths.GetBackupsDir(uid);
if (!Directory.Exists(backupsDir))
{
return -1f;
}
IOrderedEnumerable<DirectoryInfo> orderedEnumerable = from d in new DirectoryInfo(backupsDir).GetDirectories()
orderby d.Name descending
select d;
foreach (DirectoryInfo item in orderedEnumerable)
{
if (TryReadGameTime(uid, item.Name, out var gameTime))
{
return gameTime;
}
}
return -1f;
}
}
public static class SoftcoreColors
{
public const string PurpleHex = "#A855F7";
public static Color Purple => new Color(0.6588f, 0.3333f, 0.9686f);
public static void DestroyLocalize(GameObject obj)
{
UILocalize[] componentsInChildren = obj.GetComponentsInChildren<UILocalize>(true);
foreach (UILocalize val in componentsInChildren)
{
Object.Destroy((Object)(object)val);
}
}
public static GameObject CreateSoftcoreLabel(GameObject template, Transform parent, string name)
{
GameObject val = Object.Instantiate<GameObject>(template, parent);
((Object)val).name = name;
DestroyLocalize(val);
return val;
}
}
public static class SoftcorePaths
{
public static string RootPath => Path.Combine(PathsManager.ConfigPath, "Softcore_Mode");
public static string CharactersPath => Path.Combine(RootPath, "Characters");
public static string BackupsPath => Path.Combine(RootPath, "Backups");
public static string GetMetadataPath(string uid)
{
return Path.Combine(CharactersPath, uid + ".xml");
}
public static string GetBackupsDir(string uid)
{
return Path.Combine(BackupsPath, uid);
}
}
public static class SoftcoreSaveManager
{
private static void EnsureDirectoriesExist()
{
Directory.CreateDirectory(SoftcorePaths.CharactersPath);
Directory.CreateDirectory(SoftcorePaths.BackupsPath);
}
private static XDocument LoadMetadataOrNull(string uid)
{
string metadataPath = SoftcorePaths.GetMetadataPath(uid);
if (!File.Exists(metadataPath))
{
return null;
}
try
{
return XDocument.Load(metadataPath);
}
catch
{
return null;
}
}
private static void ModifyMetadata(string uid, Action<XElement> transform)
{
string metadataPath = SoftcorePaths.GetMetadataPath(uid);
if (!File.Exists(metadataPath))
{
return;
}
try
{
XDocument val = XDocument.Load(metadataPath);
transform(val.Root);
val.Save(metadataPath);
}
catch (Exception ex)
{
OutwardSoftcoreMode.LogMessage("Failed to modify metadata: " + ex.Message);
}
}
public static bool IsSoftcoreCharacter(string uid)
{
if (string.IsNullOrEmpty(uid))
{
return false;
}
return File.Exists(SoftcorePaths.GetMetadataPath(uid)) || Directory.Exists(SoftcorePaths.GetBackupsDir(uid));
}
public static string GetCharacterName(string uid)
{
XDocument obj = LoadMetadataOrNull(uid);
object result;
if (obj == null)
{
result = null;
}
else
{
XElement root = obj.Root;
if (root == null)
{
result = null;
}
else
{
XElement obj2 = ((XContainer)root).Element(XName.op_Implicit("Name"));
result = ((obj2 != null) ? obj2.Value : null);
}
}
return (string)result;
}
public static void WriteMetadata(string uid, string name)
{
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Expected O, but got Unknown
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Expected O, but got Unknown
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Expected O, but got Unknown
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Expected O, but got Unknown
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Expected O, but got Unknown
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Expected O, but got Unknown
EnsureDirectoriesExist();
XDocument val = new XDocument(new object[1] { (object)new XElement(XName.op_Implicit("SoftcoreCharacter"), new object[4]
{
(object)new XElement(XName.op_Implicit("Name"), (object)(name ?? "Unknown")),
(object)new XElement(XName.op_Implicit("PermanentDeathCount"), (object)0),
(object)new XElement(XName.op_Implicit("LastBackupGameTime"), (object)(-1)),
(object)new XElement(XName.op_Implicit("CooldownDuration"), (object)(-1f))
}) });
val.Save(SoftcorePaths.GetMetadataPath(uid));
}
public static int GetPermanentDeathCount(string uid)
{
XDocument val = LoadMetadataOrNull(uid);
if (val == null)
{
return 0;
}
XElement root = val.Root;
XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("PermanentDeathCount")) : null);
if (val2 != null && int.TryParse(val2.Value, out var result))
{
return result;
}
val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("DeathCount")) : null);
return (val2 != null && int.TryParse(val2.Value, out result)) ? result : 0;
}
public static void IncrementPermanentDeathCount(string uid)
{
int permanentDeathCount = GetPermanentDeathCount(uid);
OutwardSoftcoreMode.DebugLog($"IncrementPermanentDeathCount: uid={uid}, oldCount={permanentDeathCount}, path={SoftcorePaths.GetMetadataPath(uid)}");
string metadataPath = SoftcorePaths.GetMetadataPath(uid);
if (!File.Exists(metadataPath))
{
string name = GetCharacterName(uid) ?? "Unknown";
WriteMetadata(uid, name);
}
ModifyMetadata(uid, delegate(XElement root)
{
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Expected O, but got Unknown
XElement val = ((XContainer)root).Element(XName.op_Implicit("PermanentDeathCount"));
if (val != null)
{
int.TryParse(val.Value, out var result);
val.Value = (result + 1).ToString();
}
else
{
((XContainer)root).Add((object)new XElement(XName.op_Implicit("PermanentDeathCount"), (object)1));
}
});
int permanentDeathCount2 = GetPermanentDeathCount(uid);
OutwardSoftcoreMode.DebugLog($"IncrementPermanentDeathCount: uid={uid}, newCount={permanentDeathCount2}");
}
public static void EnsureMetadataExists(string uid, string name)
{
string metadataPath = SoftcorePaths.GetMetadataPath(uid);
if (!File.Exists(metadataPath))
{
WriteMetadata(uid, name ?? "Unknown");
OutwardSoftcoreMode.DebugLog("Created metadata for " + uid + " (name=" + name + ")");
}
}
public static float GetLastBackupGameTime(string uid)
{
float latestBackupGameTime = BackupMetadataStore.GetLatestBackupGameTime(uid);
if (latestBackupGameTime >= 0f)
{
return latestBackupGameTime;
}
XDocument val = LoadMetadataOrNull(uid);
if (val == null)
{
return -1f;
}
XElement root = val.Root;
XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("LastBackupGameTime")) : null);
float result;
return (val2 != null && float.TryParse(val2.Value, out result)) ? result : (-1f);
}
public static void SetLastBackupGameTime(string uid, float gameTime)
{
ModifyMetadata(uid, delegate(XElement root)
{
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Expected O, but got Unknown
XElement val = ((XContainer)root).Element(XName.op_Implicit("LastBackupGameTime"));
if (val != null)
{
val.Value = gameTime.ToString("F2");
}
else
{
((XContainer)root).Add((object)new XElement(XName.op_Implicit("LastBackupGameTime"), (object)gameTime.ToString("F2")));
}
});
}
private static (bool canBackup, float remainingTime) GetCooldownState(string uid)
{
float num = OutwardSoftcoreMode.SaveCooldownHours?.Value ?? 24f;
if (num <= 0f)
{
return (canBackup: true, remainingTime: 0f);
}
float lastBackupGameTime = GetLastBackupGameTime(uid);
if (lastBackupGameTime < 0f)
{
return (canBackup: true, remainingTime: 0f);
}
float num2 = ReadStoredCooldownDuration(uid, num);
float gameTimeF = EnvironmentConditions.GameTimeF;
float num3 = gameTimeF - lastBackupGameTime;
float item = Math.Max(0f, num2 - num3);
return (canBackup: num3 >= num2, remainingTime: item);
}
public static bool CanBackupNow(string uid)
{
return GetCooldownState(uid).canBackup;
}
public static float GetRemainingCooldownTime(string uid)
{
return GetCooldownState(uid).remainingTime;
}
public static string GetCharacterSaveDirectory(string uid)
{
return Path.Combine(SaveManager.GetSavePath(), "Save_" + uid);
}
private static void CopyDirectoryContents(string sourceDir, string destDir)
{
Directory.CreateDirectory(destDir);
string[] files = Directory.GetFiles(sourceDir);
foreach (string text in files)
{
string destFileName = Path.Combine(destDir, Path.GetFileName(text));
File.Copy(text, destFileName, overwrite: true);
}
}
public static bool IsRestoredBackupInstance(string uid, string instancePath)
{
if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(instancePath))
{
return false;
}
return Directory.Exists(Path.Combine(SoftcorePaths.GetBackupsDir(uid), instancePath));
}
private static float ReadStoredCooldownDuration(string uid, float fallback)
{
XDocument val = LoadMetadataOrNull(uid);
if (val == null)
{
return fallback;
}
XElement root = val.Root;
XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("CooldownDuration")) : null);
if (val2 != null && float.TryParse(val2.Value, out var result) && result > 0f)
{
return result;
}
return fallback;
}
private static void WriteCooldownDuration(string uid, float duration)
{
ModifyMetadata(uid, delegate(XElement root)
{
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Expected O, but got Unknown
XElement val = ((XContainer)root).Element(XName.op_Implicit("CooldownDuration"));
if (val != null)
{
val.Value = duration.ToString("F2");
}
else
{
((XContainer)root).Add((object)new XElement(XName.op_Implicit("CooldownDuration"), (object)duration.ToString("F2")));
}
});
}
public static void CreateBackup(string uid, string instanceTimestamp)
{
if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(instanceTimestamp))
{
return;
}
EnsureDirectoriesExist();
string text = Path.Combine(GetCharacterSaveDirectory(uid), instanceTimestamp);
if (!Directory.Exists(text))
{
OutwardSoftcoreMode.LogMessage("Backup source not found: " + text);
return;
}
string destDir = Path.Combine(SoftcorePaths.GetBackupsDir(uid), instanceTimestamp);
CopyDirectoryContents(text, destDir);
OutwardSoftcoreMode.LogMessage("Backup created for " + uid + " at " + instanceTimestamp);
float gameTimeF = EnvironmentConditions.GameTimeF;
ConfigEntry<bool> cooldownRandomizerEnabled = OutwardSoftcoreMode.CooldownRandomizerEnabled;
float duration;
if (cooldownRandomizerEnabled != null && cooldownRandomizerEnabled.Value)
{
float num = OutwardSoftcoreMode.CooldownRandomizerMinHours?.Value ?? 24f;
float num2 = OutwardSoftcoreMode.CooldownRandomizerMaxHours?.Value ?? 168f;
duration = Random.Range(num, num2);
}
else
{
duration = OutwardSoftcoreMode.SaveCooldownHours?.Value ?? 24f;
}
BackupMetadataStore.WriteGameTime(uid, instanceTimestamp, gameTimeF);
SetLastBackupGameTime(uid, gameTimeF);
WriteCooldownDuration(uid, duration);
EnforceBackupLimit(uid);
}
private static void EnforceBackupLimit(string uid)
{
string backupsDir = SoftcorePaths.GetBackupsDir(uid);
if (!Directory.Exists(backupsDir))
{
return;
}
int num = OutwardSoftcoreMode.MaxBackups?.Value ?? 10;
List<DirectoryInfo> list = (from d in new DirectoryInfo(backupsDir).GetDirectories()
orderby d.Name descending
select d).ToList();
while (list.Count > num)
{
DirectoryInfo directoryInfo = list[list.Count - 1];
try
{
directoryInfo.Delete(recursive: true);
OutwardSoftcoreMode.LogMessage("Deleted old backup: " + directoryInfo.Name);
}
catch (Exception ex)
{
OutwardSoftcoreMode.LogMessage("Failed to delete old backup: " + ex.Message);
}
list.RemoveAt(list.Count - 1);
}
}
private static HashSet<string> GetActiveCharacterUIDs()
{
HashSet<string> hashSet = new HashSet<string>();
SaveManager instance = SaveManager.Instance;
if (((instance != null) ? instance.CharacterSaves : null) == null)
{
return hashSet;
}
foreach (CharacterSaveInstanceHolder characterSafe in SaveManager.Instance.CharacterSaves)
{
if (characterSafe != null)
{
hashSet.Add(characterSafe.CharacterUID);
}
}
return hashSet;
}
public static void RestoreOrphanedBackups()
{
string path = Path.Combine(SoftcorePaths.RootPath, "restored_instances.xml");
if (File.Exists(path))
{
try
{
File.Delete(path);
OutwardSoftcoreMode.LogMessage("Cleaned up legacy restored_instances.xml index");
}
catch (Exception ex)
{
OutwardSoftcoreMode.LogMessage("Failed to delete legacy index: " + ex.Message);
}
}
if (!Directory.Exists(SoftcorePaths.BackupsPath))
{
OutwardSoftcoreMode.LogMessage("RestoreOrphanedBackups: no backups directory");
return;
}
HashSet<string> activeCharacterUIDs = GetActiveCharacterUIDs();
string[] directories = Directory.GetDirectories(SoftcorePaths.BackupsPath);
OutwardSoftcoreMode.LogMessage($"RestoreOrphanedBackups: scanning {directories.Length} backup dirs, {activeCharacterUIDs.Count} active characters");
int num = 0;
string[] array = directories;
foreach (string path2 in array)
{
string fileName = Path.GetFileName(path2);
if (!string.IsNullOrEmpty(fileName) && !activeCharacterUIDs.Contains(fileName))
{
OutwardSoftcoreMode.LogMessage("RestoreOrphanedBackups: restoring " + fileName);
RestoreCharacter(fileName);
num++;
}
}
OutwardSoftcoreMode.LogMessage($"RestoreOrphanedBackups: restored {num} characters");
if (num > 0)
{
RefreshCharacterSelectionPanels();
}
}
public static bool IsRestoredBackupCharacter(string uid)
{
XDocument val = LoadMetadataOrNull(uid);
if (val == null)
{
return false;
}
XElement root = val.Root;
XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("IsRestored")) : null);
bool result = default(bool);
return val2 != null && bool.TryParse(val2.Value, out result) && result;
}
public static void SetRestoredFlag(string uid)
{
string metadataPath = SoftcorePaths.GetMetadataPath(uid);
if (!File.Exists(metadataPath))
{
OutwardSoftcoreMode.DebugLog("SetRestoredFlag: no metadata to flag for " + uid + " — skipping");
return;
}
for (int i = 0; i < 2; i++)
{
ModifyMetadata(uid, delegate(XElement root)
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Expected O, but got Unknown
XElement val = ((XContainer)root).Element(XName.op_Implicit("IsRestored"));
if (val != null)
{
val.Value = "true";
}
else
{
((XContainer)root).Add((object)new XElement(XName.op_Implicit("IsRestored"), (object)"true"));
}
});
if (IsRestoredBackupCharacter(uid))
{
return;
}
}
OutwardSoftcoreMode.LogMessage("Failed to persist IsRestored flag for " + uid);
}
public static void ClearRestoredFlag(string uid)
{
if (!File.Exists(SoftcorePaths.GetMetadataPath(uid)))
{
return;
}
ModifyMetadata(uid, delegate(XElement root)
{
XElement val = ((XContainer)root).Element(XName.op_Implicit("IsRestored"));
if (val != null)
{
val.Value = "false";
}
});
}
private static void RestoreCharacter(string uid)
{
string backupsDir = SoftcorePaths.GetBackupsDir(uid);
if (!Directory.Exists(backupsDir))
{
return;
}
List<DirectoryInfo> list = (from d in new DirectoryInfo(backupsDir).GetDirectories()
orderby d.Name descending
select d).ToList();
if (list.Count == 0)
{
return;
}
string characterSaveDirectory = GetCharacterSaveDirectory(uid);
foreach (DirectoryInfo item in list)
{
string text = Path.Combine(characterSaveDirectory, item.Name);
if (!Directory.Exists(text))
{
CopyDirectoryContents(item.FullName, text);
OutwardSoftcoreMode.LogMessage("Restored backup " + item.Name + " for " + uid);
}
}
SetRestoredFlag(uid);
RegisterRestoredCharacter(uid);
}
private static void RegisterRestoredCharacter(string uid)
{
string characterSaveDirectory = GetCharacterSaveDirectory(uid);
if (!Directory.Exists(characterSaveDirectory))
{
return;
}
CharacterSaveInstanceHolder val = CharacterSaveInstanceHolder.PrepareCharacterSaveInstanceHolder(uid, characterSaveDirectory);
if (val == null)
{
OutwardSoftcoreMode.LogMessage("RegisterRestoredCharacter: PrepareCharacterSaveInstanceHolder returned null for " + uid);
return;
}
object value = AccessTools.Field(typeof(SaveManager), "m_charSaves").GetValue(SaveManager.Instance);
if (value != null)
{
MethodInfo methodInfo = AccessTools.Method(value.GetType(), "ContainsKey", (Type[])null, (Type[])null);
MethodInfo methodInfo2 = AccessTools.Method(value.GetType(), "Remove", (Type[])null, (Type[])null);
MethodInfo methodInfo3 = AccessTools.Method(value.GetType(), "Add", (Type[])null, (Type[])null);
if ((bool)methodInfo.Invoke(value, new object[1] { uid }))
{
methodInfo2.Invoke(value, new object[1] { uid });
}
methodInfo3.Invoke(value, new object[2] { uid, val });
}
}
private static void RefreshCharacterSelectionPanels()
{
CharacterSelectionPanel[] array = Resources.FindObjectsOfTypeAll<CharacterSelectionPanel>();
CharacterSelectionPanel[] array2 = array;
foreach (CharacterSelectionPanel val in array2)
{
if (((Behaviour)val).isActiveAndEnabled)
{
MethodInfo methodInfo = AccessTools.Method(typeof(CharacterSelectionPanel), "RefreshCharacterList", (Type[])null, (Type[])null);
methodInfo.Invoke(val, null);
}
}
}
}
}
namespace OutwardSoftcoreMode.Patches
{
[HarmonyPatch(typeof(CharacterSaveInstanceDisplay), "SetSaveInstance")]
public class Patch_CharacterSaveInstanceDisplay_SetSaveInstance
{
private struct AreaNameLayout
{
public Vector2 anchorMin;
public Vector2 anchorMax;
public Vector2 pivot;
public Vector2 anchoredPosition;
public Vector2 sizeDelta;
}
private const string LabelName = "lblSaveSoftcore";
private const float RightLabelWidth = 339.1f;
private const float LabelHeight = 35f;
private static readonly Dictionary<int, AreaNameLayout> _savedAreaNameLayout = new Dictionary<int, AreaNameLayout>();
private static void Postfix(CharacterSaveInstanceDisplay __instance, SaveInstance _instance)
{
string uid = _instance?.SaveID;
string instancePath = _instance?.InstancePath;
Transform val = ((Component)__instance).transform.Find("Data");
if (!((Object)(object)val == (Object)null))
{
if (SoftcoreSaveManager.IsRestoredBackupInstance(uid, instancePath))
{
SetupLayout(val);
UpdateLabel(val, "Backup");
}
else
{
ResetLayout(val);
HideLabel(val);
}
}
}
private static RectTransform GetAreaNameRectTransform(Transform data)
{
Transform val = data.Find("lblAreaName");
return ((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<RectTransform>() : null;
}
private static void SaveOriginalAreaNameLayout(Transform data)
{
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
int instanceID = ((Object)data).GetInstanceID();
if (!_savedAreaNameLayout.ContainsKey(instanceID))
{
RectTransform areaNameRectTransform = GetAreaNameRectTransform(data);
if (!((Object)(object)areaNameRectTransform == (Object)null))
{
_savedAreaNameLayout[instanceID] = new AreaNameLayout
{
anchorMin = areaNameRectTransform.anchorMin,
anchorMax = areaNameRectTransform.anchorMax,
pivot = areaNameRectTransform.pivot,
anchoredPosition = areaNameRectTransform.anchoredPosition,
sizeDelta = areaNameRectTransform.sizeDelta
};
}
}
}
private static void SetupLayout(Transform data)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_0101: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
//IL_012f: Unknown result type (might be due to invalid IL or missing references)
//IL_013c: Unknown result type (might be due to invalid IL or missing references)
//IL_0153: Unknown result type (might be due to invalid IL or missing references)
RectTransform areaNameRectTransform = GetAreaNameRectTransform(data);
if ((Object)(object)areaNameRectTransform != (Object)null)
{
SaveOriginalAreaNameLayout(data);
areaNameRectTransform.anchorMin = new Vector2(1f, 1f);
areaNameRectTransform.anchorMax = new Vector2(1f, 1f);
areaNameRectTransform.pivot = new Vector2(1f, 1f);
areaNameRectTransform.anchoredPosition = Vector2.zero;
areaNameRectTransform.sizeDelta = new Vector2(339.1f, 35f);
}
Transform val = data.Find("lblSaveSoftcore");
if ((Object)(object)val != (Object)null)
{
((Component)val).gameObject.SetActive(true);
return;
}
Transform val2 = data.Find("lblTime");
if (!((Object)(object)val2 == (Object)null))
{
GameObject val3 = SoftcoreColors.CreateSoftcoreLabel(((Component)val2).gameObject, data, "lblSaveSoftcore");
RectTransform component = val3.GetComponent<RectTransform>();
if ((Object)(object)component != (Object)null)
{
component.anchorMin = new Vector2(1f, 0f);
component.anchorMax = new Vector2(1f, 0f);
component.pivot = new Vector2(1f, 0f);
component.anchoredPosition = Vector2.zero;
component.sizeDelta = new Vector2(339.1f, 35f);
}
}
}
private static void UpdateLabel(Transform data, string text)
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
Transform val = data.Find("lblSaveSoftcore");
if (!((Object)(object)val == (Object)null))
{
Text componentInChildren = ((Component)val).GetComponentInChildren<Text>();
if (!((Object)(object)componentInChildren == (Object)null))
{
componentInChildren.text = text;
((Graphic)componentInChildren).color = SoftcoreColors.Purple;
componentInChildren.alignment = (TextAnchor)5;
}
}
}
private static void ResetLayout(Transform data)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
int instanceID = ((Object)data).GetInstanceID();
RectTransform areaNameRectTransform = GetAreaNameRectTransform(data);
if (!((Object)(object)areaNameRectTransform == (Object)null) && _savedAreaNameLayout.TryGetValue(instanceID, out var value))
{
areaNameRectTransform.anchorMin = value.anchorMin;
areaNameRectTransform.anchorMax = value.anchorMax;
areaNameRectTransform.pivot = value.pivot;
areaNameRectTransform.anchoredPosition = value.anchoredPosition;
areaNameRectTransform.sizeDelta = value.sizeDelta;
_savedAreaNameLayout.Remove(instanceID);
}
}
private static void HideLabel(Transform data)
{
Transform val = data.Find("lblSaveSoftcore");
if ((Object)(object)val != (Object)null)
{
((Component)val).gameObject.SetActive(false);
}
}
}
[HarmonyPatch(typeof(CharacterSaveSlot), "SetSave")]
public class Patch_CharacterSaveSlot_SetSave
{
private static void Postfix(CharacterSaveSlot __instance, CharacterSaveInstanceHolder _saveContainer)
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Expected O, but got Unknown
//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
string characterUID = _saveContainer.CharacterUID;
Transform val = (Transform)AccessTools.Field(typeof(CharacterSaveSlot), "m_hardcoreFlag").GetValue(__instance);
if ((Object)(object)val == (Object)null)
{
return;
}
Transform val2 = val.parent.Find("lblSoftcore");
if (!SoftcoreSaveManager.IsSoftcoreCharacter(characterUID))
{
if ((Object)(object)val2 != (Object)null)
{
((Component)val2).gameObject.SetActive(false);
}
return;
}
GameObject val3 = ((val2 != null) ? ((Component)val2).gameObject : null);
if ((Object)(object)val3 == (Object)null)
{
val3 = SoftcoreColors.CreateSoftcoreLabel(((Component)val).gameObject, val.parent, "lblSoftcore");
}
val3.SetActive(true);
Text componentInChildren = val3.GetComponentInChildren<Text>();
if ((Object)(object)componentInChildren != (Object)null)
{
int permanentDeathCount = SoftcoreSaveManager.GetPermanentDeathCount(characterUID);
componentInChildren.text = ((permanentDeathCount > 0) ? $"Softcore {permanentDeathCount}" : "Softcore");
((Graphic)componentInChildren).color = SoftcoreColors.Purple;
}
((Component)val).gameObject.SetActive(false);
}
}
[HarmonyPatch(typeof(CharacterSelectionPanel), "OnEnable")]
public class Patch_CharacterSelectionPanel_OnEnable
{
private static void Postfix()
{
SoftcoreSaveManager.RestoreOrphanedBackups();
}
}
[HarmonyPatch(typeof(CharacterSelectionPanel), "RefreshCharacterList")]
public class Patch_CharacterSelectionPanel_RefreshCharacterList
{
private static void Postfix(CharacterSelectionPanel __instance)
{
if (((UIElement)__instance).PlayerID <= 0)
{
return;
}
FieldInfo fieldInfo = AccessTools.Field(typeof(CharacterSelectionPanel), "m_saveSlot");
if (!(fieldInfo.GetValue(__instance) is IList list))
{
return;
}
SaveManager instance = SaveManager.Instance;
IList<CharacterSaveInstanceHolder> list2 = ((instance != null) ? instance.CharacterSaves : null);
if (list2 == null)
{
return;
}
for (int i = 0; i < list.Count && i < list2.Count; i++)
{
object obj = list[i];
if (obj == null)
{
continue;
}
Type type = obj.GetType();
PropertyInfo property = type.GetProperty("HarcoreMode");
if (property == null)
{
continue;
}
bool flag = (bool)property.GetValue(obj, null);
if (flag == CharacterManager.Instance.HardcoreMode)
{
continue;
}
CharacterSaveInstanceHolder obj2 = list2[i];
string text = ((obj2 != null) ? obj2.CharacterUID : null);
if (!string.IsNullOrEmpty(text) && SoftcoreSaveManager.IsSoftcoreCharacter(text))
{
PropertyInfo property2 = type.GetProperty("interactable");
if (property2 != null)
{
property2.SetValue(obj, true, null);
}
}
}
}
}
[HarmonyPatch(typeof(Character), "LoadPlayerSave")]
public class Patch_Character_LoadPlayerSave
{
private static void Postfix(Character __instance, PlayerSaveData _save)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
string text = UID.op_Implicit(__instance.UID);
OutwardSoftcoreMode.IsCurrentGameSoftcore = SoftcoreSaveManager.IsSoftcoreCharacter(text);
if (OutwardSoftcoreMode.IsCurrentGameSoftcore)
{
if (SoftcoreSaveManager.IsRestoredBackupCharacter(text))
{
OutwardSoftcoreMode.PendingCooldownUIDs.Add(text);
SoftcoreSaveManager.ClearRestoredFlag(text);
OutwardSoftcoreMode.DebugLog("Cooldown deferred for restored character " + text);
}
else if (SoftcoreSaveManager.GetLastBackupGameTime(text) < 0f)
{
float gameTimeF = EnvironmentConditions.GameTimeF;
SoftcoreSaveManager.SetLastBackupGameTime(text, gameTimeF);
OutwardSoftcoreMode.DebugLog($"Cooldown initialized to {gameTimeF:F2} for {text}");
}
}
}
}
[HarmonyPatch(typeof(DefeatScenariosManager), "ActivateDefeatScenario")]
public class Patch_DefeatScenariosManager_ActivateDefeatScenario
{
private static readonly List<string> _defeatedCharacterUIDs = new List<string>();
private static bool _deathTriggered;
private static bool _suppressedHardcore;
private static bool _originalSupportHardcore;
private static bool Prefix(DefeatScenario _scenario)
{
_defeatedCharacterUIDs.Clear();
_deathTriggered = false;
_suppressedHardcore = false;
_originalSupportHardcore = false;
CharacterManager instance = CharacterManager.Instance;
AddIfDead((instance != null) ? instance.GetFirstLocalCharacter() : null);
AddIfDead((instance != null) ? instance.GetSecondLocalCharacter() : null);
if (_defeatedCharacterUIDs.Count == 0)
{
return true;
}
bool flag = false;
HashSet<string> hashSet = new HashSet<string>();
List<string> list = new List<string>();
foreach (string defeatedCharacterUID in _defeatedCharacterUIDs)
{
if (!string.IsNullOrEmpty(defeatedCharacterUID) && hashSet.Add(defeatedCharacterUID) && SoftcoreSaveManager.IsSoftcoreCharacter(defeatedCharacterUID))
{
flag = true;
list.Add(defeatedCharacterUID);
}
}
if (flag)
{
EventBusPublisher.PublishDeathRollBefore(new List<string>(list));
int num = OutwardSoftcoreMode.DeathChance?.Value ?? 20;
int num2 = Random.Range(0, 100);
if (num2 < num)
{
foreach (string item in list)
{
int permanentDeathCount = SoftcoreSaveManager.GetPermanentDeathCount(item);
SoftcoreSaveManager.IncrementPermanentDeathCount(item);
int permanentDeathCount2 = SoftcoreSaveManager.GetPermanentDeathCount(item);
OutwardSoftcoreMode.LogMessage($"Softcore death for {item} — permanent death count: {permanentDeathCount} -> {permanentDeathCount2}");
}
_deathTriggered = true;
}
EventBusPublisher.PublishDeathRollAfter(_deathTriggered, _deathTriggered ? new List<string>(list) : new List<string>());
}
if (_deathTriggered)
{
MethodInfo methodInfo = AccessTools.Method(typeof(DefeatScenariosManager), "DefeatHardcoreDeath", (Type[])null, (Type[])null);
if (methodInfo != null)
{
methodInfo.Invoke(DefeatScenariosManager.Instance, null);
}
else
{
OutwardSoftcoreMode.LogMessage("DefeatHardcoreDeath method not found — setting HardcoreDeathTriggered directly");
CharacterManager.Instance.HardcoreDeathTriggered = true;
}
return false;
}
if (flag && (Object)(object)_scenario != (Object)null)
{
_suppressedHardcore = true;
_originalSupportHardcore = _scenario.SupportHardcore;
_scenario.SupportHardcore = false;
}
return true;
}
private static void AddIfDead(Character c)
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)c != (Object)null && (Object)(object)c.Stats != (Object)null && c.Stats.CurrentHealth <= 0f)
{
_defeatedCharacterUIDs.Add(UID.op_Implicit(c.UID));
}
}
private static void Postfix(DefeatScenario _scenario)
{
if (_suppressedHardcore && (Object)(object)_scenario != (Object)null)
{
_scenario.SupportHardcore = _originalSupportHardcore;
}
_defeatedCharacterUIDs.Clear();
_deathTriggered = false;
_suppressedHardcore = false;
_originalSupportHardcore = false;
}
}
[HarmonyPatch(typeof(MainScreen), "OnSplitModeChosen")]
public class Patch_MainScreen_DifficultySelection
{
private const string SoftcoreDisclaimer = "Are you sure you want to play in softcore mode? \n<color=#A855F7>In softcore mode, when you are defeated there is a chance that your character dies PERMANENTLY, but you can create manual save backups to restore if needed.</color>\n\nAdditional note: When playing coop, players must be in the same mode to play together.";
private static bool Prefix(MainScreen __instance, bool _splitActive)
{
//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
//IL_00eb: Expected O, but got Unknown
//IL_00eb: Expected O, but got Unknown
//IL_00eb: Expected O, but got Unknown
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Expected O, but got Unknown
//IL_00aa: Expected O, but got Unknown
//IL_00aa: Expected O, but got Unknown
OutwardSoftcoreMode.PendingSoftcoreCount = 0;
OutwardSoftcoreMode.IsCurrentGameSoftcore = false;
string loc = LocalizationManager.Instance.GetLoc("CharacterCreation_Mode_GameMode");
string[] array = new string[3] { "General_DifficultyNormal", "Softcore", "General_DifficultyHardcore" };
MethodInfo onHardcore = AccessTools.Method(typeof(MainScreen), "OnHardcoreModeChosen", (Type[])null, (Type[])null);
if (!_splitActive)
{
((UIElement)__instance).m_characterUI.MessagePanel.Show(loc, (string)null, array, (UnityAction)delegate
{
__instance.ShowCharacterCreation(false);
}, (UnityAction)delegate
{
OnSoftcoreModeChosen(__instance, splitActive: false);
}, (UnityAction)delegate
{
onHardcore.Invoke(__instance, new object[1] { false });
});
}
else
{
((UIElement)__instance).m_characterUI.MessagePanel.Show(loc, (string)null, array, (UnityAction)delegate
{
__instance.ShowCharacterCreation2P(false);
}, (UnityAction)delegate
{
OnSoftcoreModeChosen(__instance, splitActive: true);
}, (UnityAction)delegate
{
onHardcore.Invoke(__instance, new object[1] { true });
});
}
return false;
}
private static void OnSoftcoreModeChosen(MainScreen instance, bool splitActive)
{
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Expected O, but got Unknown
if (Object.op_Implicit((Object)(object)Global.AudioManager))
{
Global.AudioManager.PlaySound((Sounds)90001, 0f, 1f, 1f, 1f, 1f);
}
((UIElement)instance).m_characterUI.MessagePanel.Show("Are you sure you want to play in softcore mode? \n<color=#A855F7>In softcore mode, when you are defeated there is a chance that your character dies PERMANENTLY, but you can create manual save backups to restore if needed.</color>\n\nAdditional note: When playing coop, players must be in the same mode to play together.", (string)null, MakeConfirmAction(splitActive, instance), (UnityAction)delegate
{
instance.m_optionButtonsPanel.Focus();
}, true, -1f, (UnityAction)null);
}
private static UnityAction MakeConfirmAction(bool splitActive, MainScreen instance)
{
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Expected O, but got Unknown
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Expected O, but got Unknown
int count = ((!splitActive) ? 1 : 2);
if (splitActive)
{
return (UnityAction)delegate
{
OutwardSoftcoreMode.PendingSoftcoreCount = count;
instance.ShowCharacterCreation2P(true);
};
}
return (UnityAction)delegate
{
OutwardSoftcoreMode.PendingSoftcoreCount = count;
instance.ShowCharacterCreation(true);
};
}
}
[HarmonyPatch(typeof(PauseMenu), "Show")]
public class Patch_PauseMenu_AddSaveButton
{
private static void Postfix(PauseMenu __instance)
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
Character localCharacter = ((UIElement)__instance).LocalCharacter;
UID? val = ((localCharacter != null) ? new UID?(localCharacter.UID) : ((UID?)null));
string text = (val.HasValue ? UID.op_Implicit(val.GetValueOrDefault()) : null);
bool flag = !string.IsNullOrEmpty(text) && SoftcoreSaveManager.IsSoftcoreCharacter(text);
Transform buttonsContainer = GetButtonsContainer(__instance);
if ((Object)(object)buttonsContainer == (Object)null)
{
return;
}
Transform val2 = FindSoftcoreButton(buttonsContainer);
if (flag)
{
if ((Object)(object)val2 == (Object)null)
{
CreateButton(buttonsContainer);
val2 = FindSoftcoreButton(buttonsContainer);
}
if ((Object)(object)val2 != (Object)null)
{
((Component)val2).gameObject.SetActive(true);
RefreshSoftcoreSaveButton(__instance);
}
}
else if ((Object)(object)val2 != (Object)null)
{
((Component)val2).gameObject.SetActive(false);
}
}
private static Transform GetButtonsContainer(PauseMenu menu)
{
object? obj = AccessTools.Field(typeof(PauseMenu), "m_hideOnPauseButtons")?.GetValue(menu);
GameObject val = (GameObject)((obj is GameObject) ? obj : null);
return (val != null) ? val.transform : null;
}
private static Transform FindSoftcoreButton(Transform container)
{
return (container != null) ? container.Find("btnSaveSoftcore") : null;
}
private static Transform FindSoftcoreButton(PauseMenu menu)
{
Transform buttonsContainer = GetButtonsContainer(menu);
return ((Object)(object)buttonsContainer != (Object)null) ? FindSoftcoreButton(buttonsContainer) : null;
}
internal static void RefreshSoftcoreSaveButton(PauseMenu menu)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
Character localCharacter = ((UIElement)menu).LocalCharacter;
UID? val = ((localCharacter != null) ? new UID?(localCharacter.UID) : ((UID?)null));
string text = (val.HasValue ? UID.op_Implicit(val.GetValueOrDefault()) : null);
if (!string.IsNullOrEmpty(text) && SoftcoreSaveManager.IsSoftcoreCharacter(text))
{
Transform val2 = FindSoftcoreButton(menu);
if (!((Object)(object)val2 == (Object)null))
{
UpdateButtonText(((Component)val2).gameObject, text);
}
}
}
private static GameObject CreateButton(Transform parent)
{
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Expected O, but got Unknown
Transform obj = parent.Find("btnSave");
GameObject val = ((obj != null) ? ((Component)obj).gameObject : null);
if ((Object)(object)val == (Object)null)
{
OutwardSoftcoreMode.LogMessage("Cannot create softcore save button: btnSave template not found");
return null;
}
GameObject val2 = Object.Instantiate<GameObject>(val, parent);
((Object)val2).name = "btnSaveSoftcore";
val2.SetActive(true);
SoftcoreColors.DestroyLocalize(val2);
Text componentInChildren = val2.GetComponentInChildren<Text>();
if ((Object)(object)componentInChildren != (Object)null)
{
((Graphic)componentInChildren).color = SoftcoreColors.Purple;
}
Button component = val2.GetComponent<Button>();
if ((Object)(object)component != (Object)null)
{
((UnityEventBase)component.onClick).RemoveAllListeners();
((UnityEvent)component.onClick).AddListener(new UnityAction(OnSaveClicked));
}
return val2;
}
private static void OnSaveClicked()
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
OutwardSoftcoreMode.PendingManualBackupUIDs.Clear();
foreach (SplitPlayer localPlayer in SplitScreenManager.Instance.LocalPlayers)
{
if ((Object)(object)localPlayer.AssignedCharacter != (Object)null)
{
string text = UID.op_Implicit(localPlayer.AssignedCharacter.UID);
if (SoftcoreSaveManager.IsSoftcoreCharacter(text) && SoftcoreSaveManager.CanBackupNow(text))
{
OutwardSoftcoreMode.PendingManualBackupUIDs.Add(text);
}
}
}
if (OutwardSoftcoreMode.PendingManualBackupUIDs.Count <= 0)
{
return;
}
foreach (string pendingManualBackupUID in OutwardSoftcoreMode.PendingManualBackupUIDs)
{
EventBusPublisher.PublishSaveBackupBefore(pendingManualBackupUID);
}
PauseMenu[] array = Resources.FindObjectsOfTypeAll<PauseMenu>();
foreach (PauseMenu val in array)
{
if (!((Behaviour)val).isActiveAndEnabled)
{
continue;
}
Transform val2 = FindSoftcoreButton(val);
if (!((Object)(object)val2 == (Object)null))
{
Button component = ((Component)val2).GetComponent<Button>();
if ((Object)(object)component != (Object)null)
{
((Selectable)component).interactable = false;
}
}
}
SaveManager.Instance.Save();
}
private static void UpdateButtonText(GameObject btnObj, string uid)
{
//IL_0068: 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)
Text componentInChildren = btnObj.GetComponentInChildren<Text>();
if (!((Object)(object)componentInChildren == (Object)null))
{
Button component = btnObj.GetComponent<Button>();
bool flag = SoftcoreSaveManager.CanBackupNow(uid);
if (flag)
{
componentInChildren.text = "Save Backup";
((Graphic)componentInChildren).color = SoftcoreColors.Purple;
}
else
{
float remainingCooldownTime = SoftcoreSaveManager.GetRemainingCooldownTime(uid);
componentInChildren.text = "Save " + FormatCooldown(remainingCooldownTime);
((Graphic)componentInChildren).color = SoftcoreColors.Purple;
}
if ((Object)(object)component != (Object)null)
{
((Selectable)component).interactable = flag;
}
}
}
private static string FormatCooldown(float gameHours)
{
int num = Mathf.CeilToInt(gameHours * 60f);
int num2 = num / 1440;
int num3 = num % 1440 / 60;
int num4 = num % 60;
return (num2 > 0) ? $"{num2}:{num3:D2}:{num4:D2}" : $"{num3}:{num4:D2}";
}
}
[HarmonyPatch(typeof(PauseMenu), "Update")]
public class Patch_PauseMenu_Update
{
private static float _nextRefresh;
private static void Postfix(PauseMenu __instance)
{
if (!((UIElement)__instance).IsDisplayed)
{
_nextRefresh = 0f;
}
else if (!(Time.unscaledTime < _nextRefresh))
{
_nextRefresh = Time.unscaledTime + 0.5f;
Patch_PauseMenu_AddSaveButton.RefreshSoftcoreSaveButton(__instance);
}
}
}
[HarmonyPatch(typeof(SaveInstance), "Save")]
public class Patch_SaveInstance_Save
{
private static void Postfix(SaveInstance __instance, bool _saveChar, bool _saveWorld)
{
string saveID = __instance.SaveID;
if (!string.IsNullOrEmpty(saveID) && OutwardSoftcoreMode.PendingManualBackupUIDs.Contains(saveID))
{
string instancePath = __instance.InstancePath;
if (string.IsNullOrEmpty(instancePath))
{
OutwardSoftcoreMode.LogMessage("Cannot backup: missing InstancePath");
return;
}
if (!SoftcoreSaveManager.IsSoftcoreCharacter(saveID))
{
OutwardSoftcoreMode.LogMessage("Cannot backup: character is not softcore");
return;
}
string localCharacterNameByUID = GetLocalCharacterNameByUID(saveID);
SoftcoreSaveManager.EnsureMetadataExists(saveID, localCharacterNameByUID);
SoftcoreSaveManager.CreateBackup(saveID, instancePath);
EventBusPublisher.PublishSaveBackupAfter(saveID);
OutwardSoftcoreMode.PendingManualBackupUIDs.Remove(saveID);
OutwardSoftcoreMode.LogMessage("Manual softcore backup completed");
}
}
private static string GetLocalCharacterNameByUID(string uid)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
foreach (SplitPlayer localPlayer in SplitScreenManager.Instance.LocalPlayers)
{
Character assignedCharacter = localPlayer.AssignedCharacter;
UID? val = ((assignedCharacter != null) ? new UID?(assignedCharacter.UID) : ((UID?)null));
UID val2 = UID.op_Implicit(uid);
if (val.HasValue && (!val.HasValue || val.GetValueOrDefault() == val2))
{
return localPlayer.AssignedCharacter.Name;
}
}
return "Unknown";
}
}
[HarmonyPatch(typeof(SaveManager), "ConfirmAddNewSave")]
public class Patch_SaveManager_ConfirmAddNewSave
{
private static void Postfix(CharacterSave _newSave)
{
if (OutwardSoftcoreMode.PendingSoftcoreCount <= 0 && !OutwardSoftcoreMode.IsCurrentGameSoftcore)
{
return;
}
string text = ((CharacterSaveData)(_newSave?.PSave?)).UID;
if (string.IsNullOrEmpty(text))
{
OutwardSoftcoreMode.LogMessage("Cannot write softcore metadata: no UID in new save");
OutwardSoftcoreMode.PendingSoftcoreCount = 0;
return;
}
SoftcoreSaveManager.WriteMetadata(text, _newSave.PSave.Name);
OutwardSoftcoreMode.LogMessage("Softcore metadata written for " + _newSave.PSave.Name + " (" + text + ")");
OutwardSoftcoreMode.IsCurrentGameSoftcore = true;
OutwardSoftcoreMode.PendingManualBackupUIDs.Add(text);
if (OutwardSoftcoreMode.PendingSoftcoreCount > 0)
{
OutwardSoftcoreMode.PendingSoftcoreCount--;
}
}
}
[HarmonyPatch(typeof(SaveManager), "RetrieveCharacterSaves")]
public class Patch_SaveManager_RetrieveCharacterSaves
{
private static void Postfix()
{
SoftcoreSaveManager.RestoreOrphanedBackups();
}
}
}
namespace OutwardSoftcoreMode.Events
{
public enum EventName
{
SaveBackupBefore,
SaveBackupAfter,
DeathRollBefore,
DeathRollAfter
}
public enum EventParam
{
CallerUID,
SoftcoreUIDs,
RollResult,
AffectedUIDs
}
public static class EventBusKeys
{
private static readonly Dictionary<EventParam, string> ParamNames = new Dictionary<EventParam, string>
{
[EventParam.CallerUID] = "callerUID",
[EventParam.SoftcoreUIDs] = "softcoreUIDs",
[EventParam.RollResult] = "rollResult",
[EventParam.AffectedUIDs] = "affectedUIDs"
};
public static string GetEventName(EventName name)
{
return name.ToString();
}
public static string GetParamName(EventParam param)
{
if (!ParamNames.TryGetValue(param, out var value))
{
throw new ArgumentOutOfRangeException("param");
}
return value;
}
}
public static class EventBusPublisher
{
public static void PublishSaveBackupBefore(string callerUID)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Expected O, but got Unknown
//IL_0017: Expected O, but got Unknown
EventPayload val = new EventPayload { };
string paramName;
((Dictionary<string, object>)val)[paramName] = callerUID;
EventPayload val2 = val;
EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupBefore), val2);
}
public static void PublishSaveBackupAfter(string callerUID)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Expected O, but got Unknown
//IL_0017: Expected O, but got Unknown
EventPayload val = new EventPayload { };
string paramName;
((Dictionary<string, object>)val)[paramName] = callerUID;
EventPayload val2 = val;
EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupAfter), val2);
}
public static void PublishDeathRollBefore(List<string> softcoreUIDs)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Expected O, but got Unknown
//IL_0017: Expected O, but got Unknown
EventPayload val = new EventPayload { };
string paramName;
((Dictionary<string, object>)val)[paramName] = softcoreUIDs;
EventPayload val2 = val;
EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollBefore), val2);
}
public static void PublishDeathRollAfter(bool rollResult, List<string> affectedUIDs)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Expected O, but got Unknown
//IL_002c: Expected O, but got Unknown
EventPayload val = new EventPayload { };
string paramName;
((Dictionary<string, object>)val)[paramName] = rollResult;
string paramName2 = EventBusKeys.GetParamName(EventParam.AffectedUIDs);
((Dictionary<string, object>)val)[paramName2] = affectedUIDs;
EventPayload val2 = val;
EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollAfter), val2);
}
}
public static class EventBusRegister
{
public static void RegisterEvents()
{
EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupBefore), "Fired before a manual backup is created.", new(string, Type, string)[1] { (EventBusKeys.GetParamName(EventParam.CallerUID), typeof(string), "The UID of the character whose save triggered the backup.") });
EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupAfter), "Fired after a manual backup is created.", new(string, Type, string)[1] { (EventBusKeys.GetParamName(EventParam.CallerUID), typeof(string), "The UID of the character whose save was backed up.") });
EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollBefore), "Fired before the death roll is made for softcore characters.", new(string, Type, string)[1] { (EventBusKeys.GetParamName(EventParam.SoftcoreUIDs), typeof(List<string>), "List of UIDs for all softcore characters at 0 HP.") });
EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollAfter), "Fired after the death roll is made for softcore characters.", new(string, Type, string)[2]
{
(EventBusKeys.GetParamName(EventParam.RollResult), typeof(bool), "True if death was triggered, false if survived."),
(EventBusKeys.GetParamName(EventParam.AffectedUIDs), typeof(List<string>), "List of UIDs whose death count was incremented (empty if survived).")
});
}
}
}
namespace OutwardSoftcoreMode.BepInEx.Configs
{
public static class BackupsConfigs
{
public static ConfigEntry<int> MaxBackups;
public static void Init(BaseUnityPlugin plugin)
{
MaxBackups = plugin.Config.Bind<int>("Backups", "MaxBackups", 10, "Maximum number of backup instances kept per character. Oldest deleted when exceeded.");
}
}
public static class CooldownRandomizerConfigs
{
public static ConfigEntry<bool> CooldownRandomizerEnabled;
public static ConfigEntry<float> CooldownRandomizerMinHours;
public static ConfigEntry<float> CooldownRandomizerMaxHours;
public static void Init(BaseUnityPlugin plugin)
{
CooldownRandomizerEnabled = plugin.Config.Bind<bool>("Backup save cooldown randomizer", "CooldownRandomizerEnabled", true, "Enable random cooldown range for backup saves.");
CooldownRandomizerMinHours = plugin.Config.Bind<float>("Backup save cooldown randomizer", "CooldownRandomizerMinHours", 24f, "Minimum random cooldown in game hours.");
CooldownRandomizerMaxHours = plugin.Config.Bind<float>("Backup save cooldown randomizer", "CooldownRandomizerMaxHours", 168f, "Maximum random cooldown in game hours.");
}
}
public static class DeathChanceConfigs
{
public static ConfigEntry<int> DeathChance;
public static void Init(BaseUnityPlugin plugin)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Expected O, but got Unknown
DeathChance = plugin.Config.Bind<int>("Defeat", "DeathChance", 20, new ConfigDescription("Permanent death chance on defeat (percentage). Minimum 20 (like hardcore), maximum 100.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 100), Array.Empty<object>()));
}
}
public static class SaveCooldownConfigs
{
public static ConfigEntry<float> SaveCooldownHours;
public static void Init(BaseUnityPlugin plugin)
{
SaveCooldownHours = plugin.Config.Bind<float>("Backups", "SaveCooldownHours", 24f, "Minimum game hours between manual backups. Set to 0 to disable cooldown.");
}
}
}