using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using COTL_API.CustomFollowerCommand; using COTL_API.CustomInventory; using COTL_API.CustomMission; using COTL_API.CustomObjectives; using COTL_API.CustomSettings; using COTL_API.Helpers; using COTL_API.Saves; using HarmonyLib; using I2.Loc; using Lamb.UI; using Lamb.UI.FollowerInteractionWheel; using Microsoft.CodeAnalysis; using MonoMod.Utils; using Shared; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyCompany("p1xel8ted")] [assembly: AssemblyConfiguration("Release-Thunderstore")] [assembly: AssemblyDescription("Rebirth")] [assembly: AssemblyFileVersion("1.1.5.0")] [assembly: AssemblyInformationalVersion("1.1.5+f7ac2caf14bd7f6ad27b1dc9ed7e30d5a2596c3a")] [assembly: AssemblyProduct("Rebirth")] [assembly: AssemblyTitle("Rebirth")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.5.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } internal sealed class ConfigurationManagerAttributes { public delegate void CustomHotkeyDrawerFunc(ConfigEntryBase setting, ref bool isCurrentlyAcceptingInput); public bool? ShowRangeAsPercent; public Action CustomDrawer; public CustomHotkeyDrawerFunc CustomHotkeyDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; public Func ObjToStr; public Func StrToObj; } namespace Rebirth { public static class Helper { public static bool IsOld(Follower follower) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (Plugin.RebirthOldFollowers.Value) { return false; } if ((int)follower.Outfit.CurrentOutfit == 7) { if (!follower.Brain.Info.OldAge) { return follower.Brain.HasThought((Thought)146); } return true; } return false; } public static bool DoHalfStats() { return Random.Range(0f, 1f) <= (float)Plugin.XpPenaltyChance.Value / 100f; } public static bool IsUniqueFollower(FollowerBrainInfo brainInfo) { FollowerInfo info = brainInfo._info; if (info.Traits.Any((TraitType t) => FollowerTrait.UniqueTraits.Contains(t))) { return true; } if (!string.IsNullOrEmpty(info.SkinName) && new string[21] { "Webber", "Sozo", "Ratau", "Baal", "Aym", "Haro", "Klunko", "Bop", "Jalala", "Rinor", "Flinky", "Helob", "Leshy", "Heket", "Kallamar", "Shamura", "Narinder", "Plimbo", "Chemach", "Fox", "Cthulhu" }.Any((string s) => info.SkinName.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)) { return true; } return false; } } public class MissionItem : CustomMission { public override string InternalName => "REBIRTH_MISSION_1"; public override ITEM_TYPE RewardType => Plugin.RebirthItem; public override int BaseChance => Plugin.MissionBaseChance.Value; public override IntRange RewardRange => new IntRange(Plugin.MissionRewardMin.Value, Plugin.MissionRewardMax.Value); } [HarmonyPatch] [HarmonyWrapSafe] public static class Patches { private static readonly HashSet RebirthingFollowers = new HashSet(); private static readonly Dictionary RebirthDeathMessages = new Dictionary { { "English", "{0} succumbed to unknown forces..." }, { "French", "{0} a succombé à des forces inconnues..." }, { "German", "{0} erlag unbekannten Mächten..." }, { "Spanish", "{0} sucumbió a fuerzas desconocidas..." }, { "Italian", "{0} è soccombuto a forze sconosciute..." }, { "Portuguese(Brazil)", "{0} sucumbiu a forças desconhecidas..." }, { "Russian", "{0} пал жертвой неведомых сил..." }, { "Japanese", "{0}は未知の力に屈した..." }, { "Korean", "{0}이(가) 알 수 없는 힘에 굴복했습니다..." }, { "SimplifiedChinese", "{0}屈服于未知的力量..." }, { "TraditionalChinese", "{0}屈服於未知的力量..." } }; public static void MarkFollowerForRebirth(int followerId) { RebirthingFollowers.Add(followerId); Plugin.Log.LogInfo((object)$"[Rebirth] Marked follower ID {followerId} for rebirth. Total marked: {RebirthingFollowers.Count}"); } public static void UnmarkFollowerRebirth(int followerId) { bool flag = RebirthingFollowers.Remove(followerId); Plugin.Log.LogInfo((object)$"[Rebirth] Unmarked follower ID {followerId} (removed: {flag}). Remaining marked: {RebirthingFollowers.Count}"); } [HarmonyPostfix] [HarmonyPatch(typeof(FollowerInfo), "GetDeathText")] public static void FollowerInfo_GetDeathText_Postfix(FollowerInfo __instance, ref string __result) { Plugin.Log.LogInfo((object)$"[Rebirth] GetDeathText called for follower '{__instance.Name}' (ID: {__instance.ID})"); Plugin.Log.LogInfo((object)("[Rebirth] Currently marked follower IDs: [" + string.Join(", ", RebirthingFollowers) + "]")); Plugin.Log.LogInfo((object)$"[Rebirth] Is follower in rebirth set? {RebirthingFollowers.Contains(__instance.ID)}"); Plugin.Log.LogInfo((object)("[Rebirth] Original death text: " + __result)); if (RebirthingFollowers.Contains(__instance.ID)) { string currentLanguage = LocalizationManager.CurrentLanguage; Plugin.Log.LogInfo((object)("[Rebirth] Current language: " + currentLanguage)); string value; string text = (RebirthDeathMessages.TryGetValue(currentLanguage, out value) ? value : RebirthDeathMessages["English"]); Plugin.Log.LogInfo((object)("[Rebirth] Using message template: " + text)); string text2 = __result; __result = string.Format(text, __instance.Name); Plugin.Log.LogInfo((object)("[Rebirth] Changed death text from '" + text2 + "' to '" + __result + "'")); UnmarkFollowerRebirth(__instance.ID); } else { Plugin.Log.LogInfo((object)"[Rebirth] Follower not in rebirth set - using vanilla death text"); } } [HarmonyPrefix] [HarmonyPatch(typeof(DropLootOnDeath), "OnDie")] public static void DropLootOnDeath_OnDie(DropLootOnDeath __instance, Health Victim) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_003f: 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_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) if ((int)Victim.team == 2) { if (Random.Range(0, 101) <= Plugin.EnemyDropRate.Value) { Plugin.Log.LogInfo((object)("Got a Rebirth token from " + ((Object)__instance).name + "!")); InventoryItem.Spawn(Plugin.RebirthItem, Random.Range(Plugin.DropMinQuantity.Value, Plugin.DropMaxQuantity.Value + 1), ((Component)__instance).transform.position, 4f, (Action)null); } } else if (((Object)Victim).name.ToLower(CultureInfo.InvariantCulture).Contains("breakable body pile") && Random.Range(0, 101) <= Plugin.EnemyDropRate.Value) { Plugin.Log.LogInfo((object)("Got a Rebirth token from " + ((Object)__instance).name + "!")); InventoryItem.Spawn(Plugin.RebirthItem, Random.Range(Plugin.DropMinQuantity.Value, Plugin.DropMaxQuantity.Value + 1), ((Component)__instance).transform.position, 4f, (Action)null); } } } [BepInPlugin("p1xel8ted.cotl.rebirth", "Rebirth", "1.1.5")] [BepInDependency("io.github.xhayper.COTL_API", "0.3.1")] [BepInDependency("com.bepis.bepinex.configurationmanager", "18.4.1")] [HarmonyPatch] public class Plugin : BaseUnityPlugin { private const string PluginGuid = "p1xel8ted.cotl.rebirth"; private const string PluginName = "Rebirth"; private const string PluginVer = "1.1.5"; private const string RebirthSection = "── Rebirth ──"; private const string TokenDropsSection = "── Token Drops ──"; private const string DungeonChestsSection = "── Dungeon Chests ──"; private const string RefinerySection = "── Refinery ──"; private const string MissionsSection = "── Missions ──"; public static readonly ModdedSaveData> RebirthSaveData = new ModdedSaveData>("p1xel8ted.cotl.rebirth"); public static ManualLogSource Log { get; private set; } public static string PluginPath { get; private set; } public static ITEM_TYPE RebirthItem { get; private set; } private CustomObjective RebirthCollectItemQuest { get; set; } internal static ConfigEntry RebirthOldFollowers { get; private set; } internal static ConfigEntry PreserveUniqueFollowers { get; private set; } internal static ConfigEntry XpPenaltyChance { get; private set; } internal static ConfigEntry XpPenaltyMultiplier { get; private set; } internal static ConfigEntry TokenCost { get; private set; } internal static ConfigEntry EnemyDropRate { get; private set; } internal static ConfigEntry DropMinQuantity { get; private set; } internal static ConfigEntry DropMaxQuantity { get; private set; } internal static ConfigEntry ChestSpawnChance { get; private set; } internal static ConfigEntry ChestMinAmount { get; private set; } internal static ConfigEntry ChestMaxAmount { get; private set; } internal static ConfigEntry BoneCost { get; private set; } internal static ConfigEntry RefineryDuration { get; private set; } internal static ConfigEntry MissionRewardMin { get; private set; } internal static ConfigEntry MissionRewardMax { get; private set; } internal static ConfigEntry MissionBaseChance { get; private set; } private void Awake() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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_006f: 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) ((BaseModdedSaveData)RebirthSaveData).LoadOrder = (ModdedSaveLoadOrder)0; ModdedSaveManager.RegisterModdedSave((BaseModdedSaveData)(object)RebirthSaveData); Log = ((BaseUnityPlugin)this).Logger; PluginPath = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? throw new DirectoryNotFoundException(); BindConfig(); CustomFollowerCommandManager.Add((CustomFollowerCommand)(object)new RebirthFollowerCommand()); CustomFollowerCommandManager.Add((CustomFollowerCommand)(object)new RebirthSubCommand()); RebirthItem = CustomItemManager.Add((CustomInventoryItem)(object)new RebirthItem()); CustomMissionManager.Add((CustomMission)(object)new MissionItem()); RebirthCollectItemQuest = CustomObjectiveManager.CollectItem(RebirthItem, Random.Range(15, 26), false, (FollowerLocation)7, 4800f); RebirthCollectItemQuest.InitialQuestText = "Please leader, please! I'm " + StringExtensions.Wave("weary of this existence") + " and seek to be reborn! I will do anything for you! Can you please help me?"; RegisterSettings(); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "p1xel8ted.cotl.rebirth"); ModLogging.Init(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); } private void BindConfig() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Expected O, but got Unknown //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Expected O, but got Unknown //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Expected O, but got Unknown //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Expected O, but got Unknown //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_037d: Expected O, but got Unknown //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Expected O, but got Unknown //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Expected O, but got Unknown //IL_045d: Unknown result type (might be due to invalid IL or missing references) //IL_0467: Expected O, but got Unknown //IL_04b0: Unknown result type (might be due to invalid IL or missing references) //IL_04ba: Expected O, but got Unknown RebirthOldFollowers = ((BaseUnityPlugin)this).Config.Bind("── Rebirth ──", "Rebirth Old Followers", false, new ConfigDescription("Allow old followers to be reborn.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 5 } })); PreserveUniqueFollowers = ((BaseUnityPlugin)this).Config.Bind("── Rebirth ──", "Preserve Unique Followers", true, new ConfigDescription("When enabled, unique followers (Webber, Sozo, Ratau, etc.) retain their original skin and traits when reborn. Names are always randomized.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = 4 } })); XpPenaltyChance = ((BaseUnityPlugin)this).Config.Bind("── Rebirth ──", "XP Penalty Chance", 20, new ConfigDescription("Chance (%) of losing XP during rebirth.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), new object[1] { new ConfigurationManagerAttributes { Order = 3 } })); XpPenaltyMultiplier = ((BaseUnityPlugin)this).Config.Bind("── Rebirth ──", "XP Kept On Penalty", 50, new ConfigDescription("Percentage of XP kept when penalty triggers.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 90), new object[1] { new ConfigurationManagerAttributes { Order = 2, DispName = " └ XP Kept On Penalty" } })); TokenCost = ((BaseUnityPlugin)this).Config.Bind("── Rebirth ──", "Token Cost", 25, new ConfigDescription("Tokens required for subsequent rebirths.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), new object[1] { new ConfigurationManagerAttributes { Order = 1 } })); EnemyDropRate = ((BaseUnityPlugin)this).Config.Bind("── Token Drops ──", "Enemy Drop Rate", 5, new ConfigDescription("Chance (%) of tokens dropping from enemies.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), new object[1] { new ConfigurationManagerAttributes { Order = 3 } })); DropMinQuantity = ((BaseUnityPlugin)this).Config.Bind("── Token Drops ──", "Drop Min Quantity", 1, new ConfigDescription("Minimum tokens per drop.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), new object[1] { new ConfigurationManagerAttributes { Order = 2, DispName = " └ Drop Min Quantity" } })); DropMaxQuantity = ((BaseUnityPlugin)this).Config.Bind("── Token Drops ──", "Drop Max Quantity", 2, new ConfigDescription("Maximum tokens per drop.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), new object[1] { new ConfigurationManagerAttributes { Order = 1, DispName = " └ Drop Max Quantity" } })); ChestSpawnChance = ((BaseUnityPlugin)this).Config.Bind("── Dungeon Chests ──", "Chest Spawn Chance", 5, new ConfigDescription("Chance (%) of tokens appearing in dungeon chests.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), new object[1] { new ConfigurationManagerAttributes { Order = 3 } })); ChestMinAmount = ((BaseUnityPlugin)this).Config.Bind("── Dungeon Chests ──", "Chest Min Amount", 4, new ConfigDescription("Minimum tokens per chest.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), new object[1] { new ConfigurationManagerAttributes { Order = 2, DispName = " └ Chest Min Amount" } })); ChestMaxAmount = ((BaseUnityPlugin)this).Config.Bind("── Dungeon Chests ──", "Chest Max Amount", 7, new ConfigDescription("Maximum tokens per chest.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), new object[1] { new ConfigurationManagerAttributes { Order = 1, DispName = " └ Chest Max Amount" } })); BoneCost = ((BaseUnityPlugin)this).Config.Bind("── Refinery ──", "Bone Cost", 15, new ConfigDescription("Bones required to refine a token.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), new object[1] { new ConfigurationManagerAttributes { Order = 2 } })); RefineryDuration = ((BaseUnityPlugin)this).Config.Bind("── Refinery ──", "Refinery Duration", 256, new ConfigDescription("Refinery duration in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 600), new object[1] { new ConfigurationManagerAttributes { Order = 1 } })); MissionBaseChance = ((BaseUnityPlugin)this).Config.Bind("── Missions ──", "Mission Base Chance", 50, new ConfigDescription("Mission appearance chance (%).", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), new object[1] { new ConfigurationManagerAttributes { Order = 3 } })); MissionRewardMin = ((BaseUnityPlugin)this).Config.Bind("── Missions ──", "Mission Reward Min", 15, new ConfigDescription("Minimum token reward from missions.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), new object[1] { new ConfigurationManagerAttributes { Order = 2, DispName = " └ Mission Reward Min" } })); MissionRewardMax = ((BaseUnityPlugin)this).Config.Bind("── Missions ──", "Mission Reward Max", 25, new ConfigDescription("Maximum token reward from missions.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), new object[1] { new ConfigurationManagerAttributes { Order = 1, DispName = " └ Mission Reward Max" } })); } private static void RegisterSettings() { CustomSettingsManager.AddBepInExConfig("Rebirth", "Rebirth Old Followers", RebirthOldFollowers, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth", "Preserve Unique Followers", PreserveUniqueFollowers, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth", "XP Penalty Chance", XpPenaltyChance, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth", "XP Kept On Penalty", XpPenaltyMultiplier, 5, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth", "Token Cost", TokenCost, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Drops", "Enemy Drop Rate", EnemyDropRate, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Drops", "Drop Min Quantity", DropMinQuantity, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Drops", "Drop Max Quantity", DropMaxQuantity, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Chests", "Chest Spawn Chance", ChestSpawnChance, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Chests", "Chest Min Amount", ChestMinAmount, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Chests", "Chest Max Amount", ChestMaxAmount, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Refinery", "Bone Cost", BoneCost, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Refinery", "Refinery Duration", RefineryDuration, 10, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Missions", "Mission Reward Min", MissionRewardMin, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Missions", "Mission Reward Max", MissionRewardMax, 1, (ValueDisplayFormat)1, (Action)null); CustomSettingsManager.AddBepInExConfig("Rebirth - Missions", "Mission Base Chance", MissionBaseChance, 1, (ValueDisplayFormat)1, (Action)null); } } internal class RebirthFollowerCommand : CustomFollowerCommand { public override string InternalName => "REBIRTH_COMMAND"; public override Sprite CommandIcon => TextureHelper.CreateSpriteFromPath(Path.Combine(Plugin.PluginPath, "assets", "rebirth_command.png")); private static bool BornAgainFollower { get; set; } public override string GetTitle(Follower follower) { return "Rebirth"; } public RebirthFollowerCommand() { ((CommandItem)this).SubCommands = FollowerCommandGroups.AreYouSureCommands(); } public override string GetDescription(Follower follower) { return "This follower getting you down? Order a rebirth!"; } public override string GetLockedDescription(Follower follower) { if (DataManager.Instance.Followers_Recruit.Count > 0) { return "You already have a follower awaiting indoctrination!"; } if (Helper.IsOld(follower)) { return "Not enough life essence left to satisfy those below."; } return "Yeah, you shouldn't be seeing this..."; } public override bool IsAvailable(Follower follower) { if (DataManager.Instance.Followers_Recruit.Count > 0) { return false; } if (Helper.IsOld(follower)) { return false; } BornAgainFollower = SaveData.FollowerBornAgain(follower.Brain._directInfoAccess); return !BornAgainFollower; } public override bool ShouldAppearFor(Follower follower) { return !SaveData.FollowerBornAgain(follower.Brain._directInfoAccess); } private static IEnumerator GiveFollowerIE(FollowerInfo f, Follower old) { yield return DieRoutine(old); yield return (object)new WaitForSeconds(3f); BiomeBaseManager.Instance.SpawnExistingRecruits = false; yield return (object)new WaitForEndOfFrame(); yield return (object)new WaitForSeconds(3f); DataManager.Instance.Followers_Recruit.Add(f); FollowerManager.SpawnExistingRecruitsInBase(BiomeBaseManager.Instance.RecruitSpawnLocation.transform.position); FollowerRecruit obj = Object.FindObjectOfType(); if (obj != null) { obj.ManualTriggerAnimateIn(); } BiomeBaseManager.Instance.SpawnExistingRecruits = true; NotificationCentre.NotificationsEnabled = true; yield return (object)new WaitForSeconds(2f); } internal static void SpawnRecruit(Follower follower) { FollowerBrainInfo info = follower.Brain.Info; FollowerInfo info2 = info._info; bool flag = Plugin.PreserveUniqueFollowers.Value && Helper.IsUniqueFollower(info); BiomeBaseManager.Instance.SpawnExistingRecruits = true; NotificationCentre.NotificationsEnabled = false; string name = info.Name; int iD = info.ID; int xPLevel = info.XPLevel; int num = Mathf.CeilToInt((float)(xPLevel * Plugin.XpPenaltyMultiplier.Value) / 100f); bool flag2 = Helper.DoHalfStats(); FollowerInfo val = (flag ? FollowerInfo.NewCharacter((FollowerLocation)1, info2.SkinName) : FollowerInfo.NewCharacter((FollowerLocation)1, "")); if (val != null) { if (flag) { val.SkinColour = info2.SkinColour; val.Traits = new List(info2.Traits); Plugin.Log.LogInfo((object)("Unique follower rebirth: " + name + " -> " + val.Name + " (skin: " + info2.SkinName + ", traits: " + string.Join(", ", info2.Traits) + ")")); } ((MonoBehaviour)GameManager.GetInstance()).StartCoroutine(GiveFollowerIE(val, follower)); Plugin.Log.LogInfo((object)$"New follower: {val.Name} (unique: {flag})"); SaveData.AddBornAgainFollower(val); val.XPLevel = (flag2 ? num : xPLevel); } else { Plugin.Log.LogWarning((object)"New follower is null!"); NotificationCentre.NotificationsEnabled = true; } ((MonoBehaviour)GameManager.GetInstance()).StartCoroutine(ShowMessages(name, flag2)); RemoveFromDeadLists(iD); } private static IEnumerator ShowMessages(string name, bool halfXp) { yield return (object)new WaitForSeconds(3f); NotificationCentreScreen.Play(name + " died to be reborn! All hail " + name + "!"); yield return (object)new WaitForSeconds(5f); if (halfXp) { NotificationCentre.Instance.PlayGenericNotification("Oh no! " + name + " lost half of their XP during Rebirth!", (Flair)0); yield return (object)new WaitForSeconds(3f); } } private static void RemoveFromDeadLists(int id) { Follower.Followers.RemoveAll((Follower a) => a.Brain._directInfoAccess.ID == id); DataManager.Instance.Followers_Dead.RemoveAll((FollowerInfo a) => a.ID == id); DataManager.Instance.Followers_Dead_IDs.RemoveAll((int a) => a == id); } private static IEnumerator DieRoutine(Follower follower) { int followerId = follower.Brain.Info.ID; string followerName = follower.Brain.Info.Name; Plugin.Log.LogInfo((object)$"[Rebirth] DieRoutine started for '{followerName}' (ID: {followerId})"); Patches.MarkFollowerForRebirth(followerId); follower.HideAllFollowerIcons(); yield return (object)new WaitForSeconds(0.5f); follower.State.CURRENT_STATE = (State)49; yield return (object)new WaitForSeconds(1f); follower.SetBodyAnimation("wave", true); yield return (object)new WaitForSeconds(0.75f); follower.Brain._directInfoAccess.DiedOfOldAge = true; Plugin.Log.LogInfo((object)$"[Rebirth] Calling Die() for '{followerName}' (ID: {followerId})"); follower.Die((NotificationType)79, true, 1, "die", "dead", (Action)null, true); Plugin.Log.LogInfo((object)$"[Rebirth] Die() completed for '{followerName}' (ID: {followerId})"); Plugin.Log.LogInfo((object)"[Rebirth] Follower will be unmarked when GetDeathText is called during death animation"); } public override void Execute(interaction_FollowerInteraction interaction, FollowerCommands finalCommand) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 if ((int)finalCommand == 40) { interaction.Close(true, true, false); SpawnRecruit(interaction.follower); } } } public sealed class RebirthItem : CustomInventoryItem { public override Sprite InventoryIcon { get; } = TextureHelper.CreateSpriteFromPath(Path.Combine(Plugin.PluginPath, "assets", "rebirth_item.png")); public override Sprite Sprite { get; } = TextureHelper.CreateSpriteFromPath(Path.Combine(Plugin.PluginPath, "assets", "rebirth_item.png")); public override string InternalName => "REBIRTH_ITEM"; public override bool AddItemToDungeonChests => true; public override int DungeonChestSpawnChance => Plugin.ChestSpawnChance.Value; public override int DungeonChestMinAmount => Plugin.ChestMinAmount.Value; public override int DungeonChestMaxAmount => Plugin.ChestMaxAmount.Value; public override Vector3 LocalScale { get; } = new Vector3(0.5f, 0.5f, 0.5f); public override ITEM_TYPE ItemPickUpToImitate => (ITEM_TYPE)20; public override ItemRarity Rarity => (ItemRarity)1; public override bool AddItemToOfferingShrine => true; public override bool CanBeRefined => true; public override ITEM_TYPE RefineryInput => (ITEM_TYPE)9; public override int RefineryInputQty => Plugin.BoneCost.Value; public override float CustomRefineryDuration => Plugin.RefineryDuration.Value; public override ITEM_CATEGORIES ItemCategory => (ITEM_CATEGORIES)8; public override bool IsCurrency => true; public override string LocalizedDescription() { return "A special token obtained while on crusades that are used as currency to Rebirth followers."; } public override string LocalizedName() { return "Rebirth Token"; } public override string LocalizedLore() { return "Said to be dropped by Death herself."; } } internal class RebirthSubCommand : CustomFollowerCommand { private static int ItemQty => Plugin.TokenCost.Value; public override string InternalName => "REBIRTH_SUB_COMMAND"; public override Sprite CommandIcon { get; } = TextureHelper.CreateSpriteFromPath(Path.Combine(Plugin.PluginPath, "assets", "rebirth_command.png")); public override string GetTitle(Follower follower) { return $"Rebirth for {ItemQty} tokens."; } public RebirthSubCommand() { ((CommandItem)this).SubCommands = FollowerCommandGroups.AreYouSureCommands(); } public override string GetDescription(Follower follower) { return "Perform a Rebirth using special tokens obtained while on crusades."; } public override string GetLockedDescription(Follower follower) { if (DataManager.Instance.Followers_Recruit.Count > 0) { return "You already have a follower awaiting indoctrination!"; } if (Helper.IsOld(follower)) { return "Not enough life essence left to satisfy those below."; } return "Requires 25 Rebirth tokens to perform."; } public override bool ShouldAppearFor(Follower follower) { return SaveData.FollowerBornAgain(follower.Brain._directInfoAccess); } public override bool IsAvailable(Follower follower) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown if (DataManager.Instance.Followers_Recruit.Count > 0) { return false; } if (Helper.IsOld(follower)) { return false; } return Inventory.GetItemQuantity((int)Plugin.RebirthItem) >= ItemQty; } public override void Execute(interaction_FollowerInteraction interaction, FollowerCommands finalCommand) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) if ((int)finalCommand == 40) { interaction.Close(true, true, false); RebirthFollowerCommand.SpawnRecruit(interaction.follower); Inventory.ChangeItemQuantity(Plugin.RebirthItem, -ItemQty, 0); } } } public static class SaveData { public static bool FollowerBornAgain(FollowerInfo followerInfo) { if (Plugin.RebirthSaveData.Data != null) { return Plugin.RebirthSaveData.Data.Exists((int a) => a == followerInfo.ID); } return false; } public static void AddBornAgainFollower(FollowerInfo followerInfo) { Plugin.RebirthSaveData.Data?.Add(followerInfo.ID); ((BaseModdedSaveData)Plugin.RebirthSaveData).Save(true, true); Plugin.Log.LogInfo((object)("Saved follower data for " + followerInfo.Name)); } } } namespace Shared { public static class Helpers { internal static List AllFollowers => FollowerManager.Followers.SelectMany((KeyValuePair> followerList) => followerList.Value).ToList(); private static bool ContainsIgnoreCase(this string source, string value) { if (source == null) { return false; } return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; } public static bool IsMultiplierActive(float value) { return !Mathf.Approximately(value, 1f); } public static IEnumerator FilterEnumerator(IEnumerator original, Type[] typesToRemove) { while (original.MoveNext()) { object current = original.Current; if (current != null && !ArrayExtensions.Contains(typesToRemove, current.GetType())) { yield return current; } } } public static void LogCallStack(ManualLogSource logger, int skipFrames = 3, int maxFrames = 10) { StackTrace stackTrace = new StackTrace(fNeedFileInfo: false); int frameCount = stackTrace.FrameCount; for (int i = skipFrames; i < frameCount && i < maxFrames; i++) { MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod(); logger.LogWarning((object)string.Format(arg1: (methodBase?.DeclaringType)?.FullName, format: "[Frame {0}] {1}.{2}", arg0: i, arg2: methodBase?.Name)); } } public static Type GetCallingType(ICollection targetTypes, int skipFrames = 3, int maxFrames = 10) { StackTrace stackTrace = new StackTrace(fNeedFileInfo: false); int frameCount = stackTrace.FrameCount; for (int i = skipFrames; i < frameCount && i < maxFrames; i++) { Type type = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType; if (type != null && targetTypes.Contains(type)) { return type; } } return null; } public static bool IsCalledFrom(string typeNameContains, string methodNameContains = null, int skipFrames = 3, int maxFrames = 10) { StackTrace stackTrace = new StackTrace(fNeedFileInfo: false); int frameCount = stackTrace.FrameCount; for (int i = skipFrames; i < frameCount && i < maxFrames; i++) { MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod(); Type type = methodBase?.DeclaringType; if (!(type == null)) { bool num = type.FullName.ContainsIgnoreCase(typeNameContains); bool flag = methodNameContains == null || methodBase.Name.ContainsIgnoreCase(methodNameContains); if (num && flag) { return true; } } } return false; } } internal static class StartupLogger { private const string LogSourceName = "Cult of the Lamb Mods"; private static bool _scheduled; private static readonly string[] PiracyFiles = new string[76] { "SmartSteamEmu.ini", "steam_emu.ini", "goldberg_emulator.dll", "steamclient_loader.dll", "steam_api64_o.dll", "steam_api.cdx", "steam_api64.cdx.dll", "steam_interfaces.txt", "local_save.txt", "valve.ini", "coldclient.dll", "ColdClientLoader.ini", "steamless.dll", "GreenLuma", "SteamFix.dll", "SteamFix64.dll", "LumaEmu.ini", "Lumaplay", "account_name.txt", "user_steam_id.txt", "force_listen_port.txt", "goldberg_steam_appid.txt", "CreamAPI.dll", "creamapi.dll", "cream_api.ini", "ScreamAPI.dll", "UnlockAll.dll", "Koaloader.dll", "Koaloader64.dll", "OnlineFix.dll", "OnlineFix.url", "online-fix.me", "codex.ini", "codex64.dll", "CODEX", "SKIDROW", "SKIDROW.ini", "CPY", "PLAZA", "HOODLUM", "EMPRESS", "TENOKE", "PROPHET", "REVOLT", "DARKSiDERS", "RAZOR1911", "FLT", "FLT.dll", "RUNE", "RUNE.ini", "TiNYiSO", "RELOADED", "RLD!", "DOGE", "CHRONOS", "DINOByTES", "I_KnoW", "ElAmigos", "FitGirl", "DODI", "xatab", "KaOs", "IGG", "Masquerade", "3dmgame.dll", "ALI213.dll", "crack.exe", "Crack.nfo", "crackfix", "CrackOnly", "gamefix.dll", "nosTEAM", "NoSteam", "FCKDRM", "NoDRM", "VALVEEMPRESS" }; public static void EnsureStarted() { if (_scheduled) { return; } _scheduled = true; try { GameObject managerObject = Chainloader.ManagerObject; if ((Object)(object)managerObject == (Object)null) { return; } MonoBehaviour[] components = managerObject.GetComponents(); foreach (MonoBehaviour val in components) { if ((Object)(object)val != (Object)null && ((object)val).GetType().Name == "StartupLoggerRunner") { return; } } managerObject.AddComponent(); } catch (Exception arg) { Logger.CreateLogSource("Cult of the Lamb Mods").LogError((object)$"StartupLogger.EnsureStarted failed: {arg}"); } } internal static void LogSummary() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) try { string version = Application.version; Version version2 = typeof(Chainloader).Assembly.GetName().Version; GameObject managerObject = Chainloader.ManagerObject; bool flag = (Object)(object)managerObject != (Object)null && ((Enum)((Object)managerObject).hideFlags).HasFlag((Enum)(object)(HideFlags)61); string buildGUID = Application.buildGUID; Platform current = PlatformHelper.Current; string text = DetectStorefront(); ManualLogSource val = Logger.CreateLogSource("Cult of the Lamb Mods"); val.LogInfo((object)"=========================================="); val.LogInfo((object)" Cult of the Lamb Mod Summary"); val.LogInfo((object)"=========================================="); val.LogInfo((object)(" Game : ver. " + version + " (BuildGUID: " + buildGUID + ")")); val.LogInfo((object)$" BepInEx : v{version2} (Manager Hidden: {flag})"); val.LogInfo((object)$" Platform : {current}"); val.LogInfo((object)(" Storefront: " + text)); if (!flag) { val.LogWarning((object)" BepInEx Manager GameObject is NOT hidden - Unity event methods (Awake, Start, Update) will not fire on plugins!"); val.LogWarning((object)" To fix: open BepInEx/config/BepInEx.cfg, find [Chainloader] section, set HideManagerGameObject = true"); } val.LogInfo((object)"------------------------------------------"); val.LogInfo((object)" Loaded plugins:"); foreach (PluginInfo item in Chainloader.PluginInfos.Values.OrderBy((PluginInfo p) => p.Metadata.Name)) { val.LogInfo((object)$" {item.Metadata.Name} v{item.Metadata.Version} | {item.Metadata.GUID}"); } val.LogInfo((object)"------------------------------------------"); val.LogInfo((object)$" Total: {Chainloader.PluginInfos.Count} plugins"); val.LogInfo((object)"------------------------------------------"); val.LogInfo((object)" Plugin configurations:"); LogPluginConfigs(val); val.LogInfo((object)"=========================================="); if (!Chainloader.ConfigHideBepInExGOs.Value) { val.LogWarning((object)" BepInEx HideManagerGameObject was disabled - enabling it to prevent Unity event methods from failing"); Chainloader.ConfigHideBepInExGOs.Value = true; if (Object.op_Implicit((Object)(object)managerObject)) { ((Object)managerObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)managerObject); } } Logger.Sources.Remove((ILogSource)(object)val); } catch (Exception arg) { Logger.CreateLogSource("Cult of the Lamb Mods").LogError((object)$"StartupLogger failed: {arg}"); } } private static void LogPluginConfigs(ManualLogSource log) { foreach (PluginInfo item in Chainloader.PluginInfos.Values.OrderBy((PluginInfo p) => p.Metadata.Name)) { try { BaseUnityPlugin instance = item.Instance; if (instance == null) { log.LogInfo((object)(" [" + item.Metadata.Name + "] (instance not available)")); continue; } ConfigFile config = instance.Config; if (config == null || config.Keys.Count == 0) { log.LogInfo((object)(" [" + item.Metadata.Name + "] (no config entries)")); continue; } log.LogInfo((object)(" [" + item.Metadata.Name + "]")); foreach (IGrouping item2 in from k in config.Keys group k by k.Section into g orderby g.Key select g) { log.LogInfo((object)(" " + item2.Key)); foreach (ConfigDefinition item3 in item2.OrderBy((ConfigDefinition k) => k.Key)) { object arg; try { arg = config[item3].BoxedValue; } catch (Exception ex) { arg = ""; } log.LogInfo((object)$" {item3.Key} = {arg}"); } } } catch (Exception ex2) { log.LogInfo((object)(" [" + item.Metadata.Name + "] (config dump failed: " + ex2.Message + ")")); } } } private static string[] GetSearchDirs() { string currentDirectory = Directory.GetCurrentDirectory(); List list = new List { currentDirectory }; try { string[] directories = Directory.GetDirectories(currentDirectory, "*_Data"); for (int i = 0; i < directories.Length; i++) { string text = Path.Combine(directories[i], "Plugins"); if (Directory.Exists(text)) { list.Add(text); list.AddRange(Directory.GetDirectories(text)); } } } catch { } return list.ToArray(); } private static bool FileExistsInAny(string[] dirs, string filename) { return dirs.Any((string d) => File.Exists(Path.Combine(d, filename))); } private static bool DirExistsInAny(string[] dirs, string dirname) { return dirs.Any((string d) => Directory.Exists(Path.Combine(d, dirname))); } private static string DetectStorefront() { string currentDirectory = Directory.GetCurrentDirectory(); string[] dirs = GetSearchDirs(); string text = "Unknown"; if (FileExistsInAny(dirs, "steam_api.dll") || FileExistsInAny(dirs, "steam_api64.dll") || File.Exists(Path.Combine(currentDirectory, "steam_appid.txt"))) { text = "Steam"; } else if (Directory.GetFiles(currentDirectory, "goggame-*.info").Any() || FileExistsInAny(dirs, "galaxy.dll") || FileExistsInAny(dirs, "Galaxy64.dll") || FileExistsInAny(dirs, "GalaxyPeer.dll")) { text = "GOG"; } else if (FileExistsInAny(dirs, "EOSSDK-Win64-Shipping.dll") || FileExistsInAny(dirs, "EpicOnlineServices.dll") || Directory.Exists(Path.Combine(currentDirectory, ".egstore"))) { text = "Epic"; } else if (currentDirectory.Contains("WindowsApps") || File.Exists(Path.Combine(currentDirectory, "appxmanifest.xml")) || File.Exists(Path.Combine(currentDirectory, "microsoft.gameconfig"))) { text = "Xbox/Microsoft Store"; } else if (IsProcessRunning("steam")) { text = "Steam (process only)"; } else if (IsProcessRunning("GalaxyClient")) { text = "GOG (process only)"; } else if (IsProcessRunning("EpicGamesLauncher")) { text = "Epic (process only)"; } else if (IsProcessRunning("XboxApp") || IsProcessRunning("GamingServices")) { text = "Xbox (process only)"; } bool flag = PiracyFiles.Any((string pirate) => FileExistsInAny(dirs, pirate) || DirExistsInAny(dirs, pirate)); if (!flag && Directory.Exists(Path.Combine(currentDirectory, "steam_settings"))) { string path = Path.Combine(currentDirectory, "steam_settings"); flag = File.Exists(Path.Combine(path, "force_account_name.txt")) || File.Exists(Path.Combine(path, "force_steamid.txt")) || File.Exists(Path.Combine(path, "force_language.txt")); } if (flag) { text += " + Possible Pirated/Cracked Files Found!"; } return text; } private static bool IsProcessRunning(string name) { return Process.GetProcessesByName(name).Length != 0; } } internal sealed class StartupLoggerRunner : MonoBehaviour { private IEnumerator Start() { yield return null; StartupLogger.LogSummary(); } } internal static class SettingsChangeLogger { public static void Register(ConfigFile config, TimestampedLogger log) { if (config == null || log == null) { return; } try { Dictionary snapshot = new Dictionary(); foreach (KeyValuePair item in config) { snapshot[item.Key] = item.Value.BoxedValue; } config.SettingChanged += delegate(object _, SettingChangedEventArgs args) { try { ConfigEntryBase val = ((args != null) ? args.ChangedSetting : null); if (val != null) { ConfigDefinition definition = val.Definition; object boxedValue = val.BoxedValue; snapshot.TryGetValue(definition, out var value); if (!object.Equals(value, boxedValue)) { log.LogInfo("Setting changed: [" + definition.Section + "/" + definition.Key + "] '" + Format(value) + "' -> '" + Format(boxedValue) + "'"); snapshot[definition] = boxedValue; } } } catch { } }; config.ConfigReloaded += delegate { try { foreach (KeyValuePair item2 in config) { if (!snapshot.ContainsKey(item2.Key)) { snapshot[item2.Key] = item2.Value.BoxedValue; } } } catch { } }; } catch (Exception ex) { try { log.LogWarning("SettingsChangeLogger.Register failed: " + ex.GetType().Name + ": " + ex.Message); } catch { } } } private static string Format(object value) { if (value != null) { return value.ToString(); } return "null"; } } internal sealed class TimestampedLogger { private static readonly Stopwatch ProcessClock = Stopwatch.StartNew(); private readonly ManualLogSource _source; internal TimestampedLogger(ManualLogSource source) { _source = source; } private static string Prefix() { return $"[{DateTime.Now:HH:mm:ss.fff} | T+{ProcessClock.Elapsed.TotalSeconds:0.000}] "; } internal void LogInfo(object data) { _source.LogInfo((object)(Prefix() + data)); } internal void LogWarning(object data) { _source.LogWarning((object)(Prefix() + data)); } internal void LogError(object data) { _source.LogError((object)(Prefix() + data)); } internal void LogDebug(object data) { _source.LogDebug((object)(Prefix() + data)); } } internal static class ModLogging { public static void Init(ConfigFile config, ManualLogSource logger) { SettingsChangeLogger.Register(config, new TimestampedLogger(logger)); StartupLogger.EnsureStarted(); } } }