Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SealsAtHome v1.0.1
SealsAtHome.dll
Decompiled 20 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("SealsAtHome")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+57a60122bed72285ed7d897e17cdc3624de0eaeb")] [assembly: AssemblyProduct("SealsAtHome")] [assembly: AssemblyTitle("SealsAtHome")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SealsAtHome { internal static class ComponentSwap { public static TNew Replace<TNew>(GameObject prefab, Component old, Type copyFieldsOf) where TNew : Component { TNew val = prefab.AddComponent<TNew>(); foreach (FieldInfo item in UnitySerialization.SerializedFields(copyFieldsOf)) { item.SetValue(val, item.GetValue(old)); } Repoint(prefab, old, (Component)(object)val); string name = ((object)old).GetType().Name; try { Object.DestroyImmediate((Object)(object)old, true); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not remove " + name + " from " + ((Object)prefab).name + ": " + ex.Message)); } if ((Object)(object)old == (Object)null) { return val; } Repoint(prefab, (Component)(object)val, old); Object.DestroyImmediate((Object)(object)val, true); return default(TNew); } private static void Repoint(GameObject prefab, Component from, Component to) { MonoBehaviour[] componentsInChildren = prefab.GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } foreach (FieldInfo item in UnitySerialization.SerializedFields(((object)val).GetType())) { if (item.FieldType.IsInstanceOfType(to) && item.GetValue(val) == from) { item.SetValue(val, to); } } } } } [BepInPlugin("algo7.sealsathome", "SealsAtHome", "1.0.1")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "algo7.sealsathome"; public const string Name = "SealsAtHome"; public const string PluginVersion = "1.0.1"; internal static ManualLogSource Log; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; SceneManager.sceneLoaded += OnSceneLoaded; Log.LogInfo((object)"SealsAtHome loaded (v1.0.1)"); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { try { if (Application.isBatchMode) { return; } ZNetScene instance = ZNetScene.instance; if (!((Object)(object)instance == (Object)null)) { int num = Character.GetAllCharacters().Count((Character c) => (Object)(object)c != (Object)null && c.m_name.StartsWith("$enemy_seal", StringComparison.Ordinal)); Log.LogInfo((object)$"Scene '{((Scene)(ref scene)).name}': prefabs registered, {num} seals exist yet (expected 0)"); if (SealRebuild.Run(instance, out var message)) { Log.LogInfo((object)message); } else { Log.LogWarning((object)message); } } } catch (Exception arg) { Log.LogError((object)$"Seal rebuild failed, seals stay vanilla: {arg}"); } } } public sealed class SealCare : MonoBehaviour { private const float TickSeconds = 2f; private const int GrowEveryTicks = 5; public bool m_isPup; private static readonly int s_tamedAtHash = StringExtensionMethods.GetStableHashCode("SealsAtHome_TamedAt"); private static readonly int s_beatHash = StringExtensionMethods.GetStableHashCode("SealsAtHome_Beat"); private static readonly HashSet<long> s_takenFrom = new HashSet<long>(); private static bool s_errorLogged; private ZNetView m_nview; private Character m_character; private MonsterAI m_ai; private bool? m_tamedApplied; private int m_ticks; private bool m_grown; private void Awake() { m_nview = ((Component)this).GetComponent<ZNetView>(); m_character = ((Component)this).GetComponent<Character>(); m_ai = ((Component)this).GetComponent<MonsterAI>(); ((MonoBehaviour)this).InvokeRepeating("Tick", Random.Range(0.5f, 2f), 2f); } private void Tick() { try { if (m_grown || (Object)(object)m_nview == (Object)null || !m_nview.IsValid() || (Object)(object)m_character == (Object)null || (Object)(object)m_ai == (Object)null) { return; } bool flag = m_character.IsTamed(); if (m_tamedApplied != flag) { SettingsApplier.Apply((Component)(object)m_ai, flag ? SealSettings.TamedSwitch : SealSettings.WildSwitch, null); m_tamedApplied = flag; } if ((Object)(object)ZNet.instance == (Object)null) { return; } ZDO zDO = m_nview.GetZDO(); long ticks = ZNet.instance.GetTime().Ticks; long beatTicks = zDO.GetLong(s_beatHash, 0L); if (!m_nview.IsOwner()) { long owner = zDO.GetOwner(); if (Takeover.ShouldClaim(isOwner: false, owner, beatTicks, ticks)) { m_nview.ClaimOwnership(); zDO.Set(s_beatHash, ticks); LogTakeover(owner); } return; } if (Takeover.ShouldBeat(beatTicks, ticks)) { zDO.Set(s_beatHash, ticks); } string text = zDO.GetString(ZDOVars.s_tamedName, ""); if (NameCopy.ShouldWrite(text, zDO.GetString(ZDOVars.s_overrideHoverName, ""))) { string text2 = NameCopy.Target(text); zDO.Set(ZDOVars.s_overrideHoverName, text2); Plugin.Log.LogInfo((object)((text2.Length > 0) ? ("Pet name '" + text2 + "' is now shown to players without the mod too") : "Pet name cleared for players without the mod")); } if (m_isPup && ++m_ticks % 5 == 0) { switch (PupGrowth.Decide(flag, text, zDO.GetLong(s_tamedAtHash, 0L), ticks)) { case PupGrowth.Step.Stamp: zDO.Set(s_tamedAtHash, ticks); break; case PupGrowth.Step.Grow: GrowUp(); break; } } } catch (Exception arg) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"SealCare failed (logged once): {arg}"); } } } private static void LogTakeover(long owner) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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) if (!s_takenFrom.Add(owner)) { return; } string text = "a player"; foreach (PlayerInfo player in ZNet.instance.GetPlayerList()) { ZDOID characterID = player.m_characterID; if (((ZDOID)(ref characterID)).UserID == owner) { text = player.m_name; } } Plugin.Log.LogInfo((object)("Took over seals from " + text + ": their game doesn't run SealsAtHome (logged once per player)")); } private void GrowUp() { //IL_0038: 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) GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab("Seal") : null); if (!((Object)(object)val == (Object)null)) { m_grown = true; Character component = Object.Instantiate<GameObject>(val, ((Component)this).transform.position, ((Component)this).transform.rotation).GetComponent<Character>(); if ((Object)(object)component != (Object)null) { component.SetTamed(true); component.SetLevel(m_character.GetLevel()); } m_nview.Destroy(); } } } internal static class SealRebuild { private const string TameableSealsGuid = "pseudopulse.TameableSeals"; public static bool Run(ZNetScene scene, out string message) { GameObject prefab = scene.GetPrefab("Seal"); GameObject prefab2 = scene.GetPrefab("Seal_Pup"); if (IsRebuilt(prefab) && IsRebuilt(prefab2)) { message = "Seals already rebuilt (the prefabs survived the scene change)"; return true; } string text = PreFlight(scene, prefab, prefab2); if (text != null) { message = "SealsAtHome leaves the seals alone: " + text; return false; } (GameObject, bool)[] array = new(GameObject, bool)[2] { (prefab, false), (prefab2, true) }; for (int i = 0; i < array.Length; i++) { (GameObject, bool) tuple = array[i]; GameObject item = tuple.Item1; bool item2 = tuple.Item2; text = Rebuild(scene, item, item2); if (text != null) { message = "Seals stay (partly) vanilla: " + text; return false; } } message = "Seal and Seal_Pup rebuilt: seals are tameable"; return true; } private static bool IsRebuilt(GameObject prefab) { if ((Object)(object)prefab != (Object)null) { return (Object)(object)prefab.GetComponent<SealCare>() != (Object)null; } return false; } private static string PreFlight(ZNetScene scene, GameObject seal, GameObject pup) { if (Chainloader.PluginInfos.ContainsKey("pseudopulse.TameableSeals")) { return "TameableSeals (pseudopulse) is installed and changes seals too: remove one of the two mods"; } foreach (string requiredPrefab in SealSettings.RequiredPrefabs) { if ((Object)(object)scene.GetPrefab(requiredPrefab) == (Object)null) { return "the game has no prefab '" + requiredPrefab + "' (game update?)"; } } if ((Object)(object)scene.GetPrefab("FishRaw").GetComponent<ItemDrop>() == (Object)null) { return "FishRaw is not an item (game update?)"; } foreach (FieldSetting item in SealSettings.All) { string text = SealSettings.Check(item); if (text != null) { return text + " (game update?)"; } } GameObject[] array = (GameObject[])(object)new GameObject[2] { seal, pup }; foreach (GameObject val in array) { if ((Object)(object)val.GetComponent<Tameable>() != (Object)null || (Object)(object)val.GetComponent<MonsterAI>() != (Object)null || (Object)(object)val.GetComponent<Procreation>() != (Object)null) { return ((Object)val).name + " was already changed by another mod (it has a Tameable, MonsterAI or Procreation)"; } Character[] components = val.GetComponents<Character>(); if (components.Length != 1 || ((object)components[0]).GetType() != typeof(Character)) { return ((Object)val).name + "'s body isn't a plain Character (game update or another mod)"; } if (val.GetComponents<BaseAI>().Length != 1 || (Object)(object)val.GetComponent<AnimalAI>() == (Object)null) { return ((Object)val).name + "'s AI isn't a single AnimalAI (game update or another mod)"; } } return null; } private static string Rebuild(ZNetScene scene, GameObject prefab, bool isPup) { Humanoid val = ComponentSwap.Replace<Humanoid>(prefab, (Component)(object)prefab.GetComponent<Character>(), typeof(Character)); if ((Object)(object)val == (Object)null) { return "Unity refused to swap " + ((Object)prefab).name + "'s Character for a Humanoid"; } EmptyItemLists(val); MonsterAI val2 = ComponentSwap.Replace<MonsterAI>(prefab, (Component)(object)prefab.GetComponent<AnimalAI>(), typeof(BaseAI)); if ((Object)(object)val2 == (Object)null) { return "Unity refused to swap " + ((Object)prefab).name + "'s AnimalAI for a MonsterAI"; } SettingsApplier.Apply((Component)(object)val, SealSettings.HumanoidFor(isPup), Prefab); SettingsApplier.Apply((Component)(object)val2, SealSettings.WildSwitch.Concat(SealSettings.AI), Prefab); SettingsApplier.Apply((Component)(object)prefab.AddComponent<Tameable>(), SealSettings.TameableFor(isPup), Prefab); if (!isPup) { SettingsApplier.Apply((Component)(object)prefab.AddComponent<Procreation>(), SealSettings.Breeding, Prefab); } prefab.AddComponent<SealCare>().m_isPup = isPup; return null; GameObject Prefab(string name) { return scene.GetPrefab(name); } } private static void EmptyItemLists(Humanoid body) { body.m_defaultItems = (GameObject[])(object)new GameObject[0]; body.m_randomWeapon = (GameObject[])(object)new GameObject[0]; body.m_randomArmor = (GameObject[])(object)new GameObject[0]; body.m_randomShield = (GameObject[])(object)new GameObject[0]; body.m_randomSets = (ItemSet[])(object)new ItemSet[0]; body.m_randomItems = (RandomItem[])(object)new RandomItem[0]; } } internal static class PupGrowth { public enum Step { Wait, Stamp, Grow } public static Step Decide(bool tamed, string name, long tamedAtTicks, long nowTicks) { if (!tamed) { return Step.Wait; } if (tamedAtTicks <= 0 || tamedAtTicks > nowTicks) { return Step.Stamp; } if (!string.IsNullOrWhiteSpace(name)) { return Step.Wait; } if (nowTicks - tamedAtTicks < 30000000000L) { return Step.Wait; } return Step.Grow; } } internal static class NameCopy { public static string Target(string tamedName) { string text = ((tamedName != null) ? StringExtensionMethods.RemoveRichTextTags(tamedName) : null); if (!string.IsNullOrWhiteSpace(text)) { return text; } return ""; } public static bool ShouldWrite(string tamedName, string shown) { return Target(tamedName) != (shown ?? ""); } } internal static class Takeover { private static readonly long BeatTicks = 100000000L; private static readonly long StaleTicks = 300000000L; public static bool ShouldBeat(long beatTicks, long nowTicks) { if (beatTicks > 0 && beatTicks <= nowTicks) { return nowTicks - beatTicks >= BeatTicks; } return true; } public static bool ShouldClaim(bool isOwner, long owner, long beatTicks, long nowTicks) { if (!isOwner && owner != 0L) { if (beatTicks > 0 && beatTicks <= nowTicks) { return nowTicks - beatTicks > StaleTicks; } return true; } return false; } } internal enum SettingKind { Value, Effects, Prefab, Items } internal sealed class FieldSetting { public readonly Type Component; public readonly string Field; public readonly SettingKind Kind; public readonly object Value; public Type ExpectedFieldType => Kind switch { SettingKind.Effects => typeof(EffectList), SettingKind.Prefab => typeof(GameObject), SettingKind.Items => typeof(List<ItemDrop>), _ => Value.GetType(), }; public IEnumerable<string> PrefabNames { get { switch (Kind) { case SettingKind.Effects: case SettingKind.Items: return (string[])Value; case SettingKind.Prefab: return new string[1] { (string)Value }; default: return Enumerable.Empty<string>(); } } } private FieldSetting(Type component, string field, SettingKind kind, object value) { Component = component; Field = field; Kind = kind; Value = value; } public static FieldSetting Of<T>(string field, object value) { return new FieldSetting(typeof(T), field, SettingKind.Value, value); } public static FieldSetting Effects<T>(string field, params string[] prefabs) { return new FieldSetting(typeof(T), field, SettingKind.Effects, prefabs); } public static FieldSetting Prefab<T>(string field, string prefab) { return new FieldSetting(typeof(T), field, SettingKind.Prefab, prefab); } public static FieldSetting Items<T>(string field, params string[] items) { return new FieldSetting(typeof(T), field, SettingKind.Items, items); } public override string ToString() { return Component.Name + "." + Field; } } internal static class SealSettings { public const string SealPrefab = "Seal"; public const string PupPrefab = "Seal_Pup"; public const string RawFish = "FishRaw"; public const string SealSound = "sfx_seal_idle"; public const string PupSound = "sfx_babyseal_idle"; public const string Hearts = "vfx_creature_soothed"; public const string TamedEffect = "fx_creature_tamed"; public const string PetHearts = "vfx_lox_love"; public const string LoveHearts = "vfx_boar_love"; public const string BirthSplash = "vfx_boar_birth"; public const double GrowSeconds = 3000.0; public const string TamedAtKey = "SealsAtHome_TamedAt"; public const string BeatKey = "SealsAtHome_Beat"; public const double BeatSeconds = 10.0; public const double StaleSeconds = 30.0; public static readonly FieldSetting[] WildSwitch = new FieldSetting[2] { FieldSetting.Of<MonsterAI>("m_alertRange", 0f), FieldSetting.Of<MonsterAI>("m_fleeIfNotAlerted", true) }; public static readonly FieldSetting[] TamedSwitch = new FieldSetting[2] { FieldSetting.Of<MonsterAI>("m_alertRange", 10f), FieldSetting.Of<MonsterAI>("m_fleeIfNotAlerted", false) }; public static readonly FieldSetting[] AI = new FieldSetting[11] { FieldSetting.Of<MonsterAI>("m_fleeIfLowHealth", 1f), FieldSetting.Of<MonsterAI>("m_fleeTimeSinceHurt", 15f), FieldSetting.Of<MonsterAI>("m_aggravatable", false), FieldSetting.Of<MonsterAI>("m_attackPlayerObjects", false), FieldSetting.Of<MonsterAI>("m_enableHuntPlayer", false), FieldSetting.Of<MonsterAI>("m_sleeping", false), FieldSetting.Of<MonsterAI>("m_avoidLand", false), FieldSetting.Of<MonsterAI>("m_consumeRange", 1f), FieldSetting.Of<MonsterAI>("m_consumeSearchRange", 10f), FieldSetting.Of<MonsterAI>("m_consumeSearchInterval", 10f), FieldSetting.Items<MonsterAI>("m_consumeItems", new string[1] { "FishRaw" }) }; public static readonly FieldSetting[] Breeding = new FieldSetting[12] { FieldSetting.Of<Procreation>("m_updateInterval", 30f), FieldSetting.Of<Procreation>("m_totalCheckRange", 10f), FieldSetting.Of<Procreation>("m_maxCreatures", 5), FieldSetting.Of<Procreation>("m_partnerCheckRange", 3f), FieldSetting.Of<Procreation>("m_pregnancyChance", 0.33f), FieldSetting.Of<Procreation>("m_pregnancyDuration", 60f), FieldSetting.Of<Procreation>("m_requiredLovePoints", 3), FieldSetting.Prefab<Procreation>("m_offspring", "Seal_Pup"), FieldSetting.Of<Procreation>("m_minOffspringLevel", 0), FieldSetting.Of<Procreation>("m_spawnOffset", 1f), FieldSetting.Effects<Procreation>("m_loveEffects", new string[2] { "vfx_boar_love", "sfx_seal_idle" }), FieldSetting.Effects<Procreation>("m_birthEffects", new string[2] { "vfx_boar_birth", "sfx_babyseal_idle" }) }; public static IEnumerable<FieldSetting> All => WildSwitch.Concat(TamedSwitch).Concat(AI).Concat(TameableFor(pup: false)) .Concat(TameableFor(pup: true)) .Concat(Breeding) .Concat(HumanoidFor(pup: false)) .Concat(HumanoidFor(pup: true)); public static IEnumerable<string> RequiredPrefabs => new string[2] { "Seal", "Seal_Pup" }.Concat(All.SelectMany((FieldSetting s) => s.PrefabNames)).Distinct(); public static FieldSetting[] TameableFor(bool pup) { return new FieldSetting[11] { FieldSetting.Of<Tameable>("m_tamingTime", 1800f), FieldSetting.Of<Tameable>("m_fedDuration", 600f), FieldSetting.Of<Tameable>("m_commandable", true), FieldSetting.Of<Tameable>("m_startsTamed", false), FieldSetting.Of<Tameable>("m_unsummonDistance", 0f), FieldSetting.Of<Tameable>("m_unsummonOnOwnerLogoutSeconds", 0f), FieldSetting.Of<Tameable>("m_tamingSpeedMultiplierRange", 60f), FieldSetting.Of<Tameable>("m_tamingBoostMultiplier", 2f), FieldSetting.Effects<Tameable>("m_sootheEffect", new string[1] { "vfx_creature_soothed" }), FieldSetting.Effects<Tameable>("m_tamedEffect", new string[1] { "fx_creature_tamed" }), FieldSetting.Effects<Tameable>("m_petEffect", new string[2] { "vfx_lox_love", pup ? "sfx_babyseal_idle" : "sfx_seal_idle" }) }; } public static FieldSetting[] HumanoidFor(bool pup) { return new FieldSetting[1] { FieldSetting.Effects<Humanoid>("m_consumeItemEffects", new string[1] { pup ? "sfx_babyseal_idle" : "sfx_seal_idle" }) }; } public static string Check(FieldSetting setting) { FieldInfo field = setting.Component.GetField(setting.Field, BindingFlags.Instance | BindingFlags.Public); if (field == null) { return $"{setting} doesn't exist"; } if (field.FieldType != setting.ExpectedFieldType) { return $"{setting} is a {field.FieldType.Name}, not a {setting.ExpectedFieldType.Name}"; } return null; } } internal static class SettingsApplier { public static void Apply(Component target, IEnumerable<FieldSetting> settings, Func<string, GameObject> prefab) { foreach (FieldSetting setting in settings) { setting.Component.GetField(setting.Field, BindingFlags.Instance | BindingFlags.Public).SetValue(target, Resolve(setting, prefab)); } } private static object Resolve(FieldSetting setting, Func<string, GameObject> prefab) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown return setting.Kind switch { SettingKind.Effects => (object)new EffectList { m_effectPrefabs = setting.PrefabNames.Select((Func<string, EffectData>)((string n) => new EffectData { m_prefab = prefab(n) })).ToArray() }, SettingKind.Prefab => prefab((string)setting.Value), SettingKind.Items => setting.PrefabNames.Select((string n) => prefab(n).GetComponent<ItemDrop>()).ToList(), _ => setting.Value, }; } } internal static class UnitySerialization { public static bool IsSerialized(FieldInfo field) { if (field.IsStatic || field.IsInitOnly || field.IsLiteral) { return false; } if (typeof(Delegate).IsAssignableFrom(field.FieldType)) { return false; } if (field.IsPublic) { return !field.IsNotSerialized; } return ((MemberInfo)field).GetCustomAttribute<SerializeField>() != null; } public static IEnumerable<FieldInfo> SerializedFields(Type type) { Type t = type; while (t != null && t != typeof(MonoBehaviour) && t != typeof(object)) { FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (IsSerialized(fieldInfo)) { yield return fieldInfo; } } t = t.BaseType; } } } internal static class PluginInfo { public const string Version = "1.0.1"; } }