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 DvergrForHire v1.0.0
DvergrForHire.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; 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("DvergrForHire")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+7425ef36463646eaffde4879ec14756e1cd827d1")] [assembly: AssemblyProduct("DvergrForHire")] [assembly: AssemblyTitle("DvergrForHire")] [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 DvergrForHire { [HarmonyPatch] internal static class BuildingPatches { private static bool s_errorLogged; [HarmonyPatch(typeof(WearNTear), "Damage")] [HarmonyPrefix] private static bool Damage(HitData hit) { try { if (hit == null) { return true; } Character attacker = hit.GetAttacker(); return !HireRules.BuildingSafeFrom(Mercenary.IsHired(attacker), Mercenary.IsHiredSummon(attacker), ShotPatches.HiredShotHitting); } catch (Exception arg) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"Building protection failed (logged once): {arg}"); } return true; } } } internal static class DvergrSettings { public static readonly (string Prefab, int Price)[] Hireable = new(string, int)[7] { ("Dverger", 100), ("DvergerMage", 100), ("DvergerMageFire", 100), ("DvergerMageIce", 100), ("DvergerMageSupport", 100), ("DvergerAshlands", 200), ("DvergerDeepNorth", 100) }; public const int StarPercent = 50; public const string Coins = "Coins"; public const string SummonKey = "DvergrForHire_HiredSummon"; public const string HireSound = "sfx_coins_placed"; public const string Voice = "sfx_dverger_vo_idle"; public const string BeatKey = "DvergrForHire_Beat"; public const double BeatSeconds = 10.0; public const double StaleSeconds = 30.0; public const double ForgetSeconds = 300.0; public const double FollowGiveUpSeconds = 120.0; public const double RecentHireSeconds = 3.0; public const double TamedResendSeconds = 5.0; public static readonly FieldSetting[] TameableSettings = new FieldSetting[7] { 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.Effects<Tameable>("m_petEffect", new string[1] { "sfx_dverger_vo_idle" }), FieldSetting.Effects<Tameable>("m_tamedEffect", Array.Empty<string>()), FieldSetting.Effects<Tameable>("m_sootheEffect", Array.Empty<string>()) }; public static IEnumerable<string> EffectPrefabs => new string[1] { "sfx_coins_placed" }.Concat(TameableSettings.SelectMany((FieldSetting s) => s.PrefabNames)).Distinct(); public static IEnumerable<string> RequiredPrefabs => Hireable.Select(((string Prefab, int Price) h) => h.Prefab).Concat(new string[1] { "Coins" }).Concat(EffectPrefabs) .Distinct(); public static int BasePrice(string prefab) { return (from h in Hireable where h.Prefab == prefab select h.Price).FirstOrDefault(); } } internal static class DvergrSetup { public static bool Run(ZNetScene scene, out string message) { List<GameObject> list = DvergrSettings.Hireable.Select(((string Prefab, int Price) h) => scene.GetPrefab(h.Prefab)).ToList(); if (list.All((GameObject p) => (Object)(object)p != (Object)null && (Object)(object)p.GetComponent<Mercenary>() != (Object)null)) { SetStatics(scene); message = "Dvergr already hireable (the prefabs survived the scene change)"; return true; } string text = PreFlight(scene, list); if (text != null) { message = "DvergrForHire leaves the Dvergr alone: " + text; return false; } SetStatics(scene); foreach (GameObject item in list) { SettingsApplier.Apply((Component)(object)item.AddComponent<Tameable>(), (IEnumerable<FieldSetting>)DvergrSettings.TameableSettings, (Func<string, GameObject>)scene.GetPrefab); item.AddComponent<Mercenary>().m_basePrice = DvergrSettings.BasePrice(((Object)item).name); } message = "Dvergr can be hired: " + string.Join(", ", DvergrSettings.Hireable.Select(((string Prefab, int Price) h) => $"{h.Prefab} {h.Price}")); return true; } private static void SetStatics(ZNetScene scene) { GameObject prefab = scene.GetPrefab("Coins"); ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val != (Object)null) { Mercenary.s_coinName = val.m_itemData.m_shared.m_name; } if ((Object)(object)scene.GetPrefab("sfx_coins_placed") != (Object)null) { Mercenary.s_hireEffect = SettingsApplier.Effects((IEnumerable<string>)new string[1] { "sfx_coins_placed" }, (Func<string, GameObject>)scene.GetPrefab); } } private static string PreFlight(ZNetScene scene, List<GameObject> prefabs) { foreach (string requiredPrefab in DvergrSettings.RequiredPrefabs) { if ((Object)(object)scene.GetPrefab(requiredPrefab) == (Object)null) { return "the game has no prefab '" + requiredPrefab + "' (game update?)"; } } if ((Object)(object)scene.GetPrefab("Coins").GetComponent<ItemDrop>() == (Object)null) { return "Coins is not an item (game update?)"; } foreach (string effectPrefab in DvergrSettings.EffectPrefabs) { if ((Object)(object)scene.GetPrefab(effectPrefab).GetComponent<ZNetView>() == (Object)null) { return "the effect '" + effectPrefab + "' isn't networked (game update?)"; } } FieldSetting[] tameableSettings = DvergrSettings.TameableSettings; for (int i = 0; i < tameableSettings.Length; i++) { string text = tameableSettings[i].Problem(); if (text != null) { return text + " (game update?)"; } } foreach (GameObject prefab in prefabs) { if ((Object)(object)prefab.GetComponent<Humanoid>() == (Object)null || (Object)(object)prefab.GetComponent<MonsterAI>() == (Object)null) { return ((Object)prefab).name + " isn't a Humanoid with a MonsterAI (game update or another mod)"; } if ((Object)(object)prefab.GetComponent<Mercenary>() != (Object)null) { return "only some Dvergr were set up earlier this session: restart the game"; } if ((Object)(object)prefab.GetComponent<Tameable>() != (Object)null) { return ((Object)prefab).name + " can already be tamed: another mod changed the Dvergr"; } } return null; } } internal enum SettingKind { Value, Effects } internal sealed class FieldSetting { public readonly Type Component; public readonly string Field; public readonly SettingKind Kind; public readonly object Value; public Type ExpectedFieldType { get { if (Kind != SettingKind.Effects) { return Value.GetType(); } return typeof(EffectList); } } public IEnumerable<string> PrefabNames { get { if (Kind != SettingKind.Effects) { return Enumerable.Empty<string>(); } return (string[])Value; } } 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 string Problem() { FieldInfo field = Component.GetField(Field, BindingFlags.Instance | BindingFlags.Public); if (field == null) { return $"{this} doesn't exist"; } if (field.FieldType != ExpectedFieldType) { return $"{this} is a {field.FieldType.Name}, not a {ExpectedFieldType.Name}"; } return null; } public override string ToString() { return Component.Name + "." + Field; } } [HarmonyPatch] internal static class HirePatches { private static bool s_errorLogged; [HarmonyPatch(typeof(Tameable), "GetHoverText")] [HarmonyPostfix] private static void HoverText(Tameable __instance, ref string __result) { try { Mercenary component = ((Component)__instance).GetComponent<Mercenary>(); if ((Object)(object)component != (Object)null) { __result = component.HoverText(__result); } } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(Tameable), "Interact")] [HarmonyPrefix] private static bool Interact(Tameable __instance, Humanoid user, bool hold, bool alt, ref bool __result) { try { Mercenary component = ((Component)__instance).GetComponent<Mercenary>(); bool? flag = (((Object)(object)component != (Object)null) ? component.Interact(user, hold, alt) : ((bool?)null)); if (!flag.HasValue) { return true; } __result = flag.Value; return false; } catch (Exception e) { LogOnce(e); return true; } } [HarmonyPatch(typeof(Tameable), "IsHungry")] [HarmonyPostfix] private static void IsHungry(Tameable __instance, ref bool __result) { try { if (__result) { __result = HireRules.Hungry(vanillaHungry: true, Mercenary.IsHired(((Component)__instance).GetComponent<Character>())); } } catch (Exception e) { LogOnce(e); } } private static void LogOnce(Exception e) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"Hire hover / E failed (logged once): {e}"); } } } internal static class HireRules { public enum Hire { Hire, NotEnoughCoins, NotHireable } public enum Paid { Waiting, Resend, Confirmed } public enum EPress { Hire, Nothing, Vanilla, Recruiter } public static int Price(int basePrice, int level) { if (basePrice <= 0) { return 0; } return (int)Math.Round((double)basePrice * Math.Pow(1.5, Math.Max(0, level - 1)), MidpointRounding.AwayFromZero); } public static Hire Check(bool alive, bool tamed, bool provoked, int coins, int price) { if (!alive || tamed || provoked || price <= 0) { return Hire.NotHireable; } if (coins < price) { return Hire.NotEnoughCoins; } return Hire.Hire; } public static bool RecentlyHired(double hiredAt, double now) { if (hiredAt > 0.0 && now >= hiredAt) { return now - hiredAt < 3.0; } return false; } public static Paid AfterPaying(bool tamed, double lastSent, double now) { if (tamed) { return Paid.Confirmed; } if (!(now - lastSent >= 5.0)) { return Paid.Waiting; } return Paid.Resend; } public static EPress WhatEDoes(bool recruiter, bool recentlyHired, bool tamed, bool paid, bool hold, bool alt) { if (recruiter) { return EPress.Recruiter; } if (recentlyHired) { return EPress.Nothing; } if (tamed) { return EPress.Vanilla; } if (paid || hold || alt) { return EPress.Nothing; } return EPress.Hire; } public static bool MarkSummon(bool summonerHired, bool sameFrame, bool isItsSummonPrefab) { return summonerHired && sameFrame && isItsSummonPrefab; } public static bool BuildingSafeFrom(bool hired, bool hiredSummon, bool hiredShot) { return hired || hiredSummon || hiredShot; } public static bool HiredDvergrFoe(bool players, bool tamed, bool dvergr, bool provoked) { if (!players && !tamed) { return !dvergr || provoked; } return false; } public static bool ShotHits(bool foe, bool hitsFriends, bool hitsEnemies, bool hitsSameKind, bool sameKind) { if (sameKind && !hitsSameKind) { return false; } if (!foe) { return hitsFriends; } return hitsEnemies; } public static string UseLine(string action) { return "[<color=yellow><b>$KEY_Use</b></color>] " + action; } public static string Hover(string name, bool hired, bool provoked, int price, string vanilla) { if (hired) { return name + " ( Hired )\n" + UseLine("Follow / Stay") + RenameLines(vanilla); } if (provoked) { return ""; } return name + "\n" + UseLine("Hire: " + price.ToString(CultureInfo.InvariantCulture) + " coins"); } public static bool Hungry(bool vanillaHungry, bool hired) { if (vanillaHungry) { return !hired; } return false; } internal static string RenameLines(string vanilla) { string[] array = (vanilla ?? "").Split(new char[1] { '\n' }); if (array.Length <= 2) { return ""; } return "\n" + string.Join("\n", array, 2, array.Length - 2); } public static string NotEnoughCoins(int price) { return "Not enough coins (" + price.ToString(CultureInfo.InvariantCulture) + ")"; } public static string Hired(string name) { return name + " hired"; } } public sealed class HiringPost : MonoBehaviour, Hoverable, Interactable { private static readonly int s_postHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_Post"); private static bool s_errorLogged; private ZNetView m_nview; private readonly PostWatch m_recruiterWatch = new PostWatch(); private bool m_breakLogged; internal int Stars { get; private set; } internal PostKind Kind { get { if ((Object)(object)m_nview == (Object)null || !m_nview.IsValid()) { return null; } string text = m_nview.GetZDO().GetString(s_postHash, ""); if (!string.IsNullOrEmpty(text)) { return PostSettings.ById(text); } return null; } } private void Awake() { m_nview = ((Component)this).GetComponent<ZNetView>(); ((MonoBehaviour)this).InvokeRepeating("Tick", Random.Range(0.2f, 1f), 1f); } private void Tick() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) try { PostKind kind = Kind; if (kind == null || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { return; } ZDO zDO = m_nview.GetZDO(); if ((int)zDO.GetConnectionType() != 3) { return; } ZDOID connectionZDOID = zDO.GetConnectionZDOID((ConnectionType)3); bool flag = !((ZDOID)(ref connectionZDOID)).IsNone() && ZDOMan.instance.GetZDO(connectionZDOID) != null; bool areaReady = flag || ZNetScene.instance.IsAreaReady(((Component)this).transform.position); if (PostRules.PoleBreaks(linked: true, flag, areaReady, m_recruiterWatch, Time.unscaledTimeAsDouble)) { WearNTear component = ((Component)this).GetComponent<WearNTear>(); if ((Object)(object)component != (Object)null) { component.Remove(false); } if (!m_breakLogged) { m_breakLogged = true; Plugin.Log.LogInfo((object)("A " + kind.Label + " recruiter died: its hiring post broke")); } } } catch (Exception e) { LogOnce(e); } } public string GetHoverText() { try { PostKind kind = Kind; return (kind == null) ? "" : Localization.instance.Localize(PostRules.PostHover(kind.Label, Stars)); } catch (Exception e) { LogOnce(e); return ""; } } public string GetHoverName() { try { PostKind kind = Kind; if (kind != null) { return PostRules.EntryName(kind.Label); } Piece component = ((Component)this).GetComponent<Piece>(); return ((Object)(object)component != (Object)null) ? Localization.instance.Localize(component.m_name) : ""; } catch (Exception e) { LogOnce(e); return ""; } } public float GetHoverOffset() { return 0f; } public bool Interact(Humanoid user, bool hold, bool alt) { try { if (hold || alt || Kind == null) { return false; } Stars = PostRules.NextStars(Stars); return true; } catch (Exception e) { LogOnce(e); return false; } } public bool UseItem(Humanoid user, ItemData item) { return false; } private static void LogOnce(Exception e) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"Hiring post failed (logged once): {e}"); } } } public sealed class Mercenary : MonoBehaviour { private const float TickSeconds = 1f; public int m_basePrice; internal static string s_coinName; internal static EffectList s_hireEffect; private static readonly int s_beatHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_Beat"); private static readonly Dictionary<long, double> s_noMod = new Dictionary<long, double>(); private static readonly HashSet<long> s_logged = new HashSet<long>(); private static readonly HashSet<string> s_errorsLogged = new HashSet<string>(); private ZNetView m_nview; private Character m_character; private BaseAI m_ai; private Tameable m_tameable; private bool? m_hiredApplied; private readonly TakeoverWatch m_watch = new TakeoverWatch(); private double m_hiredAt; private bool m_paid; private double m_tamedSentAt; private PendingFollow m_pending; private static readonly int s_summonHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_HiredSummon"); private static readonly int s_recruiterHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_Recruiter"); private static readonly int s_poleHash = StringExtensionMethods.GetStableHashCode("piece_dvergr_lantern_pole"); private static readonly int s_fromPostHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_FromPost"); private static readonly List<ZDO> s_nearby = new List<ZDO>(); private readonly PostWatch m_postWatch = new PostWatch(); private ZDOID m_postId = ZDOID.None; private static int s_hireSpotMask; private bool Ready { get { if ((Object)(object)m_nview != (Object)null && m_nview.IsValid() && (Object)(object)m_character != (Object)null && (Object)(object)m_ai != (Object)null) { return (Object)(object)m_tameable != (Object)null; } return false; } } private bool RecentlyHired => HireRules.RecentlyHired(m_hiredAt, Time.unscaledTimeAsDouble); private int Price => HireRules.Price(m_basePrice, m_character.GetLevel()); private bool IsRecruiter => m_nview.GetZDO().GetBool(s_recruiterHash, false); private PostKind Kind => PostSettings.ByPrefab(Utils.GetPrefabName(((Component)this).gameObject)); private int PostStars { get { //IL_0023: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)ZNetScene.instance != (Object)null && !((ZDOID)(ref m_postId)).IsNone()) ? ZNetScene.instance.FindInstance(m_postId) : null); HiringPost hiringPost = (((Object)(object)val != (Object)null) ? val.GetComponent<HiringPost>() : null); if (!((Object)(object)hiringPost != (Object)null)) { return 0; } return hiringPost.Stars; } } private void Awake() { m_nview = ((Component)this).GetComponent<ZNetView>(); m_character = ((Component)this).GetComponent<Character>(); m_ai = ((Component)this).GetComponent<BaseAI>(); m_tameable = ((Component)this).GetComponent<Tameable>(); ((MonoBehaviour)this).InvokeRepeating("Tick", Random.Range(0.2f, 1f), 1f); } internal static bool IsHiredSummon(Character character) { if ((Object)(object)character == (Object)null) { return false; } ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { return component.GetZDO().GetBool(s_summonHash, false); } return false; } internal static bool IsHired(Character character) { if ((Object)(object)character != (Object)null && (Object)(object)((Component)character).GetComponent<Mercenary>() != (Object)null) { return character.IsTamed(); } return false; } internal string HoverText(string vanilla) { if (!Ready) { return vanilla; } if (IsRecruiter) { return RecruiterHoverText(vanilla); } bool hired = m_character.IsTamed() || m_paid; return Localization.instance.Localize(HireRules.Hover(m_tameable.GetName(), hired, m_ai.IsAggravated(), Price, vanilla)); } internal bool? Interact(Humanoid user, bool hold, bool alt) { if (!Ready) { return null; } switch (HireRules.WhatEDoes(IsRecruiter, RecentlyHired, m_character.IsTamed(), m_paid, hold, alt)) { case HireRules.EPress.Recruiter: return RecruiterInteract(user, hold, alt); case HireRules.EPress.Nothing: return false; case HireRules.EPress.Vanilla: m_pending = null; return null; default: return TryHire(user); } } private bool TryHire(Humanoid user) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || string.IsNullOrEmpty(s_coinName)) { return false; } int price = Price; Inventory inventory = ((Humanoid)val).GetInventory(); int num = inventory.CountItems(s_coinName, -1, true); switch (HireRules.Check(!m_character.IsDead(), m_character.IsTamed() || m_paid, m_ai.IsAggravated(), num, price)) { case HireRules.Hire.NotHireable: return false; case HireRules.Hire.NotEnoughCoins: ((Character)val).Message((MessageType)2, HireRules.NotEnoughCoins(price), 0, (Sprite)null, false); return true; default: { inventory.RemoveItem(s_coinName, price, -1, true); int num2 = inventory.CountItems(s_coinName, -1, true); if (num2 != num - price) { Plugin.Log.LogWarning((object)$"Hire stopped: paying {price} of {num} coins left {num2}"); return true; } m_hiredAt = Time.unscaledTimeAsDouble; m_paid = true; m_tamedSentAt = m_hiredAt; m_character.SetTamed(true); long owner = m_nview.GetZDO().GetOwner(); if (owner != 0L) { m_tameable.Command((Humanoid)(object)val, false); } m_pending = new PendingFollow(val.GetPlayerName(), owner, m_hiredAt); Plugin.Log.LogInfo((object)$"Hired {Utils.GetPrefabName(((Component)this).gameObject)} (level {m_character.GetLevel()}) for {price} coins"); EffectList obj = s_hireEffect; if (obj != null) { obj.Create(((Component)this).transform.position, ((Component)this).transform.rotation, (Transform)null, 1f, -1, default(ZDOID)); } ((Character)val).Message((MessageType)2, HireRules.Hired(m_tameable.GetName()), 0, (Sprite)null, false); return true; } } } private void ConfirmPayment(bool tamed) { double unscaledTimeAsDouble = Time.unscaledTimeAsDouble; switch (HireRules.AfterPaying(tamed, m_tamedSentAt, unscaledTimeAsDouble)) { case HireRules.Paid.Confirmed: m_paid = false; break; case HireRules.Paid.Resend: m_character.SetTamed(true); m_tamedSentAt = unscaledTimeAsDouble; Plugin.Log.LogInfo((object)"Sent 'hired' again: the tamed flag hasn't come back yet"); break; } } private void Tick() { try { if (!Ready) { return; } bool flag = m_character.IsTamed(); if (m_paid) { ConfirmPayment(flag); } if (m_hiredApplied != flag) { m_ai.m_aggravatable = !flag; m_hiredApplied = flag; } if (!flag || (Object)(object)ZNet.instance == (Object)null) { return; } TakeOver(); FollowUp(); bool postFound = IsRecruiter && FindPost(); if (!m_nview.IsOwner()) { m_postWatch.Reset(); } else if (!IsRecruiter || !RecruiterLeaves(postFound)) { ZDO zDO = m_nview.GetZDO(); long ticks = ZNet.instance.GetTime().Ticks; if (Takeover.ShouldBeat(zDO.GetLong(s_beatHash, 0L), ticks)) { zDO.Set(s_beatHash, ticks); } string tamedName = zDO.GetString(ZDOVars.s_tamedName, ""); if (NameCopy.ShouldWrite(tamedName, zDO.GetString(ZDOVars.s_overrideHoverName, ""))) { zDO.Set(ZDOVars.s_overrideHoverName, NameCopy.Target(tamedName)); } } } catch (Exception ex) { if (s_errorsLogged.Add(ex.GetType().FullName)) { Plugin.Log.LogError((object)$"Mercenary failed (each kind of error logged once): {ex}"); } } } private void TakeOver() { ZDO zDO = m_nview.GetZDO(); long owner = zDO.GetOwner(); TakeoverWatch.Step step = m_watch.Decide(m_nview.IsOwner(), owner, zDO.GetLong(s_beatHash, 0L), Time.unscaledTimeAsDouble, s_noMod); if (step != TakeoverWatch.Step.Wait) { m_nview.ClaimOwnership(); zDO.Set(s_beatHash, ZNet.instance.GetTime().Ticks); if (step == TakeoverWatch.Step.FoundNoMod) { LogTakeover(owner); } } } private void FollowUp() { if (m_pending == null) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { m_pending = null; return; } ZDO zDO = m_nview.GetZDO(); switch (m_pending.Decide(zDO.GetString(ZDOVars.s_follow, ""), zDO.GetOwner(), zDO.GetLong(s_beatHash, 0L), m_nview.IsOwner(), Time.unscaledTimeAsDouble)) { case PendingFollow.Step.Send: m_tameable.Command((Humanoid)(object)localPlayer, false); Plugin.Log.LogInfo((object)"Sent 'follow me' again: a game with the mod runs the hired Dvergr now"); break; case PendingFollow.Step.Done: case PendingFollow.Step.GiveUp: m_pending = null; break; } } 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_logged.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 control of hired Dvergr from " + text + ": their game doesn't run DvergrForHire (logged once per player)")); } internal static void CreateRecruiter(ZNetView pole, string kindId, Vector3 placer) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) PostKind postKind = PostSettings.ById(kindId); GameObject val = ((postKind != null && (Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(postKind.Prefab) : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)pole == (Object)null) && pole.IsValid()) { Vector3 val2 = placer - ((Component)pole).transform.position; val2.y = 0f; Vector3 val3 = ((((Vector3)(ref val2)).sqrMagnitude > 0.01f) ? ((Vector3)(ref val2)).normalized : ((Component)pole).transform.forward); Vector3 val4 = ((Component)pole).transform.position + val3 * 1.5f + Vector3.up * 0.5f; GameObject obj = Object.Instantiate<GameObject>(val, val4, Quaternion.LookRotation(val3)); ZNetView component = obj.GetComponent<ZNetView>(); component.GetZDO().Set(s_recruiterHash, true); component.GetZDO().Set(s_fromPostHash, true); pole.GetZDO().SetConnection((ConnectionType)3, component.GetZDO().m_uid); obj.GetComponent<Character>().SetTamed(true); obj.GetComponent<BaseAI>().SetPatrolPoint(); Plugin.Log.LogInfo((object)("Hiring post (" + postKind.Label + ") placed: its recruiter arrived")); } } private string RecruiterHoverText(string vanilla) { PostKind kind = Kind; if (kind == null) { return vanilla; } int price = PostRules.PostPrice(kind.Price, PostStars + 1); return Localization.instance.Localize(PostRules.RecruiterHover(kind.Label, kind.One, price, vanilla)); } private bool? RecruiterInteract(Humanoid user, bool hold, bool alt) { return PostRules.RecruiterInteract(hold, alt) switch { PostRules.RecruiterE.Vanilla => null, PostRules.RecruiterE.Nothing => false, _ => HireAtPost(user), }; } private bool HireAtPost(Humanoid user) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((user is Player) ? user : null); PostKind kind = Kind; if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer || kind == null || string.IsNullOrEmpty(s_coinName)) { return false; } GameObject val2 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(kind.Prefab) : null); if ((Object)(object)val2 == (Object)null) { return false; } int postStars = PostStars; int num = PostRules.PostPrice(kind.Price, postStars + 1); Inventory inventory = ((Humanoid)val).GetInventory(); int num2 = inventory.CountItems(s_coinName, -1, true); switch (HireRules.Check(!m_character.IsDead(), tamed: false, provoked: false, num2, num)) { case HireRules.Hire.NotHireable: return false; case HireRules.Hire.NotEnoughCoins: ((Character)val).Message((MessageType)2, HireRules.NotEnoughCoins(num), 0, (Sprite)null, false); return true; default: { inventory.RemoveItem(s_coinName, num, -1, true); int num3 = inventory.CountItems(s_coinName, -1, true); if (num3 != num2 - num) { Plugin.Log.LogWarning((object)$"Hire stopped: paying {num} of {num2} coins left {num3}"); return true; } Vector3 val3 = HireSpot(val); Vector3 val4 = Vector3.ProjectOnPlane(((Component)val).transform.position - val3, Vector3.up); GameObject val5 = Object.Instantiate<GameObject>(val2, val3, (((Vector3)(ref val4)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(val4) : Quaternion.identity); val5.GetComponent<ZNetView>().GetZDO().Set(s_fromPostHash, true); Mercenary component = val5.GetComponent<Mercenary>(); if ((Object)(object)component != (Object)null) { component.m_hiredAt = Time.unscaledTimeAsDouble; } Character component2 = val5.GetComponent<Character>(); Tameable component3 = val5.GetComponent<Tameable>(); component2.SetLevel(postStars + 1); component2.SetTamed(true); component3.Command((Humanoid)(object)val, false); EffectList obj = s_hireEffect; if (obj != null) { obj.Create(val3, val5.transform.rotation, (Transform)null, 1f, -1, default(ZDOID)); } ((Character)val).Message((MessageType)2, HireRules.Hired(component3.GetName()), 0, (Sprite)null, false); Plugin.Log.LogInfo((object)$"Hired a {kind.Label} at a hiring post (level {postStars + 1}) for {num} coins"); return true; } } } private static Vector3 HireSpot(Player player) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (s_hireSpotMask == 0) { s_hireSpotMask = LayerMask.GetMask(new string[8] { "Default", "static_solid", "Default_small", "piece", "terrain", "character", "character_net", "vehicle" }); } Transform t = ((Component)player).transform; (float, float)[] hireSpots = PostRules.HireSpots; for (int i = 0; i < hireSpots.Length; i++) { Vector3 val = At(hireSpots[i]); if (!Physics.CheckSphere(val + Vector3.up * 0.6f, 0.45f, s_hireSpotMask)) { return val; } } return At(PostRules.HireSpots[0]); Vector3 At((float Right, float Forward) o) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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) return t.position + t.right * o.Right + t.forward * o.Forward + Vector3.up * 0.5f; } } private bool FindPost() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: 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) if (ZDOMan.instance == null) { return false; } ZDOID uid = m_nview.GetZDO().m_uid; ZDO val = (((ZDOID)(ref m_postId)).IsNone() ? null : ZDOMan.instance.GetZDO(m_postId)); if (val != null && val.GetConnectionZDOID((ConnectionType)3) == uid) { return true; } s_nearby.Clear(); ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(((Component)this).transform.position), new SimulationDistance(1, 0, false), s_nearby, (List<ZDO>)null); ZDO val2 = PostRules.PostOf(s_nearby.Where((ZDO z) => z.GetPrefab() == s_poleHash), (ZDO z) => z.GetConnectionZDOID((ConnectionType)3), uid); m_postId = val2?.m_uid ?? ZDOID.None; return val2 != null; } private bool RecruiterLeaves(bool postFound) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) bool areaReady = postFound || ((Object)(object)ZNetScene.instance != (Object)null && ZNetScene.instance.IsAreaReady(((Component)this).transform.position)); if (!m_postWatch.ShouldLeave(postFound, areaReady, Time.unscaledTimeAsDouble)) { return false; } Plugin.Log.LogInfo((object)"A hiring post is gone: its recruiter left"); m_nview.Destroy(); return true; } } 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; public static bool ShouldBeat(long beatTicks, long nowTicks) { if (beatTicks > 0 && beatTicks <= nowTicks) { return nowTicks - beatTicks >= BeatTicks; } return true; } } internal sealed class TakeoverWatch { public enum Step { Wait, Claim, FoundNoMod } private bool m_watching; private long m_owner; private long m_beat; private double m_since; public Step Decide(bool isOwner, long owner, long beat, double nowSeconds, IDictionary<long, double> noMod) { if (isOwner || owner == 0L) { m_watching = false; return Step.Wait; } if (noMod.TryGetValue(owner, out var value)) { if (nowSeconds - value <= 300.0) { return Step.Claim; } noMod.Remove(owner); } if (!m_watching || owner != m_owner || beat != m_beat) { m_watching = true; m_owner = owner; m_beat = beat; m_since = nowSeconds; return Step.Wait; } if (nowSeconds - m_since <= 30.0) { return Step.Wait; } noMod[owner] = nowSeconds; return Step.FoundNoMod; } } internal sealed class PendingFollow { public enum Step { Wait, Send, Done, GiveUp } private readonly string m_hirer; private readonly double m_start; private long m_sentTo; private bool m_watching; private long m_watchOwner; private long m_watchBeat; public PendingFollow(string hirer, long sentTo, double nowSeconds) { m_hirer = hirer; m_sentTo = sentTo; m_start = nowSeconds; } public Step Decide(string follow, long owner, long beat, bool weOwnIt, double nowSeconds) { if (!string.IsNullOrEmpty(m_hirer) && follow == m_hirer) { return Step.Done; } if (nowSeconds - m_start > 120.0) { return Step.GiveUp; } if (owner == 0L || owner == m_sentTo) { m_watching = false; return Step.Wait; } if (!weOwnIt) { if (!m_watching || owner != m_watchOwner) { m_watching = true; m_watchOwner = owner; m_watchBeat = beat; return Step.Wait; } if (beat == m_watchBeat) { return Step.Wait; } } m_sentTo = owner; m_watching = false; return Step.Send; } } [BepInPlugin("algo7.dvergrforhire", "DvergrForHire", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "algo7.dvergrforhire"; public const string Name = "DvergrForHire"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; private static bool s_hireHooks; private static bool s_buildingHook; private static bool s_summonHook; private static bool s_shotHooks; private static bool s_postHooks; private void Awake() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00a8: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; if (Application.isBatchMode) { Log.LogInfo((object)"DvergrForHire loaded (v1.0.0): dedicated server, nothing to do"); return; } Harmony val = new Harmony("algo7.dvergrforhire"); s_hireHooks = TryPatch(val, typeof(HirePatches), "hire prompt and E hooks"); s_buildingHook = TryPatch(val, typeof(BuildingPatches), "building protection hook"); s_postHooks = TryPatch(val, typeof(PostPatches), "hiring post hooks"); s_summonHook = TryPatch(val, typeof(SummonPatches), "summon marker hooks"); s_shotHooks = TryPatch(val, typeof(ShotPatches), "shot hooks"); SceneManager.sceneLoaded += OnSceneLoaded; Log.LogInfo((object)"DvergrForHire loaded (v1.0.0)"); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { try { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } if (!s_hireHooks) { Log.LogWarning((object)"Hiring is off: the hire prompt / E hooks couldn't be installed (see above)"); return; } string message; bool flag = DvergrSetup.Run(instance, out message); if (flag) { Log.LogInfo((object)message); } else { Log.LogWarning((object)message); } if (flag && s_postHooks) { if (PostSetup.Run(instance, out var message2)) { Log.LogInfo((object)message2); } else { Log.LogWarning((object)message2); } } else if (flag) { Log.LogWarning((object)"Hiring posts are off: the post hooks couldn't be installed (see above)"); } if (!s_buildingHook) { Log.LogWarning((object)"Hired Dvergr can damage buildings: the protection hook couldn't be installed (see above)"); } else if (!s_summonHook) { Log.LogWarning((object)"Hired mages' mistiles can damage buildings: the summon hooks couldn't be installed (see above)"); } if (!s_shotHooks) { Log.LogWarning((object)"A hired Dvergr's shot that lands after it died can hit buildings, players and tames: the shot hooks couldn't be installed (see above)"); } } catch (Exception arg) { Log.LogError((object)$"Dvergr setup failed part-way (restart the game if hiring misbehaves): {arg}"); } } private static bool TryPatch(Harmony harmony, Type patches, string what) { try { harmony.PatchAll(patches); return true; } catch (Exception arg) { Log.LogError((object)$"Could not install the {what}: {arg}"); return false; } } } [HarmonyPatch] internal static class PostPatches { private static readonly int s_postHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_Post"); private static readonly int s_fromPostHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_FromPost"); private static bool s_errorLogged; private static PostStandIn s_placing; private static ZNetView s_placed; [HarmonyPatch(typeof(Player), "PlacePiece")] [HarmonyPrefix] private static bool PlacePiece(Player __instance, Piece piece, Vector3 pos, Quaternion rot, bool doAttack, bool cheated) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) PostStandIn postStandIn; try { postStandIn = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent<PostStandIn>() : null); if ((Object)(object)postStandIn == (Object)null) { return true; } if ((Object)(object)PostSetup.s_pole == (Object)null) { Plugin.Log.LogWarning((object)"A hiring post can't be placed: the posts weren't set up (see the log above)"); return false; } } catch (Exception e) { LogOnce(e); return true; } try { s_placing = postStandIn; s_placed = null; __instance.PlacePiece(PostSetup.s_pole, pos, rot, doAttack, cheated); if ((Object)(object)s_placed != (Object)null) { Mercenary.CreateRecruiter(s_placed, postStandIn.m_kind, ((Component)__instance).transform.position); } } catch (Exception e2) { LogOnce(e2); } finally { s_placing = null; s_placed = null; } return false; } [HarmonyPatch(typeof(Piece), "SetCreator")] [HarmonyPostfix] private static void SetCreator(Piece __instance) { try { if (!((Object)(object)s_placing == (Object)null)) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { component.GetZDO().Set(s_postHash, s_placing.m_kind); s_placed = component; } } } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] [HarmonyPostfix] private static void GenerateDropList(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result) { try { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid() && !PostRules.DropsLoot(component.GetZDO().GetBool(s_fromPostHash, false))) { __result = new List<KeyValuePair<GameObject, int>>(); } } catch (Exception e) { LogOnce(e); } } private static void LogOnce(Exception e) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"Placing a hiring post failed (logged once): {e}"); } } } internal static class PostRules { public enum RecruiterE { Vanilla, Nothing, Hire } public const int MaxStars = 2; public static readonly (float Right, float Forward)[] HireSpots = new(float, float)[4] { (0f, 2f), (1.5f, 0f), (-1.5f, 0f), (0f, -1.5f) }; public static int PostPrice(int basePrice, int level) { return (HireRules.Price(basePrice, level) * 120 + 50) / 100; } public static bool DropsLoot(bool madeByPost) { return !madeByPost; } public static int NextStars(int stars) { if (stars >= 0 && stars < 2) { return stars + 1; } return 0; } public static string StarsText(int stars) { if (stars > 0) { return new string('★', Math.Min(stars, 2)); } return "none"; } public static string EntryName(string label) { return "Hiring post: " + label; } public static string EntryDescription(string label) { return "A Dvergr " + label + " recruiter stays here. Pick the stars on the post, hire at the recruiter."; } public static string PostHover(string label, int stars) { return EntryName(label) + "\n" + HireRules.UseLine("Stars for the next hire: " + StarsText(stars)); } public static string RecruiterName(string label) { return char.ToUpperInvariant(label[0]) + label.Substring(1) + " recruiter"; } public static string RecruiterHover(string label, string one, int price, string vanilla) { return RecruiterName(label) + "\n" + HireRules.UseLine("Hire " + one + ": " + price.ToString(CultureInfo.InvariantCulture) + " coins") + HireRules.RenameLines(vanilla); } public static bool PoleBreaks(bool linked, bool recruiterFound, bool areaReady, PostWatch watch, double nowSeconds) { if (linked) { return watch.ShouldLeave(recruiterFound, areaReady, nowSeconds); } return false; } public static TPole PostOf<TPole, TId>(IEnumerable<TPole> poles, Func<TPole, TId> linkedTo, TId recruiter) { foreach (TPole pole in poles) { if (EqualityComparer<TId>.Default.Equals(linkedTo(pole), recruiter)) { return pole; } } return default(TPole); } public static RecruiterE RecruiterInteract(bool hold, bool alt) { if (!hold) { if (!alt) { return RecruiterE.Hire; } return RecruiterE.Vanilla; } return RecruiterE.Nothing; } } internal sealed class PostWatch { private double m_missingSince = -1.0; public bool ShouldLeave(bool found, bool areaReady, double nowSeconds) { if (found || !areaReady) { m_missingSince = -1.0; return false; } if (m_missingSince < 0.0) { m_missingSince = nowSeconds; return false; } return nowSeconds - m_missingSince >= 10.0; } public void Reset() { m_missingSince = -1.0; } } internal sealed class PostKind { public readonly string Id; public readonly string Label; public readonly string One; public readonly string Prefab; public readonly int Price; public readonly (string Item, int Amount)[] Fee; public PostKind(string id, string label, string one, string prefab, int price, params (string Item, int Amount)[] fee) { Id = id; Label = label; One = one; Prefab = prefab; Price = price; Fee = fee; } } internal static class PostSettings { public const string Pole = "piece_dvergr_lantern_pole"; public const UsageTagFlags Section = (UsageTagFlags)32768; public const string Hammer = "Hammer"; public const string PostKey = "DvergrForHire_Post"; public const string RecruiterKey = "DvergrForHire_Recruiter"; public const string FromPostKey = "DvergrForHire_FromPost"; public const int PremiumPercent = 20; public const float RecruiterOffset = 1.5f; public const float HireOffset = 2f; public const double PostGoneSeconds = 10.0; private static readonly (string Item, int Amount)[] Mistlands = new(string, int)[2] { ("YggdrasilWood", 10), ("BlackMarble", 5) }; public static readonly PostKind[] Kinds = new PostKind[6] { new PostKind("rogue", "rogue", "a rogue", "Dverger", 100, Mistlands), new PostKind("firemage", "fire mage", "a fire mage", "DvergerMageFire", 100, Mistlands), new PostKind("icemage", "ice mage", "an ice mage", "DvergerMageIce", 100, Mistlands), new PostKind("supportmage", "support mage", "a support mage", "DvergerMageSupport", 100, Mistlands), new PostKind("ashlands", "Ashlands", "an Ashlands Dvergr", "DvergerAshlands", 200, ("Blackwood", 10), ("Grausten", 5)), new PostKind("deepnorth", "Deep North", "a Deep North Dvergr", "DvergerDeepNorth", 400, ("Frostwood", 10), ("NornThread", 5)) }; public static IEnumerable<string> FeeItems => Kinds.SelectMany((PostKind k) => k.Fee.Select(((string Item, int Amount) f) => f.Item)).Distinct(); public static PostKind ById(string id) { return Kinds.FirstOrDefault((PostKind k) => k.Id == id); } public static PostKind ByPrefab(string prefab) { return Kinds.FirstOrDefault((PostKind k) => k.Prefab == prefab); } } public sealed class PostStandIn : MonoBehaviour { public string m_kind; } internal static class PostSetup { internal static Piece s_pole; private static GameObject s_holder; private static readonly List<GameObject> s_standIns = new List<GameObject>(); public static bool Run(ZNetScene scene, out string message) { GameObject pole; PieceTable table; string text = PreFlight(scene, out pole, out table); if (text != null) { message = "Hiring posts are off: " + text; return false; } s_pole = pole.GetComponent<Piece>(); if ((Object)(object)s_holder == (Object)null) { MakeStandIns(pole); } if ((Object)(object)pole.GetComponent<HiringPost>() == (Object)null) { pole.AddComponent<HiringPost>(); } foreach (GameObject s_standIn in s_standIns) { if (!table.m_pieces.Contains(s_standIn)) { table.m_pieces.Add(s_standIn); } } message = "Hiring posts in the hammer: " + string.Join(", ", PostSettings.Kinds.Select((PostKind k) => k.Label)); return true; } private static string PreFlight(ZNetScene scene, out GameObject pole, out PieceTable table) { table = null; pole = scene.GetPrefab("piece_dvergr_lantern_pole"); if ((Object)(object)pole == (Object)null || (Object)(object)pole.GetComponent<Piece>() == (Object)null) { return "the game has no 'piece_dvergr_lantern_pole' piece (game update?)"; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return "the item database isn't ready"; } GameObject itemPrefab = instance.GetItemPrefab("Hammer"); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); table = (((Object)(object)val != (Object)null) ? val.m_itemData.m_shared.m_buildPieces : null); if ((Object)(object)table == (Object)null) { return "the hammer has no build list (game update?)"; } foreach (string feeItem in PostSettings.FeeItems) { GameObject itemPrefab2 = instance.GetItemPrefab(feeItem); if ((Object)(object)itemPrefab2 == (Object)null || (Object)(object)itemPrefab2.GetComponent<ItemDrop>() == (Object)null) { return "the game has no item '" + feeItem + "' (game update?)"; } } PostKind[] kinds = PostSettings.Kinds; foreach (PostKind postKind in kinds) { GameObject prefab = scene.GetPrefab(postKind.Prefab); if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent<Mercenary>() == (Object)null) { return postKind.Prefab + " can't be hired (see the Dvergr setup above)"; } } return null; } private static void MakeStandIns(GameObject pole) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) s_holder = new GameObject("DvergrForHire_PostStandIns"); s_holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)s_holder); ObjectDB db = ObjectDB.instance; PostKind[] kinds = PostSettings.Kinds; foreach (PostKind postKind in kinds) { GameObject val = Object.Instantiate<GameObject>(pole, s_holder.transform); ((Object)val).name = "DvergrForHire_post_" + postKind.Id; Piece component = val.GetComponent<Piece>(); component.m_name = PostRules.EntryName(postKind.Label); component.m_description = PostRules.EntryDescription(postKind.Label); component.m_usage = (UsageTagFlags)32768; component.m_resources = component.m_resources.Concat(((IEnumerable<(string, int)>)postKind.Fee).Select((Func<(string, int), Requirement>)(((string Item, int Amount) f) => new Requirement { m_resItem = db.GetItemPrefab(f.Item).GetComponent<ItemDrop>(), m_amount = f.Amount, m_recover = false }))).ToArray(); val.AddComponent<PostStandIn>().m_kind = postKind.Id; s_standIns.Add(val); } } } 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)); } } public static EffectList Effects(IEnumerable<string> names, Func<string, GameObject> prefab) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown return new EffectList { m_effectPrefabs = names.Select((Func<string, EffectData>)((string n) => new EffectData { m_prefab = prefab(n) })).ToArray() }; } private static object Resolve(FieldSetting setting, Func<string, GameObject> prefab) { if (setting.Kind != SettingKind.Effects) { return setting.Value; } return Effects(setting.PrefabNames, prefab); } } public sealed class HiredShot : MonoBehaviour { internal string OwnerName; } [HarmonyPatch] internal static class ShotPatches { private static bool s_errorLogged; private static HiredShot s_spawningFor; internal static bool HiredShotHitting { get; private set; } [HarmonyPatch(typeof(Projectile), "Setup")] [HarmonyPostfix] private static void ProjectileSetup(Projectile __instance, Character owner) { Mark((Component)(object)__instance, owner); } [HarmonyPatch(typeof(Aoe), "Setup")] [HarmonyPostfix] private static void AoeSetup(Aoe __instance, Character owner) { Mark((Component)(object)__instance, owner); } private static void Mark(Component shot, Character owner) { try { string ownerName; if (Mercenary.IsHired(owner) || Mercenary.IsHiredSummon(owner)) { ownerName = owner.m_name; } else { if (!((Object)(object)s_spawningFor != (Object)null)) { return; } ownerName = s_spawningFor.OwnerName; } shot.gameObject.AddComponent<HiredShot>().OwnerName = ownerName; } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(Projectile), "OnHit")] [HarmonyPrefix] private static void OnHit(Projectile __instance, out bool __state) { StartHit(__instance, out __state); } [HarmonyPatch(typeof(Projectile), "OnHit")] [HarmonyFinalizer] private static void OnHitDone(bool __state) { HiredShotHitting = __state; } [HarmonyPatch(typeof(Projectile), "DoAOE")] [HarmonyPrefix] private static void DoAOE(Projectile __instance, out bool __state) { StartHit(__instance, out __state); } [HarmonyPatch(typeof(Projectile), "DoAOE")] [HarmonyFinalizer] private static void DoAOEDone(bool __state) { HiredShotHitting = __state; } [HarmonyPatch(typeof(Projectile), "SpawnOnHit")] [HarmonyPrefix] private static void SpawnOnHit(Projectile __instance, out HiredShot __state) { __state = s_spawningFor; try { s_spawningFor = ((Component)__instance).GetComponent<HiredShot>(); } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(Projectile), "SpawnOnHit")] [HarmonyFinalizer] private static void SpawnOnHitDone(HiredShot __state) { s_spawningFor = __state; } private static void StartHit(Projectile projectile, out bool previous) { previous = HiredShotHitting; try { HiredShotHitting = previous || (Object)(object)((Component)projectile).GetComponent<HiredShot>() != (Object)null; } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(Projectile), "IsValidTarget")] [HarmonyPostfix] private static void IsValidTarget(Projectile __instance, IDestructible destr, Character ___m_owner, ref bool __result) { try { if (__result && !((Object)(object)___m_owner != (Object)null)) { Character val = (Character)(object)((destr is Character) ? destr : null); if (val != null && !((Object)(object)((Component)__instance).GetComponent<HiredShot>() == (Object)null)) { __result = HireRules.ShotHits(Foe(val), __instance.m_hitFriendly, hitsEnemies: true, hitsSameKind: true, sameKind: false); } } } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(Aoe), "ShouldHit")] [HarmonyPostfix] private static void ShouldHit(Aoe __instance, Collider collider, Character ___m_owner, ref bool __result) { try { if (!__result || (Object)(object)___m_owner != (Object)null) { return; } HiredShot component = ((Component)__instance).GetComponent<HiredShot>(); if (!((Object)(object)component == (Object)null)) { GameObject val = Projectile.FindHitObject(collider); Character val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Character>() : null); if (!((Object)(object)val2 == (Object)null)) { __result = HireRules.ShotHits(Foe(val2), __instance.m_hitFriendly, __instance.m_hitEnemy, __instance.m_hitSame, val2.m_name == component.OwnerName); } } } catch (Exception e) { LogOnce(e); } } private static bool Foe(Character target) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 BaseAI baseAI = target.GetBaseAI(); return HireRules.HiredDvergrFoe((int)target.GetFaction() == 0, target.IsTamed(), (int)target.GetFaction() == 10, (Object)(object)baseAI != (Object)null && baseAI.IsAggravated()); } private static void LogOnce(Exception e) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"Hired Dvergr's shot handling failed (logged once): {e}"); } } } [HarmonyPatch] internal static class SummonPatches { private static readonly int s_summonHash = StringExtensionMethods.GetStableHashCode("DvergrForHire_HiredSummon"); private static FieldInfo s_ownerField; private static bool s_errorLogged; private static SpawnAbility s_spawner; private static bool s_spawnerHired; private static int s_frame = -1; [HarmonyPatch(typeof(SpawnAbility), "FindTarget")] [HarmonyPrefix] private static void FindTarget(SpawnAbility __instance) { try { if (s_ownerField == null) { s_ownerField = AccessTools.Field(typeof(SpawnAbility), "m_owner"); } s_spawner = __instance; object? obj = s_ownerField?.GetValue(__instance); s_spawnerHired = Mercenary.IsHired((Character)((obj is Character) ? obj : null)); s_frame = Time.frameCount; } catch (Exception e) { LogOnce(e); } } [HarmonyPatch(typeof(Character), "Awake")] [HarmonyPostfix] private static void CharacterAwake(Character __instance) { try { if ((Object)(object)s_spawner == (Object)null) { return; } if (Time.frameCount != s_frame) { s_spawner = null; return; } string name = Utils.GetPrefabName(((Component)__instance).gameObject); bool isItsSummonPrefab = s_spawner.m_spawnPrefab != null && Array.Exists(s_spawner.m_spawnPrefab, (GameObject p) => (Object)(object)p != (Object)null && ((Object)p).name == name); if (HireRules.MarkSummon(s_spawnerHired, sameFrame: true, isItsSummonPrefab)) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { component.GetZDO().Set(s_summonHash, true); } } } catch (Exception e) { LogOnce(e); } } private static void LogOnce(Exception e) { if (!s_errorLogged) { s_errorLogged = true; Plugin.Log.LogError((object)$"Marking a hired Dvergr's summon failed (logged once): {e}"); } } } internal static class PluginInfo { public const string Version = "1.0.0"; } }