using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JG224.ModCore.API; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using MagicaCloth2; using Splatform; using UnityEngine; using UnityEngine.UI; using Valheim.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("DynamicNPCs")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.2.0")] [assembly: AssemblyInformationalVersion("0.5.2")] [assembly: AssemblyProduct("DynamicNPCs")] [assembly: AssemblyTitle("DynamicNPCs")] [assembly: AssemblyVersion("0.5.2.0")] namespace MercenaryCompanions; internal static class AtomicFile { private static readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); internal static void WriteAllText(string path, string contents, Encoding encoding = null) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException("A state-file path is required.", "path"); } string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } string text = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { File.WriteAllText(text, contents ?? "", encoding ?? DefaultEncoding); if (File.Exists(path)) { File.Replace(text, path, path + ".bak", ignoreMetadataErrors: true); } else { File.Move(text, path); } } finally { try { if (File.Exists(text)) { File.Delete(text); } } catch { } } } } internal static class BannerAuthority { private const string ClaimRequestRpc = "DynamicNPCs_BannerClaimRequestV4"; private const string RemoveRequestRpc = "DynamicNPCs_BannerRemoveRequestV4"; private const string MessageReplyRpc = "DynamicNPCs_BannerMessageReplyV4"; private const string SummonRequestRpc = "DynamicNPCs_BannerSummonRequestV1"; private const string UnsummonRequestRpc = "DynamicNPCs_BannerUnsummonRequestV1"; private static readonly FieldInfo ZdoObjectsById = AccessTools.Field(typeof(ZDOMan), "m_objectsByID"); private static readonly List BannerScan = new List(); private static ZRoutedRpc _registeredRpc; private static ZDOMan _scanManager; private static int _scanIndex; private static float _nextScanAt; private static bool _reportedFirstScan; private static readonly Dictionary> MercsByBanner = new Dictionary>(); private static readonly List OrphanedMercs = new List(); private static float _loadedRosterTimer; private const float HomeSummonTeleportRange = 40f; private static readonly HashSet LoginSpawnGranted = new HashSet(); private static bool _warnedRosterScanBlind; private static string _lastRosterSummary = ""; private const float LoginRejoinWindowSeconds = 180f; private static readonly Dictionary OwnerOnlineSince = new Dictionary(); internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { instance.Register("DynamicNPCs_BannerClaimRequestV4", (Action)OnClaimRequest); instance.Register("DynamicNPCs_BannerRemoveRequestV4", (Action)OnRemoveRequest); instance.Register("DynamicNPCs_BannerMessageReplyV4", (Action)OnMessageReply); instance.Register("DynamicNPCs_BannerSummonRequestV1", (Action)OnSummonRequest); instance.Register("DynamicNPCs_BannerUnsummonRequestV1", (Action)OnUnsummonRequest); _registeredRpc = instance; MercPlugin.Log("Registered global dedicated-server banner authority RPCs."); } } internal static void ResetSceneMemory() { LoginSpawnGranted.Clear(); BannerScan.Clear(); _scanManager = null; _scanIndex = 0; _nextScanAt = 0f; _reportedFirstScan = false; MercsByBanner.Clear(); OrphanedMercs.Clear(); OwnerOnlineSince.Clear(); } private static void EnsureLoadedBannerRosters() { _loadedRosterTimer -= Time.deltaTime; if (_loadedRosterTimer > 0f) { return; } _loadedRosterTimer = 1f; try { foreach (MercBannerSpawn instance in MercBannerSpawn.Instances) { if (!((Object)(object)instance == (Object)null)) { ZNetView component = ((Component)instance).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (IsBannerZdo(val)) { ClaimServerOwnership(val); MigrateLegacyClaim(val); AutoClaimPlacedBanner(val); EnsureRoster(val); } } } } catch (Exception ex) { MercPlugin.LogWarn("Loaded-banner roster pass failed: " + ex.Message); } } internal static void Update() { Register(); if (!IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { return; } try { EnsureLoadedBannerRosters(); if (_scanManager != ZDOMan.instance) { _scanManager = ZDOMan.instance; BannerScan.Clear(); _scanIndex = 0; _nextScanAt = 0f; _reportedFirstScan = false; } if ((_scanIndex == 0 && BannerScan.Count == 0 && Time.unscaledTime < _nextScanAt) || !_scanManager.GetAllZDOsWithPrefabIterative("MercBanner", BannerScan, ref _scanIndex)) { return; } ZDO[] array = BannerScan.ToArray(); BannerScan.Clear(); _scanIndex = 0; _nextScanAt = Time.unscaledTime + 2f; if (!_reportedFirstScan) { _reportedFirstScan = true; MercPlugin.Log($"Global banner authority discovered {array.Length} persistent banner ZDO(s)."); } CollectMercenariesBucketed(); LogRosterSummary(array.Length); ZDO[] array2 = array; foreach (ZDO val in array2) { if (IsBannerZdo(val)) { ClaimServerOwnership(val); MigrateLegacyClaim(val); AutoClaimPlacedBanner(val); EnsureRoster(val); } } TrackOwnerSessions(); GrantLoginSpawns(array); EnforceOneBannerPerPlayer(array); RemoveOrphanedMercenaries(); } catch (Exception ex) { MercPlugin.LogWarn("Global banner authority update failed: " + ex); BannerScan.Clear(); _scanIndex = 0; _nextScanAt = Time.unscaledTime + 2f; } } internal static void RequestClaim(ZDOID bannerId, int progressionSeed) { //IL_0005: 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_0053: Unknown result type (might be due to invalid IL or missing references) LogClientRequest("claim", bannerId); Register(); if (ZRoutedRpc.instance == null) { MercPlugin.LogWarn($"Banner claim request not sent for {bannerId}: ZRoutedRpc.instance is null."); return; } if (((ZDOID)(ref bannerId)).IsNone()) { MercPlugin.LogWarn("Banner claim request not sent: banner ZDOID is None."); return; } ZRoutedRpc.instance.InvokeRoutedRPC("DynamicNPCs_BannerClaimRequestV4", new object[2] { bannerId, progressionSeed }); } internal static void RequestRemoval(ZDOID bannerId, int clientToolHash) { //IL_0005: 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_0053: Unknown result type (might be due to invalid IL or missing references) LogClientRequest("removal", bannerId); Register(); if (ZRoutedRpc.instance == null) { MercPlugin.LogWarn($"Banner removal request not sent for {bannerId}: ZRoutedRpc.instance is null."); return; } if (((ZDOID)(ref bannerId)).IsNone()) { MercPlugin.LogWarn("Banner removal request not sent: banner ZDOID is None."); return; } ZRoutedRpc.instance.InvokeRoutedRPC("DynamicNPCs_BannerRemoveRequestV4", new object[2] { bannerId, clientToolHash }); } private static void AutoClaimPlacedBanner(ZDO banner) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) try { if (!IsServer() || !IsBannerZdo(banner)) { return; } bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if ((flag && !WorldDataService.IsWorldStateReady) || banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) != 0L) { return; } long num = banner.GetLong(ZDOVars.s_creator, 0L); if (num == 0L) { return; } int num2 = ServerAuthority.EnsureAuthoritativePlayerStage(num, "banner placement server fallback"); if (!flag || num2 >= 0) { string text = null; if (ServerAuthority.TryFindConnectedPeerByPlayerId(num, out var _, out var playerName) && !string.IsNullOrWhiteSpace(playerName)) { text = playerName; } if (string.IsNullOrEmpty(text)) { text = "player " + num; } banner.Set(MercBannerSpawn.ClaimOwnerIdKey, num); banner.Set(MercBannerSpawn.ClaimOwnerNameKey, text); for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { banner.Set(Mercenary.BannerEmployerIds[i], num); banner.Set(Mercenary.BannerEmployerNames[i], text); banner.Set(Mercenary.BannerFollowNames[i], ""); banner.Set(MercBannerSpawn.RecruitedKeys[i], true); } MercPlugin.Log($"Banner {banner.m_uid} auto-claimed for {text} on placement."); } } catch (Exception ex) { MercPlugin.LogWarn("Banner auto-claim failed: " + ex.Message); } } private static void OnClaimRequest(long sender, ZDOID bannerId, int progressionSeed) { //IL_000b: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) MercPlugin.Log($"Banner claim request received: sender={sender}, banner={bannerId}."); if (!RequireServerHandler("claim", sender, bannerId) || !TryValidateNearby("claim", sender, bannerId, 8f, out var requester, out var banner)) { return; } bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (flag && !WorldDataService.IsWorldStateReady) { Reject(sender, "claim", bannerId, "the current world's progression store is not ready", MercLocalization.Phrase("dnpc_banner_loading")); return; } ClaimServerOwnership(banner); MigrateLegacyClaim(banner); long num = banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); if (num != 0L && num != requester.PlayerId) { Reject(sender, "claim", bannerId, $"banner belongs to player ID {num}, not requester {requester.PlayerId}", MercLocalization.Phrase("dnpc_message_banner_already_claimed")); return; } int num2 = ServerAuthority.EnsureAuthoritativeRequesterStage(requester, "banner claim server fallback"); if (flag && num2 < 0) { Reject(sender, "claim", bannerId, "the authoritative progression stage could not be persisted", MercLocalization.Phrase("dnpc_banner_loading")); return; } if (num == 0L) { banner.Set(MercBannerSpawn.ClaimOwnerIdKey, requester.PlayerId); banner.Set(MercBannerSpawn.ClaimOwnerNameKey, requester.PlayerName); for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { if (string.IsNullOrWhiteSpace(banner.GetString(MercBannerSpawn.CustomNameKeys[i], ""))) { banner.Set(MercBannerSpawn.CustomNameKeys[i], MercBannerSpawn.DefaultFirstName(MercBannerSpawn.Classes[i])); } banner.Set(Mercenary.BannerEmployerIds[i], 0L); banner.Set(Mercenary.BannerEmployerNames[i], ""); banner.Set(Mercenary.BannerFollowNames[i], ""); banner.Set(MercBannerSpawn.RecruitedKeys[i], false); } MercPlugin.Log($"{requester.PlayerName} claimed banner ZDO {bannerId} through global authority."); } for (int j = 0; j < MercBannerSpawn.Classes.Length; j++) { banner.Set(Mercenary.BannerEmployerIds[j], requester.PlayerId); banner.Set(Mercenary.BannerEmployerNames[j], requester.PlayerName); banner.Set(Mercenary.BannerFollowNames[j], ""); banner.Set(MercBannerSpawn.RecruitedKeys[j], true); } EnsureRoster(banner); MercPlugin.Log($"Banner claim request accepted: sender={sender}, banner={bannerId}, " + $"owner={requester.PlayerName}, clientStageHint={progressionSeed}, " + $"authoritativeStage={num2}."); SendMessage(sender, MercLocalization.Phrase("dnpc_message_company_arrived")); } private static void OnRemoveRequest(long sender, ZDOID bannerId, int clientToolHash) { //IL_000b: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0120: 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) MercPlugin.Log($"Banner removal request received: sender={sender}, banner={bannerId}."); if (!RequireServerHandler("removal", sender, bannerId) || !TryValidateNearby("removal", sender, bannerId, 10f, out var requester, out var banner)) { return; } if (banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) != requester.PlayerId && !ServerAuthority.IsSenderAdmin(sender)) { Reject(sender, "removal", bannerId, "sender is neither the banner owner nor a server admin", MercLocalization.Phrase("dnpc_message_banner_remove_owner")); return; } int stableHashCode = StringExtensionMethods.GetStableHashCode("Hammer"); int stableHashCode2 = StringExtensionMethods.GetStableHashCode("MercHammer"); int stableHashCode3 = StringExtensionMethods.GetStableHashCode("Mercenary Hammer"); if (clientToolHash != stableHashCode && clientToolHash != stableHashCode2 && clientToolHash != stableHashCode3) { Reject(sender, "removal", bannerId, $"remove-mode tool marker {clientToolHash} did not match a build hammer ({stableHashCode})", MercLocalization.Phrase("dnpc_message_banner_remove_hammer")); return; } string playerName = requester.PlayerName; RemoveBannerAndRoster(banner, "dismantled by " + playerName); SendMessage(sender, MercLocalization.Phrase("dnpc_message_banner_dismantled")); MercPlugin.Log($"Banner removal request accepted: sender={sender}, banner={bannerId}, admin={playerName}."); } internal static void RequestSummon(ZDOID bannerId) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) Register(); if (ZRoutedRpc.instance != null && !((ZDOID)(ref bannerId)).IsNone()) { ZRoutedRpc.instance.InvokeRoutedRPC(ServerAuthority.GetServerPeerUid(), "DynamicNPCs_BannerSummonRequestV1", new object[1] { bannerId }); } } internal static void RequestUnsummon(ZDOID bannerId) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) Register(); if (ZRoutedRpc.instance != null && !((ZDOID)(ref bannerId)).IsNone()) { ZRoutedRpc.instance.InvokeRoutedRPC(ServerAuthority.GetServerPeerUid(), "DynamicNPCs_BannerUnsummonRequestV1", new object[1] { bannerId }); } } private static void OnUnsummonRequest(long sender, ZDOID bannerId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: 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) if (!RequireServerHandler("unsummon", sender, bannerId) || !TryValidateNearby("unsummon", sender, bannerId, 10f, out var requester, out var banner)) { return; } long num = banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); if (num == 0L || num != requester.PlayerId) { Reject(sender, "unsummon", bannerId, "banner is not owned by the requester", MercLocalization.Phrase("dnpc_message_banner_not_yours")); return; } ClaimServerOwnership(banner); banner.Set(MercBannerSpawn.CompanyStabledKey, 1, false); int num2 = 0; for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { ZDOID zDOID = banner.GetZDOID(MercBannerSpawn.MemberIdKeys[i]); ZDO val = ((!((ZDOID)(ref zDOID)).IsNone() && ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(zDOID) : null); if (IsLivingMercZdo(val)) { ServerAuthority.MarkDeliberateRemoval(val.m_uid); DestroyWorldZdo(val, "company dismissed at banner"); num2++; } banner.RemoveZDOID(MercBannerSpawn.MemberIdKeys[i]); } MercPlugin.Log($"Company dismissed at banner {bannerId} by {requester.PlayerName}: " + $"{num2} member(s) removed."); } private static void OnSummonRequest(long sender, ZDOID bannerId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) if (!RequireServerHandler("summon", sender, bannerId) || !TryValidateNearby("summon", sender, bannerId, 10f, out var requester, out var banner)) { return; } long num = banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); if (num == 0L || num != requester.PlayerId) { Reject(sender, "summon", bannerId, "banner is not claimed by the requester", MercLocalization.Phrase("dnpc_message_banner_claim_before_gather")); return; } ClaimServerOwnership(banner); if (banner.GetInt(MercBannerSpawn.CompanyStabledKey, 0) != 0) { banner.Set(MercBannerSpawn.CompanyStabledKey, 0, false); for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { ZDOID zDOID = banner.GetZDOID(MercBannerSpawn.MemberIdKeys[i]); ZDO val = ((!((ZDOID)(ref zDOID)).IsNone() && ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(zDOID) : null); if (IsLivingMercZdo(val)) { ServerAuthority.MarkDeliberateRemoval(val.m_uid); DestroyWorldZdo(val, "company re-summoned while stale"); } banner.RemoveZDOID(MercBannerSpawn.MemberIdKeys[i]); banner.Set(MercBannerSpawn.LastSeenKeys[i], 0L); } MercPlugin.Log($"Company summoned back at banner {bannerId} for {requester.PlayerName}."); return; } int num2 = 0; for (int j = 0; j < MercBannerSpawn.Classes.Length; j++) { MercClass mercClass = MercBannerSpawn.Classes[j]; ZDOID zDOID2 = banner.GetZDOID(MercBannerSpawn.MemberIdKeys[j]); ZDO val2 = ((!((ZDOID)(ref zDOID2)).IsNone() && ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(zDOID2) : null); if (!IsMercenaryZdo(val2) || !string.IsNullOrEmpty(Mercenary.FollowNameOf(val2))) { continue; } ClaimServerOwnership(val2); val2.Set(Mercenary.ZdoStayDay, 0, false); Vector3 val3 = (Vector3)(mercClass switch { MercClass.Healer => new Vector3(-2f, 0f, 1f), MercClass.Tank => new Vector3(2f, 0f, 1f), _ => new Vector3(0f, 0f, -2f), }); Vector3 val4 = banner.GetPosition() + val3; if (Vector3.Distance(val2.GetPosition(), val4) <= 40f) { continue; } GameObject val5 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(zDOID2) : null); Mercenary mercenary = (((Object)(object)val5 != (Object)null) ? val5.GetComponent() : null); if (((Object)(object)mercenary != (Object)null && (Object)(object)mercenary.Ai != (Object)null && mercenary.Ai.EngagedInCombat) || val2.GetInt(ZDOVars.s_haveTargetHash, 0) != 0) { continue; } try { val2.SetPosition(val4); val2.SetRotation(Quaternion.LookRotation(-val3)); ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(val2.m_uid); } num2++; } catch (Exception ex) { MercPlugin.LogWarn($"Could not summon {mercClass} {zDOID2} home: {ex.Message}"); } } EnsureRoster(banner); SendMessage(sender, (num2 > 0) ? MercLocalization.Phrase("dnpc_message_company_gathering_far", num2) : MercLocalization.Phrase("dnpc_message_company_gathering")); MercPlugin.Log($"Banner summon accepted: sender={sender}, banner={bannerId}, teleported={num2}."); } private static void OnMessageReply(long sender, string message) { if (ServerAuthority.IsAuthoritativeServerSender(sender)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, MercLocalization.Resolve(message), 0, (Sprite)null, false); } } } private static void SendMessage(long target, string message) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(target, "DynamicNPCs_BannerMessageReplyV4", new object[1] { message ?? "" }); } } private static bool IsServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } private static void LogClientRequest(string operation, ZDOID bannerId) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) long num = 0L; try { if ((Object)(object)ZNet.instance != (Object)null) { num = ZNet.GetUID(); } } catch (Exception ex) { MercPlugin.LogWarn("Could not read local routed peer ID for banner " + operation + ": " + ex.Message); } MercPlugin.Log($"Banner client request: operation={operation}, banner={bannerId}, localPeer={num}, routedRpcExists={ZRoutedRpc.instance != null}."); } private static bool RequireServerHandler(string operation, long sender, ZDOID bannerId) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (IsServer()) { return true; } MercPlugin.LogWarn($"Banner {operation} request rejected on a non-server peer: sender={sender}, banner={bannerId}."); return false; } private static bool TryValidateNearby(string operation, long sender, ZDOID bannerId, float range, out ServerAuthority.SenderPlayerState requester, out ZDO banner) { //IL_0015: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0157: 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_015d: 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_0130: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) bool num = ServerAuthority.TryResolveSender(sender, out requester); banner = null; if (!num || requester == null) { return Reject(sender, operation, bannerId, "sender player was not found on the server", MercLocalization.Phrase("dnpc_message_banner_player_missing")); } if (requester.PlayerId == 0L) { return Reject(sender, operation, bannerId, $"character ZDO {requester.CharacterZdoId} has no persistent player ID", MercLocalization.Phrase("dnpc_message_banner_identity_not_ready")); } if (ZDOMan.instance == null || ((ZDOID)(ref bannerId)).IsNone()) { return Reject(sender, operation, bannerId, (ZDOMan.instance == null) ? "ZDOMan is unavailable" : "banner ZDOID is None", MercLocalization.Phrase("dnpc_message_banner_record_missing")); } banner = ZDOMan.instance.GetZDO(bannerId); if (banner == null || !banner.IsValid()) { return Reject(sender, operation, bannerId, "banner ZDO was not found or is invalid", MercLocalization.Phrase("dnpc_message_banner_record_missing")); } int stableHashCode = StringExtensionMethods.GetStableHashCode("MercBanner"); int prefab = banner.GetPrefab(); if (prefab != stableHashCode) { return Reject(sender, operation, bannerId, $"wrong prefab hash {prefab}; expected {stableHashCode}", MercLocalization.Phrase("dnpc_message_banner_wrong_object")); } if (requester.IsDead) { return Reject(sender, operation, bannerId, "requesting player is dead", MercLocalization.Phrase("dnpc_message_banner_while_dead")); } Vector3 position = requester.Position; Vector3 position2 = banner.GetPosition(); float num2 = Vector3.Distance(position, position2); if (num2 > range) { return Reject(sender, operation, bannerId, $"distance {num2:F2}m exceeds {range:F2}m; player={position}, banner={position2}", MercLocalization.Phrase("dnpc_message_banner_move_within", range.ToString("F0"))); } return true; } private static bool Reject(long sender, string operation, ZDOID bannerId, string reason, string playerMessage) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) MercPlugin.LogWarn($"Banner {operation} request rejected: sender={sender}, banner={bannerId}, reason={reason}."); SendMessage(sender, playerMessage); return false; } private static void LogHammerDiagnostics(ServerAuthority.SenderPlayerState requester, long sender, ZDOID bannerId, int mercHammerHash, int displayNameHash) { //IL_0035: 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) GameObject val = null; try { object obj; if (requester == null) { obj = null; } else { Player livePlayer = requester.LivePlayer; obj = ((livePlayer != null) ? ((Humanoid)livePlayer).GetCurrentWeapon() : null); } val = ((ItemData)(obj?)).m_dropPrefab; } catch (Exception ex) { MercPlugin.LogWarn($"Banner removal hammer diagnostics failed for sender={sender}, banner={bannerId}: {ex.Message}"); } MercPlugin.Log($"Banner removal hammer diagnostics: sender={sender}, banner={bannerId}, " + "currentWeaponDropPrefab=" + (((Object)(object)val != (Object)null) ? ((Object)val).name : "") + ", " + $"playerRightItemHash={requester?.RightItemHash ?? 0}, " + $"MercHammerHash={mercHammerHash}, " + $"MercenaryHammerHash={displayNameHash}."); } private static bool IsBannerZdo(ZDO zdo) { if (zdo != null && zdo.IsValid()) { return zdo.GetPrefab() == StringExtensionMethods.GetStableHashCode("MercBanner"); } return false; } private static void ClaimServerOwnership(ZDO zdo) { if (zdo != null && IsServer() && zdo.GetOwner() != ZNet.GetUID()) { zdo.SetOwner(ZNet.GetUID()); } } private static void MigrateLegacyClaim(ZDO banner) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (!IsBannerZdo(banner) || banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) != 0L) { return; } for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { long num = banner.GetLong(Mercenary.BannerEmployerIds[i], 0L); string text = banner.GetString(Mercenary.BannerEmployerNames[i], ""); if (num == 0L || string.IsNullOrWhiteSpace(text)) { continue; } banner.Set(MercBannerSpawn.ClaimOwnerIdKey, num); banner.Set(MercBannerSpawn.ClaimOwnerNameKey, text); for (int j = 0; j < MercBannerSpawn.Classes.Length; j++) { if (string.IsNullOrWhiteSpace(banner.GetString(MercBannerSpawn.CustomNameKeys[j], ""))) { banner.Set(MercBannerSpawn.CustomNameKeys[j], MercBannerSpawn.DefaultFirstName(MercBannerSpawn.Classes[j])); } } MercPlugin.Log($"Global banner authority migrated legacy claim for {text} at {banner.m_uid}."); break; } } private static void GrantLoginSpawns(ZDO[] banners) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ZNet.instance == (Object)null) { return; } foreach (ZDO val in banners) { if (!IsBannerZdo(val)) { continue; } long num = val.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); if (num != 0L && !LoginSpawnGranted.Contains(num) && val.GetInt(MercBannerSpawn.CompanyStabledKey, 0) == 0 && IsOwnerOnline(num)) { LoginSpawnGranted.Add(num); for (int j = 0; j < MercBannerSpawn.Classes.Length; j++) { val.Set(MercBannerSpawn.LastSeenKeys[j], 0L); } MercPlugin.Log($"Login spawn granted for player {num}'s banner {val.m_uid}: missing mercenaries return immediately."); } } } catch (Exception ex) { MercPlugin.LogWarn("Login spawn grant failed: " + ex.Message); } } private static bool IsOwnerOnline(long playerId) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerID() == playerId) { return true; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) { ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(peer.m_characterID) : null); if (val != null && val.GetLong(ZDOVars.s_playerID, 0L) == playerId) { return true; } } } } catch { } return false; } private static ZDO ChooseSurvivingBanner(ZDO a, ZDO b) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) try { List list = AllZdosFromTable(); int num = 0; int num2 = 0; foreach (ZDO item in list) { if (item == null || !item.IsValid() || !IsMercenaryZdo(item)) { continue; } ZDOID val = Mercenary.BannerIdOf(item); if (IsLivingMercZdo(item)) { if (val == a.m_uid) { num++; } else if (val == b.m_uid) { num2++; } } } if (num != num2) { return (num > num2) ? a : b; } Vector3 val2 = Vector3.zero; bool flag = false; foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null)) { val2 = ((Component)allPlayer).transform.position; flag = true; break; } } if (flag) { return (Vector3.Distance(a.GetPosition(), val2) <= Vector3.Distance(b.GetPosition(), val2)) ? a : b; } } catch (Exception ex) { MercPlugin.LogWarn("Duplicate-banner choice failed: " + ex.Message); } return a; } private static void NotifyOwnerOfDuplicate(long owner, ZDO removed) { try { foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && allPlayer.GetPlayerID() == owner) { ((Character)allPlayer).Message((MessageType)2, MercLocalization.Text("dnpc_message_duplicate_banner"), 0, (Sprite)null, false); break; } } } catch { } } private static void EnforceOneBannerPerPlayer(ZDO[] banners) { try { Dictionary dictionary = new Dictionary(); foreach (ZDO val in banners) { if (!IsBannerZdo(val)) { continue; } long num = val.GetLong(ZDOVars.s_creator, 0L); if (num == 0L) { num = val.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); } if (num != 0L) { if (!dictionary.TryGetValue(num, out var value)) { dictionary[num] = val; continue; } ZDO val2 = ChooseSurvivingBanner(value, val); ZDO val3 = ((val2 == value) ? val : value); dictionary[num] = val2; RemoveBannerAndRoster(val3, $"duplicate Mercenary Banner for player {num}"); NotifyOwnerOfDuplicate(num, val3); } } } catch (Exception ex) { MercPlugin.LogWarn("One-banner-per-player enforcement failed: " + ex.Message); } } private static void EnsureRoster(ZDO banner) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) if (!IsBannerZdo(banner) || (Object)(object)ZNetScene.instance == (Object)null) { return; } bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if ((flag && !MercProgressionRules.CanMutatePersonalProgression(flag, WorldDataService.IsWorldStateReady)) || banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == 0L) { return; } AdoptBeforeRosterCreation(banner); int stage; bool flag2 = TryResolveAuthoritativeCompanyStage(banner, out stage); if (flag2) { SynchronizeProgressionStage(banner, stage); } double timeSeconds = ZNet.instance.GetTimeSeconds(); for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { MercClass mercClass = MercBannerSpawn.Classes[i]; if (!string.IsNullOrWhiteSpace(banner.GetString(Mercenary.BannerEmployerNames[i], ""))) { banner.Set(MercBannerSpawn.RecruitedKeys[i], true); } List list = FindPersistentMercZdos(mercClass, banner.m_uid); ZDOID zDOID = banner.GetZDOID(MercBannerSpawn.MemberIdKeys[i]); ZDO val = ChooseCanonicalMerc(list, zDOID); if (val == null && !((ZDOID)(ref zDOID)).IsNone() && ZDOMan.instance != null) { ZDO zDO = ZDOMan.instance.GetZDO(zDOID); if (IsMercenaryZdo(zDO) && Mercenary.BannerIdOf(zDO) == banner.m_uid) { val = zDO; if (!list.Contains(zDO)) { list.Add(zDO); } } } if (val != null) { banner.Set(MercBannerSpawn.MemberIdKeys[i], val.m_uid); foreach (ZDO item in list) { if (item != null && item.m_uid != val.m_uid) { DestroyWorldZdo(item, $"duplicate {mercClass} for banner {banner.m_uid}"); } } } else { banner.RemoveZDOID(MercBannerSpawn.MemberIdKeys[i]); } if (IsLivingMercZdo(val)) { if (flag2) { SynchronizeProgressionStage(val, stage); } RecoverLostMember(banner, mercClass, val); RejoinFollowedOwner(banner, mercClass, val); long num = Mercenary.EmployerIdOf(val); string text = Mercenary.EmployerNameOf(val); string text2 = Mercenary.FollowNameOf(val); if (val.GetOwner() == ZNet.GetUID()) { Mercenary.RestoreEmploymentFromBanner(val, banner, mercClass); } long num2 = Mercenary.EmployerIdOf(val); string text3 = Mercenary.EmployerNameOf(val); string text4 = Mercenary.FollowNameOf(val); if (num != num2 || text != text3 || text2 != text4) { MercPlugin.Log($"Roster employment repaired: banner={banner.m_uid}, class={mercClass}, " + $"member={val.m_uid}, ownerId={num2}, ownerName={text3}, " + "follow=" + text4 + "."); } banner.Set(MercBannerSpawn.LastSeenKeys[i], (long)timeSeconds); continue; } if (val != null) { AnnounceFallIfUnreported(banner, mercClass, i, val); DestroyWorldZdo(val, $"dead {mercClass} ready for banner respawn"); } banner.RemoveZDOID(MercBannerSpawn.MemberIdKeys[i]); double num3 = banner.GetLong(MercBannerSpawn.LastSeenKeys[i], 0L); if (num3 <= 0.0) { num3 = banner.GetFloat(MercBannerSpawn.LastSeenKeys[i], 0f); } if (banner.GetInt(MercBannerSpawn.CompanyStabledKey, 0) != 0) { continue; } if (val == null && !((ZDOID)(ref zDOID)).IsNone() && num3 > 0.0 && timeSeconds - num3 < 120.0 && ServerAuthority.AnnouncedFallen.Add(zDOID)) { ServerAuthority.AnnounceRosterFall(RosterMemberName(banner, mercClass, i)); MercPlugin.Log($"Roster lost track of {mercClass} {zDOID} without a recorded death; presumed fallen."); } if ((!(num3 > 0.0) || !(timeSeconds - num3 < (double)MercConfig.RespawnDelaySeconds.Value)) && SpawnMerc(banner, mercClass, i, flag2 ? stage : (-1))) { banner.Set(MercBannerSpawn.LastSeenKeys[i], (long)timeSeconds); if (!((ZDOID)(ref zDOID)).IsNone()) { ServerAuthority.AnnouncedFallen.Remove(zDOID); } } } } internal static List AllZdosFromTable() { List list = new List(); if (ZDOMan.instance == null || ZdoObjectsById == null) { return list; } try { if (ZdoObjectsById.GetValue(ZDOMan.instance) is IDictionary dictionary) { list.AddRange(dictionary.Values); } } catch { } return list; } internal static void PublishCompanyStage(long playerId, int stage) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) bool personalModeEnabled = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (!IsServer() || ZDOMan.instance == null || playerId == 0L || !MercProgressionRules.CanMutatePersonalProgression(personalModeEnabled, WorldDataService.IsWorldStateReady)) { return; } try { int num = MercProgressionRules.ResolveStage(stage, -1); List list = AllZdosFromTable(); HashSet hashSet = new HashSet(); int num2 = 0; foreach (ZDO item in list) { if (IsBannerZdo(item) && item.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == playerId) { hashSet.Add(item.m_uid); if (SynchronizeProgressionStage(item, num)) { num2++; } } } if (hashSet.Count > 0) { foreach (ZDO item2 in list) { if (IsMercenaryZdo(item2) && hashSet.Contains(Mercenary.BannerIdOf(item2)) && SynchronizeProgressionStage(item2, num)) { num2++; } } } if (num2 > 0) { MercPlugin.Log($"Published company progression stage {num} for player " + $"{playerId} to {num2} persistent ZDO(s)."); } } catch (Exception ex) { MercPlugin.LogWarn("Could not publish company progression stage: " + ex.Message); } } private static bool TryResolveAuthoritativeCompanyStage(ZDO banner, out int stage) { stage = -1; if (MercConfig.ProgressionPerPlayer == null || !MercConfig.ProgressionPerPlayer.Value) { return false; } long num = banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); if (num == 0L) { return false; } int bannerStage = Mercenary.ProgressionStageOf(banner); if (!WorldDataService.IsWorldStateReady) { return false; } int stage2 = PlayerProgression.GetStage(num); int num2 = MercProgressionRules.SelectDurableStage(stage2, bannerStage, PlayerProgression.WasRecoveredFromBackup); if (num2 >= 0) { if (stage2 < 0 || num2 > stage2) { int num3 = PlayerProgression.EnsureSeeded(num, num2, PlayerProgression.WasRecoveredFromBackup ? "newer banner mirror after atomic-backup recovery" : "recovered banner progression mirror"); if (num3 < num2) { return false; } stage = num3; return true; } stage = num2; return true; } int num4 = ServerAuthority.SeedStageFromLiveGear(num); if (num4 >= 0) { stage = MercProgressionRules.ResolveStage(num4, -1); return true; } stage = PlayerProgression.EnsureSeeded(num, 0, "unobserved legacy company fallback"); return stage >= 0; } private static bool SynchronizeProgressionStage(ZDO zdo, int stage) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) try { if (zdo == null || Mercenary.ProgressionStageOf(zdo) == stage) { return false; } ClaimServerOwnership(zdo); if (!Mercenary.SetProgressionStage(zdo, stage)) { return false; } ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(zdo.m_uid); } return true; } catch (Exception ex) { MercPlugin.LogWarn("Could not synchronize a company progression mirror: " + ex.Message); return false; } } private static void WarnOnceRosterScanBlind(string reason) { if (!_warnedRosterScanBlind) { _warnedRosterScanBlind = true; MercPlugin.LogWarn("Persistent roster scan is blind (" + reason + "); banner adoption and orphan cleanup cannot work this session."); } } private static void LogRosterSummary(int bannerCount) { int num = 0; foreach (KeyValuePair> item in MercsByBanner) { num += item.Value.Count; } string text = $"{num} mercenary ZDO(s) under {MercsByBanner.Count} banner id(s), " + $"{OrphanedMercs.Count} orphan(s), {bannerCount} banner(s)"; if (!(text == _lastRosterSummary)) { _lastRosterSummary = text; MercPlugin.Log("Persistent roster census: " + text + "."); } } private static void CollectMercenariesBucketed() { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) MercsByBanner.Clear(); OrphanedMercs.Clear(); if (ZDOMan.instance == null) { return; } if (ZdoObjectsById == null) { WarnOnceRosterScanBlind("the world ZDO table field was not found"); return; } try { if (!(ZdoObjectsById.GetValue(ZDOMan.instance) is IDictionary dictionary)) { WarnOnceRosterScanBlind("the world ZDO table could not be read"); return; } int stableHashCode = StringExtensionMethods.GetStableHashCode("Merc_Tank"); int stableHashCode2 = StringExtensionMethods.GetStableHashCode("Merc_Healer"); int stableHashCode3 = StringExtensionMethods.GetStableHashCode("Merc_Archer"); foreach (KeyValuePair item in dictionary) { ZDO value = item.Value; if (value == null || !value.IsValid()) { continue; } int prefab = value.GetPrefab(); if (prefab != stableHashCode && prefab != stableHashCode2 && prefab != stableHashCode3) { continue; } ZDOID key = Mercenary.BannerIdOf(value); if (((ZDOID)(ref key)).IsNone()) { if (!value.GetBool(Mercenary.ZdoStandalone, false)) { OrphanedMercs.Add(value); } continue; } if (!MercsByBanner.TryGetValue(key, out var value2)) { value2 = (MercsByBanner[key] = new List()); } value2.Add(value); } } catch (Exception ex) { MercPlugin.LogWarn("Persistent roster scan failed: " + ex.Message); } } private static List FindPersistentMercZdos(MercClass mercClass, ZDOID bannerId) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (MercsByBanner.TryGetValue(bannerId, out var value)) { int stableHashCode = StringExtensionMethods.GetStableHashCode(PrefabName(mercClass)); foreach (ZDO item in value) { if (item != null && item.IsValid() && item.GetPrefab() == stableHashCode) { list.Add(item); } } } return list; } private static ZDO ChooseCanonicalMerc(List candidates, ZDOID trackedId) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) ZDO val = null; int num = int.MinValue; foreach (ZDO candidate in candidates) { if (candidate != null) { int num2 = (IsLivingMercZdo(candidate) ? 10000 : 0); if (!((ZDOID)(ref trackedId)).IsNone() && candidate.m_uid == trackedId) { num2 += 5000; } if (!string.IsNullOrEmpty(Mercenary.EmployerNameOf(candidate))) { num2 += 500; } if (!string.IsNullOrEmpty(Mercenary.FollowNameOf(candidate))) { num2 += 250; } if ((Object)(object)ZNetScene.instance != (Object)null && (Object)(object)ZNetScene.instance.FindInstance(candidate.m_uid) != (Object)null) { num2 += 100; } if (val == null || num2 > num || (num2 == num && ((ZDOID)(ref candidate.m_uid)).CompareTo(val.m_uid) < 0)) { val = candidate; num = num2; } } } return val; } private static void TrackOwnerSessions() { try { bool flag = MercProgressionRules.CanMutatePersonalProgression(MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value, WorldDataService.IsWorldStateReady); HashSet hashSet = new HashSet(); foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null)) { long playerID = allPlayer.GetPlayerID(); if (playerID != 0L) { hashSet.Add(playerID); } } } foreach (long item in hashSet) { if (!OwnerOnlineSince.ContainsKey(item)) { OwnerOnlineSince[item] = Time.time; } if (flag) { ServerAuthority.SeedStageFromLiveGear(item); } } List list = new List(); foreach (KeyValuePair item2 in OwnerOnlineSince) { if (!hashSet.Contains(item2.Key)) { list.Add(item2.Key); } } foreach (long item3 in list) { OwnerOnlineSince.Remove(item3); } } catch { } } internal static void HoldFollowingCompanyOnOwnerDeath(Player owner) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)owner == (Object)null || !IsServer() || ZDOMan.instance == null) { return; } long playerID = owner.GetPlayerID(); if (playerID == 0L) { return; } int num = Mathf.Max(1, WorldDataService.CurrentDay); int num2 = 0; foreach (ZDO item in AllZdosFromTable()) { if (!IsBannerZdo(item) || item.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) != playerID) { continue; } ClaimServerOwnership(item); for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { if (!string.IsNullOrEmpty(item.GetString(Mercenary.BannerFollowNames[i], ""))) { item.Set(Mercenary.BannerFollowNames[i], ""); ZDOID zDOID = item.GetZDOID(MercBannerSpawn.MemberIdKeys[i]); ZDO val = ((!((ZDOID)(ref zDOID)).IsNone()) ? ZDOMan.instance.GetZDO(zDOID) : null); if (IsLivingMercZdo(val)) { ClaimServerOwnership(val); Vector3 position = val.GetPosition(); Mercenary.SetFollowName(val, ""); val.Set(Mercenary.ZdoStayDay, num, false); val.Set(Mercenary.ZdoStayAnchor, position); val.Set(MercAI.ZdoGuideTask, ""); val.Set("merc_guideFind", ""); val.Set("merc_guideRoute", ""); ZDOMan.instance.ForceSendZDO(val.m_uid); GameObject val2 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(val.m_uid) : null); (((Object)(object)val2 != (Object)null) ? val2.GetComponent() : null)?.NotifySimulationOwnerOfStateChange(refillFood: false); num2++; } } } } if (num2 > 0) { MercPlugin.Log($"Owner death hold: {num2} following mercenary(s) kept their " + "current positions and stopped guiding for the respawn flow."); } } private static void RejoinFollowedOwner(ZDO banner, MercClass mercClass, ZDO tracked) { //IL_01a6: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0122: 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_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) try { int num = Mathf.Clamp((int)mercClass, 0, Mercenary.BannerFollowNames.Length - 1); string text = banner.GetString(Mercenary.BannerFollowNames[num], ""); if (string.IsNullOrEmpty(text)) { return; } long num2 = banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); if (num2 != 0L && OwnerOnlineSince.TryGetValue(num2, out var value) && !(Time.time - value > 180f) && TryGetOnlineOwnerPosition(num2, out var position, out var rotation) && (!((Object)(object)ZNetScene.instance != (Object)null) || !((Object)(object)ZNetScene.instance.FindInstance(tracked.m_uid) != (Object)null)) && tracked.GetInt(ZDOVars.s_haveTargetHash, 0) == 0 && !(Vector3.Distance(tracked.GetPosition(), position) <= 60f)) { Vector3 val = (Vector3)(mercClass switch { MercClass.Healer => new Vector3(-2f, 0f, 1f), MercClass.Tank => new Vector3(2f, 0f, 1f), _ => new Vector3(0f, 0f, -2f), }); ClaimServerOwnership(tracked); tracked.SetPosition(position + rotation * val); tracked.SetRotation(rotation); ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(tracked.m_uid); } MercPlugin.Log($"Rejoined owner: {mercClass} {tracked.m_uid}" + (string.IsNullOrEmpty(text) ? "" : (" following '" + text + "'")) + " was stranded far from the online owner; moved to their side."); } } catch (Exception ex) { MercPlugin.LogWarn($"Followed-owner rejoin failed for {tracked.m_uid}: {ex.Message}"); } } private static bool TryGetOnlineOwnerPosition(long playerId, out Vector3 position, out Quaternion rotation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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_00dc: 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) position = Vector3.zero; rotation = Quaternion.identity; try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerID() == playerId) { position = ((Component)localPlayer).transform.position; rotation = ((Component)localPlayer).transform.rotation; return true; } if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return false; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO != null && zDO.IsValid() && zDO.GetLong(ZDOVars.s_playerID, 0L) == playerId) { position = zDO.GetPosition(); rotation = zDO.GetRotation(); return true; } } } } catch { } return false; } private static string RosterMemberName(ZDO banner, MercClass mercClass, int index) { return banner.GetString(MercBannerSpawn.CustomNameKeys[index], MercBannerSpawn.DefaultFirstName(mercClass)) + " " + ServerAuthority.RoleTitle(mercClass); } private static void AnnounceFallIfUnreported(ZDO banner, MercClass mercClass, int index, ZDO tracked) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) try { if (tracked != null && !IsLivingMercZdo(tracked) && ServerAuthority.AnnouncedFallen.Add(tracked.m_uid)) { ServerAuthority.AnnounceRosterFall(RosterMemberName(banner, mercClass, index)); } } catch { } } private static void RecoverLostMember(ZDO banner, MercClass mercClass, ZDO tracked) { //IL_0154: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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) try { if (!string.IsNullOrEmpty(Mercenary.FollowNameOf(tracked))) { return; } int num = tracked.GetInt(Mercenary.ZdoStayDay, 0); int currentDay = WorldDataService.CurrentDay; if ((num > 0 && currentDay > 0 && currentDay - num < 1) || ((Object)(object)ZNetScene.instance != (Object)null && (Object)(object)ZNetScene.instance.FindInstance(tracked.m_uid) != (Object)null)) { return; } Vector3 val = (Vector3)(mercClass switch { MercClass.Healer => new Vector3(-2f, 0f, 1f), MercClass.Tank => new Vector3(2f, 0f, 1f), _ => new Vector3(0f, 0f, -2f), }); Vector3 val2 = banner.GetPosition() + val; if (!(Vector3.Distance(tracked.GetPosition(), val2) <= 40f)) { ClaimServerOwnership(tracked); tracked.SetPosition(val2); tracked.SetRotation(Quaternion.LookRotation(-val)); tracked.Set(Mercenary.ZdoStayDay, 0, false); ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(tracked.m_uid); } MercPlugin.Log($"Recovered lost {mercClass} {tracked.m_uid}: it was not following, " + $"not instantiated, and far from banner {banner.m_uid}; moved home."); } } catch (Exception ex) { MercPlugin.LogWarn($"Lost-member recovery failed for {tracked.m_uid}: {ex.Message}"); } } private static bool IsLivingMercZdo(ZDO zdo) { if (zdo != null && zdo.IsValid() && !zdo.GetBool(ZDOVars.s_dead, false)) { return zdo.GetFloat(ZDOVars.s_health, 1f) > 0f; } return false; } private static bool SpawnMerc(ZDO banner, MercClass mercClass, int classIndex, int progressionStage) { //IL_0069: 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) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) GameObject mercPrefab = MercPrefabs.GetMercPrefab(mercClass); if ((Object)(object)mercPrefab == (Object)null) { MercPlugin.LogWarn($"Global banner authority has no prefab for {mercClass}; it will retry."); return false; } Vector3 val = (Vector3)(mercClass switch { MercClass.Healer => new Vector3(-2f, 0f, 1f), MercClass.Tank => new Vector3(2f, 0f, 1f), _ => new Vector3(0f, 0f, -2f), }); Vector3 val2 = banner.GetRotation() * val; Vector3 val3 = banner.GetPosition() + val2; float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetSolidHeight(val3) : (-10000f)); if (num > -10000f) { val3.y = num + 0.1f; } Quaternion val4 = ((((Vector3)(ref val2)).sqrMagnitude > 0.001f) ? Quaternion.LookRotation(-val2) : Quaternion.identity); GameObject val5 = Object.Instantiate(mercPrefab, val3, val4); ZNetView component = val5.GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { ZDO zDO = component.GetZDO(); ClaimServerOwnership(zDO); zDO.Set(ZDOVars.s_modelIndex, (mercClass == MercClass.Healer) ? 1 : 0, false); Mercenary.SetBannerId(zDO, banner.m_uid); Mercenary.RestoreEmploymentFromBanner(zDO, banner, mercClass); if (progressionStage >= 0) { Mercenary.SetProgressionStage(zDO, progressionStage); } banner.Set(MercBannerSpawn.MemberIdKeys[classIndex], zDO.m_uid); MercPlugin.Log($"Global banner authority spawned {mercClass} {zDO.m_uid} for banner {banner.m_uid}."); MercPlugin.Log($"Employment synchronized: banner={banner.m_uid}, class={mercClass}, " + $"member={zDO.m_uid}, ownerId={Mercenary.EmployerIdOf(zDO)}, " + "ownerName=" + Mercenary.EmployerNameOf(zDO) + ", follow=" + Mercenary.FollowNameOf(zDO) + "."); return true; } Object.Destroy((Object)(object)val5); MercPlugin.LogWarn($"Global banner authority could not create a network ZDO for {mercClass}; it will retry."); return false; } private static void RemoveBannerAndRoster(ZDO banner, string reason) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (!IsBannerZdo(banner) || ZDOMan.instance == null) { return; } ZDOID uid = banner.m_uid; Dictionary dictionary = new Dictionary(); for (int i = 0; i < MercBannerSpawn.Classes.Length; i++) { ZDOID zDOID = banner.GetZDOID(MercBannerSpawn.MemberIdKeys[i]); ZDO val = ((!((ZDOID)(ref zDOID)).IsNone()) ? ZDOMan.instance.GetZDO(zDOID) : null); if (IsMercenaryZdo(val)) { dictionary[val.m_uid] = val; } foreach (ZDO item in FindPersistentMercZdos(MercBannerSpawn.Classes[i], uid)) { dictionary[item.m_uid] = item; } } try { foreach (ZDO item2 in AllZdosFromTable()) { if (item2 != null && item2.IsValid() && IsMercenaryZdo(item2) && Mercenary.BannerIdOf(item2) == uid) { dictionary[item2.m_uid] = item2; } } } catch (Exception ex) { MercPlugin.LogWarn("Banner removal safety sweep failed: " + ex.Message); } foreach (ZDO value in dictionary.Values) { DestroyWorldZdo(value, "its banner was " + reason); } try { MercBannerSpawn.ServerRemovalInProgress = true; DestroyWorldZdo(banner, "banner " + reason); } finally { MercBannerSpawn.ServerRemovalInProgress = false; } MercPlugin.Log($"Global banner authority removed banner {uid} and {dictionary.Count} mercenary ZDO(s): {reason}."); } private static void RemoveOrphanedMercenaries() { //IL_0052: 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) try { List list = new List(OrphanedMercs); if (ZDOMan.instance != null && ZdoObjectsById != null && ZdoObjectsById.GetValue(ZDOMan.instance) is IDictionary) { foreach (KeyValuePair> item in MercsByBanner) { if (!IsBannerZdo(ZDOMan.instance.GetZDO(item.Key))) { list.AddRange(item.Value); } } } int num = 0; int num2 = 0; foreach (ZDO item2 in list) { if (item2 != null && item2.IsValid()) { ZDO val = FindOwnerBannerFor(item2); if (val != null && AdoptIntoBanner(val, item2)) { num++; continue; } DestroyWorldZdo(item2, $"orphaned from missing banner {Mercenary.BannerIdOf(item2)}"); num2++; } } if (num > 0 || num2 > 0) { MercPlugin.Log($"Orphan sweep: adopted {num} living mercenary ZDO(s) into their " + $"employer's current banner; removed {num2} with no home banner."); } } catch (Exception ex) { MercPlugin.LogWarn("Orphaned mercenary cleanup failed: " + ex.Message); } } private static ZDO FindOwnerBannerFor(ZDO orphan) { try { long num = Mercenary.EmployerIdOf(orphan); if (num == 0L) { return null; } foreach (ZDO item in AllZdosFromTable()) { if (IsBannerZdo(item) && item.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == num) { return item; } } } catch (Exception ex) { MercPlugin.LogWarn("Owner banner lookup failed: " + ex.Message); } return null; } private static bool AdoptIntoBanner(ZDO banner, ZDO orphan) { return TryAdoptLivingOrphan(banner, orphan); } private static void AdoptBeforeRosterCreation(ZDO banner) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) bool flag = false; string[] memberIdKeys = MercBannerSpawn.MemberIdKeys; foreach (string text in memberIdKeys) { if (!IsLivingMercZdo(ZDOMan.instance.GetZDO(banner.GetZDOID(text)))) { flag = true; break; } } if (!flag) { return; } long num = banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L); foreach (ZDO item in AllZdosFromTable()) { if (IsMercenaryZdo(item) && IsLivingMercZdo(item) && Mercenary.EmployerIdOf(item) == num && !item.GetBool(Mercenary.ZdoStandalone, false)) { ZDOID val = Mercenary.BannerIdOf(item); if (!IsBannerZdo(ZDOMan.instance.GetZDO(val))) { TryAdoptLivingOrphan(banner, item); } } } } internal static bool TryAdoptLivingOrphan(ZDO banner, ZDO orphan) { //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: 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_00cc: Unknown result type (might be due to invalid IL or missing references) try { if (!IsServer() || !IsBannerZdo(banner) || !IsLivingMercZdo(orphan) || Mercenary.EmployerIdOf(orphan) == 0L || Mercenary.EmployerIdOf(orphan) != banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L)) { return false; } int prefab = orphan.GetPrefab(); MercClass mercClass = ((prefab != StringExtensionMethods.GetStableHashCode("Merc_Tank")) ? ((prefab == StringExtensionMethods.GetStableHashCode("Merc_Healer")) ? MercClass.Healer : ((prefab == StringExtensionMethods.GetStableHashCode("Merc_Archer")) ? MercClass.Archer : ((MercClass)(-1)))) : MercClass.Tank); if (mercClass < MercClass.Tank) { return false; } int num = Mathf.Clamp((int)mercClass, 0, MercBannerSpawn.MemberIdKeys.Length - 1); ZDOID zDOID = banner.GetZDOID(MercBannerSpawn.MemberIdKeys[num]); ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(zDOID) : null); if (val != null && val.IsValid() && val.m_uid != orphan.m_uid && IsLivingMercZdo(val)) { return false; } ZDOID val2 = Mercenary.BannerIdOf(orphan); ClaimServerOwnership(banner); ClaimServerOwnership(orphan); banner.Set(MercBannerSpawn.CustomNameKeys[num], orphan.GetString(Mercenary.ZdoCustomName, MercBannerSpawn.DefaultFirstName(mercClass))); banner.Set(Mercenary.BannerEmployerIds[num], Mercenary.EmployerIdOf(orphan)); banner.Set(Mercenary.BannerEmployerNames[num], Mercenary.EmployerNameOf(orphan)); banner.Set(Mercenary.BannerFollowNames[num], Mercenary.FollowNameOf(orphan)); banner.Set(MercBannerSpawn.RecruitedKeys[num], true); Mercenary.SetBannerId(orphan, banner.m_uid); Mercenary.RestoreEmploymentFromBanner(orphan, banner, mercClass); banner.Set(MercBannerSpawn.MemberIdKeys[num], orphan.m_uid); MercPlugin.Log($"Adopted mercenary {orphan.m_uid} ({mercClass}) into banner {banner.m_uid}; " + $"its previous banner {val2} no longer exists."); return true; } catch (Exception ex) { MercPlugin.LogWarn($"Mercenary adoption failed for {orphan.m_uid}: {ex.Message}"); return false; } } private static bool IsMercenaryZdo(ZDO zdo) { if (zdo == null || !zdo.IsValid()) { return false; } int prefab = zdo.GetPrefab(); if (prefab != StringExtensionMethods.GetStableHashCode("Merc_Tank") && prefab != StringExtensionMethods.GetStableHashCode("Merc_Healer")) { return prefab == StringExtensionMethods.GetStableHashCode("Merc_Archer"); } return true; } internal static void DestroyWorldZdo(ZDO zdo, string reason) { //IL_000c: 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_002d: 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) if (zdo == null || ZDOMan.instance == null) { return; } ZDOID uid = zdo.m_uid; ClaimServerOwnership(zdo); GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(uid) : null); if ((Object)(object)val != (Object)null) { ZNetView component = val.GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && !component.IsOwner()) { component.ClaimOwnership(); } ZNetScene.instance.Destroy(val); } else { ClaimServerOwnership(zdo); ZDOMan.instance.DestroyZDO(zdo); } MercPlugin.Log($"Removed ZDO {uid}: {reason}"); } private static string PrefabName(MercClass mercClass) { return mercClass switch { MercClass.Archer => "Merc_Archer", MercClass.Healer => "Merc_Healer", _ => "Merc_Tank", }; } } internal static class ServerAuthority { internal sealed class SenderPlayerState { internal long SenderUid; internal long PeerUid; internal ZDOID CharacterZdoId; internal Player LivePlayer; internal ZDO PlayerZdo; internal long PlayerId; internal string PlayerName; internal Vector3 Position; internal bool IsDead; internal int RightItemHash; } internal sealed class MercenaryState { internal ZDOID MercId; internal ZDO MercZdo; internal ZDOID BannerId; internal ZDO BannerZdo; internal MercClass Class; internal string Name; internal Vector3 Position; internal long EmployerId; internal string EmployerName; internal string FollowName; } private sealed class ResourceJob { internal long Requester; internal long PlayerId; internal long Owner; internal int Token; internal float Expires; internal float NextHit; internal MercResourceWork.Target Target; internal int Stage; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__6_0; public static Func <>9__9_0; public static Func <>9__9_2; public static Comparison <>9__66_0; internal void b__6_0(ConsoleEventArgs args) { _companyConsole = args.Context; string text = string.Join(" ", args.Args.Skip(1).ToArray()); if (string.IsNullOrWhiteSpace(text)) { text = "status"; } if (text.Length > 160) { args.Context.AddString("Company command is too long."); return; } Register(); if (IsServer()) { OnCompanyCommand(ZNet.GetUID(), text); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_CompanyV1", new object[1] { text }); } } internal bool b__9_0(long id) { if (id != ZNet.GetUID()) { return ZNet.instance.GetPeer(id) == null; } return false; } internal string b__9_2(CompanyPreset p) { return p.Name + ": " + p.Formation.ToString() + " / " + p.Duty; } internal int b__66_0(MercenaryState left, MercenaryState right) { int num = (int)left.Class; return num.CompareTo((int)right.Class); } } private const string CompanyCommandRpc = "DynamicNPCs_CompanyV1"; private const string CompanyReplyRpc = "DynamicNPCs_CompanyReplyV1"; internal const string CompanyFormationKey = "dnpc.company.formation.v1"; private const string CompanyPresetsKey = "dnpc.company.presets.v1"; private static readonly Dictionary LastCompanyCommand = new Dictionary(); private static Terminal _companyConsole; private const string AgentRequestRpc = "MercAgentRequestV1"; private const string AgentReplyRpc = "MercAgentReplyV1"; private const string CleanupRequestRpc = "DynamicNPCs_AdminClearRadiusRequestV1"; private const string CleanupReplyRpc = "DynamicNPCs_AdminClearRadiusReplyV1"; private const string PurgeRequestRpc = "DynamicNPCs_AdminPurgeMercsRequestV1"; private const string PlayerMessageRpc = "DynamicNPCs_PlayerMessageV1"; private const string MercRehireRequestRpc = "DynamicNPCs_MercRehireRequestV1"; private const string MercCommandRequestRpc = "DynamicNPCs_MercCommandRequestV1"; private const string MercRenameRequestRpc = "DynamicNPCs_MercRenameRequestV1"; private const string MercDismissRequestRpc = "DynamicNPCs_MercDismissRequestV1"; private const string MercConversationRequestRpc = "DynamicNPCs_MercConversationRequestV1"; private const string MercResurrectionRequestRpc = "DynamicNPCs_MercResurrectionRequestV1"; private const string MercSpeechReplyRpc = "DynamicNPCs_MercSpeechReplyV1"; private const string MercGuideApplyRpc = "DynamicNPCs_MercGuideApplyV1"; private const string MercResurrectionApplyRpc = "DynamicNPCs_MercResurrectionApplyV1"; private const string ContextCompanyOrderRequestRpc = "DynamicNPCs_ContextCompanyOrderRequestV1"; private const string ContextTargetApplyRpc = "DynamicNPCs_ContextTargetApplyV1"; private static readonly object RequestLock = new object(); private static readonly Dictionary PendingAi = new Dictionary(); private static readonly Dictionary LastAiRequest = new Dictionary(); private static readonly Queue AiRequestTimes = new Queue(); private static readonly object ContextOrderLock = new object(); private static readonly Dictionary LastContextOrderAt = new Dictionary(); private static readonly Dictionary LastContextOrderSequence = new Dictionary(); private static readonly object IdentityLock = new object(); private static readonly Dictionary PinnedPeerPlayerIds = new Dictionary(); private static readonly Dictionary PinnedPlayerPeers = new Dictionary(); private static readonly FieldInfo PeerSocketField = AccessTools.Field(typeof(ZNetPeer), "m_socket"); private static readonly FieldInfo ZdoObjectsById = AccessTools.Field(typeof(ZDOMan), "m_objectsByID"); private static ZRoutedRpc _registeredRpc; internal static readonly HashSet AnnouncedFallen = new HashSet(); private const string ResourceRequestRpc = "DynamicNPCs_ResourceWorkRequestV1"; private const string ResourceApplyRpc = "DynamicNPCs_ResourceWorkApplyV1"; private const string ResourceSwingRpc = "DynamicNPCs_ResourceWorkSwingV1"; private const string ResourceStopRpc = "DynamicNPCs_ResourceWorkStopV1"; private static ZRoutedRpc _resourceRpc; private static int _resourceToken; private static readonly Dictionary ResourceJobs = new Dictionary(); internal static void RegisterCompanyCommand() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__6_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { _companyConsole = args.Context; string text = string.Join(" ", args.Args.Skip(1).ToArray()); if (string.IsNullOrWhiteSpace(text)) { text = "status"; } if (text.Length > 160) { args.Context.AddString("Company command is too long."); } else { Register(); if (IsServer()) { OnCompanyCommand(ZNet.GetUID(), text); } else { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_CompanyV1", new object[1] { text }); } } } }; <>c.<>9__6_0 = val; obj = (object)val; } new ConsoleCommand("dnpc_company", "company status | preset list | preset save | preset apply/delete ", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static void OnCompanyReply(long sender, string message) { if (!IsAuthoritativeServerSender(sender) || message == null || message.Length > 1800) { return; } if ((Object)(object)_companyConsole != (Object)null) { _companyConsole.AddString(message); return; } Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, message, 0, (Sprite)null, false); } } private static void ReplyCompany(long sender, string message) { message = Bound(message, 1800); if (sender == ZNet.GetUID()) { OnCompanyReply(sender, message); return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "DynamicNPCs_CompanyReplyV1", new object[1] { message }); } } private static void OnCompanyCommand(long sender, string payload) { //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0650: Unknown result type (might be due to invalid IL or missing references) //IL_0684: Unknown result type (might be due to invalid IL or missing references) //IL_066d: Unknown result type (might be due to invalid IL or missing references) //IL_0689: Unknown result type (might be due to invalid IL or missing references) //IL_06b1: Unknown result type (might be due to invalid IL or missing references) //IL_06bd: Unknown result type (might be due to invalid IL or missing references) //IL_06c7: Unknown result type (might be due to invalid IL or missing references) //IL_06cc: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || payload == null || payload.Length > 160 || !TryResolveSender(sender, out var requester)) { return; } float unscaledTime = Time.unscaledTime; if (LastCompanyCommand.TryGetValue(sender, out var value) && unscaledTime - value < 1f) { return; } long[] array = LastCompanyCommand.Keys.Where((long id) => id != ZNet.GetUID() && ZNet.instance.GetPeer(id) == null).ToArray(); foreach (long key in array) { LastCompanyCommand.Remove(key); } LastCompanyCommand[sender] = unscaledTime; try { string[] parts = payload.Trim().ToLowerInvariant().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); ZDO val = ((IEnumerable)BannerAuthority.AllZdosFromTable()).FirstOrDefault((Func)((ZDO zdo) => zdo != null && zdo.IsValid() && zdo.GetPrefab() == StringExtensionMethods.GetStableHashCode("MercBanner") && zdo.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == requester.PlayerId)); if (parts.Length == 1 && parts[0] == "status") { StringBuilder stringBuilder = new StringBuilder("Your company: player=" + requester.PlayerId + ", banner=" + ((val != null) ? ((object)Unsafe.As(ref val.m_uid)/*cast due to .constrained prefix*/).ToString() : "missing") + "\n"); foreach (ZDO item in BannerAuthority.AllZdosFromTable()) { if (item != null && item.IsValid() && Mercenary.EmployerIdOf(item) == requester.PlayerId && TryResolveMercenary(item.m_uid, out var state, out var _, allowMissingBanner: true)) { stringBuilder.Append(state.Name).Append(": id=").Append(state.MercId) .Append(", owner=") .Append(item.GetOwner()) .Append(", duty=") .Append(string.IsNullOrEmpty(state.FollowName) ? "hold" : "follow") .Append(", distance=") .Append(Vector3.Distance(requester.Position, state.Position).ToString("F0")) .Append("m, ") .Append((state.BannerZdo == null) ? "orphan: awaiting owned banner recovery" : "roster linked") .AppendLine(); } } ReplyCompany(sender, stringBuilder.ToString()); return; } if (val == null) { throw new ArgumentException("Claim a company banner first."); } if (requester.IsDead) { throw new ArgumentException("Company presets cannot change while you are dead."); } if (parts.Length < 2 || parts[0] != "preset") { throw new ArgumentException("Use status or preset list/save/apply/delete."); } List list = CompanyPresetRules.Decode(val.GetString("dnpc.company.presets.v1", "")); if (parts.Length == 2 && parts[1] == "list") { ReplyCompany(sender, (list.Count == 0) ? "No presets. Save one with: dnpc_company preset save travel roles follow" : string.Join("\n", list.Select((CompanyPreset p) => p.Name + ": " + p.Formation.ToString() + " / " + p.Duty))); return; } if (parts.Length < 3 || !CompanyPresetRules.ValidName(parts[2])) { throw new ArgumentException("Use a 1–24 character name containing a-z, 0-9, - or _."); } CompanyPreset companyPreset = list.FirstOrDefault((CompanyPreset p) => p.Name == parts[2]); if (parts[1] == "save" && parts.Length == 5) { if (!Enum.TryParse(parts[3], ignoreCase: true, out var result) || !Enum.IsDefined(typeof(CompanyFormation), result) || !Enum.TryParse(parts[4], ignoreCase: true, out var result2) || !Enum.IsDefined(typeof(CompanyDuty), result2)) { throw new ArgumentException("Choose roles/tight/line and follow/hold."); } if (companyPreset == null) { if (list.Count >= 8) { throw new ArgumentException("Delete a preset before adding a ninth."); } list.Add(companyPreset = new CompanyPreset { Name = parts[2] }); } companyPreset.Formation = result; companyPreset.Duty = result2; EnsureZdoServerOwnership(val); val.Set("dnpc.company.presets.v1", CompanyPresetRules.Encode(list)); ReplyCompany(sender, "Saved preset " + companyPreset.Name + "."); return; } if (parts[1] == "delete" && parts.Length == 3 && companyPreset != null) { list.Remove(companyPreset); EnsureZdoServerOwnership(val); val.Set("dnpc.company.presets.v1", CompanyPresetRules.Encode(list)); ReplyCompany(sender, "Deleted preset " + companyPreset.Name + "."); return; } if (parts[1] == "apply" && parts.Length == 3 && companyPreset != null) { ConfigEntry contextCompanyOrdersEnabled = MercConfig.ContextCompanyOrdersEnabled; if (contextCompanyOrdersEnabled == null || !contextCompanyOrdersEnabled.Value) { throw new ArgumentException("The server disabled company orders."); } List list2 = ListOwnedCompany(requester); if (list2.Count == 0) { throw new ArgumentException("No employed company is ready."); } EnsureZdoServerOwnership(val); val.Set("dnpc.company.formation.v1", (int)companyPreset.Formation); foreach (MercenaryState item2 in list2) { EnsureZdoServerOwnership(item2.MercZdo); item2.MercZdo.Set("dnpc.company.formation.v1", (int)companyPreset.Formation); } if (companyPreset.Duty == CompanyDuty.Follow) { ApplyCompanyRecall(sender, requester, list2); } else { ApplyCompanyHold(sender, requester, list2, requester.Position); Vector3 forward = (((Object)(object)requester.LivePlayer != (Object)null) ? ((Component)requester.LivePlayer).transform.forward : Vector3.forward); foreach (MercenaryState item3 in list2) { item3.MercZdo.Set(Mercenary.ZdoStayAnchor, requester.Position + MercFormation.Offset(item3.Class, forward, MercMovementMode.Travel, companyPreset.Formation)); } } ReplyCompany(sender, "Applied " + companyPreset.Name + ": " + companyPreset.Formation.ToString() + " / " + companyPreset.Duty.ToString() + "."); return; } throw new ArgumentException("Unknown preset or syntax. Use preset list, save , apply , or delete ."); } catch (Exception ex) { ReplyCompany(sender, "Company: " + ex.Message); } } internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { instance.Register("MercAgentRequestV1", (Action)OnAgentRequest); instance.Register("DynamicNPCs_CompanyV1", (Action)OnCompanyCommand); instance.Register("DynamicNPCs_CompanyReplyV1", (Action)OnCompanyReply); instance.Register("MercAgentReplyV1", (Action)OnAgentReply); instance.Register("DynamicNPCs_AdminClearRadiusRequestV1", (Action)OnCleanupRequest); instance.Register("DynamicNPCs_AdminClearRadiusReplyV1", (Action)OnCleanupReply); instance.Register("DynamicNPCs_AdminPurgeMercsRequestV1", (Action)OnPurgeRequest); instance.Register("DynamicNPCs_PlayerMessageV1", (Action)OnPlayerMessage); instance.Register("DynamicNPCs_MercRehireRequestV1", (Action)OnMercRehireRequest); instance.Register("DynamicNPCs_MercCommandRequestV1", (Action)OnMercCommandRequest); instance.Register("DynamicNPCs_MercRenameRequestV1", (Action)OnMercRenameRequest); instance.Register("DynamicNPCs_MercDismissRequestV1", (Action)OnMercDismissRequest); instance.Register("DynamicNPCs_GuideMenuRequestV1", (Method)OnGuideMenuRequest); instance.Register("DynamicNPCs_MercConversationRequestV1", (Action)OnMercConversationRequest); instance.Register("DynamicNPCs_MercResurrectionRequestV1", (Action)OnMercResurrectionRequest); instance.Register("DynamicNPCs_MercSpeechReplyV1", (Action)OnMercSpeechReply); instance.Register("DynamicNPCs_MercGuideApplyV1", (Action)OnMercGuideApply); instance.Register("DynamicNPCs_MercResurrectionApplyV1", (Action)OnMercResurrectionApply); instance.Register("DynamicNPCs_ContextCompanyOrderRequestV1", (Method)OnContextCompanyOrderRequest); instance.Register("DynamicNPCs_ContextTargetApplyV1", (Method)OnContextTargetApply); MercResourceWork.Register(); _registeredRpc = instance; MercPlugin.Log("Registered server-authoritative mercenary RPC protocol."); } } internal static void Reset() { MercResourceWork.Reset(); lock (RequestLock) { PendingAi.Clear(); LastAiRequest.Clear(); AiRequestTimes.Clear(); } lock (ContextOrderLock) { LastContextOrderAt.Clear(); LastContextOrderSequence.Clear(); } lock (IdentityLock) { PinnedPeerPlayerIds.Clear(); PinnedPlayerPeers.Clear(); } AnnouncedFallen.Clear(); } internal static void SendAgentQuestion(string question) { if (ZRoutedRpc.instance != null) { string text = Bound(question, 1000); if (!string.IsNullOrEmpty(text)) { string text2 = "{\"q\":\"" + MiniJson.Escape(text) + "\"}"; ZRoutedRpc.instance.InvokeRoutedRPC("MercAgentRequestV1", new object[1] { text2 }); } } } internal static void RequestNpcCleanup(float radius) { Register(); float num = Mathf.Clamp(radius, 1f, 200f); MercPlugin.Log($"Admin NPC cleanup client request: radius={num:F1}m, " + $"localPeer={(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0)}, " + $"routedRpcExists={ZRoutedRpc.instance != null}."); if (ZRoutedRpc.instance == null) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, MercLocalization.Text("dnpc_message_cleanup_network_failed"), 0, (Sprite)null, false); } } else { ZRoutedRpc.instance.InvokeRoutedRPC("DynamicNPCs_AdminClearRadiusRequestV1", new object[1] { num }); } } internal static void RequestMercPurge() { Register(); if (ZRoutedRpc.instance == null) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, MercLocalization.Text("dnpc_message_purge_network_failed"), 0, (Sprite)null, false); } } else { ZRoutedRpc.instance.InvokeRoutedRPC("DynamicNPCs_AdminPurgeMercsRequestV1", Array.Empty()); } } internal static void RequestMercRehire(ZDOID mercId, int progressionSeed) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) Register(); MercPlugin.Log($"Mercenary rehire client request: mercZdo={mercId}, " + $"localPeer={(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0)}, " + $"routedRpcExists={ZRoutedRpc.instance != null}."); if (ZRoutedRpc.instance == null || ((ZDOID)(ref mercId)).IsNone()) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, MercLocalization.Text("dnpc_message_rehire_network_failed"), 0, (Sprite)null, false); } } else { ZRoutedRpc.instance.InvokeRoutedRPC("DynamicNPCs_MercRehireRequestV1", new object[2] { mercId, progressionSeed }); } } internal static void RequestMercCommand(ZDOID mercId, bool message) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) Register(); MercPlugin.Log($"Mercenary command client request: mercZdo={mercId}, localPeer={(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0)}."); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_MercCommandRequestV1", new object[2] { mercId, message }); } } internal static void RequestMercRename(ZDOID mercId, string name) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) Register(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_MercRenameRequestV1", new object[2] { mercId, Bound(name, 24) }); } } internal static void RequestMercDismiss(ZDOID mercId) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) Register(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_MercDismissRequestV1", new object[1] { mercId }); } } internal static void RequestMercConversation(ZDOID mercId, string payload) { //IL_000a: 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) Register(); MercPlugin.Log(string.Format("Merc conversation client request: mercZdo={0}, payloadLength={1}.", mercId, (payload ?? "").Length)); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_MercConversationRequestV1", new object[2] { mercId, Bound(payload, 10000) }); } } internal static void RequestMercResurrection(ZDOID mercId, ZDOID playerId) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) Register(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_MercResurrectionRequestV1", new object[2] { mercId, playerId }); } } internal static void RequestContextCompanyOrder(MercContextOrderType order, ZDOID targetId, Vector3 point, int sequence) { //IL_0025: 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) Register(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_ContextCompanyOrderRequestV1", new object[4] { (int)order, targetId, point, sequence }); } } internal static long GetServerPeerUid() { if ((Object)(object)ZNet.instance == (Object)null) { return 0L; } if (ZNet.instance.IsServer()) { return ZNet.GetUID(); } return ZNet.instance.GetServerPeer()?.m_uid ?? 0; } private static void OnAgentRequest(long sender, string payload) { if (string.IsNullOrEmpty(payload) || payload.Length > 10000) { return; } MercPlugin.Log($"Private agent request received: sender={sender}."); if (!IsServer()) { MercPlugin.LogWarn($"Private agent request rejected: sender={sender}, reason=handler is not the authoritative server."); return; } if (!TryResolveSender(sender, out var state)) { MercPlugin.LogWarn($"Private agent request rejected: sender={sender}, reason=sender player/character ZDO could not be resolved."); SendAgentReply(sender, "I cannot identify your character on the server yet. Try again in a moment."); return; } if (!MercConfig.AgentChatEnabled.Value) { MercPlugin.LogWarn($"Private agent request rejected: sender={sender}, reason=AgentChatEnabled is false."); SendAgentReply(sender, "Private ! agent chat is disabled by the server."); return; } if (!TryBeginAi(sender, state, null, agentMode: true)) { MercPlugin.LogWarn($"Private agent request rejected: sender={sender}, reason=AI request is already pending or rate-limited."); return; } string text = Bound(ReadString(MiniJson.Parse(payload) as Dictionary, "q"), 1000); if (string.IsNullOrWhiteSpace(text)) { AbortAi(sender); return; } MercPlugin.Log($"Private agent request accepted: sender={sender}, playerId={state.PlayerId}, " + $"playerName={state.PlayerName}, livePlayer={(Object)(object)state.LivePlayer != (Object)null}, " + $"questionLength={text.Length}."); LlmBrain.ProcessServerRequest(sender, state, (Mercenary)null, text, agentMode: true, new GuideClientSnapshot(), proactive: false); } private static void OnAgentReply(long sender, string speaker, string text) { if (IsAuthoritativeServerSender(sender)) { LlmBrain.SayAsAgent(text, speaker); } } private static void OnCleanupRequest(long sender, float requestedRadius) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) MercPlugin.Log($"Admin NPC cleanup request received (cleanup v2): sender={sender}, " + $"radius={requestedRadius:F1}m."); if (!IsServer()) { MercPlugin.LogWarn("Admin NPC cleanup request rejected on a non-server peer."); return; } if (!TryResolveSender(sender, out var state)) { RejectCleanup(sender, "sender player/character ZDO could not be resolved"); return; } if (!IsSenderAdmin(sender)) { RejectCleanup(sender, state.PlayerName + " is not a server admin"); return; } float num = Mathf.Clamp(requestedRadius, 1f, 200f); if (!TryDestroyNonPlayerCharacters(state.Position, num, out var mercenaryCount, out var npcCount, out var debrisCount, out var failure)) { RejectCleanup(sender, failure); return; } string message = ((debrisCount > 0) ? $"Removed {mercenaryCount} mercenaries, {npcCount} other NPCs, and {debrisCount} orphaned objects within {num:F0}m." : $"Removed {mercenaryCount} mercenaries and {npcCount} other NPCs within {num:F0}m."); MercPlugin.Log($"Admin NPC cleanup request accepted: sender={sender}, player={state.PlayerName}, " + $"center={state.Position}, radius={num:F1}m, mercenaries={mercenaryCount}, " + $"npcs={npcCount}, debris={debrisCount}."); SendCleanupReply(sender, message); } private static void OnPurgeRequest(long sender) { MercPlugin.Log($"Admin mercenary purge request received (cleanup v2): sender={sender}."); if (!IsServer()) { MercPlugin.LogWarn("Admin mercenary purge request rejected on a non-server peer."); return; } if (!TryResolveSender(sender, out var state)) { RejectCleanup(sender, "sender player/character ZDO could not be resolved"); return; } if (!IsSenderAdmin(sender)) { RejectCleanup(sender, state.PlayerName + " is not a server admin"); return; } if (!TryPurgeAllMercenaries(out var purged, out var failure)) { RejectCleanup(sender, failure); return; } MercPlugin.Log($"Admin mercenary purge accepted: sender={sender}, " + $"player={state.PlayerName}, purged={purged}."); SendCleanupReply(sender, $"Purged {purged} mercenaries world-wide. Recognized banners respawn their rosters."); } private static bool TryPurgeAllMercenaries(out int purged, out string failure) { purged = 0; failure = ""; if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { failure = "server world records are unavailable"; return false; } try { int stableHashCode = StringExtensionMethods.GetStableHashCode("Merc_Tank"); int stableHashCode2 = StringExtensionMethods.GetStableHashCode("Merc_Healer"); int stableHashCode3 = StringExtensionMethods.GetStableHashCode("Merc_Archer"); foreach (ZDO item in BannerAuthority.AllZdosFromTable()) { if (item != null && item.IsValid()) { int prefab = item.GetPrefab(); if (prefab == stableHashCode || prefab == stableHashCode2 || prefab == stableHashCode3) { DestroyIsolated(item, "admin world purge (id table)", ref purged); } } } MercPlugin.Log($"Admin world purge swept the ZDO id table and removed {purged} mercenary ZDO(s)."); return true; } catch (Exception ex) { MercPlugin.LogWarn($"Merc purge failed: {ex}"); failure = "purge failed: " + ex.Message; return false; } } internal static void OnMercCommandRequest(long sender, ZDOID mercId, bool message) { //IL_000b: 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) MercPlugin.Log($"Mercenary follow/stay request received: sender={sender}, mercZdo={mercId}."); if (TryValidateMercRequest("follow/stay", sender, mercId, 8f, requireAssigned: true, out var requester, out var merc)) { ApplyIndividualDuty(sender, requester, merc, merc.FollowName != requester.PlayerName, message); } } private static void ApplyIndividualDuty(long sender, SenderPlayerState requester, MercenaryState merc, bool follow, bool message) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) EnsureZdoServerOwnership(merc.BannerZdo); int num = (int)merc.Class; bool flag = !follow; ClearCompanyGuideState(sender, requester, merc); SendContextTarget(sender, merc, requester.CharacterZdoId, MercContextTargetMode.Clear, default(ZDOID)); merc.BannerZdo.Set(Mercenary.BannerFollowNames[num], flag ? "" : requester.PlayerName); if (!flag) { merc.BannerZdo.Set(Mercenary.BannerEmployerIds[num], requester.PlayerId); merc.BannerZdo.Set(Mercenary.BannerEmployerNames[num], requester.PlayerName); } if (flag) { EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set(Mercenary.ZdoStayDay, Mathf.Max(1, WorldDataService.CurrentDay), false); merc.MercZdo.Set(Mercenary.ZdoStayAnchor, merc.Position); } else { EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set(Mercenary.ZdoStayDay, 0, false); } merc.MercZdo.Set(Mercenary.ZdoPreciseHold, flag); Mercenary.SetFollowName(merc.MercZdo, flag ? "" : requester.PlayerName); GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(merc.MercId) : null); Mercenary mercenary = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); mercenary?.NotifySimulationOwnerOfStateChange(refillFood: false); if (!flag) { mercenary?.NotifySimulationOwnerOfRecall(); } if (message) { MercSay(merc, mercenary, sender, flag ? MercLocalization.Phrase("dnpc_speech_hold_position") : MercLocalization.Phrase("dnpc_speech_following")); } LlmBrain.RecordEvent("command:" + merc.Name, merc.Name + (flag ? " was asked to stay at the current location." : " was asked to follow the player."), 2f); MercPlugin.Log($"Mercenary follow/stay request accepted: sender={sender}, merc={merc.Name}, following={!flag}."); } private static void OnContextCompanyOrderRequest(long sender, int orderValue, ZDOID targetId, Vector3 point, int sequence) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Unknown result type (might be due to invalid IL or missing references) //IL_046f: Unknown result type (might be due to invalid IL or missing references) string text; if (!Enum.IsDefined(typeof(MercContextOrderType), orderValue)) { text = "invalid:" + orderValue; } else { MercContextOrderType mercContextOrderType = (MercContextOrderType)orderValue; text = mercContextOrderType.ToString(); } string operation = text; if (!IsServer()) { return; } if (MercConfig.ContextCompanyOrdersEnabled == null || !MercConfig.ContextCompanyOrdersEnabled.Value) { RejectContextOrder(sender, operation, "context company orders are disabled by the server"); return; } if (!Enum.IsDefined(typeof(MercContextOrderType), orderValue) || orderValue == 0) { RejectContextOrder(sender, operation, "order enum is outside the allowed set"); return; } if (!TryResolveSender(sender, out var requester)) { RejectContextOrder(sender, operation, "sender could not be resolved"); return; } if (requester.IsDead) { RejectContextOrder(sender, operation, "requesting player is dead"); return; } if (!TryAcceptContextOrder(sender, sequence, out var reason)) { RejectContextOrder(sender, operation, reason); return; } if (!MercContextOrders.IsFinite(point)) { RejectContextOrder(sender, operation, "point contains a non-finite coordinate"); return; } MercContextOrderType mercContextOrderType2 = (MercContextOrderType)orderValue; bool num = mercContextOrderType2 == MercContextOrderType.Hold || mercContextOrderType2 == MercContextOrderType.GuidePoint; bool flag = mercContextOrderType2 == MercContextOrderType.FocusEnemy || mercContextOrderType2 == MercContextOrderType.HuntAnimal; if (num) { Vector3 val = point - requester.Position; val.y = 0f; if (((Vector3)(ref val)).magnitude > 60f || Mathf.Abs(point.y - requester.Position.y) > 40f) { RejectContextOrder(sender, operation, "marked point is outside the allowed range"); return; } } else if (!flag && !((ZDOID)(ref targetId)).IsNone()) { RejectContextOrder(sender, operation, "this order must not carry a target identity"); return; } Character target = null; if (flag) { if (!TryResolveContextTarget(targetId, out target, out var reason2)) { RejectContextOrder(sender, operation, reason2); return; } if (Vector3.Distance(requester.Position, ((Component)target).transform.position) > 45f) { RejectContextOrder(sender, operation, "target is outside the allowed range"); return; } if (mercContextOrderType2 == MercContextOrderType.HuntAnimal && !MercContextOrders.IsHuntableAnimal(target)) { RejectContextOrder(sender, operation, "target is not untamed huntable game"); return; } if (mercContextOrderType2 == MercContextOrderType.FocusEnemy && ((Object)(object)requester.LivePlayer == (Object)null || !BaseAI.IsEnemy((Character)(object)requester.LivePlayer, target))) { RejectContextOrder(sender, operation, "target is not hostile to the authenticated player"); return; } point = ((Component)target).transform.position; } List list = ListOwnedCompany(requester); if (flag) { list.RemoveAll((MercenaryState member) => !MercCommandRules.MayReceiveTarget(IsAssignedTo(member, requester, requireFollowing: false), IsAssignedTo(member, requester, requireFollowing: true), !member.MercZdo.GetBool(ZDOVars.s_dead, false) && member.MercZdo.GetFloat(ZDOVars.s_health, 0f) > 0f)); } if (list.Count == 0) { if (flag) { SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_context_no_followers")); } else { RejectContextOrder(sender, operation, "requester has no employed mercenary roster"); } return; } if ((mercContextOrderType2 == MercContextOrderType.FocusEnemy || mercContextOrderType2 == MercContextOrderType.HuntAnimal) && !HasLoadedCompanyMember(list, (mercContextOrderType2 == MercContextOrderType.HuntAnimal) ? MercClass.Archer : ((MercClass)(-1)))) { RejectContextOrder(sender, operation, (mercContextOrderType2 == MercContextOrderType.HuntAnimal) ? "Fen is not loaded close enough to receive the hunt mark" : "no company member is loaded to receive the focus mark"); return; } switch (mercContextOrderType2) { case MercContextOrderType.Recall: ApplyCompanyRecall(sender, requester, list); break; case MercContextOrderType.Hold: ApplyCompanyHold(sender, requester, list, point); break; case MercContextOrderType.GuidePoint: if (!ApplyCompanyGuidePoint(sender, requester, list, point)) { return; } break; case MercContextOrderType.FocusEnemy: ApplyCompanyTarget(sender, requester, list, targetId, MercContextTargetMode.Focus); break; case MercContextOrderType.HuntAnimal: ApplyCompanyHunt(sender, requester, list, targetId); break; case MercContextOrderType.CancelGuide: ApplyCompanyCancel(sender, requester, list); break; } MercenaryState mercenaryState = CompanySpeaker(list); string text2 = mercContextOrderType2 switch { MercContextOrderType.HuntAnimal => MercLocalization.Phrase("dnpc_speech_company_hunt", MercContextOrders.AnimalLabel(target)), MercContextOrderType.FocusEnemy => MercLocalization.Phrase("dnpc_speech_company_focus"), MercContextOrderType.GuidePoint => MercLocalization.Phrase("dnpc_speech_company_guide"), MercContextOrderType.Hold => MercLocalization.Phrase("dnpc_speech_company_hold"), MercContextOrderType.Recall => MercLocalization.Phrase("dnpc_speech_company_recall"), _ => MercLocalization.Phrase("dnpc_speech_company_cancel"), }; if (mercenaryState != null) { MercSay(mercenaryState, LiveMerc(mercenaryState), sender, text2); } else { SendPlayerMessage(sender, text2); } LlmBrain.RecordEvent("company-order", requester.PlayerName + " issued company order " + mercContextOrderType2.ToString() + ".", 2f); MercPlugin.Log($"Context company order accepted: sender={sender}, playerId={requester.PlayerId}, " + $"order={mercContextOrderType2}, roster={list.Count}, target={targetId}, point={point}."); } private static bool TryAcceptContextOrder(long sender, int sequence, out string reason) { int tickCount = Environment.TickCount; lock (ContextOrderLock) { if (LastContextOrderSequence.TryGetValue(sender, out var value) && value == sequence) { reason = "duplicate request sequence was replayed"; return false; } if (LastContextOrderAt.TryGetValue(sender, out var value2)) { int num = tickCount - value2; if (num >= 0 && num < 400) { reason = "request arrived inside the 400ms command rate limit"; return false; } } LastContextOrderSequence[sender] = sequence; LastContextOrderAt[sender] = tickCount; } reason = ""; return true; } private static void RejectContextOrder(long sender, string operation, string reason) { MercPlugin.LogWarn($"Context company order rejected: sender={sender}, order={operation}, reason={reason}."); SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_company_order_failed")); } private static bool TryResolveContextTarget(ZDOID targetId, out Character target, out string reason) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) target = null; reason = "target identity is missing"; if (((ZDOID)(ref targetId)).IsNone() || (Object)(object)ZNetScene.instance == (Object)null) { return false; } GameObject val = ZNetScene.instance.FindInstance(targetId); ZNetView val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); target = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)target == (Object)null || (Object)(object)val2 == (Object)null || !val2.IsValid() || val2.GetZDO().m_uid != targetId) { target = null; reason = "target is not a loaded server Character with that ZDO identity"; return false; } if (target.IsDead() || target.IsPlayer() || target is Mercenary || target.IsTamed()) { target = null; reason = "target is dead, player-owned, tamed, or a mercenary"; return false; } reason = ""; return true; } private static List ListOwnedCompany(SenderPlayerState requester) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) List list = new List(); HashSet hashSet = new HashSet(); foreach (ZDO item in BannerAuthority.AllZdosFromTable()) { if (item != null && item.IsValid() && hashSet.Add(item.m_uid)) { int prefab = item.GetPrefab(); if ((prefab == StringExtensionMethods.GetStableHashCode("Merc_Tank") || prefab == StringExtensionMethods.GetStableHashCode("Merc_Healer") || prefab == StringExtensionMethods.GetStableHashCode("Merc_Archer")) && TryResolveMercenary(item.m_uid, out var state, out var _) && IsAssignedTo(state, requester, requireFollowing: false)) { list.Add(state); } } } list.Sort(delegate(MercenaryState left, MercenaryState right) { int num = (int)left.Class; return num.CompareTo((int)right.Class); }); return list; } private static bool HasLoadedCompanyMember(List company, MercClass requiredClass) { foreach (MercenaryState item in company) { if (requiredClass < MercClass.Tank || item.Class == requiredClass) { Mercenary mercenary = LiveMerc(item); if ((Object)(object)mercenary != (Object)null && !((Character)mercenary).IsDead()) { return true; } } } return false; } private static MercenaryState CompanySpeaker(List company) { MercenaryState mercenaryState = null; foreach (MercenaryState item in company) { if (!((Object)(object)LiveMerc(item) == (Object)null)) { if (item.Class == MercClass.Healer) { return item; } mercenaryState = mercenaryState ?? item; } } MercenaryState mercenaryState2 = mercenaryState; if (mercenaryState2 == null) { if (company.Count <= 0) { return null; } mercenaryState2 = company[0]; } return mercenaryState2; } private static void ApplyCompanyRecall(long sender, SenderPlayerState requester, List company) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) foreach (MercenaryState item in company) { ClearCompanyGuideState(sender, requester, item); SendContextTarget(sender, item, requester.CharacterZdoId, MercContextTargetMode.Clear, default(ZDOID)); SetMercFollowing(item, requester); MercRecallRecovery.TryRecover(item, requester); LiveMerc(item)?.NotifySimulationOwnerOfRecall(); } } private static void ApplyCompanyHold(long sender, SenderPlayerState requester, List company, Vector3 point) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_009c: 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) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) foreach (MercenaryState item in company) { ClearCompanyGuideState(sender, requester, item); SendContextTarget(sender, item, requester.CharacterZdoId, MercContextTargetMode.Clear, default(ZDOID)); EnsureZdoServerOwnership(item.BannerZdo); EnsureZdoServerOwnership(item.MercZdo); item.BannerZdo.Set(Mercenary.BannerFollowNames[(int)item.Class], ""); Mercenary.SetFollowName(item.MercZdo, ""); item.MercZdo.Set(Mercenary.ZdoStayDay, Mathf.Max(1, WorldDataService.CurrentDay), false); item.MercZdo.Set(Mercenary.ZdoStayAnchor, point + MercContextOrders.HoldOffset(item.Class)); item.MercZdo.Set(Mercenary.ZdoPreciseHold, true); LiveMerc(item)?.NotifySimulationOwnerOfStateChange(refillFood: false); } } private static bool ApplyCompanyGuidePoint(long sender, SenderPlayerState requester, List company, Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) if (!GuideFinder.PlanRoute(requester.Position, point, out var waypoints, out var _)) { RejectContextOrder(sender, MercContextOrderType.GuidePoint.ToString(), "no bounded land or short-water route reaches the marked point"); return false; } string text = ((waypoints != null && waypoints.Count > 1) ? GuideFinder.SerializeRoute(waypoints) : ""); Vector3 destination = ((waypoints != null && waypoints.Count > 0) ? waypoints[0] : point); foreach (MercenaryState item in company) { SendContextTarget(sender, item, requester.CharacterZdoId, MercContextTargetMode.Clear, default(ZDOID)); SetMercFollowing(item, requester); EnsureZdoServerOwnership(item.MercZdo); item.MercZdo.Set("merc_guideFind", ""); item.MercZdo.Set("merc_guideRoute", text); SendGuideTask(sender, item, requester.CharacterZdoId, GuideTaskType.Custom, BossTarget.None, destination); } return true; } private static void ApplyCompanyTarget(long sender, SenderPlayerState requester, List company, ZDOID targetId, MercContextTargetMode mode) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) foreach (MercenaryState item in company) { ClearCompanyGuideState(sender, requester, item); SendContextTarget(sender, item, requester.CharacterZdoId, mode, targetId); } } private static void ApplyCompanyHunt(long sender, SenderPlayerState requester, List company, ZDOID targetId) { //IL_001c: 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) foreach (MercenaryState item in company) { ClearCompanyGuideState(sender, requester, item); SendContextTarget(sender, item, requester.CharacterZdoId, (item.Class == MercClass.Archer) ? MercContextTargetMode.Hunt : MercContextTargetMode.ProtectHunt, targetId); } } private static void ApplyCompanyCancel(long sender, SenderPlayerState requester, List company) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) foreach (MercenaryState item in company) { ClearCompanyGuideState(sender, requester, item); SendContextTarget(sender, item, requester.CharacterZdoId, MercContextTargetMode.Clear, default(ZDOID)); } } internal static void CancelOwnedCompanyGuides(SenderPlayerState requester) { if (!IsServer() || requester == null || requester.PlayerId == 0L) { return; } foreach (MercenaryState item in ListOwnedCompany(requester)) { ClearCompanyGuideState(requester.SenderUid, requester, item); } } private static void ClearCompanyGuideState(long sender, SenderPlayerState requester, MercenaryState merc) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) MercResourceWork.CancelForMerc(merc.MercId); EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set(MercAI.ZdoGuideTask, ""); merc.MercZdo.Set("merc_guideFind", ""); merc.MercZdo.Set("merc_guideRoute", ""); SendGuideTask(sender, merc, requester.CharacterZdoId, GuideTaskType.Cancel, BossTarget.None, Vector3.zero); } private static void OnMercRenameRequest(long sender, ZDOID mercId, string requestedName) { //IL_000b: 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_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) MercPlugin.Log($"Mercenary rename request received: sender={sender}, mercZdo={mercId}."); if (!TryValidateMercRequest("rename", sender, mercId, 8f, requireAssigned: false, out var requester, out var merc)) { return; } EnsureZdoServerOwnership(merc.BannerZdo); if (merc.BannerZdo.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) != requester.PlayerId && merc.EmployerId != requester.PlayerId) { RejectMercRequest(sender, "rename", mercId, "requester does not manage this banner"); return; } string text = MercBannerSpawn.SanitizeName(requestedName); if (string.IsNullOrWhiteSpace(text)) { text = MercBannerSpawn.DefaultFirstName(merc.Class); } merc.BannerZdo.Set(MercBannerSpawn.CustomNameKeys[(int)merc.Class], text); string arg = text + " " + RoleTitle(merc.Class); SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_renamed", text, RoleArgument(merc.Class))); MercPlugin.Log($"Mercenary rename request accepted: sender={sender}, mercZdo={mercId}, name={arg}."); } private static void OnGuideMenuRequest(long sender, ZDOID mercId, int taskValue, int bossValue, string findKey, string hintsJson) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0474: Unknown result type (might be due to invalid IL or missing references) //IL_0479: Unknown result type (might be due to invalid IL or missing references) //IL_0487: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_04f4: Unknown result type (might be due to invalid IL or missing references) //IL_04f9: Unknown result type (might be due to invalid IL or missing references) //IL_0503: Unknown result type (might be due to invalid IL or missing references) //IL_0508: Unknown result type (might be due to invalid IL or missing references) //IL_0514: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_0523: Unknown result type (might be due to invalid IL or missing references) //IL_0528: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_055b: Unknown result type (might be due to invalid IL or missing references) //IL_054c: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_05f4: Unknown result type (might be due to invalid IL or missing references) //IL_0600: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Unknown result type (might be due to invalid IL or missing references) //IL_0651: Unknown result type (might be due to invalid IL or missing references) MercPlugin.Log($"Guide menu request received: sender={sender}, merc={mercId}, " + string.Format("task={0}, boss={1}, find={2}.", taskValue, bossValue, findKey ?? "")); if (!IsServer()) { MercPlugin.LogWarn("Guide menu request rejected on a non-server peer."); } else { if (!TryValidateMercRequest("guide-menu", sender, mercId, 16f, requireAssigned: true, out var requester, out var merc)) { return; } try { Mercenary mercenary = LiveMerc(merc); if (taskValue >= 7 && taskValue <= 9) { if (Vector3.Distance(requester.Position, merc.Position) > 8f) { RejectMercRequest(sender, "individual-order", mercId, "move within 8 metres"); } else if (taskValue == 9) { OnMercDismissRequest(sender, mercId); } else { ApplyIndividualDuty(sender, requester, merc, taskValue == 7, message: true); } return; } int num; switch (taskValue) { case 3: ApplyCompanyCancel(sender, requester, ListOwnedCompany(requester)); MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_company_cancel")); return; case 2: { if ((Object)(object)mercenary == (Object)null || merc.BannerZdo == null) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_no_banner")); return; } Vector3 position2 = merc.BannerZdo.GetPosition(); EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set("merc_guideFind", ""); merc.MercZdo.Set("merc_guideRoute", ""); SetMercFollowing(merc, requester); mercenary.RequestGuideTask(requester, GuideTaskType.Custom, BossTarget.None, position2); MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_return_banner", Mathf.CeilToInt(Vector3.Distance(requester.Position, position2)))); return; } case 4: { if (findKey != null && findKey.IndexOf(':') >= 0 && GuideFinder.ByKey(findKey) == null) { GuideClientSnapshot snapshot = GuideClientSnapshot.FromJson(hintsJson); if (!GuideFinder.TryResolveDynamicTarget(findKey, requester.Position, snapshot, requester.PlayerName, out var destination, out var label)) { string text = findKey.Substring(findKey.IndexOf(':') + 1); MercSay(merc, mercenary, sender, string.IsNullOrEmpty(text) ? MercLocalization.Phrase("dnpc_speech_could_not_find") : MercLocalization.Phrase("dnpc_speech_could_not_find_named", text)); return; } if ((Object)(object)mercenary == (Object)null) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_cannot_move")); return; } List waypoints = null; Vector3 blockage = Vector3.zero; if (!GuideFinder.PlanRoute(requester.Position, destination, out waypoints, out blockage) && ((Object)(object)mercenary.Ai == (Object)null || !mercenary.Ai.TestLandPath(destination))) { string englishDirection = GuideFinder.Direction(requester.Position, blockage); MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_water_route", MercLocalization.DirectionArgument(englishDirection))); return; } Vector3 destination2 = ((waypoints != null && waypoints.Count > 0) ? waypoints[0] : destination); SetMercFollowing(merc, requester); EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set("merc_guideFind", ""); merc.MercZdo.Set("merc_guideRoute", (waypoints != null && waypoints.Count > 1) ? GuideFinder.SerializeRoute(waypoints) : ""); mercenary.RequestGuideTask(requester, GuideTaskType.Custom, BossTarget.None, destination2); MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_target_this_way", label)); MercPlugin.Log("Guide dynamic target '" + findKey + "' for " + requester.PlayerName + ": guiding to " + label + "."); return; } GuideFindTarget guideFindTarget = GuideFinder.ByKey(findKey); if (guideFindTarget == null) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_unknown_find")); return; } if (!GuideFinder.GateAllows(guideFindTarget, requester.LivePlayer, out var refusal)) { MercSay(merc, mercenary, sender, refusal); MercPlugin.Log("Guide find '" + guideFindTarget.Key + "' refused by progression gate for " + requester.PlayerName + "."); return; } bool flag = guideFindTarget.Kind == GuideFindKind.Creature; float maxDistance = ((guideFindTarget.Kind == GuideFindKind.Location) ? 1000000f : 2000f); Vector3 position = requester.Position; string what = GuideFinder.TargetLabelArgument(guideFindTarget); bool flag2 = GuideFinder.FindNearest(guideFindTarget, requester.Position, maxDistance, out position, out what); if (!flag2 && !flag) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_no_known_target", what ?? GuideFinder.TargetLabelArgument(guideFindTarget))); return; } if ((Object)(object)mercenary == (Object)null) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_cannot_move")); return; } float num2 = Vector3.Distance(requester.Position, position); string englishDirection2 = GuideFinder.Direction(requester.Position, position); List waypoints2 = null; Vector3 blockage2 = Vector3.zero; if (flag2 && !GuideFinder.PlanRoute(requester.Position, position, out waypoints2, out blockage2) && ((Object)(object)mercenary.Ai == (Object)null || !mercenary.Ai.TestLandPath(position))) { string text2 = GuideFinder.Direction(requester.Position, blockage2); MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_water_route", MercLocalization.DirectionArgument(text2))); MercPlugin.Log("Guide find '" + guideFindTarget.Key + "' refused for " + requester.PlayerName + ": no land route around the open water " + text2 + " of them."); return; } bool flag3 = waypoints2 != null && waypoints2.Count > 1; Vector3 destination3 = ((waypoints2 != null && waypoints2.Count > 0) ? waypoints2[0] : position); SetMercFollowing(merc, requester); EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set("merc_guideFind", guideFindTarget.Key); merc.MercZdo.Set("merc_guideRoute", flag3 ? GuideFinder.SerializeRoute(waypoints2) : ""); mercenary.RequestGuideTask(requester, GuideTaskType.Custom, BossTarget.None, destination3); if (!flag2) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_target_not_seen", GuideFinder.TargetLabelArgument(guideFindTarget))); return; } if (flag3) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_target_land_detour", what)); MercPlugin.Log($"Guide find '{guideFindTarget.Key}' for {requester.PlayerName}: {num2:F0}m away, " + $"{waypoints2.Count}-waymark land route planned around open water."); return; } MercSay(merc, mercenary, sender, (num2 > 2000f) ? MercLocalization.Phrase("dnpc_speech_target_far", what, MercLocalization.DirectionArgument(englishDirection2)) : MercLocalization.Phrase("dnpc_speech_target_nearest", what)); MercPlugin.Log($"Guide find '{guideFindTarget.Key}' for {requester.PlayerName}: {num2:F0}m away, guiding."); return; } default: num = 0; break; case 6: num = 5; break; case 5: num = 4; break; case 1: num = 1; break; } GuideTaskType guideTaskType = (GuideTaskType)num; if (guideTaskType == GuideTaskType.None || !Enum.IsDefined(typeof(BossTarget), bossValue)) { MercSay(merc, mercenary, sender, MercLocalization.Phrase("dnpc_speech_unknown_option")); return; } EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set("merc_guideFind", ""); SetMercFollowing(merc, requester); GuideTaskResult guideTaskResult = GuideTasks.Execute(merc, new GuideCommand(guideTaskType, (BossTarget)bossValue), requester, GuideClientSnapshot.FromJson(hintsJson)); MercSay(merc, mercenary, sender, guideTaskResult?.Message ?? MercLocalization.Phrase("dnpc_speech_on_it")); } catch (Exception ex) { MercPlugin.LogWarn("Guide menu request failed: " + ex); MercSay(merc, LiveMerc(merc), sender, MercLocalization.Phrase("dnpc_speech_trip_failed")); } } } private static Mercenary LiveMerc(MercenaryState merc) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)ZNetScene.instance != (Object)null && merc != null) ? ZNetScene.instance.FindInstance(merc.MercId) : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent(); } private static void OnMercDismissRequest(long sender, ZDOID mercId) { //IL_000b: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) MercPlugin.Log($"Mercenary dismiss request received: sender={sender}, mercZdo={mercId}."); if (TryValidateMercRequest("dismiss", sender, mercId, 8f, requireAssigned: true, out var _, out var merc)) { MercResourceWork.CancelForMerc(mercId); EnsureZdoServerOwnership(merc.BannerZdo); int num = (int)merc.Class; merc.BannerZdo.Set(Mercenary.BannerFollowNames[num], ""); EnsureZdoServerOwnership(merc.MercZdo); Mercenary.SetFollowName(merc.MercZdo, ""); merc.MercZdo.Set(Mercenary.ZdoStayDay, WorldDataService.CurrentDay, false); Vector3 position = merc.BannerZdo.GetPosition(); merc.MercZdo.Set(Mercenary.ZdoStayAnchor, position); merc.MercZdo.Set(Mercenary.ZdoPreciseHold, false); merc.MercZdo.Set(MercAI.ZdoGuideTask, ""); merc.MercZdo.Set("merc_guideFind", ""); merc.MercZdo.Set("merc_guideRoute", ""); float num2 = Vector3.Distance(merc.Position, position); if (num2 > 30f && ZDOMan.instance != null) { merc.MercZdo.SetPosition(position); ZDOMan.instance.ForceSendZDO(merc.MercZdo.m_uid); } LiveMerc(merc)?.NotifySimulationOwnerOfStateChange(refillFood: false); SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_merc_returning", merc.Name)); LlmBrain.RecordEvent("dismiss:" + merc.Name, merc.Name + " was sent back to the banner to stand guard.", 2f); MercPlugin.Log($"Mercenary dismiss request accepted: sender={sender}, merc={merc.Name}, " + $"follow cleared, stay order at banner (distance {num2:F0}m)."); } } private static void OnMercConversationRequest(long sender, ZDOID mercId, string payload) { //IL_0021: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(payload) || payload.Length > 10000) { return; } MercPlugin.Log($"Merc conversation request received: sender={sender}, mercZdo={mercId}."); if (!TryValidateMercRequest("conversation", sender, mercId, Mathf.Max(1f, MercConfig.ChatHearingRange.Value), requireAssigned: false, out var requester, out var merc)) { return; } if (!MercConfig.HearPlayerChat.Value) { RejectMercRequest(sender, "conversation", mercId, "HearPlayerChat is disabled"); } else { if (!TryBeginAi(sender, requester, null, merc, agentMode: false)) { return; } Dictionary dictionary = MiniJson.Parse(payload ?? "") as Dictionary; string value = ((dictionary != null && dictionary.TryGetValue("q", out var value2)) ? (value2 as string) : ""); object value3; string json = ((dictionary != null && dictionary.TryGetValue("g", out value3)) ? (value3 as string) : ""); bool flag = default(bool); int num; if (dictionary != null && dictionary.TryGetValue("p", out var value4)) { if (value4 is bool) { flag = (bool)value4; num = 1; } else { num = 0; } } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; value = Bound(value, 1000); if (string.IsNullOrWhiteSpace(value)) { AbortAi(sender); RejectMercRequest(sender, "conversation", mercId, "question payload was empty"); } else if (flag2) { AbortAi(sender); } else { MercPlugin.Log($"Merc conversation request accepted: sender={sender}, merc={merc.Name}, " + $"playerId={requester.PlayerId}, isEmployer={IsAssignedTo(merc, requester, requireFollowing: false)}, " + $"isFollowingSpeaker={IsAssignedTo(merc, requester, requireFollowing: true)}, " + $"distance={Vector3.Distance(merc.Position, requester.Position):F1}m."); LlmBrain.ProcessServerRequest(sender, requester, merc, value, agentMode: false, GuideClientSnapshot.FromJson(json), flag2); } } } private static void OnMercResurrectionRequest(long sender, ZDOID mercId, ZDOID playerId) { //IL_000b: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) MercPlugin.Log($"Resurrect request received: sender={sender}, mercZdo={mercId}, playerZdo={playerId}."); if (TryValidateMercRequest("resurrect", sender, mercId, Mathf.Max(1f, MercConfig.ResurrectionRange.Value), requireAssigned: true, out var requester, out var merc)) { if (merc.Class != MercClass.Healer || requester.CharacterZdoId != playerId || merc.FollowName != requester.PlayerName || !MercConfig.ResurrectionEnabled.Value) { RejectMercRequest(sender, "resurrect", mercId, "healer, following, or player validation failed"); return; } if (merc.MercZdo.GetLong(Mercenary.ZdoResurrectReady, 0L) > (long)Math.Floor(ResurrectionManager.NetworkTime)) { RejectMercRequest(sender, "resurrect", mercId, "Resurrect is on cooldown"); return; } long num = ResolveSimulationPeer(merc, sender); ZRoutedRpc.instance.InvokeRoutedRPC(num, "DynamicNPCs_MercResurrectionApplyV1", new object[2] { mercId, playerId }); MercPlugin.Log($"Resurrect request accepted: sender={sender}, merc={merc.Name}, executionPeer={num}."); } } private static void OnMercSpeechReply(long sender, ZDOID mercId, string speaker, string text) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (IsAuthoritativeServerSender(sender)) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(mercId) : null); Mercenary mercenary = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)mercenary != (Object)null) { mercenary.ShowServerSpeech(text, addToChat: true); } else if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString(speaker, MercLocalization.Resolve(text), (Type)1, false); } } } private static void OnMercGuideApply(long sender, ZDOID mercId, ZDOID playerId, string payload) { //IL_001e: 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) if (IsAuthoritativeServerSender(sender)) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(mercId) : null); (((Object)(object)val != (Object)null) ? val.GetComponent() : null)?.ApplyServerGuidePayload(playerId, payload); } } private static void OnMercResurrectionApply(long sender, ZDOID mercId, ZDOID playerId) { //IL_001e: 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) if (IsAuthoritativeServerSender(sender)) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(mercId) : null); (((Object)(object)val != (Object)null) ? val.GetComponent() : null)?.ApplyServerResurrection(playerId); } } private static void OnContextTargetApply(long sender, ZDOID mercId, ZDOID playerId, int modeValue, ZDOID targetId) { //IL_0035: 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_0055: Unknown result type (might be due to invalid IL or missing references) if (IsAuthoritativeServerSender(sender) && Enum.IsDefined(typeof(MercContextTargetMode), modeValue)) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(mercId) : null); (((Object)(object)val != (Object)null) ? val.GetComponent() : null)?.ApplyServerContextTarget(playerId, modeValue, targetId); } } private static bool TryValidateMercRequest(string operation, long sender, ZDOID mercId, float range, bool requireAssigned, out SenderPlayerState requester, out MercenaryState merc) { //IL_001d: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0178: 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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0094: 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_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) requester = null; merc = null; if (!IsServer()) { return false; } if (!TryResolveSender(sender, out requester)) { return RejectMercRequest(sender, operation, mercId, "sender could not be resolved"); } if (requester.IsDead) { return RejectMercRequest(sender, operation, mercId, "requesting player is dead"); } if (!TryResolveMercenary(mercId, out merc, out var reason, allowMissingBanner: true)) { return RejectMercRequest(sender, operation, mercId, reason); } if (merc.BannerZdo == null) { if (!CompanyPresetRules.MayRepair(merc.EmployerId, requester.PlayerId, Vector3.Distance(requester.Position, merc.Position), range)) { return RejectMercRequest(sender, operation, mercId, "orphan recovery requires its nearby authenticated employer"); } ZDO val = NearestClaimedBanner(ListBannersForRehire(), requester, merc.Position); if (val == null) { return RejectMercRequest(sender, operation, mercId, "the banner this mercenary answered to is gone; place a Mercenary Banner to take command"); } if (!BannerAuthority.TryAdoptLivingOrphan(val, merc.MercZdo)) { return RejectMercRequest(sender, operation, mercId, "the company role already has a living member"); } ZDOID val2 = Mercenary.BannerIdOf(merc.MercZdo); if (!((ZDOID)(ref val2)).IsNone() && ZDOMan.instance != null) { ZDOMan.instance.ForceSendZDO(merc.MercZdo.m_uid); } if (!TryResolveMercenary(mercId, out merc, out var reason2)) { return RejectMercRequest(sender, operation, mercId, reason2); } MercPlugin.Log($"Healed mercenary {mercId} banner link to {val.m_uid} for {requester.PlayerName} ({operation})."); } float num = Vector3.Distance(requester.Position, merc.Position); if (num > range) { return RejectMercRequest(sender, operation, mercId, $"distance {num:F1}m exceeds {range:F1}m; player={requester.Position}, merc={merc.Position}"); } if (requireAssigned && !IsAssignedTo(merc, requester, requireFollowing: false)) { return RejectMercRequest(sender, operation, mercId, $"mercenary serves playerId={merc.EmployerId}, name={merc.EmployerName}"); } return true; } internal static bool TryResolveMercenary(ZDOID mercId, out MercenaryState state, out string reason, bool allowMissingBanner = false) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0123: 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_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) state = null; reason = "mercenary ZDO was not found"; if (ZDOMan.instance == null || ((ZDOID)(ref mercId)).IsNone()) { return false; } ZDO zDO = ZDOMan.instance.GetZDO(mercId); if (zDO == null || !zDO.IsValid()) { return false; } int prefab = zDO.GetPrefab(); MercClass mercClass; if (prefab == StringExtensionMethods.GetStableHashCode("Merc_Tank")) { mercClass = MercClass.Tank; } else if (prefab == StringExtensionMethods.GetStableHashCode("Merc_Healer")) { mercClass = MercClass.Healer; } else { if (prefab != StringExtensionMethods.GetStableHashCode("Merc_Archer")) { reason = $"ZDO prefab {prefab} is not a DynamicNPC mercenary"; return false; } mercClass = MercClass.Archer; } ZDOID val = Mercenary.BannerIdOf(zDO); ZDO val2 = ((!((ZDOID)(ref val)).IsNone()) ? ZDOMan.instance.GetZDO(val) : null); if (val2 == null || !val2.IsValid() || val2.GetPrefab() != StringExtensionMethods.GetStableHashCode("MercBanner")) { if (!allowMissingBanner) { reason = $"mercenary banner {val} is missing"; return false; } val2 = null; } int num = (int)mercClass; string text = ((val2 != null) ? val2.GetString(MercBannerSpawn.CustomNameKeys[num], MercBannerSpawn.DefaultFirstName(mercClass)) : zDO.GetString(Mercenary.ZdoCustomName, MercBannerSpawn.DefaultFirstName(mercClass))); state = new MercenaryState { MercId = mercId, MercZdo = zDO, BannerId = val, BannerZdo = val2, Class = mercClass, Name = text + " " + RoleTitle(mercClass), Position = zDO.GetPosition(), EmployerId = ((val2 != null) ? val2.GetLong(Mercenary.BannerEmployerIds[num], 0L) : Mercenary.EmployerIdOf(zDO)), EmployerName = ((val2 != null) ? val2.GetString(Mercenary.BannerEmployerNames[num], "") : Mercenary.EmployerNameOf(zDO)), FollowName = ((val2 != null) ? val2.GetString(Mercenary.BannerFollowNames[num], "") : Mercenary.FollowNameOf(zDO)) }; reason = ""; return true; } internal static bool IsAssignedTo(MercenaryState merc, SenderPlayerState requester, bool requireFollowing) { if (merc == null || requester == null) { return false; } if (merc.EmployerId == 0L || requester.PlayerId == 0L) { return false; } if (merc.EmployerId == requester.PlayerId) { if (requireFollowing) { return merc.FollowName == requester.PlayerName; } return true; } return false; } internal static void EnsureZdoServerOwnership(ZDO zdo) { try { if (zdo != null && zdo.IsValid() && (Object)(object)ZNet.instance != (Object)null && zdo.GetOwner() != ZNet.GetUID()) { zdo.SetOwner(ZNet.GetUID()); } } catch (Exception ex) { MercPlugin.LogWarn("Could not claim ZDO ownership before writing: " + ex.Message); } } private static bool RejectMercRequest(long sender, string operation, ZDOID mercId, string reason) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) MercPlugin.LogWarn($"Mercenary {operation} request rejected: sender={sender}, mercZdo={mercId}, reason={reason}."); SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_merc_request_failed")); return false; } internal static string RoleTitle(MercClass @class) { return MercLocalization.EnglishText(RoleToken(@class)); } private static string RoleArgument(MercClass @class) { return MercLocalization.TokenArgument(RoleToken(@class)); } private static string RoleToken(MercClass @class) { return @class switch { MercClass.Healer => "dnpc_role_mender", MercClass.Tank => "dnpc_role_bulwark", _ => "dnpc_role_fletcher", }; } internal static void MarkDeliberateRemoval(ZDOID id) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!((ZDOID)(ref id)).IsNone()) { AnnouncedFallen.Add(id); } } internal static void AnnounceMercenaryFallen(Mercenary fallen) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (IsServer() && !((Object)(object)fallen == (Object)null)) { ZNetView component = ((Component)fallen).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null || AnnouncedFallen.Add(val.m_uid)) { AnnounceFallenByName(fallen.GetName(), fallen); } } } internal static void AnnounceRosterFall(string name) { if (IsServer()) { AnnounceFallenByName(name, null); } } private static void AnnounceFallenByName(string name, Mercenary fallen) { try { float num = ((MercConfig.RespawnDelaySeconds != null) ? MercConfig.RespawnDelaySeconds.Value : 60f); string text = ((num <= 90f) ? MercLocalization.Phrase("dnpc_speech_fallen_minute", name) : MercLocalization.Phrase("dnpc_speech_fallen_minutes", name, Mathf.CeilToInt(num / 60f))); Mercenary mercenary = null; foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance == (Object)(object)fallen) && !((Character)instance).IsDead()) { if (instance.Class == MercClass.Healer) { mercenary = instance; break; } mercenary = mercenary ?? instance; } } if ((Object)(object)mercenary != (Object)null) { mercenary.Say(text); } else if ((Object)(object)fallen != (Object)null) { ZNetView component = ((Component)fallen).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); long num2 = ((val != null) ? Mercenary.EmployerIdOf(val) : 0); if (num2 != 0L && TryFindConnectedPeerByPlayerId(num2, out var peerUid, out var _)) { SendPlayerMessage(peerUid, text); } } MercPlugin.Log(MercLocalization.Resolve(text)); } catch (Exception ex) { MercPlugin.LogWarn("Mercenary fall announcement failed: " + ex.Message); } } internal static void SendMercSpeech(long target, MercenaryState merc, string text) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null && merc != null && !string.IsNullOrWhiteSpace(text)) { ZRoutedRpc.instance.InvokeRoutedRPC(target, "DynamicNPCs_MercSpeechReplyV1", new object[3] { merc.MercId, Bound(merc.Name, 60), Bound(text, 1200) }); } } internal static void MercSay(MercenaryState merc, Mercenary live, long targetPeer, string text) { if (merc != null && !string.IsNullOrWhiteSpace(text)) { if ((Object)(object)live == (Object)null) { live = LiveMerc(merc); } if ((Object)(object)live != (Object)null) { live.Say(text); } else { SendMercSpeech(targetPeer, merc, text); } } } internal static void SetMercFollowing(MercenaryState merc, SenderPlayerState requester) { if (merc == null || merc.BannerZdo == null || merc.MercZdo == null || requester == null || requester.PlayerId == 0L || !IsServer()) { return; } try { if (!(merc.FollowName == requester.PlayerName) || merc.MercZdo.GetInt(Mercenary.ZdoStayDay, 0) != 0 || merc.MercZdo.GetBool(Mercenary.ZdoPreciseHold, false)) { EnsureZdoServerOwnership(merc.BannerZdo); int num = (int)merc.Class; merc.BannerZdo.Set(Mercenary.BannerFollowNames[num], requester.PlayerName); merc.BannerZdo.Set(Mercenary.BannerEmployerIds[num], requester.PlayerId); merc.BannerZdo.Set(Mercenary.BannerEmployerNames[num], requester.PlayerName); EnsureZdoServerOwnership(merc.MercZdo); merc.MercZdo.Set(Mercenary.ZdoStayDay, 0, false); merc.MercZdo.Set(Mercenary.ZdoPreciseHold, false); Mercenary.SetFollowName(merc.MercZdo, requester.PlayerName); LiveMerc(merc)?.NotifySimulationOwnerOfStateChange(refillFood: false); MercPlugin.Log("Guide auto-follow: " + merc.Name + " now follows " + requester.PlayerName + "."); } } catch (Exception ex) { MercPlugin.LogWarn("Guide auto-follow failed: " + ex.Message); } } internal static void EnsureGuideFollow(Mercenary merc, MercenaryState mercState, Player player, SenderPlayerState requester) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (!IsServer()) { return; } long num = requester?.PlayerId ?? 0; string text = requester?.PlayerName; if (num == 0L && (Object)(object)player != (Object)null) { num = player.GetPlayerID(); text = player.GetPlayerName(); } if (num == 0L || string.IsNullOrEmpty(text)) { return; } try { MercenaryState state = mercState; if (state == null && (Object)(object)merc != (Object)null) { ZNetView component = ((Component)merc).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null || !TryResolveMercenary(val.m_uid, out state, out var _)) { return; } } SetMercFollowing(state, new SenderPlayerState { PlayerId = num, PlayerName = text }); } catch (Exception ex) { MercPlugin.LogWarn("Guide follow resolve failed: " + ex.Message); } } internal static void SendGuideTask(long target, MercenaryState merc, ZDOID playerId, GuideTaskType task, BossTarget boss, Vector3 destination) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: 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) if (ZRoutedRpc.instance != null && merc != null) { string[] obj = new string[11] { "{\"task\":", null, null, null, null, null, null, null, null, null, null }; int num = (int)task; obj[1] = num.ToString(); obj[2] = ",\"boss\":"; num = (int)boss; obj[3] = num.ToString(); obj[4] = ",\"x\":"; obj[5] = destination.x.ToString(CultureInfo.InvariantCulture); obj[6] = ",\"y\":"; obj[7] = destination.y.ToString(CultureInfo.InvariantCulture); obj[8] = ",\"z\":"; obj[9] = destination.z.ToString(CultureInfo.InvariantCulture); obj[10] = "}"; string text = string.Concat(obj); long num2 = ResolveSimulationPeer(merc, target); ZRoutedRpc.instance.InvokeRoutedRPC(num2, "DynamicNPCs_MercGuideApplyV1", new object[3] { merc.MercId, playerId, text }); MercPlugin.Log($"Guide execution routed: merc={merc.Name}, mercZdo={merc.MercId}, " + $"playerZdo={playerId}, executionPeer={num2}, task={task}, boss={boss}."); } } private static void SendContextTarget(long target, MercenaryState merc, ZDOID playerId, MercContextTargetMode mode, ZDOID targetId) { //IL_0027: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null && merc != null) { long num = ResolveSimulationPeer(merc, target); ZRoutedRpc.instance.InvokeRoutedRPC(num, "DynamicNPCs_ContextTargetApplyV1", new object[4] { merc.MercId, playerId, (int)mode, targetId }); MercPlugin.Log($"Context target routed: merc={merc.Name}, executionPeer={num}, " + $"mode={mode}, target={targetId}."); } } private static long ResolveSimulationPeer(MercenaryState merc, long fallbackPeer) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (merc?.MercZdo == null || (Object)(object)ZNet.instance == (Object)null) { return fallbackPeer; } long owner = merc.MercZdo.GetOwner(); if (owner == 0L) { return fallbackPeer; } if (owner == ZNet.GetUID()) { if (!((Object)(object)(((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(merc.MercId) : null) != (Object)null)) { return fallbackPeer; } return owner; } if (ZNet.instance.GetPeer(owner) == null) { return fallbackPeer; } return owner; } private static void OnMercRehireRequest(long sender, ZDOID mercId, int progressionSeed) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_011a: 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_0147: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b0: Unknown result type (might be due to invalid IL or missing references) //IL_038e: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_04ee: Unknown result type (might be due to invalid IL or missing references) //IL_0578: Unknown result type (might be due to invalid IL or missing references) //IL_058b: Unknown result type (might be due to invalid IL or missing references) ZDO val = ((ZDOMan.instance != null && !((ZDOID)(ref mercId)).IsNone()) ? ZDOMan.instance.GetZDO(mercId) : null); ZDOID val2 = (ZDOID)((val != null) ? Mercenary.BannerIdOf(val) : default(ZDOID)); ZDO banner = ((ZDOMan.instance != null && !((ZDOID)(ref val2)).IsNone()) ? ZDOMan.instance.GetZDO(val2) : null); long num = ((val != null) ? val.GetOwner() : 0); long num2 = Mercenary.EmployerIdOf(val); string arg = Mercenary.EmployerNameOf(val); long num3 = ((banner != null) ? banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) : 0); MercPlugin.Log($"Mercenary rehire request received: sender={sender}, mercZdo={mercId}, " + $"bannerZdo={val2}, mercNetworkOwner={num}, " + $"serverUid={(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0)}, " + $"ownerId={num2}, ownerName={arg}, bannerClaimId={num3}."); if (!IsServer()) { RejectMercRehire(sender, mercId, "handler is not the authoritative server"); return; } if (!TryResolveSender(sender, out var state)) { RejectMercRehire(sender, mercId, "sender player/character ZDO could not be resolved"); return; } if (state.IsDead) { RejectMercRehire(sender, mercId, "requesting player is dead"); return; } bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (flag && !WorldDataService.IsWorldStateReady) { RejectMercRehire(sender, mercId, "the current world's progression store is not ready"); return; } if (val == null || !val.IsValid()) { RejectMercRehire(sender, mercId, "mercenary ZDO was not found"); return; } int num4 = val.GetInt(Mercenary.ZdoClass, -1); if (!Enum.IsDefined(typeof(MercClass), num4)) { RejectMercRehire(sender, mercId, $"invalid mercenary class {num4}"); return; } MercClass mercClass = (MercClass)num4; if (val.GetPrefab() != StringExtensionMethods.GetStableHashCode(mercClass switch { MercClass.Archer => "Merc_Archer", MercClass.Healer => "Merc_Healer", _ => "Merc_Tank", })) { RejectMercRehire(sender, mercId, "ZDO prefab does not match its mercenary class"); return; } List list = ListBannersForRehire(); bool flag2 = banner == null || !banner.IsValid() || banner.GetPrefab() != StringExtensionMethods.GetStableHashCode("MercBanner"); bool flag3 = !flag2 && !list.Exists((ZDO b) => b != null && b.m_uid == banner.m_uid); bool flag4 = false; if (flag2 || flag3) { ZDO val3 = NearestClaimedBanner(list, state, val.GetPosition()); if (val3 == null) { RejectMercRehire(sender, mercId, flag3 ? "the banner this mercenary answers to is derelict; place and claim a banner to take it in" : $"banner ZDO {val2} was not found and no claimed banner exists to relink"); return; } banner = val3; val2 = val3.m_uid; num3 = state.PlayerId; flag4 = true; } float num5 = Vector3.Distance(state.Position, val.GetPosition()); if (num5 > 8f) { RejectMercRehire(sender, mercId, $"distance {num5:F1}m exceeds 8m; player={state.Position}, merc={val.GetPosition()}"); return; } if (num3 != state.PlayerId) { RejectMercRehire(sender, mercId, $"banner claim ID {num3} does not match requester {state.PlayerId}"); return; } if (num2 != 0L && num2 != state.PlayerId) { RejectMercRehire(sender, mercId, $"mercenary already serves player ID {num2} ({arg})"); return; } int num6 = EnsureAuthoritativeRequesterStage(state, "mercenary rehire server fallback"); if (flag && num6 < 0) { RejectMercRehire(sender, mercId, "the authoritative progression stage could not be persisted"); return; } if (flag4) { Mercenary.SetBannerId(val, val2); MercPlugin.Log($"Relinked stray mercenary {mercId} to claimed banner {val2} for rehire " + $"(broken link: {flag2})."); } int num7 = Mathf.Clamp(num4, 0, MercBannerSpawn.Classes.Length - 1); if (banner.GetOwner() != ZNet.GetUID()) { banner.SetOwner(ZNet.GetUID()); } banner.Set(Mercenary.BannerEmployerIds[num7], state.PlayerId); banner.Set(Mercenary.BannerEmployerNames[num7], state.PlayerName); banner.Set(Mercenary.BannerFollowNames[num7], state.PlayerName); banner.Set(MercBannerSpawn.RecruitedKeys[num7], true); banner.Set(MercBannerSpawn.MemberIdKeys[num7], mercId); banner.Set(MercBannerSpawn.LastSeenKeys[num7], ((Object)(object)ZNet.instance != (Object)null) ? ((long)ZNet.instance.GetTimeSeconds()) : 0); if (val.GetOwner() == ZNet.GetUID()) { Mercenary.RestoreEmploymentFromBanner(val, banner, mercClass); } if (num6 >= 0) { Mercenary.SetProgressionStage(val, num6); } GameObject val4 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(mercId) : null); Mercenary mercenary = (((Object)(object)val4 != (Object)null) ? val4.GetComponent() : null); mercenary?.NotifySimulationOwnerOfStateChange(refillFood: true); string text = (((Object)(object)mercenary != (Object)null) ? mercenary.GetName() : (val.GetString(Mercenary.ZdoCustomName, MercBannerSpawn.DefaultFirstName(mercClass)) + " " + mercClass switch { MercClass.Healer => "the Mender", MercClass.Tank => "the Bulwark", _ => "the Fletcher", })); MercPlugin.Log($"Mercenary rehire request accepted: sender={sender}, mercZdo={mercId}, " + $"bannerZdo={val2}, class={mercClass}, ownerId={state.PlayerId}, " + "ownerName=" + state.PlayerName + ", follow=" + state.PlayerName + ", " + $"clientStageHint={progressionSeed}, authoritativeStage={num6}."); SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_joined_company", text)); LlmBrain.RecordEvent("rehire:" + text, text + " returned to active service.", 2f); } private static void RejectMercRehire(long sender, ZDOID mercId, string reason) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) MercPlugin.LogWarn($"Mercenary rehire request rejected: sender={sender}, mercZdo={mercId}, reason={reason}."); SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_rehire_failed")); } internal static List ListBannersForRehire() { List list = new List(); if (ZDOMan.instance == null) { return list; } int num = 0; int num2 = 0; while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative("MercBanner", list, ref num) && ++num2 <= 200000) { } return list; } internal static ZDO NearestClaimedBanner(List banners, SenderPlayerState requester, Vector3 position) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) ZDO val = null; float num = float.MaxValue; foreach (ZDO banner in banners) { if (banner != null && banner.IsValid() && banner.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == requester.PlayerId) { float num2 = Vector3.Distance(banner.GetPosition(), position); if (val == null || num2 < num) { val = banner; num = num2; } } } return val; } private static void OnCleanupReply(long sender, string message) { if (IsAuthoritativeServerSender(sender)) { string text = message ?? MercLocalization.Text("dnpc_message_cleanup_finished"); MercPlugin.Log(text); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false); } } } private static void OnPlayerMessage(long sender, string message) { if (IsAuthoritativeServerSender(sender)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, MercLocalization.Resolve(message), 0, (Sprite)null, false); } } } internal static void SendPlayerMessage(long target, string message) { if (ZRoutedRpc.instance != null && !string.IsNullOrWhiteSpace(message)) { if (target == ZNet.GetUID() && (Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, MercLocalization.Resolve(message), 0, (Sprite)null, false); return; } ZRoutedRpc.instance.InvokeRoutedRPC(target, "DynamicNPCs_PlayerMessageV1", new object[1] { Bound(message, 300) }); } } private static void RejectCleanup(long sender, string reason) { MercPlugin.LogWarn($"Admin NPC cleanup request rejected: sender={sender}, reason={reason}."); SendCleanupReply(sender, "NPC cleanup rejected: " + reason + "."); } private static void SendCleanupReply(long target, string message) { if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(target, "DynamicNPCs_AdminClearRadiusReplyV1", new object[1] { message ?? "" }); } } private static bool TryDestroyNonPlayerCharacters(Vector3 center, float radius, out int mercenaryCount, out int npcCount, out int debrisCount, out string failure) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) mercenaryCount = 0; npcCount = 0; debrisCount = 0; failure = ""; if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || ZdoObjectsById == null) { failure = "server world records are unavailable"; return false; } try { if (!(ZdoObjectsById.GetValue(ZDOMan.instance) is IDictionary dictionary)) { failure = "server ZDO index is unavailable"; return false; } float num = radius * radius; List list = new List(); List list2 = new List(); List list3 = new List(); foreach (ZDO item in new List(dictionary.Values)) { if (item == null || !item.IsValid()) { continue; } Vector3 val = item.GetPosition() - center; if (((Vector3)(ref val)).sqrMagnitude > num) { continue; } int prefab = item.GetPrefab(); GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab); if ((Object)(object)prefab2 == (Object)null) { if (prefab != 0) { list3.Add(item); MercPlugin.Log($"Cleanup candidate (unknown prefab {prefab}): " + $"{item.m_uid} at {item.GetPosition()}."); } } else if (!((Object)(object)prefab2.GetComponent() == (Object)null) && !((Object)(object)prefab2.GetComponent() != (Object)null) && !prefab2.GetComponent().IsTamed() && !((Object)(object)prefab2.GetComponent() != (Object)null)) { if ((Object)(object)prefab2.GetComponent() != (Object)null || prefab == StringExtensionMethods.GetStableHashCode("Merc_Tank") || prefab == StringExtensionMethods.GetStableHashCode("Merc_Healer") || prefab == StringExtensionMethods.GetStableHashCode("Merc_Archer")) { list.Add(item); } else { list2.Add(item); } } } foreach (ZDO item2 in list) { DestroyIsolated(item2, "admin radius cleanup (mercenary)", ref mercenaryCount); } foreach (ZDO item3 in list2) { DestroyIsolated(item3, "admin radius cleanup (npc)", ref npcCount); } foreach (ZDO item4 in list3) { DestroyIsolated(item4, "admin radius cleanup (orphaned object)", ref debrisCount); } return true; } catch (Exception ex) { MercPlugin.LogWarn($"NPC cleanup scan failed: {ex}"); failure = "cleanup scan failed: " + ex.Message; return false; } } private static void DestroyIsolated(ZDO zdo, string reason, ref int removedCount) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { if (zdo != null && zdo.IsValid()) { MarkDeliberateRemoval(zdo.m_uid); BannerAuthority.DestroyWorldZdo(zdo, reason); removedCount++; } } catch (Exception arg) { MercPlugin.LogWarn("Cleanup skipped " + ((zdo != null) ? ((object)Unsafe.As(ref zdo.m_uid)/*cast due to .constrained prefix*/).ToString() : "") + " " + $"({reason}): {arg}"); } } internal static bool TryBeginAi(long sender, SenderPlayerState requester, Mercenary merc, bool agentMode) { return TryBeginAi(sender, requester, merc, null, agentMode); } internal static bool TryBeginAi(long sender, SenderPlayerState requester, Mercenary merc, MercenaryState mercState, bool agentMode) { if (!IsServer() || requester == null) { return false; } int tickCount = Environment.TickCount; string text = null; lock (RequestLock) { WatchdogAndEvictLocked(tickCount); int value; if (PendingAi.ContainsKey(sender)) { text = "I am still answering your last question."; } else if (LastAiRequest.TryGetValue(sender, out value) && tickCount - value >= 0 && (float)(tickCount - value) < Mathf.Max(0.25f, MercConfig.AiRequestCooldownSeconds.Value) * 1000f) { text = "Give me a moment, then ask again."; } else if (!WithinServerRateBudgetLocked(tickCount)) { text = "The whole company needs a breath; ask again in a little while."; } else { PendingAi[sender] = tickCount; LastAiRequest[sender] = tickCount; } } if (text == null) { return true; } if (agentMode) { SendAgentReply(sender, text); } else if ((Object)(object)merc != (Object)null) { merc.Say(text); } else { SendMercSpeech(sender, mercState, text); } return false; } private static void WatchdogAndEvictLocked(int now) { if (PendingAi.Count > 0) { List list = new List(); foreach (KeyValuePair item in PendingAi) { if (now - item.Value > 15000) { list.Add(item.Key); } } foreach (long item2 in list) { PendingAi.Remove(item2); MercPlugin.LogWarn($"Dialogue pending slot for sender {item2} timed out and was released."); } } if (LastAiRequest.Count <= 32 || !((Object)(object)ZNet.instance != (Object)null)) { return; } List list2 = new List(); foreach (long key in LastAiRequest.Keys) { if (key != ZNet.GetUID() && ZNet.instance.GetPeer(key) == null) { list2.Add(key); } } foreach (long item3 in list2) { LastAiRequest.Remove(item3); PendingAi.Remove(item3); } } private static bool WithinServerRateBudgetLocked(int now) { return KnowledgeRules.TryConsumeRequestBudget(AiRequestTimes, now, MercConfig.ServerDialogueRequestsPerMinute.Value); } internal static bool TryUseKnowledgeBudget(long sender) { if (!IsServer()) { return false; } lock (RequestLock) { if (WithinServerRateBudgetLocked(Environment.TickCount)) { return true; } } SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_knowledge_busy")); return false; } internal static void CompleteAi(long sender, Mercenary merc, bool agentMode, string text) { CompleteAi(sender, merc, null, agentMode, text); } internal static void CompleteAi(long sender, Mercenary merc, MercenaryState mercState, bool agentMode, string text) { try { string text2 = Bound(text, 1200); if (string.IsNullOrEmpty(text2)) { text2 = "I cannot answer that just now."; } if (agentMode) { SendAgentReply(sender, text2); } else if ((Object)(object)merc != (Object)null) { merc.Say(text2); } else { SendMercSpeech(sender, mercState, text2); } } finally { lock (RequestLock) { PendingAi.Remove(sender); } } } internal static void CompleteAiSilent(long sender) { lock (RequestLock) { PendingAi.Remove(sender); } } internal static void BeginServerProactiveComment(Mercenary merc, Player player) { } internal static void AbortAi(long sender) { lock (RequestLock) { PendingAi.Remove(sender); } } private static void SendAgentReply(long target, string reply) { if (ZRoutedRpc.instance != null) { string text = Bound(MercConfig.AgentName.Value, 60); if (string.IsNullOrEmpty(text)) { text = "Valheim Agent"; } if (target == ZNet.GetUID() && (Object)(object)Player.m_localPlayer != (Object)null) { LlmBrain.SayAsAgent(reply, text); return; } ZRoutedRpc.instance.InvokeRoutedRPC(target, "MercAgentReplyV1", new object[2] { text, Bound(reply, 1200) }); } } internal static bool TryResolveSender(long sender, out SenderPlayerState state) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_012e: 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_0203: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) state = null; if ((Object)(object)ZNet.instance == (Object)null) { LogSenderResolution(sender, null, default(ZDOID), null, 0L, "", resolved: false); return false; } ZNetPeer peer = ZNet.instance.GetPeer(sender); ZDOID val = (ZDOID)(((??)peer?.m_characterID) ?? default(ZDOID)); Player val2 = null; if (peer != null && peer.m_uid == sender && !((ZDOID)(ref val)).IsNone()) { val2 = FindLivePlayer(val); } else { if (sender != ZNet.GetUID() || !((Object)(object)Player.m_localPlayer != (Object)null)) { LogSenderResolution(sender, peer, val, null, 0L, (peer != null) ? peer.m_playerName : "", resolved: false); return false; } val2 = Player.m_localPlayer; ZNetView component = ((Component)val2).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { val = component.GetZDO().m_uid; } } ZDO val3 = ((ZDOMan.instance != null && !((ZDOID)(ref val)).IsNone()) ? ZDOMan.instance.GetZDO(val) : null); if ((val3 == null || !val3.IsValid()) && (Object)(object)val2 != (Object)null) { ZNetView component2 = ((Component)val2).GetComponent(); val3 = (((Object)(object)component2 != (Object)null && component2.IsValid()) ? component2.GetZDO() : null); if (val3 != null) { val = val3.m_uid; } } if ((val3 == null || !val3.IsValid()) && (Object)(object)val2 == (Object)null) { LogSenderResolution(sender, peer, val, null, 0L, (peer != null) ? peer.m_playerName : "", resolved: false); return false; } long num = ((val3 != null) ? val3.GetLong(ZDOVars.s_playerID, ((Object)(object)val2 != (Object)null) ? val2.GetPlayerID() : 0) : (((Object)(object)val2 != (Object)null) ? val2.GetPlayerID() : 0)); string text = ((val3 != null) ? val3.GetString(ZDOVars.s_playerName, ((Object)(object)val2 != (Object)null) ? val2.GetPlayerName() : ((peer != null) ? peer.m_playerName : "")) : (((Object)(object)val2 != (Object)null) ? val2.GetPlayerName() : ((peer != null) ? peer.m_playerName : ""))); Vector3 position = ((val3 != null) ? val3.GetPosition() : ((Component)val2).transform.position); bool isDead = ((val3 == null) ? ((Character)val2).IsDead() : (val3.GetBool(ZDOVars.s_dead, false) || val3.GetFloat(ZDOVars.s_health, 1f) <= 0f)); int rightItemHash = ((val3 != null) ? val3.GetInt(ZDOVars.s_rightItem, 0) : 0); if (num != 0L && !ValidatePinnedIdentity(sender, peer, num)) { LogSenderResolution(sender, peer, val, val2, num, text, resolved: false); return false; } state = new SenderPlayerState { SenderUid = sender, PeerUid = (peer?.m_uid ?? sender), CharacterZdoId = val, LivePlayer = val2, PlayerZdo = val3, PlayerId = num, PlayerName = (string.IsNullOrWhiteSpace(text) ? "Player" : text), Position = position, IsDead = isDead, RightItemHash = rightItemHash }; LogSenderResolution(sender, peer, val, val2, state.PlayerId, state.PlayerName, resolved: true); return true; } private static Player FindLivePlayer(ZDOID characterId) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref characterId)).IsNone()) { return null; } if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject val = ZNetScene.instance.FindInstance(characterId); Player val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 != (Object)null) { return val2; } } foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null)) { ZNetView component = ((Component)allPlayer).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && component.GetZDO().m_uid == characterId) { return allPlayer; } } } return null; } private static void LogSenderResolution(long sender, ZNetPeer peer, ZDOID characterId, Player livePlayer, long playerId, string playerName, bool resolved) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) MercPlugin.LogDebug($"sender resolver: senderUid={sender}, " + $"peerUid={peer?.m_uid ?? 0}, characterZdoId={characterId}, " + $"livePlayer={(Object)(object)livePlayer != (Object)null}, playerId={playerId}, " + "playerName=" + (string.IsNullOrWhiteSpace(playerName) ? "" : playerName) + ", " + $"resolved={resolved}."); } private static bool ValidatePinnedIdentity(long sender, ZNetPeer peer, long playerId) { if (peer == null && sender == ZNet.GetUID()) { return true; } long num = peer?.m_uid ?? sender; lock (IdentityLock) { if (PinnedPeerPlayerIds.Count > 64 && (Object)(object)ZNet.instance != (Object)null) { List list = new List(); foreach (long key in PinnedPeerPlayerIds.Keys) { if (key != ZNet.GetUID() && ZNet.instance.GetPeer(key) == null) { list.Add(key); } } foreach (long item in list) { if (PinnedPeerPlayerIds.TryGetValue(item, out var value) && PinnedPlayerPeers.TryGetValue(value, out var value2) && value2 == item) { PinnedPlayerPeers.Remove(value); } PinnedPeerPlayerIds.Remove(item); } } if (PinnedPeerPlayerIds.TryGetValue(num, out var value3)) { if (value3 != playerId) { MercPlugin.LogWarn($"Identity pin rejected: peer {num} claimed playerId {playerId} " + $"after {value3} was pinned for this connection."); return false; } return true; } if (PinnedPlayerPeers.TryGetValue(playerId, out var value4) && (value4 == ZNet.GetUID() || ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetPeer(value4) != null)) && value4 != num) { MercPlugin.LogWarn($"Identity pin rejected: playerId {playerId} is already pinned to " + $"live peer {value4}; refusing peer {num}."); return false; } PinnedPeerPlayerIds[num] = playerId; PinnedPlayerPeers[playerId] = num; return true; } } internal static bool IsSenderAdmin(long sender) { if (!IsServer()) { return false; } try { if (sender == ZNet.GetUID()) { return ZNet.instance.LocalPlayerIsAdminOrHost(); } ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj = ((peer == null) ? null : PeerSocketField?.GetValue(peer)); string text = ((obj != null) ? AccessTools.Method(obj.GetType(), "GetHostName", (Type[])null, (Type[])null) : null)?.Invoke(obj, null) as string; return !string.IsNullOrEmpty(text) && ZNet.instance.IsAdmin(text); } catch (Exception ex) { MercPlugin.LogWarn("Could not verify remote admin for banner removal: " + ex.Message); return false; } } internal static int SeedStageFromLiveGear(long playerId) { return SeedStageFromObservedGear(playerId, null); } private static int SeedStageFromObservedGear(long playerId, ZDO boundPlayerZdo) { try { if (playerId == 0L || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !WorldDataService.IsWorldStateReady) { return -1; } Player livePlayer; int num = StageFromReplicatedEquipment(FindConnectedPlayerEquipmentZdo(playerId, boundPlayerZdo, out livePlayer)); if (num < 0) { return -1; } if ((Object)(object)livePlayer != (Object)null) { try { foreach (ItemData equippedItem in ((Humanoid)livePlayer).GetInventory().GetEquippedItems()) { if (equippedItem != null && !((Object)(object)equippedItem.m_dropPrefab == (Object)null)) { int num2 = GearTable.StageOfItem(Utils.GetPrefabName(equippedItem.m_dropPrefab)); if (num2 > num) { num = num2; } } } } catch (Exception ex) { MercPlugin.LogDebug("Live inventory was unavailable during gear seeding: " + ex.Message); } } int stage = PlayerProgression.GetStage(playerId); if (stage >= num) { return (boundPlayerZdo != null) ? PlayerProgression.EnsureSeeded(playerId, num, "equipped gear (server-observed)") : stage; } return PlayerProgression.EnsureSeeded(playerId, num, "equipped gear (server-observed)"); } catch (Exception ex2) { MercPlugin.LogWarn("Live gear stage seeding failed: " + ex2.Message); } return -1; } private static ZDO FindConnectedPlayerEquipmentZdo(long playerId, ZDO boundPlayerZdo, out Player livePlayer) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) livePlayer = null; if (boundPlayerZdo != null && boundPlayerZdo.IsValid() && boundPlayerZdo.GetLong(ZDOVars.s_playerID, 0L) == playerId) { livePlayer = FindLivePlayer(boundPlayerZdo.m_uid); return boundPlayerZdo; } foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && allPlayer.GetPlayerID() == playerId) { livePlayer = allPlayer; ZNetView component = ((Component)allPlayer).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val != null && val.IsValid()) { return val; } break; } } if (ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null) { return null; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO != null && zDO.IsValid() && zDO.GetLong(ZDOVars.s_playerID, 0L) == playerId) { return zDO; } } } return null; } internal static int StageFromReplicatedEquipment(ZDO playerZdo) { if (playerZdo == null || !playerZdo.IsValid()) { return MercProgressionRules.ResolveObservedEquipmentStage(false); } return MercProgressionRules.ResolveObservedEquipmentStage(true, GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_leftItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_rightItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_leftBackItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_rightBackItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_chestItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_legItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_helmetItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_shoulderItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_utilityItem, 0)), GearTable.StageOfItemHash(playerZdo.GetInt(ZDOVars.s_trinketItem, 0))); } internal static int EnsureAuthoritativePlayerStage(long playerId, string fallbackSource) { return EnsureAuthoritativePlayerStageCore(playerId, fallbackSource, null); } private static int EnsureAuthoritativePlayerStageCore(long playerId, string fallbackSource, ZDO boundPlayerZdo) { bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (!flag) { return StageDirector.GetStage(); } if (!MercProgressionRules.CanMutatePersonalProgression(flag, WorldDataService.IsWorldStateReady)) { return -1; } int num = SeedStageFromObservedGear(playerId, boundPlayerZdo); if (num < 0) { return PlayerProgression.EnsureSeeded(playerId, 0, fallbackSource); } return num; } internal static int EnsureAuthoritativeRequesterStage(SenderPlayerState requester, string fallbackSource) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (MercConfig.ProgressionPerPlayer == null || !MercConfig.ProgressionPerPlayer.Value) { return StageDirector.GetStage(); } if (requester == null || requester.PlayerId == 0L || requester.PlayerZdo == null || !requester.PlayerZdo.IsValid() || requester.PlayerZdo.m_uid != requester.CharacterZdoId || requester.PlayerZdo.GetLong(ZDOVars.s_playerID, 0L) != requester.PlayerId) { return -1; } return EnsureAuthoritativePlayerStageCore(requester.PlayerId, fallbackSource, requester.PlayerZdo); } internal static bool TryFindConnectedPeerByPlayerId(long playerId, out long peerUid, out string playerName) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) peerUid = 0L; playerName = ""; if (playerId == 0L || (Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return false; } try { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO != null && zDO.IsValid() && zDO.GetLong(ZDOVars.s_playerID, 0L) == playerId) { peerUid = peer.m_uid; playerName = zDO.GetString(ZDOVars.s_playerName, peer.m_playerName ?? "Player"); return true; } } } } catch (Exception ex) { MercPlugin.LogWarn($"Connected peer/player-ID lookup failed for playerId={playerId}: {ex.Message}"); } return false; } internal static bool IsAuthoritativeServerSender(long sender) { try { if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (IsServer()) { return sender == ZNet.GetUID(); } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); return serverPeer != null && serverPeer.m_uid == sender; } catch { return false; } } private static bool IsServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } private static string ReadString(Dictionary data, string key) { if (data == null || !data.TryGetValue(key, out var value) || !(value is string value2)) { return ""; } return Bound(value2, (key == "q") ? 1000 : 8000); } private static string Bound(string value, int max) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (text.Length > max) { text = text.Substring(0, max); } if (text.Length > 0 && char.IsHighSurrogate(text[text.Length - 1])) { text = text.Substring(0, text.Length - 1); } return text; } internal static void RegisterResourceWork() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _resourceRpc) { instance.Register("DynamicNPCs_ResourceWorkRequestV1", (Method)OnResourceRequest); instance.Register("DynamicNPCs_ResourceWorkApplyV1", (Method)OnResourceApply); instance.Register("DynamicNPCs_ResourceWorkSwingV1", (Action)OnResourceSwing); instance.Register("DynamicNPCs_ResourceWorkStopV1", (Action)OnResourceStop); _resourceRpc = instance; } } internal static void ResetResourceWork() { ResourceJobs.Clear(); } internal static void RequestResourceWork(ZDOID target, Vector3 point, int collider, int sequence) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) Register(); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_ResourceWorkRequestV1", new object[4] { target, point, collider, sequence }); } } internal static void RequestResourceSwing(ZDOID merc, int token, bool strike) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_ResourceWorkSwingV1", new object[3] { merc, token, strike }); } } internal static void CancelResourceWork(ZDOID merc) { //IL_000c: 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_003c: Unknown result type (might be due to invalid IL or missing references) if (IsServer() && ResourceJobs.TryGetValue(merc, out var value)) { ResourceJobs.Remove(merc); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(0L, "DynamicNPCs_ResourceWorkStopV1", new object[2] { merc, value.Token }); } } } private static void OnResourceRequest(long sender, ZDOID id, Vector3 point, int collider, int sequence) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || MercConfig.ContextCompanyOrdersEnabled == null || !MercConfig.ContextCompanyOrdersEnabled.Value || !TryResolveSender(sender, out var state) || state.IsDead || !TryAcceptContextOrder(sender, sequence, out var prefab)) { return; } if (!MercResourceWork.TryResolve(id, point, collider, out var target) || Vector3.Distance(state.Position, target.Point) > 35f || !MercResourceWork.ClearLine(state.Position + Vector3.up * 1.5f, target, ((Object)(object)state.LivePlayer != (Object)null) ? ((Component)state.LivePlayer).transform : null)) { SendPlayerMessage(sender, MercLocalization.Phrase("dnpc_message_work_target_failed")); return; } List list = new List(); foreach (KeyValuePair resourceJob in ResourceJobs) { if (Time.time >= resourceJob.Value.Expires) { list.Add(resourceJob.Key); } } foreach (ZDOID item in list) { CancelResourceWork(item); } if (ResourceJobs.Count >= 256) { return; } int num = 0; bool flag = false; int num2 = ResourceStage(state); foreach (MercenaryState item2 in ListOwnedCompany(state)) { Mercenary mercenary = LiveMerc(item2); if (!IsAssignedTo(item2, state, requireFollowing: true) || (Object)(object)mercenary == (Object)null || (Object)(object)mercenary.Ai == (Object)null || !ResourceContextAllowed(mercenary, state) || Vector3.Distance(((Component)mercenary).transform.position, target.Point) > 35f) { continue; } if (!MercResourceWork.TryTool(num2, target, out var _, out prefab)) { flag = true; continue; } CancelResourceWork(item2.MercId); int num3 = ++_resourceToken; if (num3 == 0) { num3 = ++_resourceToken; } long owner = item2.MercZdo.GetOwner(); if (owner != 0L) { ResourceJobs[item2.MercId] = new ResourceJob { Requester = sender, PlayerId = state.PlayerId, Owner = owner, Token = num3, Expires = Time.time + 120f, NextHit = Time.time + 0.5f, Target = target, Stage = num2 }; ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(0L, "DynamicNPCs_ResourceWorkApplyV1", new object[5] { item2.MercId, id, point, collider | (num2 << 12), num3 }); } num++; } } SendPlayerMessage(sender, MercLocalization.Phrase((num <= 0) ? (flag ? "dnpc_message_work_tier_failed" : "dnpc_message_work_followers_failed") : (target.Chopping ? "dnpc_message_work_chop" : "dnpc_message_work_mine"))); } private static bool ResourceContextAllowed(Mercenary merc, SenderPlayerState requester) { //IL_0051: 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) if ((Object)(object)merc != (Object)null && requester != null && !requester.IsDead && MercResourceRules.CanWork(!((Character)merc).IsDead(), merc.IsAssignedTo(requester, requireFollowing: true), ((Character)merc).InWater() || ((Character)merc).IsSwimming(), ((Character)merc).IsAttachedToShip(), MercStealth.ShouldFollowSneak(merc), Vector3.Distance(((Component)merc).transform.position, requester.Position)) && (Object)(object)merc.Ai != (Object)null && !merc.Ai.IsResurrectionActive && !merc.Ai.HasResourceWorkThreat()) { if (!((Object)(object)requester.LivePlayer == (Object)null)) { if (!((Character)requester.LivePlayer).IsAttachedToShip() && !((Character)requester.LivePlayer).InWater()) { return !((Character)requester.LivePlayer).IsSwimming(); } return false; } return true; } return false; } private static void OnResourceApply(long sender, ZDOID mercId, ZDOID targetId, Vector3 localPoint, int colliderAndStage, int token) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (!IsAuthoritativeServerSender(sender) || token == 0 || (Object)(object)ZNetScene.instance == (Object)null || colliderAndStage < 0 || colliderAndStage > 32767) { return; } int colliderIndex = colliderAndStage & 0xFFF; int stage = colliderAndStage >> 12; GameObject obj = ZNetScene.instance.FindInstance(mercId); MercAI mercAI = ((obj != null) ? obj.GetComponent() : null); if (!((Object)(object)mercAI == (Object)null) && MercResourceWork.TryResolve(targetId, localPoint, colliderIndex, out var target)) { ZNetView component = ((Component)mercAI).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner()) { mercAI.PrepareResourceWork(); } mercAI.ResourceWorker.Begin(mercAI, target, token, stage); } } private static void OnResourceStop(long sender, ZDOID mercId, int token) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (IsAuthoritativeServerSender(sender) && !((Object)(object)ZNetScene.instance == (Object)null)) { GameObject obj = ZNetScene.instance.FindInstance(mercId); MercAI mercAI = ((obj != null) ? obj.GetComponent() : null); mercAI?.ResourceWorker.CancelToken(mercAI, token); } } private static void OnResourceSwing(long sender, ZDOID mercId, int token, bool strike) { //IL_000c: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: 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_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Expected O, but got Unknown if (!IsServer() || !ResourceJobs.TryGetValue(mercId, out var value) || value.Token != token) { return; } ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(mercId) : null); if (sender != value.Owner && (val == null || val.GetOwner() != sender)) { return; } if (!strike || sender != value.Owner) { CancelResourceWork(mercId); } else { if (Time.time < value.NextHit) { return; } value.NextHit = Time.time + 2f - 0.1f; if (Time.time >= value.Expires || value.Target == null || !value.Target.Valid || !TryResolveSender(value.Requester, out var state) || state.PlayerId != value.PlayerId || !TryResolveMercenary(mercId, out var state2, out var prefab) || state2.MercZdo.GetOwner() != sender || !IsAssignedTo(state2, state, requireFollowing: true)) { CancelResourceWork(mercId); return; } Mercenary mercenary = LiveMerc(state2); if (!ResourceContextAllowed(mercenary, state) || (Object)(object)mercenary.Ai == (Object)null || mercenary.Ai.IsGuiding || mercenary.Ai.IsResurrectionActive || mercenary.Ai.HasResourceWorkThreat() || MercConfig.ContextCompanyOrdersEnabled == null || !MercConfig.ContextCompanyOrdersEnabled.Value || Vector3.Distance(state.Position, value.Target.Point) > 35f || Vector3.Distance(((Character)mercenary).GetCenterPoint(), value.Target.Point) > 3.5f || !MercResourceWork.ClearLine(((Character)mercenary).GetCenterPoint(), value.Target, ((Component)mercenary).transform) || ResourceStage(state) < value.Stage || !MercResourceWork.TryTool(value.Stage, value.Target, out var tool, out prefab)) { CancelResourceWork(mercId); return; } DamageTypes damage = tool.GetDamage(); HitData val2 = new HitData { m_point = value.Target.Point }; Vector3 val3 = value.Target.Point - ((Character)mercenary).GetCenterPoint(); val2.m_dir = ((Vector3)(ref val3)).normalized; val2.m_hitCollider = value.Target.Collider; val2.m_toolTier = (short)tool.m_shared.m_toolTier; val2.m_skill = tool.m_shared.m_skillType; val2.m_radius = 0f; val2.m_damage = new DamageTypes { m_chop = (value.Target.Chopping ? damage.m_chop : 0f), m_pickaxe = (value.Target.Chopping ? 0f : damage.m_pickaxe) }; HitData val4 = val2; val4.SetAttacker((Character)(object)mercenary); value.Target.Damage(val4); } } private static int ResourceStage(SenderPlayerState requester) { if (requester == null || requester.PlayerId == 0L) { return -1; } return Mathf.Max(0, Mathf.Max(PlayerProgression.GetKnownStage(requester.PlayerId), StageFromReplicatedEquipment(requester.PlayerZdo))); } } internal enum CompanyFormation { Roles, Tight, Line } internal enum CompanyDuty { Follow, Hold } internal sealed class CompanyPreset { internal string Name = string.Empty; internal CompanyFormation Formation; internal CompanyDuty Duty; } internal static class CompanyPresetRules { internal const int MaximumPresets = 8; internal static bool ValidName(string name) { if (!string.IsNullOrEmpty(name) && name.Length <= 24) { return name.All((char c) => (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'); } return false; } internal static List Decode(string text) { List list = new List(); if (string.IsNullOrEmpty(text)) { return list; } if (text.Length > 512) { throw new ArgumentException("Preset store exceeds its limit."); } HashSet hashSet = new HashSet(StringComparer.Ordinal); string[] array = text.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ',' }); if (array2.Length != 3 || !ValidName(array2[0]) || !hashSet.Add(array2[0]) || !int.TryParse(array2[1], out var result) || result < 0 || result > 2 || !int.TryParse(array2[2], out var result2) || result2 < 0 || result2 > 1) { throw new ArgumentException("Preset store contains an invalid entry."); } list.Add(new CompanyPreset { Name = array2[0], Formation = (CompanyFormation)result, Duty = (CompanyDuty)result2 }); if (list.Count > 8) { throw new ArgumentException("At most eight presets are supported."); } } return list; } internal static string Encode(IEnumerable presets) { string text = string.Join(";", presets.OrderBy((CompanyPreset p) => p.Name, StringComparer.Ordinal).Select(delegate(CompanyPreset p) { string[] obj = new string[5] { p.Name, ",", null, null, null }; int formation = (int)p.Formation; obj[2] = formation.ToString(); obj[3] = ","; formation = (int)p.Duty; obj[4] = formation.ToString(); return string.Concat(obj); })); Decode(text); return text; } internal static MercMovementSlot Slot(MercClass role, MercMovementMode mode, CompanyFormation formation) { MercMovementSlot result = MercMovementRules.Slot(role, mode); if (mode != MercMovementMode.BaseRoam) { switch (formation) { case CompanyFormation.Roles: break; case CompanyFormation.Line: return new MercMovementSlot(0f, -3f - (float)role * 2.5f, 1.1f, 12f); default: return new MercMovementSlot(result.Side * 0.7f, result.Forward * 0.7f, Math.Max(0.8f, result.SoftRadius * 0.7f), result.MaxReferenceDistance); } } return result; } internal static bool MayRepair(long employerId, long requesterId, float distance, float maximumDistance) { if (employerId != 0L && employerId == requesterId && !float.IsNaN(distance) && !float.IsInfinity(distance) && distance >= 0f) { return distance <= maximumDistance; } return false; } } public static class ConversationRouter { private static int _lastMessageHash; private static string _lastMessageText = ""; private static float _lastMessageAt = -10f; public static bool TryHearAgentChat(Type type, string text) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (string.IsNullOrWhiteSpace(text) || (int)type == 3) { return false; } if (!text.StartsWith("!", StringComparison.Ordinal)) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return true; } if (!MercConfig.AgentChatEnabled.Value) { LlmBrain.SayAsAgent(MercLocalization.Text("dnpc_agent_disabled")); return true; } string text2 = text.Substring(1).TrimStart(Array.Empty()); if (string.IsNullOrWhiteSpace(text2)) { LlmBrain.SayAsAgent(MercLocalization.Text("dnpc_agent_question_hint")); return true; } if (IsImmediateDuplicate(text)) { return true; } if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString(localPlayer.GetPlayerName(), text, (Type)1, false); } LlmBrain.AskAgent(text2); return true; } public static void HearLocalPlayerChat(Type type, string text) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || string.IsNullOrWhiteSpace(text) || (int)type == 3) { return; } string text2 = text.Trim(); if (!text2.StartsWith("/", StringComparison.Ordinal) && !TryHearAgentChat(type, text2) && MercConfig.HearPlayerChat.Value && !IsImmediateDuplicate(text2) && !SnikkVisitService.TryHearGossipChat(localPlayer, text2) && !FarmerService.TryHearChat(localPlayer, text2)) { Mercenary mercenary = SelectResponder(localPlayer, text2); if ((Object)(object)mercenary == (Object)null) { MercPlugin.LogDebug($"No recruited mercenary within {Mathf.Max(1f, MercConfig.ChatHearingRange.Value):0}m heard the local chat line."); return; } MercPlugin.LogDebug(mercenary.GetName() + " heard " + localPlayer.GetPlayerName() + " within " + $"{Vector3.Distance(((Component)mercenary).transform.position, ((Component)localPlayer).transform.position):0}m"); LlmBrain.Ask(mercenary, text2); } } private static bool IsImmediateDuplicate(string message) { int hashCode = message.GetHashCode(); if (hashCode == _lastMessageHash && message == _lastMessageText && Time.unscaledTime - _lastMessageAt < 0.75f) { return true; } _lastMessageHash = hashCode; _lastMessageText = message; _lastMessageAt = Time.unscaledTime; return false; } private static Mercenary SelectResponder(Player player, string message) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(1f, MercConfig.ChatHearingRange.Value); string words = " " + Regex.Replace(message.ToLowerInvariant(), "[^a-z0-9]+", " ") + " "; Mercenary result = null; int num2 = int.MinValue; float num3 = float.MaxValue; foreach (Mercenary instance in Mercenary.Instances) { if ((Object)(object)instance == (Object)null || ((Character)instance).IsDead()) { continue; } float num4 = Vector3.Distance(((Component)instance).transform.position, ((Component)player).transform.position); if (!(num4 > num)) { int num5 = AddressScore(instance, words); if (instance.IsAssignedTo(player, requireFollowing: true)) { num5 += 50; } if (num5 > num2 || (num5 == num2 && num4 < num3)) { result = instance; num2 = num5; num3 = num4; } } } return result; } private static int AddressScore(Mercenary merc, string words) { string name = merc.GetName(); int num = name.IndexOf(" the ", StringComparison.OrdinalIgnoreCase); string text = ((num > 0) ? name.Substring(0, num) : name); switch (merc.Class) { case MercClass.Healer: if (!HasAny(words, name, text, "mender", "healer")) { return 0; } return 100; case MercClass.Archer: if (!HasAny(words, name, text, "fletcher", "archer")) { return 0; } return 100; default: if (!HasAny(words, name, text, "bulwark", "tank")) { return 0; } return 100; } } private static bool HasAny(string words, params string[] choices) { foreach (string text in choices) { if (!string.IsNullOrWhiteSpace(text)) { string text2 = Regex.Replace(text.ToLowerInvariant(), "[^a-z0-9]+", " ").Trim(); if (text2.Length > 0 && words.Contains(" " + text2 + " ")) { return true; } } } return false; } } internal sealed class DialogueObservation { internal DialogueContext Context = new DialogueContext(); internal bool HealthKnown; internal float HealthFraction; internal bool WetKnown; internal bool NightKnown; internal bool CombatKnown; internal bool ThreatsKnown; } internal sealed class DialogueEventState { private sealed class Channel { internal string Current = ""; internal string Committed = ""; internal double Since; } internal const double NormalGapSeconds = 35.0; internal const double UrgentGapSeconds = 8.0; internal const double RepeatSeconds = 180.0; internal const double StaleSeconds = 10.0; private readonly Dictionary _channels = new Dictionary(); private readonly Dictionary _lastEvents = new Dictionary(); private readonly Dictionary _variants = new Dictionary(); private double _lastObservation = double.NegativeInfinity; private double _lastSpeech = double.NegativeInfinity; private double _conversationUntil = double.NegativeInfinity; private bool _lowHealth; private bool _healthKnown; internal void ReserveConversation(double now) { if (Finite(now)) { _lastSpeech = now; _conversationUntil = now + 35.0; } } internal bool MayOfferOptionalRemark(double now) { if (Finite(now) && now >= _conversationUntil) { return now - _lastSpeech >= 35.0; } return false; } internal string Observe(DialogueObservation observation, double now) { if (observation == null || observation.Context == null || !Finite(now)) { return ""; } if (now < _lastObservation) { _lastSpeech = double.NegativeInfinity; _conversationUntil = double.NegativeInfinity; _lastEvents.Clear(); _variants.Clear(); } if (now < _lastObservation || now - _lastObservation > 10.0) { _channels.Clear(); _healthKnown = false; } _lastObservation = now; DialogueContext context = observation.Context; bool flag = observation.HealthKnown && Finite(observation.HealthFraction) && observation.HealthFraction > 0f && observation.HealthFraction <= 1f; if (flag) { if (!_healthKnown) { _lowHealth = observation.HealthFraction <= 0.35f; } else if (_lowHealth) { _lowHealth = observation.HealthFraction < 0.6f; } else { _lowHealth = observation.HealthFraction <= 0.35f; } } _healthKnown = flag; List list = new List(6); Add(list, Change("health", flag, _lowHealth ? "low_health" : "recovered", now, (!_lowHealth) ? 6 : 0, _lowHealth)); string text = context.Threat ?? ""; Add(list, Change("threat", observation.ThreatsKnown, (text.Length == 0) ? "" : ("threat_" + text), now, 2.0, text.Length != 0)); string text2 = Change("combat", observation.CombatKnown, context.InCombat ? "combat" : "clear", now, (!context.InCombat) ? 8 : 0, context.InCombat); if (text2 != "combat" || text.Length == 0) { Add(list, text2); } bool flag2 = context.InteriorKnown && !context.IsInterior; Add(list, Change("biome", flag2 && context.BiomeKnown && !string.IsNullOrEmpty(context.Biome), "biome_" + context.Biome, now, 6.0)); Add(list, Change("wet", observation.WetKnown, context.IsWet ? "wet" : "dry", now, 4.0)); Add(list, Change("night", flag2 && observation.NightKnown, context.IsNight ? "night" : "dawn", now, 4.0)); if (now < _conversationUntil) { return ""; } foreach (string item in list) { bool flag3 = item == "low_health" || item == "combat" || item.StartsWith("threat_", StringComparison.Ordinal); if ((flag3 || (!context.InCombat && observation.CombatKnown && observation.ThreatsKnown && text.Length == 0)) && !(now - _lastSpeech < (flag3 ? 8.0 : 35.0)) && (!_lastEvents.TryGetValue(item, out var value) || !(now - value < 180.0))) { _lastSpeech = now; _lastEvents[item] = now; return item; } } return ""; } internal int NextVariant(string eventKey, DialogueRole role) { string key = eventKey + ":" + role; _variants.TryGetValue(key, out var value); _variants[key] = 1 - value; return value; } private string Change(string channelKey, bool known, string value, double now, double delay, bool warnInitially = false) { if (!known) { _channels.Remove(channelKey); return ""; } if (!_channels.TryGetValue(channelKey, out var value2)) { value2 = new Channel { Current = value, Committed = (warnInitially ? "" : value), Since = now }; _channels[channelKey] = value2; } if (value2.Current != value) { value2.Current = value; value2.Since = now; } if (value2.Committed == value || now - value2.Since < delay) { return ""; } value2.Committed = value; return value; } private static void Add(List candidates, string candidate) { if (!string.IsNullOrEmpty(candidate)) { candidates.Add(candidate); } } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } internal static class DialogueObservationRules { internal const double CombatFreshSeconds = 8.0; internal static bool IsFreshCombatObservation(long currentOwner, long observingOwner, long sampledMilliseconds, double nowSeconds) { if (currentOwner == 0L || currentOwner != observingOwner || sampledMilliseconds <= 0 || double.IsNaN(nowSeconds) || double.IsInfinity(nowSeconds) || nowSeconds <= 0.0) { return false; } double num = nowSeconds - (double)sampledMilliseconds / 1000.0; if (num >= -0.5) { return num <= 8.0; } return false; } } internal enum DialogueRole { Tank, Healer, Archer, Farmer, Visitor, Guide } internal sealed class DialogueContext { internal string Biome = ""; internal string Threat = ""; internal bool InCombat; internal bool LowHealth; internal bool IsNight; internal bool IsWet; internal bool HealthKnown; internal bool WetKnown; internal bool NightKnown; internal bool ThreatsKnown; internal bool CombatKnown; internal bool BiomeKnown; internal bool InteriorKnown; internal bool IsInterior; internal bool SpeakerHealthKnown; internal bool SpeakerLowHealth; } internal static class DialogueRules { private enum QuestionFacet { General, Location, Combat, Taming, Breeding, Riding, Food, Drop, Summon, Craft, Process, Repair, Prepare, Unknown } internal const int MaximumQuestionLength = 512; private static readonly string[][] Creatures = new string[18][] { new string[3] { "troll", "troll", "trolls" }, new string[4] { "skeleton", "skeleton", "skeletons", "rancid remains" }, new string[6] { "greydwarf", "greydwarf", "greydwarfs", "greydwarves", "greydwarf brute", "greydwarf shaman" }, new string[3] { "draugr", "draugr", "draugrs" }, new string[5] { "blob", "blob", "blobs", "oozer", "oozers" }, new string[3] { "abomination", "abomination", "abominations" }, new string[3] { "wraith", "wraith", "wraiths" }, new string[3] { "wolf", "wolf", "wolves" }, new string[3] { "drake", "drake", "drakes" }, new string[4] { "stonegolem", "stone golem", "stone golems", "stonegolem" }, new string[5] { "fuling", "fuling", "fulings", "goblin", "goblins" }, new string[6] { "deathsquito", "deathsquito", "deathsquitos", "deathsquitoes", "mosquito", "mosquitoes" }, new string[3] { "lox", "lox", "loxen" }, new string[5] { "growth", "growth", "growths", "tar pit", "tar pits" }, new string[5] { "seeker", "seeker", "seekers", "seeker soldier", "seeker soldiers" }, new string[3] { "gjall", "gjall", "gjalls" }, new string[3] { "tick", "tick", "ticks" }, new string[4] { "serpent", "serpent", "serpents", "sea serpent" } }; private static readonly string[][] Bosses = new string[7][] { new string[2] { "eikthyr", "eikthyr" }, new string[3] { "elder", "elder", "the elder" }, new string[3] { "bonemass", "bonemass", "bone mass" }, new string[2] { "moder", "moder" }, new string[2] { "yagluth", "yagluth" }, new string[3] { "queen", "queen", "the queen" }, new string[2] { "fader", "fader" } }; private static readonly string[][] OtherCreatures = new string[14][] { new string[3] { "boar", "boar", "boars" }, new string[2] { "deer", "deer" }, new string[3] { "hare", "hare", "hares" }, new string[5] { "hen", "hen", "hens", "chicken", "chickens" }, new string[3] { "neck", "neck", "necks" }, new string[3] { "leech", "leech", "leeches" }, new string[3] { "surtling", "surtling", "surtlings" }, new string[3] { "fenring", "fenring", "fenrings" }, new string[3] { "bat", "bat", "bats" }, new string[4] { "dvergr", "dvergr", "dverger", "dvergers" }, new string[3] { "asksvin", "asksvin", "asksvins" }, new string[3] { "morgen", "morgen", "morgens" }, new string[5] { "valkyrie", "fallen valkyrie", "fallen valkyries", "valkyrie", "valkyries" }, new string[5] { "charred", "charred", "charred warrior", "charred marksman", "charred twitcher" } }; private static readonly string[][] Materials = new string[30][] { new string[5] { "iron", "iron", "scrap iron", "sunken crypt", "sunken crypts" }, new string[2] { "copper", "copper" }, new string[2] { "tin", "tin" }, new string[2] { "bronze", "bronze" }, new string[3] { "silver", "silver", "wishbone" }, new string[3] { "blackmetal", "black metal", "blackmetal" }, new string[2] { "flametal", "flametal" }, new string[2] { "finewood", "fine wood" }, new string[2] { "corewood", "core wood" }, new string[3] { "ancientbark", "ancient bark", "elder bark" }, new string[3] { "surtlingcore", "surtling core", "surtling cores" }, new string[3] { "greydwarfeye", "greydwarf eye", "greydwarf eyes" }, new string[3] { "queenbee", "queen bee", "queen bees" }, new string[4] { "honey", "honey", "beehive", "beehives" }, new string[3] { "trollhide", "troll hide", "troll hides" }, new string[3] { "wolfpelt", "wolf pelt", "wolf pelts" }, new string[3] { "deerhide", "deer hide", "deer hides" }, new string[2] { "leather", "leather scraps" }, new string[3] { "chain", "chain", "chains" }, new string[2] { "coal", "coal" }, new string[2] { "flint", "flint" }, new string[2] { "obsidian", "obsidian" }, new string[2] { "chitin", "chitin" }, new string[3] { "turnipseeds", "turnip seeds", "turnip seed" }, new string[3] { "onionseeds", "onion seeds", "onion seed" }, new string[3] { "carrotseeds", "carrot seeds", "carrot seed" }, new string[3] { "yggdrasilwood", "yggdrasil wood", "yggdrasilwood" }, new string[2] { "softtissue", "soft tissue" }, new string[2] { "sap", "sap" }, new string[3] { "eitr", "refined eitr", "eitr" } }; private static readonly string[][] Biomes = new string[9][] { new string[3] { "meadows", "meadows", "meadow" }, new string[3] { "blackforest", "black forest", "blackforest" }, new string[3] { "swamp", "swamp", "swamps" }, new string[3] { "mountain", "mountain", "mountains" }, new string[2] { "plains", "plains" }, new string[3] { "mistlands", "mistlands", "mistland" }, new string[3] { "ashlands", "ashlands", "ashland" }, new string[3] { "ocean", "ocean", "sea" }, new string[3] { "deepnorth", "deep north", "deepnorth" } }; private static readonly string[][] Topics = new string[25][] { new string[5] { "iron", "iron", "scrap iron", "sunken crypt", "sunken crypts" }, new string[4] { "copper_tin", "copper", "tin", "bronze" }, new string[3] { "silver", "silver", "wishbone" }, new string[4] { "advanced_metal", "black metal", "blackmetal", "flametal" }, new string[15] { "farming", "farm", "farming", "crop", "crops", "plant", "planting", "carrot", "carrots", "turnip", "turnips", "barley", "flax", "onion", "onions" }, new string[6] { "wood", "wood", "fine wood", "core wood", "tree", "trees" }, new string[8] { "corpse", "tombstone", "corpse", "grave", "died", "death", "body back", "gear back" }, new string[5] { "portal", "portal", "portals", "teleport", "teleporting" }, new string[11] { "sailing", "boat", "boats", "ship", "sailing", "sail", "wind", "river", "swim", "swimming", "water" }, new string[5] { "poison", "poison", "poisoned", "poison resistance", "antidote" }, new string[5] { "cold", "cold", "freezing", "frost", "frost resistance" }, new string[4] { "fire", "fire", "burning", "fire resistance" }, new string[7] { "rest", "rested", "rest", "resting", "comfort", "sleep", "bed" }, new string[9] { "food", "food", "foods", "eat", "eating", "hungry", "hunger", "cook", "cooking" }, new string[10] { "heal", "heal", "healing", "heal me", "hurt", "injured", "wounded", "health", "potion", "mead" }, new string[4] { "stamina", "stamina", "tired", "exhausted" }, new string[9] { "block", "block", "blocking", "parry", "parrying", "shield", "shields", "dodge", "dodging" }, new string[5] { "repair", "repair", "repairing", "broken", "durability" }, new string[14] { "combat", "fight", "fighting", "combat", "attack", "weapon", "weapons", "sword", "mace", "bow", "arrow", "arrows", "spear", "axe" }, new string[8] { "progression", "progression", "progress", "boss", "bosses", "forsaken", "what next", "next step" }, new string[6] { "identity", "who are you", "your name", "about yourself", "your story", "your role" }, new string[10] { "orders", "command", "commands", "orders", "follow", "stay", "guard", "hold", "go home", "come here" }, new string[4] { "joke", "joke", "jokes", "make me laugh" }, new string[7] { "fear", "afraid", "scared", "fear", "frightened", "worried", "panic" }, new string[8] { "help", "help", "advice", "tip", "tips", "what can you do", "what should we do", "what should i do" } }; internal static string ReplyToken(string question, DialogueRole role, DialogueContext context, int variant) { string text = IntentKey(question, context); if (text.StartsWith("fact_", StringComparison.Ordinal)) { return "dnpc_dialogue_" + text; } object obj; switch (role) { case DialogueRole.Farmer: case DialogueRole.Visitor: case DialogueRole.Guide: switch (text) { case "reply_identity": case "reply_help": case "reply_orders": return "dnpc_dialogue_neutral_" + text + "_" + role.ToString().ToLowerInvariant(); default: return "dnpc_dialogue_neutral_" + text; } default: obj = "tank"; break; case DialogueRole.Archer: obj = "archer"; break; case DialogueRole.Healer: obj = "healer"; break; } string text2 = (string)obj; return "dnpc_dialogue_" + text + "_" + text2 + "_" + (((variant & 1) == 0) ? "0" : "1"); } internal static string IntentKey(string question, DialogueContext context) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return "reply_unknown"; } string text = QuestionText(question); if (text.Length == 0) { return "reply_unknown"; } context = context ?? new DialogueContext(); string text2 = ProcessedMaterial(text); if (text2 != null) { return "fact_material_" + text2 + "_process"; } if (Regex.IsMatch(text, "\\b(?:and|also)\\s+(?:how|where|what|when|why|can|could|should|fight|tame|build|craft|repair|summon|find|smelt|breed)\\b")) { return "reply_multiple"; } QuestionFacet questionFacet = Facet(text); string text3 = MaskMaterialNames(text); string text4 = Match(Bosses, text3); string text5 = Creature(text3); string text6 = Match(Materials, text); string text7 = Match(Biomes, text); if (questionFacet == QuestionFacet.General && HasAny(text, "and", "or") && Has(text, "tell me about") && Match(Topics, text) == "multiple") { return "reply_multiple"; } if ((text4 != null || text5 != null) && HasAny(text, "and", "or") && (HasAny(text, "portal", "portals") || (text6 != null && questionFacet == QuestionFacet.General))) { return "reply_multiple"; } if (text4 == null && (questionFacet == QuestionFacet.Drop || questionFacet == QuestionFacet.Location)) { string text8 = (HasAny(text, "swamp key") ? "elder" : (HasAny(text, "hard antler", "hard antlers") ? "eikthyr" : (Has(text, "wishbone") ? "bonemass" : (HasAny(text, "dragon tear", "dragon tears") ? "moder" : (HasAny(text, "torn spirit", "torn spirits") ? "yagluth" : (Has(text, "majestic carapace") ? "queen" : null)))))); if (text8 != null) { return "fact_boss_" + text8 + "_reward"; } } if (questionFacet == QuestionFacet.Craft && text4 == null && !HasAny(text, "tame", "taming", "breed", "breeding")) { if (HasAny(text, "portal", "portals")) { return "reply_portal_build"; } if (text5 != null && !HasAny(text, "armor", "armour", "cape", "helmet", "weapon", "trophy", "shield", "bow", "arrow", "arrows", "meat", "food")) { return "reply_unknown"; } return "reply_craft"; } if (questionFacet == QuestionFacet.Repair) { if (!HasAny(text, "portal", "portals")) { return "reply_repair"; } return "reply_portal_repair"; } if (text4 == "multiple" || text5 == "multiple") { return "reply_multiple"; } if (text4 != null && text4 != "multiple") { if (questionFacet == QuestionFacet.Summon && (text5 == null || HasAny(text, "trophy", "trophies"))) { return "fact_boss_" + text4 + "_summon"; } if (text5 != null) { return "reply_multiple"; } if (questionFacet == QuestionFacet.General && HasAny(text, "what is", "what are", "who is", "who was")) { if (!IdentityQuestion(text, Bosses, text4)) { return "reply_unknown"; } return "fact_boss_" + text4 + "_identity"; } switch (questionFacet) { case QuestionFacet.Drop: return "fact_boss_" + text4 + "_reward"; case QuestionFacet.Location: return "fact_boss_location"; case QuestionFacet.Craft: return "reply_craft"; case QuestionFacet.Riding: return "fact_cannot_ride"; case QuestionFacet.Taming: case QuestionFacet.Breeding: case QuestionFacet.Food: return "fact_cannot_tame"; default: return "reply_boss_" + text4; case QuestionFacet.Unknown: return "reply_unknown"; } } if (text5 != null) { if (questionFacet == QuestionFacet.General && HasAny(text, "what is", "what are", "who is")) { if (!IdentityQuestion(text, Creatures, text5) && !IdentityQuestion(text, OtherCreatures, text5)) { return "reply_unknown"; } return "fact_creature_" + text5 + "_location"; } switch (questionFacet) { case QuestionFacet.Location: return "fact_creature_" + text5 + "_location"; case QuestionFacet.Drop: return "fact_creature_" + text5 + "_drop"; case QuestionFacet.Summon: if (!(text5 == "skeleton") && !(text5 == "troll")) { return "fact_creature_no_summon"; } return "fact_summon_" + text5; case QuestionFacet.Riding: if (!(text5 == "lox") && !(text5 == "asksvin")) { return "fact_cannot_ride"; } return "fact_riding_" + text5; case QuestionFacet.Taming: case QuestionFacet.Breeding: case QuestionFacet.Food: if (text5 == "hen") { return "fact_hens"; } if (text5 != "boar" && text5 != "wolf" && text5 != "lox" && text5 != "asksvin") { return "fact_cannot_tame"; } if (questionFacet != QuestionFacet.Breeding) { return "fact_taming_" + text5; } return "fact_breeding"; case QuestionFacet.Unknown: return "reply_unknown"; default: if (Match(OtherCreatures, text3) == null) { return "reply_creature_" + text5; } return "fact_creature_" + text5 + "_combat"; } } if (text7 == "multiple") { return "reply_multiple"; } if (HasAny(text, "portal", "portals", "teleport", "teleporting")) { if (HasAny(text, "carry", "carrying", "transport", "through", "metal", "metals", "ore", "ores")) { return "fact_portal_transport"; } return questionFacet switch { QuestionFacet.Prepare => "reply_portal_build", QuestionFacet.Unknown => "reply_unknown", _ => "reply_portal", }; } if (text6 == "multiple" && (!Has(text, "copper") || !Has(text, "tin") || Match(Materials, text.Replace("copper", "").Replace("tin", "")) != null)) { return "reply_multiple"; } if (text6 != null && text6 != "multiple") { if (HasAny(text, "how much", "how many") && !HasAny(text, "stored", "storage", "chest", "stock")) { return "fact_material_quantity"; } if (HasAny(text, "nearby", "around here", "near me", "near us")) { return "fact_material_" + text6 + "_location"; } switch (questionFacet) { case QuestionFacet.Location: return "fact_material_" + text6 + "_location"; case QuestionFacet.Process: return "fact_material_" + ((IsMetal(text6) || text6 == "eitr" || text6 == "coal") ? text6 : "other") + "_process"; } if (!HasAny(text, "queen bee", "queen bees", "beehive", "beehives", "honey", "elder bark", "ancient bark") && !IsMetal(text6) && text6 != "finewood" && text6 != "corewood") { if (questionFacet != QuestionFacet.Unknown) { return "fact_material_" + text6 + "_location"; } return "reply_unknown"; } } if (HasAny(text, "queen bee", "queen bees", "beehive", "beehives", "honey")) { return "reply_bees"; } if (Has(text, "elder bark") || Has(text, "ancient bark")) { return "reply_elder_bark"; } if (HasAny(text, "poison", "poisoned", "antidote")) { return "reply_poison"; } if (HasAny(text, "cold", "freezing", "frost resistance")) { return "reply_cold"; } if (HasAny(text, "burning", "fire resistance")) { return "reply_fire"; } string text9 = (context.BiomeKnown ? Match(Biomes, Normalize(context.Biome ?? "")) : null); if (text9 == "multiple") { text9 = null; } bool flag = context.InteriorKnown && context.IsInterior; string text10 = ((context.InteriorKnown && !context.IsInterior) ? text9 : null); bool flag2 = HasAny(text, "here", "this biome", "this zone", "this area", "around here"); bool flag3 = HasAny(text, "resources", "materials", "gather", "gathering", "mine", "mining", "what grows"); bool flag4 = HasAny(text, "what lives", "which creatures", "what creatures", "which enemies", "what enemies", "what monsters", "what spawns"); if (flag4 && HasAny(text, "nearby", "right now", "around us", "are here")) { return ObservedThreat(context); } if ((text7 != null || flag2) && (flag3 || flag4) && text6 == null) { if (text7 == null && flag) { return "reply_interior_resources"; } string text11 = text7 ?? text10; if (text11 == null) { return "reply_location_unknown"; } return "fact_biome_" + text11 + (flag4 ? "_creatures" : "_resources"); } if (HasAny(text, "how are you", "are you okay", "are you ok", "your status", "are you hurt", "are you injured", "are you wounded")) { if (!context.SpeakerHealthKnown || !context.SpeakerLowHealth) { if (!context.CombatKnown || !context.InCombat) { if (!context.SpeakerHealthKnown) { return "reply_status_unknown"; } return "reply_status_ready"; } return "reply_status_combat"; } return "reply_status_hurt"; } if (HasAny(text, "how am i", "am i okay", "am i ok", "my status", "am i hurt", "am i injured", "am i wounded")) { if (!context.HealthKnown) { return "reply_player_unknown"; } if (!context.LowHealth) { return "reply_player_ready"; } return "reply_player_hurt"; } if (HasAny(text, "are we safe", "any danger", "any enemies", "what is nearby", "whats nearby", "what is around us", "whats around us", "what do you see", "what is that", "what are we fighting", "what are you fighting")) { return ObservedThreat(context); } if (HasAny(text, "where am i", "where are we", "what biome", "which biome")) { if (!flag) { if (text10 == null) { return "reply_location_unknown"; } return "event_biome_" + text10; } return "reply_interior"; } if (HasAny(text, "thanks", "thank you") && HasAny(text, "for healing", "for helping", "for the help", "for that") && !HasAny(text, "how", "where", "what", "why", "when", "can", "could", "should", "would")) { return "reply_thanks"; } string text12 = MatchFirst(Topics, text); if (Array.IndexOf(new string[5] { "what should i bring", "what should we bring", "how do i prepare", "how should i prepare", "how should we prepare" }, text) >= 0) { if (!flag) { if (text10 == null) { return "reply_location_unknown"; } return "reply_biome_" + text10; } return "reply_interior"; } if ((flag2 || text7 != null) && HasAny(text, "prepare", "preparing", "preparation", "bring", "need", "survive", "survival", "what should i do", "what should we do", "what can i do")) { if (text7 != null) { return "reply_biome_" + text7; } if (HasAny(text, "what should i do", "what should we do", "what can i do")) { if (context.HealthKnown && context.LowHealth) { return "event_low_health"; } if ((context.ThreatsKnown && ThreatKey(context.Threat) != null) || (context.CombatKnown && context.InCombat)) { return ObservedThreat(context); } } if (!flag) { if (text10 == null) { return "reply_location_unknown"; } return "reply_biome_" + text10; } return "reply_interior"; } if (text12 == "help") { if (context.HealthKnown && context.LowHealth) { return "event_low_health"; } string text13 = (context.ThreatsKnown ? ThreatKey(context.Threat) : null); if (text13 != null) { return "event_threat_" + text13; } if (context.CombatKnown && context.InCombat) { return "reply_status_combat"; } if (!Has(text, "what can you do")) { if (flag) { return "reply_interior"; } if (text10 != null) { return "reply_biome_" + text10; } } } switch (text12) { case "iron": case "copper_tin": case "silver": case "advanced_metal": case "farming": case "wood": return "reply_" + text12; case "food": case "heal": case "rest": case "stamina": case "block": return "reply_" + text12; default: if (text7 != null) { if (questionFacet != QuestionFacet.Unknown) { return "reply_biome_" + text7; } return "reply_unknown"; } if (text12 != null) { return "reply_" + text12; } if (HasAny(text, "what time", "is it night", "is it day", "time of day")) { if (!context.NightKnown) { return "reply_environment_unknown"; } if (!context.IsNight) { return "reply_day"; } return "event_night"; } if (HasAny(text, "weather", "are you wet", "is it raining")) { return "reply_environment_unknown"; } if (HasAny(text, "am i wet", "is it wet")) { if (!context.WetKnown) { return "reply_environment_unknown"; } if (!context.IsWet) { return "reply_dry"; } return "event_wet"; } if (HasAny(text, "night", "dark", "darkness")) { return "reply_night"; } if (HasAny(text, "wet", "rain", "raining")) { return "reply_wet"; } if (OnlySocialWords(text, "hi", "hello", "hey", "hail", "greetings", "good", "morning", "evening", "friend", "there", "bram", "mira", "fen")) { return "reply_greeting"; } if (OnlySocialWords(text, "thank", "thanks", "you", "thankyou", "thankful", "cheers", "much", "so", "very", "bram", "mira", "fen")) { return "reply_thanks"; } if (OnlySocialWords(text, "sorry", "apologies", "apologize", "i", "am", "im", "my", "fault", "about", "that", "bram", "mira", "fen")) { return "reply_apology"; } return "reply_unknown"; } } internal static string FollowupSubject(string question) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return ""; } string text = QuestionText(question); if (Facet(text) == QuestionFacet.Craft || Facet(text) == QuestionFacet.Repair) { return ""; } if (IntentKey(text, null) == "reply_unknown" || IntentKey(text, null) == "reply_multiple") { return ""; } string text2 = MaskMaterialNames(text); string text3 = Match(Bosses, text2); string text4 = Creature(text2); string text5 = Match(Materials, text); if (text3 == "multiple" || text4 == "multiple" || text5 == "multiple") { return ""; } if (text3 != null) { if (text4 != null && Facet(text) != QuestionFacet.Summon) { return ""; } return Label(Bosses, text3); } if (text4 != null) { if (text5 != null) { return ""; } return Label(Creatures, text4) ?? Label(OtherCreatures, text4); } if (text5 != null) { return Label(Materials, text5); } string text6 = Match(Biomes, text); if (text6 != null && !(text6 == "multiple")) { return Label(Biomes, text6); } return ""; } internal static bool IsProcessedMaterialQuestion(string question) { if (!string.IsNullOrWhiteSpace(question) && question.Length <= 512) { return ProcessedMaterial(QuestionText(question)) != null; } return false; } private static string ProcessedMaterial(string text) { Match match = Regex.Match(text, "^(?:(?:how (?:do|can|should) i|how to|can (?:i|you)|could (?:i|you)|i want to|please)\\s+)?(?:make|smelt|process|refine|smelting|processing|refining)\\s+(?:some\\s+)?(scrap iron|iron|copper|tin|silver|black metal(?: scrap)?|blackmetal|flametal|refined eitr|eitr|coal)(?:\\s+(?:ore|bars?))?$"); if (!match.Success) { return null; } return Match(Materials, match.Groups[1].Value); } private static string Label(string[][] groups, string key) { foreach (string[] array in groups) { if (array[0] == key) { return array[1]; } } return null; } private static bool IdentityQuestion(string text, string[][] groups, string key) { foreach (string[] array in groups) { if (!(array[0] == key)) { continue; } for (int j = 1; j < array.Length; j++) { if (Regex.IsMatch(text, "^(?:who is|who was|what is|what are)\\s+(?:(?:a|an|the)\\s+)?" + Regex.Escape(array[j]) + "(?: in valheim)?$")) { return true; } } } return false; } private static string ObservedThreat(DialogueContext context) { string text = (context.ThreatsKnown ? ThreatKey(context.Threat) : null); if (text == null) { if (!context.CombatKnown || !context.InCombat) { return "reply_no_threat"; } return "reply_status_combat"; } return "event_threat_" + text; } private static string Creature(string text) { string text2 = Match(Creatures, text); string text3 = Match(OtherCreatures, text); object obj; if (text2 == null || text3 == null) { obj = text2; if (obj == null) { return text3; } } else { obj = "multiple"; } return (string)obj; } private static bool IsMetal(string material) { switch (material) { default: return material == "flametal"; case "iron": case "copper": case "tin": case "bronze": case "silver": case "blackmetal": return true; } } private static string MaskMaterialNames(string text) { string[][] materials = Materials; foreach (string[] array in materials) { for (int j = 1; j < array.Length; j++) { if (array[j].IndexOf(' ') >= 0) { text = ReplacePhrase(text, array[j], "material"); } } } string[] array2 = new string[6] { "wolf meat", "lox meat", "boar meat", "deer meat", "serpent meat", "chicken meat" }; foreach (string phrase in array2) { text = ReplacePhrase(text, phrase, "food"); } return text; } private static string ReplacePhrase(string text, string phrase, string replacement) { return (" " + text + " ").Replace(" " + phrase + " ", " " + replacement + " ").Trim(); } private static string QuestionText(string question) { string input = Normalize(question); input = Regex.Replace(input, "\\bwhats\\b", "what is"); input = Regex.Replace(input, "\\bwheres\\b", "where is"); input = Regex.Replace(input, "\\bwhos\\b", "who is"); if (Regex.IsMatch(input, "^(?:hi|hey|hello|hail|thanks|thank you|sorry|apologies)\\b")) { Match match = Regex.Match(input, "\\b(?:how|where|what|why|when|which|can|could|should|would|is|are|do|does)\\b"); if (match.Success) { input = input.Substring(match.Index); } } if (Regex.IsMatch(input, "^(?:where\\b|(?:can|could) i (?:find|get|obtain)\\b|is there\\b|are there\\b)")) { Match match2 = Regex.Match(input, "\\s+(?:to (?:craft|make|build|fight|summon|tame)|for (?:crafting|making|building|fighting|summoning|taming))\\b"); if (match2.Success) { input = input.Substring(0, match2.Index); } } return input; } private static QuestionFacet Facet(string text) { if (HasAny(text, "summon", "summoning", "offering", "offerings", "offer", "sacrifice", "sacrifices")) { return QuestionFacet.Summon; } if (HasAny(text, "drop", "drops", "loot", "reward", "rewards", "unlock", "unlocks", "what do i get", "what will i get")) { return QuestionFacet.Drop; } if (HasAny(text, "breed", "breeding", "mate", "mating", "babies", "offspring")) { return QuestionFacet.Breeding; } if (HasAny(text, "ride", "riding", "saddle", "saddles")) { return QuestionFacet.Riding; } if (HasAny(text, "tame", "taming", "domesticate", "domesticating", "hatch", "hatching")) { return QuestionFacet.Taming; } if (HasAny(text, "feed", "feeding", "eat", "eats", "eating", "diet") && !HasAny(text, "what should i eat", "what do i eat", "what can i eat")) { return QuestionFacet.Food; } if (HasAny(text, "repair", "repairing", "fix", "fixing", "broken", "durability")) { return QuestionFacet.Repair; } if (HasAny(text, "smelt", "smelting", "process", "processing", "refine", "refining", "furnace")) { return QuestionFacet.Process; } if (HasAny(text, "craft", "crafting", "make", "making", "build", "building", "recipe", "ingredients", "upgrade", "upgrading")) { return QuestionFacet.Craft; } if (HasAny(text, "fight", "fighting", "kill", "killing", "defeat", "beat", "against", "counter", "damage", "weak", "weakness", "weaknesses", "weakspot", "weak spot", "weak point", "immune", "resistant", "aim", "shoot", "hit", "block", "parry", "dodge")) { return QuestionFacet.Combat; } if (HasAny(text, "where", "location", "habitat", "locate", "find", "found", "spawn", "spawns", "live", "lives", "come from", "get", "obtain", "source")) { return QuestionFacet.Location; } if (HasAny(text, "prepare", "preparing", "preparation", "bring", "need", "survive", "survival", "deal with")) { return QuestionFacet.Prepare; } if (HasAny(text, "tell me about", "what about", "how about", "what is", "what are", "who is", "who was", "what should i know", "what should we know", "how do i use", "how can i use", "can i use")) { return QuestionFacet.General; } if (HasAny(text, "how", "why", "when", "can", "could", "should", "would", "does", "do", "is", "are")) { return QuestionFacet.Unknown; } return QuestionFacet.General; } internal static string ThreatKey(string prefab) { string text = Normalize(prefab ?? "").Replace(" clone", "").Replace(" ", ""); if (text.Length == 0) { return null; } switch (text) { case "dragon": case "goblinking": return "boss"; case "hatchling": return "drake"; default: { string[][] bosses = Bosses; foreach (string[] array in bosses) { if (text == array[0] || (array[0] == "elder" && text == "gdking") || (array[0] == "queen" && text == "seekerqueen")) { return "boss"; } } if (text == "boss") { return "boss"; } bosses = Creatures; foreach (string[] array2 in bosses) { if (text == array2[0]) { return array2[0]; } } if (text.StartsWith("greydwarf", StringComparison.Ordinal)) { return "greydwarf"; } if (text.StartsWith("draugr", StringComparison.Ordinal)) { return "draugr"; } if (text.StartsWith("skeleton", StringComparison.Ordinal)) { return "skeleton"; } switch (text) { case "blobelite": case "oozer": return "blob"; case "blobtar": return "growth"; default: if (text.StartsWith("goblin", StringComparison.Ordinal) || text.StartsWith("fuling", StringComparison.Ordinal)) { return "fuling"; } if (text.StartsWith("seeker", StringComparison.Ordinal)) { return "seeker"; } return "unknown"; } } } } internal static string StripAddress(string question, params string[] aliases) { if (string.IsNullOrWhiteSpace(question) || aliases == null) { return question; } string text = question.TrimStart(Array.Empty()); int i = 0; string[] array = new string[4] { "hi", "hey", "hello", "hail" }; foreach (string text2 in array) { if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase) && text.Length > text2.Length && !char.IsLetterOrDigit(text[text2.Length])) { for (i = text2.Length; i < text.Length && (char.IsWhiteSpace(text[i]) || text[i] == ','); i++) { } break; } } int num = -1; array = aliases; foreach (string text3 in array) { if (string.IsNullOrWhiteSpace(text3)) { continue; } string text4 = text3.Trim(); if (text.Length <= i + text4.Length || string.Compare(text, i, text4, 0, text4.Length, StringComparison.OrdinalIgnoreCase) != 0) { continue; } int k = i + text4.Length; if (char.IsWhiteSpace(text[k]) || text[k] == ',' || text[k] == ':' || text[k] == ';' || text[k] == '!' || text[k] == '?') { for (; k < text.Length && (char.IsWhiteSpace(text[k]) || ",:;!?".IndexOf(text[k]) >= 0); k++) { } if (k < text.Length && k > num) { num = k; } } } if (num < 0) { return question; } return text.Substring(num); } private static string Match(string[][] groups, string text) { string text2 = null; foreach (string[] array in groups) { for (int j = 1; j < array.Length; j++) { if (Has(text, array[j])) { if (text2 != null && text2 != array[0]) { return "multiple"; } text2 = array[0]; break; } } } return text2; } private static string MatchFirst(string[][] groups, string text) { foreach (string[] array in groups) { for (int j = 1; j < array.Length; j++) { if (Has(text, array[j])) { return array[0]; } } } return null; } private static bool HasAny(string text, params string[] phrases) { foreach (string phrase in phrases) { if (Has(text, phrase)) { return true; } } return false; } private static bool Has(string text, string phrase) { return (" " + text + " ").IndexOf(" " + phrase + " ", StringComparison.Ordinal) >= 0; } private static bool OnlySocialWords(string text, params string[] allowed) { string[] array = text.Split(new char[1] { ' ' }); foreach (string value in array) { if (Array.IndexOf(allowed, value) < 0) { return false; } } return true; } private static string Normalize(string text) { StringBuilder stringBuilder = new StringBuilder(text.Length); for (int i = 0; i < text.Length; i++) { char c = char.ToLowerInvariant(text[i]); if (char.IsLetterOrDigit(c)) { stringBuilder.Append(c); } else if (c != '\'' && c != '’' && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); } } return stringBuilder.ToString().Trim(); } } internal static class FarmerService { private sealed class ConversationState { internal FarmerObservation Previous; internal bool HadAudience; internal float NextRemark; internal float NextGreeting; internal int Variation; } private const string PlaceRpc = "jg224.dnpc.FarmerPlaceV1"; private const string RemoveRpc = "jg224.dnpc.FarmerRemoveV1"; private static readonly FieldInfo AllWards = AccessTools.Field(typeof(PrivateArea), "m_allAreas"); private static readonly MethodInfo WardEnabled = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null); private static readonly MethodInfo WardInside = AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null); private static readonly MethodInfo WardPermitted = AccessTools.Method(typeof(PrivateArea), "IsPermitted", (Type[])null, (Type[])null); private static readonly List FarmerScan = new List(); private static readonly HashSet KnownFarmers = new HashSet(); private static readonly Dictionary PlacementRequests = new Dictionary(); private static int _scanIndex; private static bool _indexReady; private static float _nextScan; private const string RequestRpc = "jg224.dnpc.FarmerTalkV1"; private const string SpeechRpc = "jg224.dnpc.FarmerSpeechV1"; private const float ChatRange = 12f; private const float SpeechRange = 20f; private static ZRoutedRpc _registered; private static readonly Dictionary Requests = new Dictionary(); private static readonly Dictionary States = new Dictionary(); private static void ResetPlacementMemory() { FarmerScan.Clear(); KnownFarmers.Clear(); PlacementRequests.Clear(); _scanIndex = 0; _indexReady = false; _nextScan = 0f; } internal static void Update() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) Register(); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || Time.unscaledTime < _nextScan || !ZDOMan.instance.GetAllZDOsWithPrefabIterative("DynamicNPC_Farmer", FarmerScan, ref _scanIndex)) { return; } foreach (ZDO item in FarmerScan) { if (item != null && item.IsValid()) { KnownFarmers.Add(item.m_uid); } } KnownFarmers.RemoveWhere((ZDOID id) => ZDOMan.instance.GetZDO(id) == null || !ZDOMan.instance.GetZDO(id).IsValid()); FarmerScan.Clear(); _scanIndex = 0; _indexReady = true; _nextScan = Time.unscaledTime + 30f; } internal static void RequestPlace(Player player, Vector3 position, Quaternion rotation) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) Register(); if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && ZRoutedRpc.instance != null) { long serverPeerUid = ServerAuthority.GetServerPeerUid(); if (serverPeerUid != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerUid, "jg224.dnpc.FarmerPlaceV1", new object[2] { position, rotation }); } } } internal static void RequestRemoval(FarmerNpc farmer, Player player) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) Register(); if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && FarmerPlacement.IsPlaced(farmer) && ZRoutedRpc.instance != null) { long serverPeerUid = ServerAuthority.GetServerPeerUid(); if (serverPeerUid != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerUid, "jg224.dnpc.FarmerRemoveV1", new object[1] { farmer.Nview.GetZDO().m_uid }); } } } private static bool PlacementRequester(long sender, out ServerAuthority.SenderPlayerState player) { player = null; if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || !ServerAuthority.TryResolveSender(sender, out player) || player.PlayerId == 0L || player.IsDead || player.RightItemHash != StringExtensionMethods.GetStableHashCode("Hammer")) { return false; } float now = Time.unscaledTime; if (PlacementRequests.TryGetValue(sender, out var value) && now < value) { return false; } long[] array = (from pair in PlacementRequests where pair.Value <= now select pair.Key).ToArray(); foreach (long key in array) { PlacementRequests.Remove(key); } if (PlacementRequests.Count >= 128) { return false; } PlacementRequests[sender] = now + 1f; return true; } private static void OnPlaceRequest(long sender, Vector3 position, Quaternion rotation) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015b: 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_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: 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) if (!PlacementRequester(sender, out var player)) { return; } if (!_indexReady) { PlacementMessage(sender, "dnpc_farmer_place_wait"); return; } if (!TryCreatorPlatform(player, out var platform)) { PlacementMessage(sender, "dnpc_farmer_place_wait"); return; } if (!FarmerAuthorityRules.ValidatePlacementTransform(player.Position.x, player.Position.y, player.Position.z, position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w) || !CanPlaceAt(position, player.PlayerId)) { PlacementMessage(sender, "dnpc_farmer_place_invalid"); return; } foreach (ZDOID knownFarmer in KnownFarmers) { ZDO zDO = ZDOMan.instance.GetZDO(knownFarmer); if (zDO != null && zDO.IsValid() && zDO.GetPrefab() == StringExtensionMethods.GetStableHashCode("DynamicNPC_Farmer") && zDO.GetLong(ZDOVars.s_creator, 0L) == player.PlayerId) { PlacementMessage(sender, "dnpc_farmer_place_existing"); return; } } GameObject val = null; try { GameObject prefab = FarmerPlacement.GetPrefab(); if ((Object)(object)prefab == (Object)null) { throw new InvalidOperationException("Farmer prefab is not registered."); } Quaternion val2 = Quaternion.Euler(0f, ((Quaternion)(ref rotation)).eulerAngles.y, 0f); val = Object.Instantiate(prefab, position, val2); FarmerNpc component = val.GetComponent(); ZNetView component2 = val.GetComponent(); Piece component3 = val.GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || !component2.IsValid() || (Object)(object)component3 == (Object)null) { throw new InvalidOperationException("Farmer instance has no persistent body or piece."); } ZDO zDO2 = component2.GetZDO(); zDO2.SetOwner(ZNet.GetUID()); component3.SetCreator(player.PlayerId, platform); FarmerNpc.SetHome(zDO2, position); KnownFarmers.Add(zDO2.m_uid); PlacementMessage(sender, "dnpc_farmer_place_done"); } catch (Exception ex) { if ((Object)(object)val != (Object)null) { ZNetView component4 = val.GetComponent(); if ((Object)(object)component4 != (Object)null && component4.IsValid()) { ZNetScene.instance.Destroy(val); } else { Object.Destroy((Object)(object)val); } } MercPlugin.LogWarn("Farmer placement failed: " + ex.Message); PlacementMessage(sender, "dnpc_farmer_place_failed"); } } private static bool TryCreatorPlatform(ServerAuthority.SenderPlayerState requester, out PlatformUserID platform) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: 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_0083: 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) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Invalid comparison between Unknown and I4 //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) platform = PlatformUserID.None; if (requester == null || ((ZDOID)(ref requester.CharacterZdoId)).IsNone() || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return false; } if (requester.SenderUid == ZNet.GetUID()) { if ((Object)(object)requester.LivePlayer == (Object)null || (Object)(object)requester.LivePlayer != (Object)(object)Player.m_localPlayer) { return false; } IDistributionPlatform distributionPlatform = PlatformManager.DistributionPlatform; ILocalUser val = ((distributionPlatform != null) ? distributionPlatform.LocalUser : null); if (val == null) { return false; } platform = ((IUser)val).PlatformUserID; return ((PlatformUserID)(ref platform)).IsValid; } ZNetPeer peer = ZNet.instance.GetPeer(requester.SenderUid); if (peer == null || !peer.IsReady() || peer.m_uid != requester.SenderUid || peer.m_characterID != requester.CharacterZdoId || peer.m_socket == null) { return false; } string hostName = peer.m_socket.GetHostName(); if (string.IsNullOrEmpty(hostName)) { return false; } if ((int)ZNet.m_onlineBackend == 0) { platform = new PlatformUserID("Steam", hostName); } else { if ((int)ZNet.m_onlineBackend != 1) { return true; } if (!PlatformUserID.TryParse(hostName, ref platform)) { return false; } } return ((PlatformUserID)(ref platform)).IsValid; } private static bool CanPlaceAt(Vector3 position, long playerId) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null || !ZNetScene.instance.IsAreaReady(position) || Location.IsInsideNoBuildLocation(position) || !WardAccess(position, playerId)) { return false; } int mask = LayerMask.GetMask(new string[4] { "Default", "static_solid", "piece", "terrain" }); RaycastHit val = default(RaycastHit); if (!Physics.Raycast(position + Vector3.up * 0.45f, Vector3.down, ref val, 0.9f, mask, (QueryTriggerInteraction)1) || ((RaycastHit)(ref val)).normal.y < 0.65f || (Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent() != (Object)null || (Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent() != (Object)null) { return false; } WaterVolume val2 = null; if (Floating.GetWaterLevel(position, ref val2) > position.y + 0.05f) { return false; } return !Physics.CheckCapsule(position + Vector3.up * 0.35f, position + Vector3.up * 1.5f, 0.3f, mask, (QueryTriggerInteraction)1); } private static bool WardAccess(Vector3 position, long playerId) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (AllWards == null || WardEnabled == null || WardInside == null || WardPermitted == null || !(AllWards.GetValue(null) is IEnumerable enumerable)) { return false; } bool flag = false; bool flag2 = default(bool); foreach (PrivateArea item in enumerable) { if ((Object)(object)item == (Object)null) { continue; } object obj = WardEnabled.Invoke(item, Array.Empty()); if (!(obj is bool) || !(bool)obj) { continue; } obj = WardInside.Invoke(item, new object[2] { position, 0f }); if (!(obj is bool) || !(bool)obj) { continue; } flag = true; Piece component = ((Component)item).GetComponent(); if (!((Object)(object)component != (Object)null) || component.GetCreator() != playerId) { obj = WardPermitted.Invoke(item, new object[1] { playerId }); int num; if (obj is bool) { flag2 = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag2 ? 1u : 0u)) == 0) { continue; } } return true; } return !flag; } private static void OnRemoveRequest(long sender, ZDOID id) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if (!PlacementRequester(sender, out var player)) { return; } FarmerNpc farmerNpc = Find(id); if (!((Object)(object)farmerNpc == (Object)null) && ZNetScene.instance.IsAreaReady(((Component)farmerNpc).transform.position) && !Location.IsInsideNoBuildLocation(((Component)farmerNpc).transform.position)) { long creator = ((Component)farmerNpc).GetComponent().GetCreator(); long playerId = player.PlayerId; bool admin = ServerAuthority.IsSenderAdmin(sender); Vector3 val = player.Position - ((Component)farmerNpc).transform.position; if (FarmerAuthorityRules.CanDismiss(creator, playerId, admin, ((Vector3)(ref val)).sqrMagnitude, WardAccess(((Component)farmerNpc).transform.position, player.PlayerId))) { farmerNpc.Nview.GetZDO().SetOwner(ZNet.GetUID()); ZNetScene.instance.Destroy(((Component)farmerNpc).gameObject); KnownFarmers.Remove(id); States.Remove(id); PlacementMessage(sender, "dnpc_farmer_remove_done"); return; } } PlacementMessage(sender, "dnpc_farmer_remove_denied"); } private static void PlacementMessage(long sender, string token) { ServerAuthority.SendPlayerMessage(sender, MercLocalization.Phrase(token)); } internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registered) { instance.Register("jg224.dnpc.FarmerTalkV1", (Action)OnRequest); instance.Register("jg224.dnpc.FarmerSpeechV1", (Action)OnSpeech); instance.Register("jg224.dnpc.FarmerPlaceV1", (Action)OnPlaceRequest); instance.Register("jg224.dnpc.FarmerRemoveV1", (Action)OnRemoveRequest); _registered = instance; } } internal static void ResetSceneMemory() { Requests.Clear(); States.Clear(); ResetPlacementMemory(); } internal static void RequestInspect(FarmerNpc farmer, Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { Request(farmer, ""); } } internal static bool TryHearChat(Player player, string text) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !FarmerRules.IsAddressed(text)) { return false; } FarmerNpc farmerNpc = null; float num = 12f; foreach (FarmerNpc instance in FarmerNpc.Instances) { if (FarmerPlacement.IsPlaced(instance) && FarmerInspection.IsFinite(((Component)instance).transform.position) && FarmerInspection.IsFinite(((Component)player).transform.position)) { float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)instance).transform.position); if (!(num2 > num)) { farmerNpc = instance; num = num2; } } } if ((Object)(object)farmerNpc == (Object)null) { return false; } Request(farmerNpc, text); return true; } private static void Request(FarmerNpc farmer, string text) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) Register(); if (!((Object)(object)farmer == (Object)null) && !((Object)(object)farmer.Nview == (Object)null) && farmer.Nview.IsValid() && ZRoutedRpc.instance != null) { string text2 = (text ?? "").Trim(); if (text2.Length > 320) { text2 = text2.Substring(0, 320); } long serverPeerUid = ServerAuthority.GetServerPeerUid(); if (serverPeerUid != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerUid, "jg224.dnpc.FarmerTalkV1", new object[2] { farmer.Nview.GetZDO().m_uid, text2 }); } } } private unsafe static void OnRequest(long sender, ZDOID id, string question) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || question == null || question.Length > 320 || !ServerAuthority.TryResolveSender(sender, out var state) || state.IsDead || state.PlayerId == 0L) { return; } FarmerNpc farmerNpc = Find(id); if ((Object)(object)farmerNpc == (Object)null || !FarmerInspection.IsFinite(state.Position) || !FarmerInspection.IsFinite(((Component)farmerNpc).transform.position) || Vector3.Distance(state.Position, ((Component)farmerNpc).transform.position) > ((question.Length == 0) ? 5f : 12f) || (question.Length > 0 && (!MercConfig.HearPlayerChat.Value || !FarmerRules.IsAddressed(question)))) { return; } float now = Time.unscaledTime; if ((Requests.TryGetValue(sender, out var value) && now < value) || !ServerAuthority.TryUseKnowledgeBudget(sender)) { return; } long[] array = (from pair in Requests where pair.Value <= now select pair.Key).ToArray(); foreach (long key in array) { Requests.Remove(key); } if (Requests.Count >= 128) { return; } Requests[sender] = now + 3f; ConversationState conversationState = State(id); if (conversationState != null) { string question2 = FarmerRules.StripAddress(question); if (question.Length > 0 && !FarmerRules.IsFarmQuestion(question2) && !FarmerRules.IsResidentQuestion(question2)) { conversationState.NextRemark = now + 45f; SendText(sender, id, NpcSupport.Answer(question2, state, DialogueRole.Farmer, ((object)(*(ZDOID*)(&id))/*cast due to .constrained prefix*/).ToString())); return; } NpcSupport.Forget(state, DialogueRole.Farmer, ((object)(*(ZDOID*)(&id))/*cast due to .constrained prefix*/).ToString()); FarmerObservation snapshot = ((question.Length == 0 || FarmerRules.ShouldInspect(question2)) ? FarmerInspection.Capture(farmerNpc) : new FarmerObservation()); FarmerReply[] replies = ((question.Length == 0) ? FarmerRules.Inspect(snapshot, conversationState.Variation++) : new FarmerReply[1] { FarmerRules.Reply(question2, snapshot, conversationState.Variation++, MercDialogue.CaptureContext(null, null, state)) }); conversationState.NextRemark = now + 45f; Send(sender, id, replies); } } internal static void Poll() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) Register(); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZDOID[] array = States.Keys.Where((ZDOID id) => (Object)(object)Find(id) == (Object)null).ToArray(); foreach (ZDOID key in array) { States.Remove(key); } if (!MercConfig.ContextualRemarksEnabled.Value) { return; } List source = NearbyPlayers(); float unscaledTime = Time.unscaledTime; FarmerNpc[] array2 = FarmerNpc.Instances.ToArray(); foreach (FarmerNpc farmer in array2) { if ((Object)(object)farmer == (Object)null || (Object)(object)farmer.Nview == (Object)null || !farmer.Nview.IsValid()) { continue; } ZDOID uid = farmer.Nview.GetZDO().m_uid; ConversationState conversationState = State(uid); if (conversationState == null) { continue; } ServerAuthority.SenderPlayerState[] array3 = source.Where((ServerAuthority.SenderPlayerState player) => Vector3.Distance(player.Position, ((Component)farmer).transform.position) <= 20f).ToArray(); if (array3.Length == 0) { conversationState.HadAudience = false; conversationState.Previous = null; continue; } FarmerObservation farmerObservation = FarmerInspection.Capture(farmer); FarmerObservation previous = conversationState.Previous; FarmerRemark? farmerRemark = null; if (farmerObservation.ThreatKnown && farmerObservation.ThreatNearby && (previous == null || !previous.ThreatNearby)) { farmerRemark = FarmerRemark.Threat; } else if (!conversationState.HadAudience && unscaledTime >= conversationState.NextGreeting) { farmerRemark = FarmerRemark.Greeting; } else if (previous != null) { if (farmerObservation.TimeKnown && previous.TimeKnown && farmerObservation.IsNight != previous.IsNight) { farmerRemark = (farmerObservation.IsNight ? FarmerRemark.Night : FarmerRemark.Day); } else if (Problems(farmerObservation) > Problems(previous)) { farmerRemark = FarmerRemark.CropProblem; } else if (farmerObservation.UnhappyHives > previous.UnhappyHives) { farmerRemark = FarmerRemark.HiveProblem; } else if (farmerObservation.Ready > previous.Ready) { farmerRemark = FarmerRemark.CropsReady; } } conversationState.Previous = farmerObservation; conversationState.HadAudience = true; if (farmerRemark.HasValue && !(unscaledTime < conversationState.NextRemark)) { FarmerReply farmerReply = FarmerRules.Remark(farmerRemark.Value, conversationState.Variation++); ServerAuthority.SenderPlayerState[] array4 = array3; for (int num2 = 0; num2 < array4.Length; num2++) { Send(array4[num2].PeerUid, uid, new FarmerReply[1] { farmerReply }); } conversationState.NextRemark = unscaledTime + 90f; if (farmerRemark == FarmerRemark.Greeting) { conversationState.NextGreeting = unscaledTime + 300f; } } } } private static int Problems(FarmerObservation value) { return value.Crowded + value.WrongBiome + value.Uncultivated + value.RoofBlocked; } private static List NearbyPlayers() { List list = new List(); if ((Object)(object)Player.m_localPlayer != (Object)null && ServerAuthority.TryResolveSender(ZNet.GetUID(), out var state) && !state.IsDead) { list.Add(state); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_uid != ZNet.GetUID() && ServerAuthority.TryResolveSender(peer.m_uid, out var state2) && !state2.IsDead) { list.Add(state2); } } return list; } private static ConversationState State(ZDOID id) { //IL_0005: 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) if (States.TryGetValue(id, out var value)) { return value; } if (States.Count >= 128) { return null; } value = new ConversationState(); States[id] = value; return value; } private static FarmerNpc Find(ZDOID id) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref id)).IsNone() || (Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject val = ZNetScene.instance.FindInstance(id); FarmerNpc farmerNpc = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if (!FarmerPlacement.IsPlaced(farmerNpc)) { return null; } return farmerNpc; } private static void Send(long peer, ZDOID id, FarmerReply[] replies) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance == null || replies == null) { return; } List list = new List(); foreach (FarmerReply item in replies.Take(3)) { list.Add(MercLocalization.Phrase(item.Token, item.Arguments)); } string text = "[" + string.Join(",", list.Select((string line) => "\"" + MiniJson.Escape(line) + "\"")) + "]"; if (text.Length <= 4096) { ZRoutedRpc.instance.InvokeRoutedRPC(peer, "jg224.dnpc.FarmerSpeechV1", new object[2] { id, text }); } } private static void SendText(long peer, ZDOID id, string answer) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null && !string.IsNullOrWhiteSpace(answer)) { if (answer.Length > 1200) { answer = answer.Substring(0, 1200); } string text = "[\"" + MiniJson.Escape(answer) + "\"]"; if (text.Length <= 4096) { ZRoutedRpc.instance.InvokeRoutedRPC(peer, "jg224.dnpc.FarmerSpeechV1", new object[2] { id, text }); } } } private static void OnSpeech(long sender, ZDOID id, string payload) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (!ServerAuthority.IsAuthoritativeServerSender(sender) || string.IsNullOrEmpty(payload) || payload.Length > 4096) { return; } FarmerNpc farmerNpc = Find(id); Player localPlayer = Player.m_localPlayer; if ((Object)(object)farmerNpc == (Object)null || (Object)(object)localPlayer == (Object)null || !FarmerInspection.IsFinite(((Component)localPlayer).transform.position) || !FarmerInspection.IsFinite(((Component)farmerNpc).transform.position) || Vector3.Distance(((Component)localPlayer).transform.position, ((Component)farmerNpc).transform.position) > 22f || !(MiniJson.Parse(payload) is List { Count: <=3 } list)) { return; } List list2 = new List(); foreach (object item in list) { if (item is string text) { list2.Add(MercLocalization.Resolve(text)); } } if (list2.Count > 0) { farmerNpc.ShowServerSpeech(string.Join("\n", list2)); } } } public sealed class FarmerNpc : Humanoid, Interactable { public static readonly List Instances = new List(); private static readonly int HomeKey = StringExtensionMethods.GetStableHashCode("jg224.dnpc.farmer.homeV1"); private static readonly int HomeSetKey = StringExtensionMethods.GetStableHashCode("jg224.dnpc.farmer.homeSetV1"); private bool _initialized; private float _appearanceTimer; private bool _hasWaveTrigger; public ZNetView Nview => ((Component)this).GetComponent(); public bool IsPlaced => FarmerPlacement.IsPlaced(this); internal bool Initialized => _initialized; public Vector3 HomePosition { get { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = Nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return ((Component)this).transform.position; } Vector3 vec = nview.GetZDO().GetVec3(HomeKey, ((Component)this).transform.position); if (!FarmerInspection.IsFinite(vec)) { return ((Component)this).transform.position; } return vec; } } public override void Awake() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Invalid comparison between Unknown and I4 ZNetView nview = Nview; if (ZNetView.m_forceDisableInit || (Object)(object)nview == (Object)null || !nview.IsValid()) { ((Behaviour)this).enabled = false; CharacterAnimEvent[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { ((Behaviour)componentsInChildren[i]).enabled = false; } FootStep[] componentsInChildren2 = ((Component)this).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { ((Behaviour)componentsInChildren2[i]).enabled = false; } return; } ((Humanoid)this).Awake(); _initialized = true; if (!Instances.Contains(this)) { Instances.Add(this); } Animator componentInChildren = ((Component)this).GetComponentInChildren(); if (!((Object)(object)componentInChildren != (Object)null)) { return; } AnimatorControllerParameter[] parameters = componentInChildren.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val.name == "emote_wave" && (int)val.type == 9) { _hasWaveTrigger = true; } } } public override void Start() { if (_initialized) { ((Humanoid)this).Start(); UpdateAppearance(); } } public override void OnDestroy() { Instances.Remove(this); if (_initialized) { ((Humanoid)this).OnDestroy(); } } private void Update() { if (_initialized) { _appearanceTimer -= Time.deltaTime; if (!(_appearanceTimer > 0f)) { _appearanceTimer = 2f; UpdateAppearance(); } } } private void UpdateAppearance() { //IL_009d: 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) if (!((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner() && !((Object)(object)base.m_visEquipment == (Object)null)) { if (((Character)this).m_nview.GetZDO().GetInt(ZDOVars.s_modelIndex, -1) != 1) { ((Character)this).m_nview.GetZDO().Set(ZDOVars.s_modelIndex, 1, false); } base.m_visEquipment.SetHairItem(MercVisuals.ItemHash("Hair6")); base.m_visEquipment.SetBeardItem(0); base.m_visEquipment.SetHairColor(new Vector3(0.32f, 0.2f, 0.1f)); base.m_visEquipment.SetSkinColor(new Vector3(0.92f, 0.77f, 0.63f)); } } public string GetName() { return MercLocalization.Text("dnpc_farmer_name"); } public override string GetHoverName() { return GetName(); } public override string GetHoverText() { if (!IsPlaced) { return GetName(); } string text = (ZInput.IsGamepadActive() ? MercLocalization.Binding("JoyUse", "A") : MercLocalization.Binding("Use", "E")); return GetName() + "\n[" + text + "] " + MercLocalization.Text("dnpc_farmer_action_inspect"); } public bool Interact(Humanoid user, bool hold, bool alt) { if (!hold) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && IsPlaced) { FarmerService.RequestInspect(this, val); return true; } } return false; } public bool UseItem(Humanoid user, ItemData item) { return false; } public static void SetHome(ZDO zdo, Vector3 position) { //IL_000b: 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) if (zdo != null && zdo.IsValid() && FarmerInspection.IsFinite(position) && !((Object)(object)ZNet.instance == (Object)null) && zdo.GetOwner() == ZNet.GetUID()) { zdo.Set(HomeKey, position); zdo.Set(HomeSetKey, true); } } internal void EnsureHome() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner() && !((Character)this).m_nview.GetZDO().GetBool(HomeSetKey, false)) { SetHome(((Character)this).m_nview.GetZDO(), ((Component)this).transform.position); } } internal bool TryWave() { if (!_hasWaveTrigger || !_initialized || (Object)(object)((Character)this).m_zanim == (Object)null || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || ((Character)this).IsDead() || ((Character)this).IsSwimming()) { return false; } ((Character)this).m_zanim.SetTrigger("emote_wave"); return true; } internal void ShowServerSpeech(string text) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (_initialized && !string.IsNullOrWhiteSpace(text) && !((Object)(object)Chat.instance == (Object)null)) { Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 25f, Mathf.Clamp(4f + (float)text.Length / 30f, 4f, 18f), "", text, false); ((Terminal)Chat.instance).AddString(GetName(), text, (Type)1, false); } } } public sealed class FarmerInteractionProxy : MonoBehaviour, Hoverable, Interactable { private FarmerNpc _target; private FarmerNpc Target { get { if (!((Object)(object)_target != (Object)null)) { return _target = ((Component)this).GetComponentInParent(); } return _target; } } private void Awake() { _target = ((Component)this).GetComponentInParent(); } public void Bind(FarmerNpc target) { _target = target; } public string GetHoverName() { if (!((Object)(object)Target != (Object)null)) { return ""; } return ((Character)Target).GetHoverName(); } public string GetHoverText() { if (!((Object)(object)Target != (Object)null)) { return ""; } return ((Character)Target).GetHoverText(); } public float GetHoverOffset() { if (!((Object)(object)Target != (Object)null)) { return 0f; } return ((Character)Target).GetHoverOffset(); } public bool Interact(Humanoid user, bool hold, bool alt) { if ((Object)(object)Target != (Object)null) { return Target.Interact(user, hold, alt); } return false; } public bool UseItem(Humanoid user, ItemData item) { return false; } } public sealed class FarmerAI : MonsterAI { private static readonly Func BaseUpdateAI = CreateBaseUpdate(); private static readonly int SolidMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "terrain", "blocker", "vehicle" }); private FarmerNpc _farmer; private Vector3 _destination; private bool _walking; private float _nextWalkAt; private float _nextWaveAt; private float _poseUntil; private float _walkUntil; private float _nextErrorAt; public override void Awake() { ZNetView component = ((Component)this).GetComponent(); if (ZNetView.m_forceDisableInit || (Object)(object)component == (Object)null || !component.IsValid()) { ((Behaviour)this).enabled = false; return; } ((MonsterAI)this).Awake(); _farmer = ((Component)this).GetComponent(); _nextWalkAt = Time.time + Random.Range(12f, 20f); _nextWaveAt = Time.time + Random.Range(12f, 20f); } public override bool UpdateAI(float dt) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_farmer == (Object)null) { _farmer = ((Component)this).GetComponent(); } if ((Object)(object)_farmer == (Object)null || !_farmer.Initialized || !_farmer.IsPlaced || ((Character)_farmer).IsDead() || BaseUpdateAI == null) { return false; } if (!BaseUpdateAI(this, dt)) { _walking = false; return false; } try { _farmer.EnsureHome(); ((BaseAI)this).SetAggravated(false, (AggravatedReason)0); ((BaseAI)this).SetAlerted(false); Vector3 homePosition = _farmer.HomePosition; if (((Character)_farmer).IsSwimming() || HorizontalDistance(((Component)this).transform.position, homePosition) > 4f || Time.time < _poseUntil) { _walking = false; ((BaseAI)this).StopMoving(); return true; } Player closestPlayer = Player.GetClosestPlayer(((Component)this).transform.position, 5f); if ((Object)(object)closestPlayer != (Object)null && !((Character)closestPlayer).IsDead() && !Physics.Linecast(((Character)_farmer).GetEyePoint(), ((Character)closestPlayer).GetEyePoint(), SolidMask, (QueryTriggerInteraction)1)) { _walking = false; ((BaseAI)this).StopMoving(); ((BaseAI)this).LookAt(((Character)closestPlayer).GetEyePoint()); _nextWalkAt = Mathf.Max(_nextWalkAt, Time.time + 8f); if (Time.time >= _nextWaveAt) { _nextWaveAt = Time.time + Random.Range(45f, 80f); if (_farmer.TryWave()) { _poseUntil = Time.time + 3f; } } return true; } if (_walking) { if (Time.time >= _walkUntil || HorizontalDistance(((Component)this).transform.position, _destination) < 0.6f) { _walking = false; ((BaseAI)this).StopMoving(); _nextWalkAt = Time.time + Random.Range(12f, 22f); } else { ((BaseAI)this).MoveTo(dt, _destination, 0.6f, false); } return true; } ((BaseAI)this).StopMoving(); if (Time.time < _nextWalkAt) { return true; } _nextWalkAt = Time.time + Random.Range(12f, 22f); if (TryChoosePoint(homePosition, out _destination)) { _walking = true; _walkUntil = Time.time + 8f; } return true; } catch (Exception ex) { _walking = false; ((BaseAI)this).StopMoving(); if (Time.time >= _nextErrorAt) { _nextErrorAt = Time.time + 30f; MercPlugin.LogWarn("Farmer idle navigation paused: " + ex.Message); } return false; } } private bool TryChoosePoint(Vector3 home, out Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) point = home; RaycastHit val3 = default(RaycastHit); for (int i = 0; i < 6; i++) { Vector2 val = Random.insideUnitCircle * 3f; Vector3 val2 = home + new Vector3(val.x, 0f, val.y); if (!((Object)(object)ZoneSystem.instance == (Object)null) && ZoneSystem.instance.IsZoneLoaded(val2) && Physics.Raycast(val2 + Vector3.up * 2f, Vector3.down, ref val3, 4f, SolidMask, (QueryTriggerInteraction)1) && !(((RaycastHit)(ref val3)).normal.y < 0.7f) && !(Mathf.Abs(((RaycastHit)(ref val3)).point.y - home.y) > 1.2f)) { val2 = ((RaycastHit)(ref val3)).point + Vector3.up * 0.05f; if (!MercAI.IsWetAt(val2) && !((Object)(object)Player.GetClosestPlayer(val2, 1.5f) != (Object)null) && !Physics.CheckCapsule(val2 + Vector3.up * 0.4f, val2 + Vector3.up * 1.6f, 0.3f, SolidMask, (QueryTriggerInteraction)1) && ((BaseAI)this).HavePath(val2) && !Physics.Linecast(((Component)this).transform.position + Vector3.up * 0.7f, val2 + Vector3.up * 0.7f, SolidMask, (QueryTriggerInteraction)1)) { point = val2; return true; } } } return false; } private static float HorizontalDistance(Vector3 first, Vector3 second) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) first.y = (second.y = 0f); return Vector3.Distance(first, second); } private static Func CreateBaseUpdate() { MethodInfo methodInfo = AccessTools.Method(typeof(BaseAI), "UpdateAI", new Type[1] { typeof(float) }, (Type[])null); if (methodInfo == null) { return null; } DynamicMethod dynamicMethod = new DynamicMethod("FarmerBaseAI_UpdateAI", typeof(bool), new Type[2] { typeof(FarmerAI), typeof(float) }, restrictedSkipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Call, methodInfo); iLGenerator.Emit(OpCodes.Ret); return (Func)dynamicMethod.CreateDelegate(typeof(Func)); } } internal static class FarmerInspection { private static readonly Collider[] SurveyHits = (Collider[])(object)new Collider[512]; private static readonly HashSet SupportedPlants = new HashSet(); private static readonly HashSet SupportedReadyCrops = new HashSet(); private static readonly HashSet AmbiguousReadyCrops = new HashSet(); private static readonly HashSet CropItems = new HashSet(StringComparer.Ordinal) { "Carrot", "CarrotSeeds", "Turnip", "TurnipSeeds", "Onion", "OnionSeeds", "Barley", "Flax", "MushroomJotunPuffs", "MushroomMagecap" }; private static readonly MethodInfo HiveFreeSpace = AccessTools.Method(typeof(Beehive), "HaveFreeSpace", (Type[])null, (Type[])null); private static readonly MethodInfo HiveBiome = AccessTools.Method(typeof(Beehive), "CheckBiome", (Type[])null, (Type[])null); private static readonly FieldInfo PlantSpawnTime = AccessTools.Field(typeof(Plant), "m_spawnTime"); private static ZNetScene _catalogueScene; private static readonly int SightMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "terrain", "blocker", "vehicle" }); internal static void ResetSceneMemory() { SupportedPlants.Clear(); SupportedReadyCrops.Clear(); AmbiguousReadyCrops.Clear(); Array.Clear(SurveyHits, 0, SurveyHits.Length); _catalogueScene = null; } internal static FarmerObservation Capture(FarmerNpc farmer) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: 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_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) FarmerObservation farmerObservation = new FarmerObservation(); if ((Object)(object)farmer == (Object)null || !farmer.IsPlaced || ((Character)farmer).IsDead() || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.IsZoneLoaded(((Component)farmer).transform.position)) { return farmerObservation; } if ((Object)(object)EnvMan.instance != (Object)null) { farmerObservation.TimeKnown = true; farmerObservation.IsNight = !EnvMan.IsDay(); } CaptureThreat(farmer, farmerObservation); try { if (!EnsureCatalogue()) { return farmerObservation; } } catch { return farmerObservation; } Vector3 position = ((Component)farmer).transform.position; HashSet hashSet = new HashSet(); try { int num = Physics.OverlapSphereNonAlloc(position, 12f, SurveyHits, -1, (QueryTriggerInteraction)2); farmerObservation.SurveyKnown = true; farmerObservation.SurveyCapped = num >= SurveyHits.Length; for (int i = 0; i < num; i++) { Collider val = SurveyHits[i]; if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy) { continue; } Plant componentInParent = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && Within(position, ((Component)componentInParent).transform.position) && hashSet.Add(((Object)componentInParent).GetInstanceID()) && SupportedPlants.Contains(StringExtensionMethods.GetStableHashCode(Utils.GetPrefabName(((Component)componentInParent).gameObject)))) { AddPlant(componentInParent, farmerObservation); continue; } Pickable componentInParent2 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null && Within(position, ((Component)componentInParent2).transform.position) && hashSet.Add(((Object)componentInParent2).GetInstanceID())) { int stableHashCode = StringExtensionMethods.GetStableHashCode(Utils.GetPrefabName(((Component)componentInParent2).gameObject)); if (AmbiguousReadyCrops.Contains(stableHashCode)) { farmerObservation.SurveyCapped = true; } if (SupportedReadyCrops.Contains(stableHashCode)) { AddReadyCrop(componentInParent2, farmerObservation); } } else { Beehive componentInParent3 = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null && Within(position, ((Component)componentInParent3).transform.position) && hashSet.Add(((Object)componentInParent3).GetInstanceID())) { AddHive(componentInParent3, farmerObservation); } } } } catch { farmerObservation.SurveyCapped = true; } finally { Array.Clear(SurveyHits, 0, SurveyHits.Length); } return farmerObservation; } private static bool EnsureCatalogue() { if ((Object)(object)_catalogueScene == (Object)(object)ZNetScene.instance && SupportedPlants.Count != 0) { return true; } ResetSceneMemory(); if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return false; } HashSet hashSet = new HashSet(); foreach (ZoneVegetation item in ZoneSystem.instance.m_vegetation) { if ((Object)(object)item.m_prefab != (Object)null) { hashSet.Add(StringExtensionMethods.GetStableHashCode(((Object)item.m_prefab).name)); } } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { Plant val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null); if ((Object)(object)val == (Object)null || !val.m_needCultivatedGround || val.m_grownPrefabs == null) { continue; } GameObject[] grownPrefabs = val.m_grownPrefabs; foreach (GameObject val2 in grownPrefabs) { Pickable val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent() : null); if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.m_itemPrefab == (Object)null) && CropItems.Contains(((Object)val3.m_itemPrefab).name)) { SupportedPlants.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab).name)); int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)val2).name); if (hashSet.Contains(stableHashCode) || ((Object)val3.m_itemPrefab).name == "MushroomJotunPuffs" || ((Object)val3.m_itemPrefab).name == "MushroomMagecap") { AmbiguousReadyCrops.Add(stableHashCode); } else { SupportedReadyCrops.Add(stableHashCode); } } } } _catalogueScene = ZNetScene.instance; return SupportedPlants.Count != 0; } private static void AddPlant(Plant plant, FarmerObservation result) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Invalid comparison between Unknown and I4 //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Invalid comparison between Unknown and I4 //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Invalid comparison between Unknown and I4 //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Invalid comparison between Unknown and I4 //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Invalid comparison between Unknown and I4 FarmerCropCondition farmerCropCondition = FarmerCropCondition.Unknown; try { ZNetView component = ((Component)plant).GetComponent(); bool flag = (Object)(object)component != (Object)null && component.IsValid() && PlantSpawnTime != null && PlantSpawnTime.GetValue(plant) is float num && Time.time - num >= 12f && (Object)(object)Heightmap.FindHeightmap(((Component)plant).transform.position) != (Object)null; Status status = plant.GetStatus(); flag = flag && ((int)status == 0 || (int)status == 2 || (int)status == 1 || (int)status == 3 || (int)status == 4); farmerCropCondition = FarmerRules.ClassifyCrop(ready: false, (int)status == 2, (int)status == 3, (int)status == 4, (int)status == 1, flag); } catch { } result.CropCount++; switch (farmerCropCondition) { case FarmerCropCondition.Growing: result.Growing++; break; case FarmerCropCondition.Crowded: result.Crowded++; break; case FarmerCropCondition.WrongBiome: result.WrongBiome++; break; case FarmerCropCondition.Uncultivated: result.Uncultivated++; break; case FarmerCropCondition.RoofBlocked: result.RoofBlocked++; break; default: result.UnknownCrops++; break; } } private static void AddReadyCrop(Pickable pickable, FarmerObservation result) { ZNetView component = ((Component)pickable).GetComponent(); if (!((Object)(object)component != (Object)null) || !component.IsValid() || pickable.CanBePicked()) { result.CropCount++; if ((Object)(object)component != (Object)null && component.IsValid()) { result.Ready++; } else { result.UnknownCrops++; } } } private static void AddHive(Beehive hive, FarmerObservation result) { result.HiveCount++; ZNetView component = ((Component)hive).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || HiveFreeSpace == null || HiveBiome == null) { result.UnknownHives++; return; } try { int num = component.GetZDO().GetInt(ZDOVars.s_level, 0); if (num < 0 || num > hive.m_maxHoney || hive.m_maxHoney > 1000) { result.UnknownHives++; return; } bool num2 = (bool)HiveBiome.Invoke(hive, null) && (bool)HiveFreeSpace.Invoke(hive, null); result.KnownHoney += num; if (!num2) { result.UnhappyHives++; } } catch { result.UnknownHives++; } } private static void CaptureThreat(FarmerNpc farmer, FarmerObservation result) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) Player closestPlayer = Player.GetClosestPlayer(((Component)farmer).transform.position, 12f); if ((Object)(object)closestPlayer == (Object)null || ((Character)closestPlayer).IsDead()) { return; } result.ThreatKnown = true; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsDead() && !allCharacter.IsPlayer() && !(allCharacter is Mercenary) && !((Object)(object)((Component)allCharacter).GetComponent() != (Object)null) && !allCharacter.IsTamed() && Within(((Component)farmer).transform.position, ((Component)allCharacter).transform.position) && BaseAI.IsEnemy((Character)(object)closestPlayer, allCharacter) && !Physics.Linecast(((Character)farmer).GetEyePoint(), allCharacter.GetCenterPoint(), SightMask, (QueryTriggerInteraction)1)) { result.ThreatNearby = true; break; } } } private static bool Within(Vector3 origin, Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(position)) { return Vector3.Distance(origin, position) <= 12f; } return false; } internal static bool IsFinite(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(point.x) && !float.IsInfinity(point.x) && !float.IsNaN(point.y) && !float.IsInfinity(point.y) && !float.IsNaN(point.z)) { return !float.IsInfinity(point.z); } return false; } } [HarmonyPatch] internal static class FarmerDamagePatches { private static IEnumerable TargetMethods() { yield return AccessTools.Method(typeof(Character), "Damage", new Type[1] { typeof(HitData) }, (Type[])null); yield return AccessTools.Method(typeof(Character), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }, (Type[])null); yield return AccessTools.Method(typeof(Character), "ApplyDamage", (Type[])null, (Type[])null); } [HarmonyPriority(800)] private static bool Prefix(Character __instance) { return !(__instance is FarmerNpc); } } [HarmonyPatch(typeof(BaseAI), "IsEnemy", new Type[] { typeof(Character) })] internal static class FarmerEnemyInstancePatch { private static bool Prefix(BaseAI __instance, Character other, ref bool __result) { if (!(other is FarmerNpc) && ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).GetComponent() == (Object)null)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(BaseAI), "IsEnemy", new Type[] { typeof(Character), typeof(Character) })] internal static class FarmerEnemyStaticPatch { private static bool Prefix(Character a, Character b, ref bool __result) { if (!(a is FarmerNpc) && !(b is FarmerNpc)) { return true; } __result = false; return false; } } internal static class FarmerPlacement { internal const string PrefabName = "DynamicNPC_Farmer"; private static GameObject _container; private static GameObject _prefab; private static ZNetScene _registeredScene; private static PieceTable _hammerTable; private static Sprite _icon; private static Texture2D _iconTexture; private static readonly Dictionary Glyphs = new Dictionary { ['F'] = new string[7] { "11111", "10000", "10000", "11110", "10000", "10000", "10000" }, ['A'] = new string[7] { "01110", "10001", "10001", "11111", "10001", "10001", "10001" }, ['R'] = new string[7] { "11110", "10001", "10001", "11110", "10100", "10010", "10001" }, ['M'] = new string[7] { "10001", "11011", "10101", "10101", "10001", "10001", "10001" }, ['E'] = new string[7] { "11111", "10000", "10000", "11110", "10000", "10000", "11111" } }; internal static bool IsRegistered { get { if ((Object)(object)_registeredScene != (Object)null) { return (Object)(object)_registeredScene == (Object)(object)ZNetScene.instance; } return false; } } internal static void Register(ZNetScene scene) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown if ((Object)(object)scene == (Object)null || IsRegistered) { return; } GameObject val = MercPrefabs.PlayerPrefabSource(); GameObject val2 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Hammer") : null); PieceTable val3 = ((!((Object)(object)val2 != (Object)null)) ? null : val2.GetComponent()?.m_itemData?.m_shared?.m_buildPieces); GameObject itemPrefabSafe = MercUtil.GetItemPrefabSafe("ArmorRagsChest"); GameObject itemPrefabSafe2 = MercUtil.GetItemPrefabSafe("ArmorRagsLegs"); if ((Object)(object)val == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)itemPrefabSafe == (Object)null || (Object)(object)itemPrefabSafe2 == (Object)null) { return; } try { if ((Object)(object)_prefab == (Object)null) { if ((Object)(object)_container == (Object)null) { _container = new GameObject("DynamicNPCs_FarmerPrefab"); _container.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_container); } _prefab = Object.Instantiate(val, _container.transform, false); ((Object)_prefab).name = "DynamicNPC_Farmer"; Build(_prefab, itemPrefabSafe, itemPrefabSafe2); } MercPrefabs.RegisterPrefab(scene, _prefab); if ((Object)(object)scene.GetPrefab("DynamicNPC_Farmer") != (Object)(object)_prefab) { throw new InvalidOperationException("Farmer network prefab registration was not retained"); } if (!val3.m_pieces.Contains(_prefab)) { val3.m_pieces.Add(_prefab); } _hammerTable = val3; _registeredScene = scene; MercPlugin.Log("Registered Runa the Grower in the vanilla Hammer Misc tab."); } catch (Exception ex) { MercPlugin.LogWarn("[Farmer] Prefab registration failed: " + ex); if ((Object)(object)_prefab != (Object)null) { Transform parent = _prefab.transform.parent; GameObject container = _container; if ((Object)(object)parent == (Object)(object)((container != null) ? container.transform : null)) { val3.m_pieces.Remove(_prefab); Object.DestroyImmediate((Object)(object)_prefab); _prefab = null; } } } } internal static GameObject GetPrefab() { if (!((Object)(object)ZNetScene.instance != (Object)null)) { return null; } return ZNetScene.instance.GetPrefab("DynamicNPC_Farmer"); } internal static bool IsFarmer(Piece piece) { if ((Object)(object)piece != (Object)null) { return (Object)(object)((Component)piece).GetComponent() != (Object)null; } return false; } internal static bool IsPlaced(FarmerNpc farmer) { if ((Object)(object)farmer == (Object)null) { return false; } ZNetView component = ((Component)farmer).GetComponent(); Piece component2 = ((Component)farmer).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && (Object)(object)component2 != (Object)null && component2.GetCreator() != 0L) { return component.GetZDO().GetPrefab() == StringExtensionMethods.GetStableHashCode("DynamicNPC_Farmer"); } return false; } internal static void ResetScene() { _registeredScene = null; _hammerTable = null; } internal static void Shutdown() { if ((Object)(object)_hammerTable != (Object)null && (Object)(object)_prefab != (Object)null) { _hammerTable.m_pieces.Remove(_prefab); } ResetScene(); _prefab = null; if ((Object)(object)_container != (Object)null) { Object.Destroy((Object)(object)_container); } _container = null; if ((Object)(object)_icon != (Object)null) { Object.Destroy((Object)(object)_icon); } if ((Object)(object)_iconTexture != (Object)null) { Object.Destroy((Object)(object)_iconTexture); } _icon = null; _iconTexture = null; } private static void Build(GameObject go, GameObject chest, GameObject legs) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) Player component = go.GetComponent(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("Farmer source has no Player component"); } Component[] array = (Component[])(object)new Component[3] { (Component)go.GetComponent(), (Component)go.GetComponent(), (Component)go.GetComponent() }; foreach (Component val in array) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } } FarmerNpc farmerNpc = go.AddComponent(); MercPrefabs.CopyCharacterData(component, (Humanoid)(object)farmerNpc); ((Character)farmerNpc).m_name = "$dnpc_farmer_name"; ((Character)farmerNpc).m_faction = (Faction)0; ((Character)farmerNpc).m_group = "dynamicnpcs_townsfolk"; ((Character)farmerNpc).m_aiSkipTarget = true; ((Character)farmerNpc).m_health = 100f; ((Humanoid)farmerNpc).m_defaultItems = (GameObject[])(object)new GameObject[2] { chest, legs }; ((Humanoid)farmerNpc).m_randomWeapon = Array.Empty(); ((Humanoid)farmerNpc).m_randomShield = Array.Empty(); FarmerAI farmerAI = go.AddComponent(); ((MonsterAI)farmerAI).m_attackPlayerObjects = false; ((BaseAI)farmerAI).m_aggravatable = false; ((BaseAI)farmerAI).m_passiveAggresive = false; ((MonsterAI)farmerAI).m_enableHuntPlayer = false; ((BaseAI)farmerAI).m_avoidFire = true; ((BaseAI)farmerAI).m_avoidLava = true; ((BaseAI)farmerAI).m_avoidWater = true; ((BaseAI)farmerAI).m_randomMoveRange = 0f; ((BaseAI)farmerAI).m_randomMoveInterval = 99999f; ((MonsterAI)farmerAI).m_maxChaseDistance = 0f; ((BaseAI)farmerAI).m_pathAgentType = (AgentType)6; MercUtil.SetPrivate(farmerAI, false, "m_patrol", typeof(BaseAI), typeof(MonsterAI)); MercPrefabs.RebindPlayerReferences(go, component, (Character)(object)farmerNpc); MercPrefabs.RebindAnimationComponents(go, (Character)(object)farmerNpc, (MonsterAI)(object)farmerAI); Object.DestroyImmediate((Object)(object)component); MercPrefabs.RebindAnimationComponents(go, (Character)(object)farmerNpc, (MonsterAI)(object)farmerAI); Rigidbody component2 = go.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.isKinematic = false; } ZNetView component3 = go.GetComponent(); if ((Object)(object)component3 == (Object)null) { throw new InvalidOperationException("Farmer source has no ZNetView"); } component3.m_persistent = true; component3.m_distant = false; go.AddComponent(); Piece obj = go.AddComponent(); obj.m_name = "$dnpc_farmer_name"; obj.m_description = "$dnpc_farmer_description"; obj.m_category = (PieceCategory)0; obj.m_enabled = true; obj.m_canBeRemoved = true; obj.m_craftingStation = null; obj.m_resources = Array.Empty(); obj.m_noInWater = true; obj.m_noClipping = true; obj.m_allowAltGroundPlacement = false; ((StaticTarget)obj).m_primaryTarget = false; ((StaticTarget)obj).m_randomTarget = false; obj.m_icon = BuildIcon(); GameObject val2 = new GameObject("FarmerBuildVolume"); val2.transform.SetParent(go.transform, false); int num = LayerMask.NameToLayer("piece"); if (num < 0) { throw new InvalidOperationException("Valheim piece layer is unavailable"); } val2.layer = num; BoxCollider val3 = val2.AddComponent(); val3.center = new Vector3(0f, 0.9f, 0f); val3.size = new Vector3(0.6f, 1.8f, 0.6f); go.AddComponent().BuildVolume = val3; Collider[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Collider val4 in componentsInChildren) { if (!((Object)(object)val4 == (Object)null) && !((Object)(object)((Component)val4).gameObject == (Object)(object)go)) { FarmerInteractionProxy farmerInteractionProxy = ((Component)val4).GetComponent(); if ((Object)(object)farmerInteractionProxy == (Object)null) { farmerInteractionProxy = ((Component)val4).gameObject.AddComponent(); } farmerInteractionProxy.Bind(farmerNpc); } } } private static Sprite BuildIcon() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0159: 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_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_icon != (Object)null) { return _icon; } Color32[] array = (Color32[])(object)new Color32[6400]; Color32 color = default(Color32); ((Color32)(ref color))..ctor((byte)20, (byte)24, (byte)28, (byte)238); Color32 color2 = default(Color32); ((Color32)(ref color2))..ctor((byte)167, (byte)194, (byte)119, byte.MaxValue); Color32 color3 = default(Color32); ((Color32)(ref color3))..ctor((byte)245, (byte)238, (byte)214, byte.MaxValue); Fill(array, 80, 2, 2, 76, 76, color); Fill(array, 80, 2, 2, 76, 3, color2); Fill(array, 80, 2, 75, 76, 3, color2); Fill(array, 80, 2, 2, 3, 76, color2); Fill(array, 80, 75, 2, 3, 76, color2); int num = (80 - ("FARMER".Length * 6 - 1) * 2) / 2; for (int i = 0; i < "FARMER".Length; i++) { string[] array2 = Glyphs["FARMER"[i]]; for (int j = 0; j < array2.Length; j++) { for (int k = 0; k < array2[j].Length; k++) { if (array2[j][k] == '1') { Fill(array, 80, num + (i * 6 + k) * 2, 33 + (6 - j) * 2, 2, 2, color3); } } } } _iconTexture = new Texture2D(80, 80, (TextureFormat)4, false) { name = "DynamicNPCs_Farmer_Icon", filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1 }; _iconTexture.SetPixels32(array); _iconTexture.Apply(false, true); _icon = Sprite.Create(_iconTexture, new Rect(0f, 0f, 80f, 80f), new Vector2(0.5f, 0.5f), 80f); ((Object)_icon).name = ((Object)_iconTexture).name; return _icon; } private static void Fill(Color32[] pixels, int size, int x, int y, int width, int height, Color32 color) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) for (int i = y; i < y + height; i++) { for (int j = x; j < x + width; j++) { if (i >= 0 && i < size && j >= 0 && j < size) { pixels[i * size + j] = color; } } } } } public sealed class FarmerPlacementPreview : MonoBehaviour { public BoxCollider BuildVolume; private bool _preview; private void Awake() { ZNetView component = ((Component)this).GetComponent(); _preview = ZNetView.m_forceDisableInit || (Object)(object)component == (Object)null || !component.IsValid(); if ((Object)(object)BuildVolume != (Object)null) { ((Collider)BuildVolume).isTrigger = !_preview; } if (!_preview) { ((Behaviour)this).enabled = false; } } private void Start() { //IL_004a: 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) if (_preview) { VisEquipment component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null) { component.SetModel(1); component.SetHairItem(MercVisuals.ItemHash("Hair6")); component.SetBeardItem(0); component.SetHairColor(new Vector3(0.32f, 0.2f, 0.1f)); component.SetSkinColor(new Vector3(0.92f, 0.77f, 0.63f)); component.SetChestItem(MercVisuals.ItemHash("ArmorRagsChest")); component.SetLegItem(MercVisuals.ItemHash("ArmorRagsLegs")); component.CustomUpdate(0f, 0f); } ((Behaviour)this).enabled = false; } } } [HarmonyPatch(typeof(Player), "PlacePiece")] internal static class Player_PlaceFarmer_Patch { private static bool Prefix(Player __instance, Piece piece, Vector3 pos, Quaternion rot) { //IL_0020: 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) if (!FarmerPlacement.IsFarmer(piece)) { return true; } if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && MercBanner.HasBuildHammerEquipped(__instance)) { FarmerService.RequestPlace(__instance, pos, rot); } return false; } } [HarmonyPatch(typeof(Player), "RemovePiece")] [HarmonyPriority(800)] internal static class Player_RemoveFarmer_Patch { private static readonly FieldInfo RemoveRayMask = AccessTools.Field(typeof(Player), "m_removeRayMask"); private static readonly FieldInfo MaxPlaceDistance = AccessTools.Field(typeof(Player), "m_maxPlaceDistance"); private static bool Prefix(Player __instance, ref bool __result) { //IL_005e: 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_00bf: 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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)GameCamera.instance == (Object)null) { return true; } if (RemoveRayMask == null || MaxPlaceDistance == null) { return true; } int num = (int)RemoveRayMask.GetValue(__instance); Transform transform = ((Component)GameCamera.instance).transform; RaycastHit val = default(RaycastHit); if (!Physics.Raycast(transform.position, transform.forward, ref val, 50f, num) || (Object)(object)((RaycastHit)(ref val)).collider == (Object)null) { return true; } Piece componentInParent = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent(); if (!FarmerPlacement.IsFarmer(componentInParent)) { return true; } __result = false; if (!MercBanner.HasBuildHammerEquipped(__instance) || (Object)(object)((Character)__instance).m_eye == (Object)null || Vector3.Distance(((Character)__instance).m_eye.position, ((RaycastHit)(ref val)).point) >= (float)MaxPlaceDistance.GetValue(__instance)) { return false; } if (Location.IsInsideNoBuildLocation(((Component)componentInParent).transform.position)) { ((Character)__instance).Message((MessageType)2, "$msg_nobuildzone", 0, (Sprite)null, false); return false; } if (!PrivateArea.CheckAccess(((Component)componentInParent).transform.position, 0f, true, false)) { return false; } FarmerNpc component = ((Component)componentInParent).GetComponent(); if (!FarmerPlacement.IsPlaced(component)) { return false; } FarmerService.RequestRemoval(component, __instance); __result = true; return false; } } internal enum FarmerCropCondition { Unknown, Growing, Ready, Crowded, WrongBiome, Uncultivated, RoofBlocked } internal enum FarmerRemark { Greeting, Threat, Night, Day, CropProblem, CropsReady, HiveProblem } internal sealed class FarmerObservation { internal bool SurveyKnown; internal bool SurveyCapped; internal int CropCount; internal int Growing; internal int Ready; internal int Crowded; internal int WrongBiome; internal int Uncultivated; internal int RoofBlocked; internal int UnknownCrops; internal int HiveCount; internal int KnownHoney; internal int UnhappyHives; internal int UnknownHives; internal bool TimeKnown; internal bool IsNight; internal bool ThreatKnown; internal bool ThreatNearby; } internal readonly struct FarmerReply { internal readonly string Token; internal readonly object[] Arguments; internal FarmerReply(string token, params object[] arguments) { Token = token; Arguments = arguments ?? Array.Empty(); } } internal static class FarmerAuthorityRules { internal const float PlacementRange = 8f; internal const float DismissRange = 8f; internal const float MaximumCoordinate = 100000f; internal const double QuaternionLengthSquaredTolerance = 0.02; internal static bool ValidatePlacementTransform(float px, float py, float pz, float rx, float ry, float rz, float qx, float qy, float qz, float qw) { if (!Coordinate(px) || !Coordinate(py) || !Coordinate(pz) || !Coordinate(rx) || !Coordinate(ry) || !Coordinate(rz) || !Finite(qx) || !Finite(qy) || !Finite(qz) || !Finite(qw)) { return false; } double num = (double)rx - (double)px; double num2 = (double)ry - (double)py; double num3 = (double)rz - (double)pz; if (num * num + num2 * num2 + num3 * num3 > 64.0) { return false; } return Math.Abs((double)qx * (double)qx + (double)qy * (double)qy + (double)qz * (double)qz + (double)qw * (double)qw - 1.0) <= 0.02; } internal static bool CanDismiss(long creatorId, long requesterId, bool admin, float distanceSquared, bool wardAccess) { return requesterId != 0L && (admin || (creatorId != 0L && creatorId == requesterId)) && Finite(distanceSquared) && distanceSquared >= 0f && distanceSquared <= 64f && wardAccess; } private static bool Coordinate(float value) { if (Finite(value)) { return Math.Abs(value) <= 100000f; } return false; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal static class FarmerRules { internal const string DisplayName = "Runa the Grower"; internal const float InspectionRadius = 12f; internal const int MaximumQuestionLength = 512; internal const int MaximumObservedObjects = 4096; private static readonly string[][] CropNames = new string[7][] { new string[3] { "carrot", "carrot", "carrots" }, new string[3] { "turnip", "turnip", "turnips" }, new string[3] { "onion", "onion", "onions" }, new string[2] { "barley", "barley" }, new string[2] { "flax", "flax" }, new string[5] { "jotun", "jotun puff", "jotun puffs", "jotunpuff", "jotunpuffs" }, new string[3] { "magecap", "magecap", "magecaps" } }; private static readonly string[][] BiomeNames = new string[9][] { new string[3] { "meadows", "meadows", "meadow" }, new string[3] { "blackforest", "black forest", "blackforest" }, new string[3] { "swamp", "swamp", "swamps" }, new string[3] { "mountain", "mountain", "mountains" }, new string[2] { "plains", "plains" }, new string[3] { "mistlands", "mistlands", "mistland" }, new string[3] { "ashlands", "ashlands", "ashland" }, new string[3] { "ocean", "ocean", "sea" }, new string[3] { "deepnorth", "deep north", "deepnorth" } }; internal static FarmerCropCondition ClassifyCrop(bool ready, bool crowded, bool wrongBiome, bool uncultivated, bool roofBlocked, bool statusKnown) { if (!statusKnown) { return FarmerCropCondition.Unknown; } int num = (crowded ? 1 : 0) + (wrongBiome ? 1 : 0) + (uncultivated ? 1 : 0) + (roofBlocked ? 1 : 0); if (num > 1 || (ready && num > 0)) { return FarmerCropCondition.Unknown; } if (ready) { return FarmerCropCondition.Ready; } if (wrongBiome) { return FarmerCropCondition.WrongBiome; } if (uncultivated) { return FarmerCropCondition.Uncultivated; } if (crowded) { return FarmerCropCondition.Crowded; } if (roofBlocked) { return FarmerCropCondition.RoofBlocked; } return FarmerCropCondition.Growing; } internal static bool IsAddressed(string question) { return AddressEnd(question) >= 0; } internal static string StripAddress(string question) { int num = AddressEnd(question); if (num < 0) { return question; } return question.TrimStart(Array.Empty()).Substring(num).Trim(); } internal static bool IsFarmQuestion(string question) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return false; } string text = Normalize(StripAddress(question) ?? ""); if (HasAny(text, "player", "online", "last seen", "last online", "who harvested", "who collected", "who picked", "sunken crypt", "sunken crypts", "grow stronger", "grow my skill", "grow skills", "grow my beard", "world seed", "worlds seed", "seed number", "force field", "field of view")) { return false; } bool flag = HasAny(text, "plant", "planting", "grow", "growing", "sow", "sowing", "cultivate", "cultivation", "harvest", "harvesting"); if (!flag && HasAny(text, "recipe", "recipes", "cook", "cooking", "craft", "crafting", "soup", "stew", "mead", "cauldron", "chest", "chests", "storage", "inventory", "container", "containers")) { return false; } if (HasAny(text, "how do i plant", "how to plant", "what can i plant", "what should i plant", "what can i grow", "what should i grow", "where should i plant", "where to plant")) { return true; } if (HasAny(text, "crop", "crops", "garden", "gardening", "field", "fields", "farm", "farming", "cultivator", "cultivated soil", "cultivation", "uncultivated", "seed", "seeds", "seedling", "seedlings", "hive", "hives", "beehive", "beehives", "bee", "bees", "honey")) { return true; } if (CropKey(text).Length > 0 && (OnlyCropName(text) || flag || RequestsWork(text) || GeneralAdvice(text) || ReportWords(text) || HasAny(text, "biome", "biomes", "spacing", "soil", "water", "watering", "fertilizer", "fertiliser"))) { return true; } if (text == "wrong biome" || text == "what needs attention") { return true; } if (RequestsWork(text)) { if (!HasAny(text, "animal", "animals", "livestock") && !(text == "harvest")) { return text == "replant"; } return true; } return false; } internal static bool IsResidentQuestion(string question) { if (question != null && question.Length <= 512) { return ResidentTopic(Normalize(StripAddress(question) ?? "")) != null; } return false; } internal static bool ShouldInspect(string question) { if (question != null && question.Length <= 512 && ResidentTopic(Normalize(StripAddress(question) ?? "")) == "danger") { return true; } if (!IsFarmQuestion(question)) { return false; } string text = Normalize(StripAddress(question) ?? ""); if (RequestsWork(text) || GeneralAdvice(text) || OnlyCropName(text) || HasAny(text, "per plant", "per crop", "base yield", "base harvest", "how many seeds do", "how many seeds will")) { return false; } if (!ReportWords(text) && !HasAny(text, "wrong biome", "uncultivated", "not cultivated")) { switch (text) { default: return text == "what needs attention"; case "crops": case "crop": case "garden": case "field": case "farm": case "honey": case "hive": case "hives": case "bees": break; } } return true; } internal static FarmerReply Reply(string question, FarmerObservation snapshot, int variation) { return Reply(question, snapshot, variation, null); } internal static FarmerReply Reply(string question, FarmerObservation snapshot, int variation, DialogueContext context) { if (question == null || question.Length > 512) { return Voice("unknown", variation); } string text = Normalize(StripAddress(question) ?? ""); snapshot = snapshot ?? new FarmerObservation(); string text2 = ResidentTopic(text); switch (text2) { case "time": return Voice((context == null || !context.NightKnown) ? ((!snapshot.TimeKnown) ? "time_unknown" : (snapshot.IsNight ? "night" : "day")) : (context.IsNight ? "night" : "day"), variation); case "weather": return Voice("weather_unknown", variation); case "danger": return Voice((!snapshot.ThreatKnown) ? "threat_unknown" : (snapshot.ThreatNearby ? "threat" : "no_threat"), variation); default: return Voice(text2, variation); case null: { if (!IsFarmQuestion(question)) { return Voice("unknown", variation); } if (RequestsWork(text)) { return Voice("work_unavailable", variation); } if (!ShouldInspect(question)) { return FarmAdvice(text, context, variation); } if (HiveQuestion(text)) { if (!ValidHives(snapshot)) { return Hives(snapshot, variation); } if (HasAny(text, "unhappy", "happy", "not producing", "space", "spacing", "crowded", "crowding", "too close", "roof", "why")) { if (snapshot.UnhappyHives <= 0) { if (snapshot.UnknownHives != 0 || snapshot.HiveCount <= 0) { return Hives(snapshot, variation); } return Voice("support_hive_checked", variation); } return new FarmerReply("dnpc_farmer_hive_attention_" + Variant(variation), snapshot.UnhappyHives); } return Hives(snapshot, variation); } string text3 = CropKey(text); if (text3.Length > 0 && text3 != "multiple") { if (!ValidCrops(snapshot) || snapshot.CropCount == 0) { return Crops(snapshot, variation); } return new FarmerReply("dnpc_farmer_support_named_summary_" + Variant(variation), CropName(text3), snapshot.Ready, snapshot.Growing, snapshot.Crowded + snapshot.WrongBiome + snapshot.Uncultivated + snapshot.RoofBlocked, snapshot.UnknownCrops); } if (HasAny(text, "wrong biome", "which biome", "what biome")) { return Diagnosis(snapshot, FarmerCropCondition.WrongBiome, variation); } if (HasAny(text, "uncultivated", "not cultivated", "cultivate", "cultivated soil")) { return Diagnosis(snapshot, FarmerCropCondition.Uncultivated, variation); } if (HasAny(text, "crowded", "crowding", "too close", "space", "spacing")) { return Diagnosis(snapshot, FarmerCropCondition.Crowded, variation); } if (HasAny(text, "roof", "sunlight", "covered", "no sun")) { return Diagnosis(snapshot, FarmerCropCondition.RoofBlocked, variation); } if (HasAny(text, "why", "not growing", "wont grow", "dying", "what needs attention")) { return Advice(snapshot, variation); } return Crops(snapshot, variation); } } } internal static FarmerReply Remark(FarmerRemark remark, int variation) { return remark switch { FarmerRemark.Threat => Voice("threat", variation), FarmerRemark.Night => Voice("night", variation), FarmerRemark.Day => Voice("day", variation), FarmerRemark.CropProblem => Voice("remark_crop_problem", variation), FarmerRemark.CropsReady => Voice("remark_crops_ready", variation), FarmerRemark.HiveProblem => Voice("remark_hive_problem", variation), _ => Voice("greeting", variation), }; } internal static FarmerReply[] Inspect(FarmerObservation snapshot, int variation) { snapshot = snapshot ?? new FarmerObservation(); if (snapshot.SurveyKnown) { return new FarmerReply[3] { Crops(snapshot, variation), Hives(snapshot, variation), Advice(snapshot, variation) }; } return new FarmerReply[1] { Voice("survey_unknown", variation) }; } private static FarmerReply Crops(FarmerObservation snapshot, int variation) { if (!ValidCrops(snapshot)) { return Voice("crops_unknown", variation); } if (snapshot.CropCount == 0) { return Voice(snapshot.SurveyCapped ? "crops_none_partial" : "crops_none", variation); } return new FarmerReply("dnpc_farmer_crops_summary_" + Variant(variation), snapshot.Ready, snapshot.Growing, snapshot.Crowded + snapshot.WrongBiome + snapshot.Uncultivated + snapshot.RoofBlocked, snapshot.UnknownCrops); } private static FarmerReply Hives(FarmerObservation snapshot, int variation) { if (!ValidHives(snapshot)) { return Voice("hives_unknown", variation); } if (snapshot.HiveCount == 0) { return Voice(snapshot.SurveyCapped ? "hives_none_partial" : "hives_none", variation); } if (snapshot.UnknownHives == snapshot.HiveCount) { return new FarmerReply("dnpc_farmer_hives_unconfirmed_" + Variant(variation), snapshot.HiveCount); } return new FarmerReply("dnpc_farmer_hives_summary_" + Variant(variation), snapshot.HiveCount, snapshot.KnownHoney, snapshot.UnhappyHives, snapshot.UnknownHives); } private static FarmerReply Advice(FarmerObservation snapshot, int variation) { if (snapshot.ThreatKnown && snapshot.ThreatNearby) { return Voice("threat", variation); } if (!snapshot.SurveyKnown) { return Voice("survey_unknown", variation); } if (snapshot.SurveyCapped) { return Voice("survey_partial", variation); } if (ValidCrops(snapshot)) { if (snapshot.WrongBiome > 0) { return Diagnosis(snapshot, FarmerCropCondition.WrongBiome, variation); } if (snapshot.Uncultivated > 0) { return Diagnosis(snapshot, FarmerCropCondition.Uncultivated, variation); } if (snapshot.Crowded > 0) { return Diagnosis(snapshot, FarmerCropCondition.Crowded, variation); } if (snapshot.RoofBlocked > 0) { return Diagnosis(snapshot, FarmerCropCondition.RoofBlocked, variation); } } if (ValidHives(snapshot) && snapshot.UnhappyHives > 0) { return new FarmerReply("dnpc_farmer_hive_attention_" + Variant(variation), snapshot.UnhappyHives); } if (!ValidCrops(snapshot) || !ValidHives(snapshot) || snapshot.UnknownCrops > 0 || snapshot.UnknownHives > 0) { return Voice("uncertain_attention", variation); } if (snapshot.Ready > 0 || snapshot.KnownHoney > 0) { return Voice("manual_harvest", variation); } return Voice((snapshot.CropCount + snapshot.HiveCount > 0) ? "growing_attention" : "empty_attention", variation); } private static FarmerReply Diagnosis(FarmerObservation snapshot, FarmerCropCondition condition, int variation) { string text; int num; switch (condition) { case FarmerCropCondition.WrongBiome: text = "biome"; num = snapshot.WrongBiome; break; case FarmerCropCondition.Uncultivated: text = "soil"; num = snapshot.Uncultivated; break; case FarmerCropCondition.Crowded: text = "spacing"; num = snapshot.Crowded; break; default: text = "sun"; num = snapshot.RoofBlocked; break; } if (!ValidCrops(snapshot) || num <= 0) { return Voice("advice_" + text, variation); } return new FarmerReply("dnpc_farmer_problem_" + text + "_" + Variant(variation), num); } private static bool ValidCrops(FarmerObservation snapshot) { if (snapshot.SurveyKnown && Count(snapshot.CropCount) && Count(snapshot.Growing) && Count(snapshot.Ready) && Count(snapshot.Crowded) && Count(snapshot.WrongBiome) && Count(snapshot.Uncultivated) && Count(snapshot.RoofBlocked) && Count(snapshot.UnknownCrops)) { return (long)snapshot.Growing + (long)snapshot.Ready + snapshot.Crowded + snapshot.WrongBiome + snapshot.Uncultivated + snapshot.RoofBlocked + snapshot.UnknownCrops == snapshot.CropCount; } return false; } private static bool ValidHives(FarmerObservation snapshot) { if (snapshot.SurveyKnown && Count(snapshot.HiveCount) && Count(snapshot.UnhappyHives) && Count(snapshot.UnknownHives) && snapshot.KnownHoney >= 0 && snapshot.KnownHoney <= 1048576 && snapshot.UnhappyHives + snapshot.UnknownHives <= snapshot.HiveCount) { if (snapshot.HiveCount <= snapshot.UnknownHives) { return snapshot.KnownHoney == 0; } return true; } return false; } private static bool Count(int value) { if (value >= 0) { return value <= 4096; } return false; } private static int Variant(int value) { return value & 1; } private static FarmerReply Voice(string topic, int variation) { return new FarmerReply("dnpc_farmer_" + topic + "_" + Variant(variation)); } private static FarmerReply FarmAdvice(string text, DialogueContext context, int variation) { string text2 = CropKey(text); string text3 = MentionedBiome(text); bool flag = HasAny(text, "here", "this biome", "this zone", "this area", "where we are", "around us"); bool flag2 = HasAny(text, "what can i plant", "what should i plant", "what can i grow", "what should i grow", "which crops", "what crops"); bool flag3 = (flag && (flag2 || HasAny(text, "plant", "grow", "keep", "place", "build", "biome", "biomes", "suit", "suitable", "put"))) || (text3.Length == 0 && flag2); if (flag3) { if (context == null || !context.InteriorKnown) { return Voice("support_site_unknown", variation); } if (context.IsInterior) { return Voice("support_interior", variation); } } if (text2.Length > 0 && text2 != "multiple" && HasAny(text, "seed", "seeds") && HasAny(text, "find", "get", "where") && !HasAny(text, "plant", "grow", "sow", "biome", "biomes") && !flag) { return Voice("support_" + text2, variation); } string text4 = text3; if (text4.Length == 0 && context != null && context.BiomeKnown && flag3) { text4 = MentionedBiome(Normalize(context.Biome ?? "")); } bool flag4 = flag3 || text3.Length > 0 || HasAny(text, "biome", "biomes"); if (HiveQuestion(text)) { if (HasAny(text, "sleep", "sleeping", "night", "nighttime")) { return Voice("support_bees_sleep", variation); } if (flag4) { if (text4.Length == 0) { return Voice(flag ? "support_bees_biome_unknown" : "support_bees", variation); } if (text4 != "multiple") { object obj; switch (text4) { default: obj = "wrong_zone_"; break; case "meadows": case "blackforest": case "plains": obj = "right_zone_"; break; } return new FarmerReply("dnpc_farmer_support_bees_" + (string?)obj + Variant(variation), BiomeName(text4)); } } return Voice("support_bees", variation); } if (HasAny(text, "watering", "water", "fertilize", "fertilizer", "fertiliser", "fertilizing", "rain")) { return Voice("support_care", variation); } if (HasAny(text, "how long", "how fast", "growth time", "grow time", "at night", "overnight")) { return Voice("support_growth", variation); } if (HasAny(text, "space", "spacing", "crowding", "too close", "needs more room")) { return Voice("support_spacing", variation); } if (HasAny(text, "roof", "sunlight", "sun", "covered", "no sun")) { return Voice("support_sun", variation); } if (text2.Length > 0 && text2 != "multiple") { if (flag4 && text4 != "multiple") { if (text4.Length == 0) { return new FarmerReply("dnpc_farmer_support_crop_" + (flag ? "zone_unknown_" : "biomes_") + Variant(variation), CropName(text2), SuitableBiomes(text2)); } if (!CanGrow(text2, text4)) { return new FarmerReply("dnpc_farmer_support_crop_wrong_zone_" + Variant(variation), CropName(text2), BiomeName(text4), SuitableBiomes(text2)); } if (text4 == "ashlands") { return new FarmerReply("dnpc_farmer_support_crop_ashlands_" + Variant(variation), CropName(text2)); } return new FarmerReply("dnpc_farmer_support_crop_right_zone_" + Variant(variation), CropName(text2), BiomeName(text4)); } return Voice("support_" + text2, variation); } if (text2 == "multiple") { return Voice("support_mixed_crops", variation); } if (HasAny(text, "seed", "seeds", "seedling", "seedlings")) { return Voice("support_seeds", variation); } if (HasAny(text, "harvest", "harvesting", "pick", "collect", "gather")) { return Voice("support_harvest", variation); } if (HasAny(text, "cultivated", "cultivate", "soil")) { return Voice("support_soil", variation); } if (flag4 || flag2) { if (text4.Length == 0) { return Voice("support_biome_unknown", variation); } switch (text4) { case "meadows": case "blackforest": return new FarmerReply("dnpc_farmer_support_zone_vegetables_" + Variant(variation), BiomeName(text4)); case "swamp": case "plains": case "mistlands": case "ashlands": return Voice("support_zone_" + text4, variation); default: if (text4 != "multiple") { return new FarmerReply("dnpc_farmer_support_zone_unsuitable_" + Variant(variation), BiomeName(text4)); } return Voice("support_mixed_crops", variation); } } return Voice("support_start", variation); } private static string ResidentTopic(string text) { if (text.Length == 0 || OnlyGreeting(text)) { return "greeting"; } if (text.EndsWith(" please", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - 7).TrimEnd(Array.Empty()); } if (IsOneOf(text, "who are you", "whats your name", "what is your name", "your name", "your story", "tell me your story", "tell me about yourself", "about yourself")) { return "identity"; } if (IsOneOf(text, "what can you do", "whats your job", "what is your job", "your job", "your work", "what do you do", "what do you do here", "how can you help", "how can you help me")) { return "job"; } if (IsOneOf(text, "thanks", "thank you", "thank you runa", "many thanks")) { return "thanks"; } if (IsOneOf(text, "how are you", "how are you today", "good to see you", "nice to meet you")) { return "greeting"; } if (IsOneOf(text, "what time", "what time is it", "is it night", "is it night here", "is it day", "is it day here", "daytime", "nighttime", "time of day", "what is the time of day")) { return "time"; } if (IsOneOf(text, "weather", "hows the weather", "how is the weather", "what is the weather", "whats the weather", "what is the weather here", "is it raining", "is it snowing", "is there a storm", "forecast")) { return "weather"; } if (IsOneOf(text, "are we safe", "are we safe here", "is it safe here", "is this area safe", "any danger", "any danger nearby", "any threats nearby", "any enemies nearby", "any monsters nearby", "what do you see", "what is nearby", "whats nearby", "do you see any enemies", "do you see any monsters", "do you see any danger")) { return "danger"; } return null; } private static bool IsOneOf(string text, params string[] choices) { foreach (string text2 in choices) { if (text == text2) { return true; } } return false; } private static bool GeneralAdvice(string text) { return HasAny(text, "how do i", "how can i", "how should i", "how to", "how long", "how fast", "how often", "can i", "should i", "do i", "what do i", "why do", "where can i", "where do i", "where to", "which biome", "what biome", "what can i", "what should i", "which crops", "what crops", "seed cycle", "seed cycles", "get seeds", "produce seeds", "make seeds", "tell me how", "explain", "need water", "need watering", "need fertilizer", "need fertiliser", "do bees need", "do crops need", "sleep", "sleeping"); } private static bool ReportWords(string text) { return HasAny(text, "inspect", "check", "survey", "status", "count", "how are", "how is", "how many", "how much", "ready", "unhappy", "happy", "not growing", "wont grow", "not producing", "crowded", "crowding", "too close", "dying", "blocked", "covered", "roof", "sunlight", "no sun", "needs more room", "what needs attention", "why are", "why is", "why wont", "what is wrong", "whats wrong"); } private static bool HiveQuestion(string text) { return HasAny(text, "hive", "hives", "beehive", "beehives", "bee", "bees", "honey"); } private static string CropKey(string text) { string text2 = ""; string[][] cropNames = CropNames; foreach (string[] array in cropNames) { for (int j = 1; j < array.Length; j++) { if (HasAny(text, array[j])) { if (text2.Length > 0 && text2 != array[0]) { return "multiple"; } text2 = array[0]; break; } } } return text2; } private static bool OnlyCropName(string text) { string[][] cropNames = CropNames; foreach (string[] array in cropNames) { for (int j = 1; j < array.Length; j++) { if (text == array[j]) { return true; } } } return false; } private static string MentionedBiome(string text) { string text2 = ""; string[][] biomeNames = BiomeNames; foreach (string[] array in biomeNames) { for (int j = 1; j < array.Length; j++) { if (HasAny(text, array[j])) { if (text2.Length > 0 && text2 != array[0]) { return "multiple"; } text2 = array[0]; break; } } } return text2; } private static bool CanGrow(string crop, string biome) { switch (crop) { case "barley": case "flax": return biome == "plains"; case "jotun": case "magecap": return biome == "mistlands"; default: switch (biome) { case "meadows": case "blackforest": case "plains": case "ashlands": return true; default: if (crop == "turnip") { if (!(biome == "swamp")) { return biome == "mistlands"; } return true; } return false; } } } private static string SuitableBiomes(string crop) { switch (crop) { case "barley": case "flax": return "the Plains"; case "jotun": case "magecap": return "the Mistlands"; default: return "Meadows, Black Forest, Plains, or heat-shielded Ashlands"; case "turnip": return "Meadows, Black Forest, Swamp, Plains, Mistlands, or heat-shielded Ashlands"; } } private static string CropName(string crop) { return crop switch { "onion" => "onions", "turnip" => "turnips", "carrot" => "carrots", "magecap" => "magecaps", "jotun" => "Jotun puffs", _ => crop, }; } private static string BiomeName(string biome) { switch (biome) { case "blackforest": return "Black Forest"; case "deepnorth": return "Deep North"; case "mountain": return "Mountains"; default: if (biome.Length != 0) { return char.ToUpperInvariant(biome[0]) + biome.Substring(1); } return "unknown terrain"; } } private static bool RequestsWork(string text) { if (HasAny(text, "how to", "how do i", "how can i", "tell me", "explain", "show me how", "teach me", "advice", "can i", "should i", "do i")) { return false; } for (int i = 0; i < 3; i++) { bool flag = false; string[] array = new string[7] { "can you", "could you", "will you", "would you", "please", "i want you to", "i need you to" }; foreach (string text2 in array) { if (Starts(text, text2)) { text = text.Substring(text2.Length).TrimStart(Array.Empty()); flag = true; break; } } if (!flag) { break; } } if (!Starts(text, "harvest") && !Starts(text, "replant") && !Starts(text, "collect") && !Starts(text, "pick") && !Starts(text, "gather") && !Starts(text, "plant") && !Starts(text, "sow") && !Starts(text, "water") && !Starts(text, "fertilize")) { return Starts(text, "feed"); } return true; } private static int AddressEnd(string question) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return -1; } string text = question.TrimStart(Array.Empty()); int i = 0; string[] array = new string[4] { "hello", "hey", "hi", "hail" }; foreach (string text2 in array) { if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase) && text.Length > text2.Length && Separator(text[text2.Length])) { for (i = text2.Length; i < text.Length && Separator(text[i]); i++) { } break; } } array = new string[3] { "Runa the Grower", "Runa", "farmer" }; foreach (string text3 in array) { if (text.Length < i + text3.Length || string.Compare(text, i, text3, 0, text3.Length, StringComparison.OrdinalIgnoreCase) != 0) { continue; } int k = i + text3.Length; if (k >= text.Length || Separator(text[k])) { for (; k < text.Length && Separator(text[k]); k++) { } return k; } } return -1; } private static bool Separator(char value) { if (!char.IsWhiteSpace(value)) { return ",:;!?".IndexOf(value) >= 0; } return true; } private static bool Starts(string text, string word) { if (!(text == word)) { return text.StartsWith(word + " ", StringComparison.Ordinal); } return true; } private static bool HasAny(string text, params string[] phrases) { foreach (string text2 in phrases) { if ((" " + text + " ").IndexOf(" " + text2 + " ", StringComparison.Ordinal) >= 0) { return true; } } return false; } private static bool OnlyGreeting(string text) { string[] array = text.Split(new char[1] { ' ' }); foreach (string value in array) { if (Array.IndexOf(new string[9] { "hello", "hi", "hey", "hail", "good", "morning", "evening", "there", "friend" }, value) < 0) { return false; } } return true; } private static string Normalize(string text) { StringBuilder stringBuilder = new StringBuilder(text.Length); for (int i = 0; i < text.Length; i++) { char c = char.ToLowerInvariant(text[i]); if (char.IsLetterOrDigit(c)) { stringBuilder.Append(c); } else if (c != '\'' && c != '’' && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); } } return stringBuilder.ToString().Trim(); } } internal static class GuideDialogueRules { internal static bool ShouldCancel(string text) { string words = Normalize(text); if (IsInformational(words) || IsNegated(words) || HasAny(words, "stop following", "stop follow", "stop talking", "stop speaking")) { return false; } return Regex.IsMatch(Normalize(WithoutAddress(text)).Trim(), "^(?:(?:please|just)\\s+)*(?:(?:can|could|would|will)\\s+you\\s+)?(?:please\\s+)?(?:(?:stop|cancel)(?:\\s+(?:(?:the|our|my|your|this|current|active)\\s+)?(?:guide|guidance|guiding|leading|search|searching|hunt|hunting|trip|task|errand|that|it)(?:\\s+(?:me|us))?(?:\\s+(?:to|for)\\s+[a-z0-9 ]+)?)?|never mind|nevermind|forget it)(?:\\s+(?:please|now))?$", RegexOptions.CultureInvariant); } internal static bool MayStartGuide(string text) { string words = Normalize(text); if (IsInformational(words) || IsNegated(words) || ShouldCancel(text) || HasAny(words, "stop following", "stop follow")) { return false; } if (HasAny(words, "take me", "lead me", "guide me", "bring me", "walk me", "go to", "point me", "show me the way")) { return true; } return Regex.IsMatch(Normalize(WithoutAddress(text)).Trim(), "^(?:(?:please|just)\\s+)*(?:(?:can|could|would|will)\\s+you\\s+)?(?:please\\s+)?(?:help me find|find|search(?: for)?|look for|show me|show where)\\s+", RegexOptions.CultureInvariant); } internal static bool AllowsModelAction(string text) { if (!ShouldCancel(text)) { return MayStartGuide(text); } return true; } internal static bool ContainsTerm(string text, string term) { string text2 = Normalize(term).Trim(); if (text2.Length > 0) { return Normalize(text).Contains(" " + text2 + " "); } return false; } internal static bool MatchesFindTarget(string text, string key, string label) { if (ContainsTerm(text, key) || ContainsTerm(text, label) || ContainsTerm(text, label + "s")) { return true; } return key switch { "wolf" => ContainsTerm(text, "wolves"), "raspberry" => ContainsTerm(text, "raspberries"), "blueberry" => ContainsTerm(text, "blueberries"), _ => ContainsTerm(text, key + "s"), }; } private static bool IsInformational(string words) { if (!HasAny(words, "how", "why", "what", "when", "explain", "tell me about", "can i", "could i", "should i", "should we", "do you know", "does", "help me fight", "help me defeat", "help me beat", "help me kill", "help me tame", "help me build", "help me craft", "help me cook", "help me repair", "help me upgrade", "help me survive", "recipe", "recipes")) { if (HasAny(words, "is it")) { return !HasAny(words, "where is it"); } return false; } return true; } private static bool IsNegated(string words) { if (HasAny(words, "do not", "don t", "dont", "cannot", "can t", "cant", "won t", "wont", "would not", "wouldn t", "no need", "not to", "not now", "not yet", "no longer", "not asking", "not looking", "not interested")) { return true; } return Regex.IsMatch(words, "\\b(?:never|not)\\s+(?:please\\s+)?(?:follow|lead|guide|take|bring|walk|show|find|search|look|go|cancel|stop)\\b", RegexOptions.CultureInvariant); } private static string WithoutAddress(string text) { return Regex.Replace(text ?? "", "^\\s*(?:(?:hi|hello|hey)\\s+)?[\\p{L}][\\p{L}\\p{N}'_-]*(?:\\s+the\\s+(?:mender|bulwark|fletcher))?\\s*[,!:]\\s*", "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } private static bool HasAny(string words, params string[] terms) { foreach (string text in terms) { if (words.Contains(" " + text + " ")) { return true; } } return false; } private static string Normalize(string text) { return " " + Regex.Replace((text ?? "").ToLowerInvariant(), "[^a-z0-9]+", " ").Trim() + " "; } } internal enum GuideFindKind { Creature, Object, Location } internal sealed class GuideFindTarget { public string Key; public string Label; public string LabelToken; public GuideFindKind Kind; public string[] NameContains; public string[] NameExcludes; public string RequiredGlobalKey; public string Refusal; public string RefusalToken; public string BypassItemContains; } internal static class GuideFinder { public const string ZdoGuideFindKey = "merc_guideFind"; internal static readonly GuideFindTarget[] Targets = new GuideFindTarget[22] { new GuideFindTarget { Key = "deer", Label = "deer", LabelToken = "dnpc_find_deer", Kind = GuideFindKind.Creature, NameContains = new string[1] { "deer" } }, new GuideFindTarget { Key = "boar", Label = "boar", LabelToken = "dnpc_find_boar", Kind = GuideFindKind.Creature, NameContains = new string[1] { "boar" } }, new GuideFindTarget { Key = "hare", Label = "hare", LabelToken = "dnpc_find_hare", Kind = GuideFindKind.Creature, NameContains = new string[1] { "hare" } }, new GuideFindTarget { Key = "wolf", Label = "wolf", LabelToken = "dnpc_find_wolf", Kind = GuideFindKind.Creature, NameContains = new string[1] { "wolf" } }, new GuideFindTarget { Key = "lox", Label = "lox", LabelToken = "dnpc_find_lox", Kind = GuideFindKind.Creature, NameContains = new string[1] { "lox" } }, new GuideFindTarget { Key = "raspberry", Label = "raspberries", LabelToken = "dnpc_find_raspberries", Kind = GuideFindKind.Object, NameContains = new string[1] { "raspberry" } }, new GuideFindTarget { Key = "blueberry", Label = "blueberries", LabelToken = "dnpc_find_blueberries", Kind = GuideFindKind.Object, NameContains = new string[1] { "blueberr" } }, new GuideFindTarget { Key = "mushroom", Label = "mushrooms", LabelToken = "dnpc_find_mushrooms", Kind = GuideFindKind.Object, NameContains = new string[1] { "mushroom" } }, new GuideFindTarget { Key = "thistle", Label = "thistle", LabelToken = "dnpc_find_thistle", Kind = GuideFindKind.Object, NameContains = new string[1] { "thistle" } }, new GuideFindTarget { Key = "carrot", Label = "carrots", LabelToken = "dnpc_find_carrots", Kind = GuideFindKind.Object, NameContains = new string[1] { "carrot" } }, new GuideFindTarget { Key = "turnip", Label = "turnips", LabelToken = "dnpc_find_turnips", Kind = GuideFindKind.Object, NameContains = new string[1] { "turnip" } }, new GuideFindTarget { Key = "barley", Label = "barley", LabelToken = "dnpc_find_barley", Kind = GuideFindKind.Object, NameContains = new string[1] { "barley" } }, new GuideFindTarget { Key = "flax", Label = "flax", LabelToken = "dnpc_find_flax", Kind = GuideFindKind.Object, NameContains = new string[1] { "flax" } }, new GuideFindTarget { Key = "onion", Label = "onions", LabelToken = "dnpc_find_onions", Kind = GuideFindKind.Object, NameContains = new string[1] { "onion" } }, new GuideFindTarget { Key = "copper", Label = "copper vein", LabelToken = "dnpc_find_copper", Kind = GuideFindKind.Object, NameContains = new string[1] { "copper" }, NameExcludes = new string[3] { "ingot", "ore", "scrap" }, RequiredGlobalKey = "defeated_eikthyr", BypassItemContains = "pickaxe", Refusal = "You need a pickaxe to mine copper. Craft any pickaxe first - the Antler Pickaxe from the Hard antlers dropped by Eikthyr is the earliest!", RefusalToken = "dnpc_guide_refusal_copper" }, new GuideFindTarget { Key = "tin", Label = "tin deposit", LabelToken = "dnpc_find_tin", Kind = GuideFindKind.Object, NameContains = new string[1] { "tin" }, NameExcludes = new string[2] { "ingot", "ore" }, RequiredGlobalKey = "defeated_eikthyr", BypassItemContains = "pickaxe", Refusal = "You need a pickaxe to mine tin. Craft any pickaxe first - the Antler Pickaxe from the Hard antlers dropped by Eikthyr is the earliest!", RefusalToken = "dnpc_guide_refusal_tin" }, new GuideFindTarget { Key = "sunkencrypt", Label = "sunken crypt", LabelToken = "dnpc_find_sunken_crypt", Kind = GuideFindKind.Location, NameContains = new string[3] { "sunkencrypt", "sunken_crypt", "sunken crypt" }, RequiredGlobalKey = "defeated_gdking", BypassItemContains = "cryptkey", Refusal = "You need the Swamp Key to open Sunken Crypts. Defeat the Elder first!", RefusalToken = "dnpc_guide_refusal_crypt" }, new GuideFindTarget { Key = "trollcave", Label = "troll cave", LabelToken = "dnpc_find_troll_cave", Kind = GuideFindKind.Location, NameContains = new string[3] { "trollcave", "troll_cave", "troll cave" } }, new GuideFindTarget { Key = "burial", Label = "burial chamber", LabelToken = "dnpc_find_burial_chamber", Kind = GuideFindKind.Location, NameContains = new string[2] { "crypt", "burial" }, NameExcludes = new string[1] { "sunken" } }, new GuideFindTarget { Key = "frostcave", Label = "frost cave", LabelToken = "dnpc_find_frost_cave", Kind = GuideFindKind.Location, NameContains = new string[6] { "icecave", "ice_cave", "ice cave", "frostcave", "frost_cave", "frost cave" } }, new GuideFindTarget { Key = "fulingvillage", Label = "Fuling village", LabelToken = "dnpc_find_fuling_village", Kind = GuideFindKind.Location, NameContains = new string[10] { "goblinvillage", "goblin_village", "goblin village", "fulingvillage", "fuling_village", "fuling village", "goblincamp", "goblin_camp", "fulingcamp", "fuling_camp" } }, new GuideFindTarget { Key = "tarpit", Label = "tar pit", LabelToken = "dnpc_find_tar_pit", Kind = GuideFindKind.Location, NameContains = new string[3] { "tarpit", "tar_pit", "tar pit" } } }; private static Dictionary _prefabNamesById; private static List> _locationRecords; private static ZoneSystem _locationScene; public const string ZdoGuideRoute = "merc_guideRoute"; internal static Character FindNearestLoadedCreature(string key, Vector3 origin, float maxDistance) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) GuideFindTarget guideFindTarget = ByKey(key); if (guideFindTarget == null || guideFindTarget.Kind != GuideFindKind.Creature) { return null; } Character result = null; float num = maxDistance * maxDistance; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsDead() && !(allCharacter is Player) && !(allCharacter is Mercenary) && NameMatches(guideFindTarget, Utils.GetPrefabName(((Component)allCharacter).gameObject))) { Vector3 val = ((Component)allCharacter).transform.position - origin; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = allCharacter; } } } return result; } internal static GuideFindTarget ByKey(string key) { GuideFindTarget[] targets = Targets; foreach (GuideFindTarget guideFindTarget in targets) { if (string.Equals(guideFindTarget.Key, key, StringComparison.OrdinalIgnoreCase)) { return guideFindTarget; } } return null; } internal static string TargetLabelArgument(GuideFindTarget target) { object obj; if (target == null || string.IsNullOrEmpty(target.LabelToken)) { obj = target?.Label; if (obj == null) { return ""; } } else { obj = MercLocalization.TokenArgument(target.LabelToken); } return (string)obj; } internal static bool NameMatches(GuideFindTarget target, string name) { if (string.IsNullOrEmpty(name)) { return false; } string text = name.ToLowerInvariant(); string[] nameExcludes; if (target.NameExcludes != null) { nameExcludes = target.NameExcludes; foreach (string value in nameExcludes) { if (text.Contains(value)) { return false; } } } nameExcludes = target.NameContains; foreach (string value2 in nameExcludes) { if (text.Contains(value2)) { return true; } } return false; } internal static bool GateAllows(GuideFindTarget target, out string refusal) { return GateAllows(target, null, out refusal); } internal static bool GateAllows(GuideFindTarget target, Player player, out string refusal) { refusal = null; if (target.RequiredGlobalKey == null) { return true; } if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(target.RequiredGlobalKey)) { return true; } if ((Object)(object)player != (Object)null && !string.IsNullOrEmpty(target.BypassItemContains) && PlayerHasItemLike(player, target.BypassItemContains)) { return true; } refusal = ((!string.IsNullOrEmpty(target.RefusalToken)) ? MercLocalization.Phrase(target.RefusalToken) : target.Refusal); return false; } private static bool PlayerHasItemLike(Player player, string needle) { try { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return false; } string value = needle.ToLowerInvariant(); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem?.m_shared != null) { string text = (((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : allItem.m_shared.m_name); if (!string.IsNullOrEmpty(text) && text.ToLowerInvariant().Contains(value)) { return true; } } } } catch { } return false; } internal static bool FindNearest(GuideFindTarget target, Vector3 origin, float maxDistance, out Vector3 position, out string what) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: 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) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; what = null; float num = maxDistance * maxDistance; try { if (target.Kind == GuideFindKind.Creature) { foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsDead() && !(allCharacter is Player) && !(allCharacter is Mercenary) && NameMatches(target, Utils.GetPrefabName(((Component)allCharacter).gameObject))) { Vector3 val = ((Component)allCharacter).transform.position - origin; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; position = ((Component)allCharacter).transform.position; what = TargetLabelArgument(target); } } } } else if (target.Kind == GuideFindKind.Object) { Dictionary dictionary = PrefabNamesById(); foreach (ZDO item in BannerAuthority.AllZdosFromTable()) { if (item != null && item.IsValid() && dictionary.TryGetValue(item.GetPrefab(), out var value) && NameMatches(target, value)) { Vector3 val2 = item.GetPosition() - origin; float sqrMagnitude2 = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude2 < num) { num = sqrMagnitude2; position = item.GetPosition(); what = TargetLabelArgument(target); } } } } else { foreach (KeyValuePair item2 in ZoneLocationRecords()) { string key = item2.Key; if (NameMatches(target, key)) { Vector3 val3 = item2.Value - origin; float sqrMagnitude3 = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude3 < num) { num = sqrMagnitude3; position = item2.Value; what = TargetLabelArgument(target); } } } } } catch (Exception ex) { MercPlugin.LogWarn("Guide find failed: " + ex.Message); return false; } return what != null; } internal static Dictionary PrefabNamesById() { if (_prefabNamesById != null && (Object)(object)ZNetScene.instance != (Object)null) { return _prefabNamesById; } Dictionary dictionary = new Dictionary(); if ((Object)(object)ZNetScene.instance != (Object)null) { FieldInfo fieldInfo = AccessTools.Field(typeof(ZNetScene), "m_namedPrefabs"); if (fieldInfo != null && fieldInfo.GetValue(ZNetScene.instance) is IDictionary dictionary2) { foreach (DictionaryEntry item in dictionary2) { object? value = item.Value; GameObject val = (GameObject)((value is GameObject) ? value : null); if (val != null && item.Key is int key) { dictionary[key] = ((Object)val).name; } } } } _prefabNamesById = dictionary; return dictionary; } private static IEnumerable MakeGeneric(IDictionary dictionary) { if (dictionary == null) { yield break; } foreach (DictionaryEntry item in dictionary) { yield return item; } } internal static List> ZoneLocationRecords() { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (_locationRecords != null && (Object)(object)_locationScene == (Object)(object)ZoneSystem.instance) { return _locationRecords; } List> list = new List>(); if ((Object)(object)ZoneSystem.instance != (Object)null) { FieldInfo fieldInfo = AccessTools.Field(typeof(ZoneSystem), "m_locations"); if (fieldInfo != null && fieldInfo.GetValue(ZoneSystem.instance) is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null) { FieldInfo fieldInfo2 = AccessTools.Field(item.GetType(), "m_prefabName"); FieldInfo fieldInfo3 = AccessTools.Field(item.GetType(), "m_position"); string text = ((fieldInfo2 != null) ? (fieldInfo2.GetValue(item) as string) : null); object obj = ((fieldInfo3 != null) ? fieldInfo3.GetValue(item) : null); if (text != null && obj is Vector3 value) { list.Add(new KeyValuePair(text, value)); } } } } } _locationRecords = list; _locationScene = ZoneSystem.instance; return list; } internal static bool RouteBlockedByWater(Vector3 start, Vector3 end, out Vector3 blockage) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00da: 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_00a0: 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) blockage = Vector3.zero; Vector3 val = end - start; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 20f) { return false; } int num = Mathf.Clamp(Mathf.CeilToInt(magnitude / 4f), 5, 500); float num2 = magnitude / (float)num; bool flag = false; float num3 = 0f; Vector3 val2 = Vector3.zero; for (int i = 0; i <= num; i++) { Vector3 val3 = Vector3.Lerp(start, end, (float)i / (float)num); if (MercAI.IsWetAtAnyRange(val3)) { if (!flag) { flag = true; val2 = val3; } num3 += num2; } else if (flag) { if (num3 > MercAI.MaxGuideSwimWidth && blockage == Vector3.zero) { blockage = val2; } flag = false; num3 = 0f; } } if (flag && num3 > MercAI.MaxGuideSwimWidth && blockage == Vector3.zero) { blockage = val2; } return blockage != Vector3.zero; } internal static bool PlanRoute(Vector3 start, Vector3 end, out List waypoints, out Vector3 blockage) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_005d: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_067c: Unknown result type (might be due to invalid IL or missing references) //IL_05c6: Unknown result type (might be due to invalid IL or missing references) //IL_05ed: Unknown result type (might be due to invalid IL or missing references) //IL_05f2: Unknown result type (might be due to invalid IL or missing references) //IL_05f4: Unknown result type (might be due to invalid IL or missing references) //IL_0633: Unknown result type (might be due to invalid IL or missing references) //IL_0638: Unknown result type (might be due to invalid IL or missing references) //IL_06ea: Unknown result type (might be due to invalid IL or missing references) //IL_064c: Unknown result type (might be due to invalid IL or missing references) //IL_064e: Unknown result type (might be due to invalid IL or missing references) //IL_0653: Unknown result type (might be due to invalid IL or missing references) //IL_0647: Unknown result type (might be due to invalid IL or missing references) //IL_0648: Unknown result type (might be due to invalid IL or missing references) //IL_06c4: Unknown result type (might be due to invalid IL or missing references) //IL_06a4: Unknown result type (might be due to invalid IL or missing references) //IL_06ad: Unknown result type (might be due to invalid IL or missing references) //IL_06fd: Unknown result type (might be due to invalid IL or missing references) //IL_0705: Unknown result type (might be due to invalid IL or missing references) //IL_0657: Unknown result type (might be due to invalid IL or missing references) waypoints = null; if (!RouteBlockedByWater(start, end, out blockage)) { waypoints = new List { end }; return true; } try { float num = HorizontalMagnitude(start, end); float num2 = Mathf.Max(150f, num * 0.35f); float cell = Mathf.Clamp(num / 48f, 24f, 64f); float minX = Mathf.Min(start.x, end.x) - num2; float minZ = Mathf.Min(start.z, end.z) - num2; int width = Mathf.CeilToInt((Mathf.Max(start.x, end.x) + num2 - minX) / cell) + 1; int num3 = Mathf.CeilToInt((Mathf.Max(start.z, end.z) + num2 - minZ) / cell) + 1; if (width > 140 || num3 > 140) { float num4 = (float)Mathf.Max(width, num3) / 140f; cell *= num4; width = Mathf.CeilToInt((Mathf.Max(start.x, end.x) + num2 - minX) / cell) + 1; num3 = Mathf.CeilToInt((Mathf.Max(start.z, end.z) + num2 - minZ) / cell) + 1; } float[] array = new float[width * num3]; for (int i = 0; i < num3; i++) { for (int j = 0; j < width; j++) { Vector3 val = CellCenter(j, i); if (!MercAI.IsWetAtAnyRange(val)) { array[Index(j, i)] = 1f; } else { array[Index(j, i)] = (HasShoreWithin(val, MercAI.MaxGuideSwimWidth) ? 6f : float.PositiveInfinity); } } } int x = Mathf.Clamp(Mathf.RoundToInt((start.x - minX) / cell), 0, width - 1); int z = Mathf.Clamp(Mathf.RoundToInt((start.z - minZ) / cell), 0, num3 - 1); int endX = Mathf.Clamp(Mathf.RoundToInt((end.x - minX) / cell), 0, width - 1); int endZ = Mathf.Clamp(Mathf.RoundToInt((end.z - minZ) / cell), 0, num3 - 1); array[Index(x, z)] = Mathf.Min(array[Index(x, z)], 6f); array[Index(endX, endZ)] = Mathf.Min(array[Index(endX, endZ)], 6f); int[] array2 = new int[width * num3]; float[] array3 = new float[width * num3]; for (int k = 0; k < array3.Length; k++) { array3[k] = float.PositiveInfinity; } for (int l = 0; l < array2.Length; l++) { array2[l] = -1; } bool[] array4 = new bool[width * num3]; List heap = new List { Index(x, z) }; List heapF = new List { 0f }; array3[Index(x, z)] = 0f; int num5 = Index(endX, endZ); bool flag = false; while (heap.Count > 0) { int num6 = Pop(); if (array4[num6]) { continue; } array4[num6] = true; if (num6 == num5) { flag = true; break; } int num7 = num6 % width; int num8 = num6 / width; for (int m = -1; m <= 1; m++) { for (int n = -1; n <= 1; n++) { if (n == 0 && m == 0) { continue; } int num9 = num7 + n; int num10 = num8 + m; if (num9 < 0 || num10 < 0 || num9 >= width || num10 >= num3) { continue; } int num11 = Index(num9, num10); if (!float.IsInfinity(array[num11]) && !array4[num11] && (n == 0 || m == 0 || (!float.IsInfinity(array[Index(num7 + n, num8)]) && !float.IsInfinity(array[Index(num7, num8 + m)])))) { float num12 = ((n != 0 && m != 0) ? (cell * 1.414f) : cell); float num13 = (array[num6] + array[num11]) * 0.5f * num12; float num14 = array3[num6] + num13; if (num14 < array3[num11]) { array3[num11] = num14; array2[num11] = num6; Push(num11, num14 + Heuristic(num9, num10)); } } } } } if (!flag) { return false; } List list = new List(); for (int num15 = num5; num15 != -1; num15 = array2[num15]) { list.Add(num15); } list.Reverse(); List list2 = new List { start }; int num16 = 0; while (num16 < list.Count - 1) { int num17 = num16 + 1; for (int num18 = list.Count - 1; num18 > num16; num18--) { Vector3 start2 = CellCenter(list[num16] % width, list[num16] / width); Vector3 end2 = CellCenter(list[num18] % width, list[num18] / width); if (!RouteBlockedByWater(start2, end2, out var _)) { num17 = num18; break; } } Vector3 point = CellCenter(list[num17] % width, list[num17] / width); point = ((num17 != list.Count - 1) ? SnapToDryGround(point) : end); list2.Add(point); num16 = num17; } waypoints = new List { list2[0] }; for (int num19 = 1; num19 < list2.Count; num19++) { if (num19 == list2.Count - 1 || HorizontalMagnitude(waypoints[waypoints.Count - 1], list2[num19]) >= 40f) { waypoints.Add(list2[num19]); } } waypoints[waypoints.Count - 1] = end; if (waypoints.Count == 2 && HorizontalMagnitude(waypoints[0], waypoints[1]) < 1f) { waypoints.RemoveAt(0); } return true; Vector3 CellCenter(int num20, int num21) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) return new Vector3(minX + (float)num20 * cell, 0f, minZ + (float)num21 * cell); } float Heuristic(int num21, int num23) { float num20 = Mathf.Abs(num21 - endX); float num22 = Mathf.Abs(num23 - endZ); return num20 + num22 + -0.58599997f * Mathf.Min(num20, num22); } int Index(int num21, int num20) { return num20 * width + num21; } int Pop() { int result = heap[0]; int num20 = heap.Count - 1; List list3 = heap; List list4 = heap; int index = num20; int value = heap[num20]; int value2 = heap[0]; list3[0] = value; list4[index] = value2; List list5 = heapF; List list6 = heapF; index = num20; float value3 = heapF[num20]; float value4 = heapF[0]; list5[0] = value3; list6[index] = value4; heap.RemoveAt(num20); heapF.RemoveAt(num20); int num21 = 0; while (true) { int num22 = num21 * 2 + 1; int num23 = num22 + 1; int num24 = num21; if (num22 < heap.Count && heapF[num22] < heapF[num24]) { num24 = num22; } if (num23 < heap.Count && heapF[num23] < heapF[num24]) { num24 = num23; } if (num24 == num21) { break; } list4 = heap; index = num24; List list7 = heap; value2 = num21; value = heap[num21]; int value5 = heap[num24]; list4[index] = value; list7[value2] = value5; list6 = heapF; value2 = num24; List list8 = heapF; index = num21; value4 = heapF[num21]; value3 = heapF[num24]; list6[value2] = value4; list8[index] = value3; num21 = num24; } return result; } void Push(int node, float f) { heap.Add(node); heapF.Add(f); int num20 = heap.Count - 1; while (num20 > 0) { int num21 = (num20 - 1) / 2; if (heapF[num21] <= heapF[num20]) { break; } List list3 = heap; int index = num21; List list4 = heap; int index2 = num20; int value = heap[num20]; int value2 = heap[num21]; list3[index] = value; list4[index2] = value2; List list5 = heapF; index2 = num21; List list6 = heapF; index = num20; float value3 = heapF[num20]; float value4 = heapF[num21]; list5[index2] = value3; list6[index] = value4; num20 = num21; } } } catch (Exception ex) { MercPlugin.LogWarn("Route planning failed: " + ex.Message); return false; } } private static float HorizontalMagnitude(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.z - b.z; return Mathf.Sqrt(num * num + num2 * num2); } private static bool HasShoreWithin(Vector3 center, float range) { //IL_0017: 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_0032: 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_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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0087: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[4] { new Vector3(1f, 0f, 0f), new Vector3(-1f, 0f, 0f), new Vector3(0f, 0f, 1f), new Vector3(0f, 0f, -1f) }; foreach (Vector3 val in array) { for (float num = 12f; num <= range; num += 12f) { if (!MercAI.IsWetAtAnyRange(center + val * num)) { return true; } } } return false; } private static Vector3 SnapToDryGround(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0055: Unknown result type (might be due to invalid IL or missing references) if (!MercAI.IsWetAtAnyRange(point)) { return point; } for (int i = 1; i <= 3; i++) { for (int j = -i; j <= i; j++) { for (int k = -i; k <= i; k++) { if (Mathf.Max(Mathf.Abs(k), Mathf.Abs(j)) == i) { Vector3 val = point + new Vector3((float)k * 16f, 0f, (float)j * 16f); if (!MercAI.IsWetAtAnyRange(val)) { return val; } } } } } return point; } internal static string SerializeRoute(List waypoints) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (waypoints == null || waypoints.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder(); foreach (Vector3 waypoint in waypoints) { if (stringBuilder.Length > 0) { stringBuilder.Append(';'); } float x = waypoint.x; StringBuilder stringBuilder2 = stringBuilder.Append(x.ToString("F1", CultureInfo.InvariantCulture)).Append(','); x = waypoint.z; stringBuilder2.Append(x.ToString("F1", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } internal static bool TryResolveDynamicTarget(string key, Vector3 origin, GuideClientSnapshot snapshot, string askerName, out Vector3 destination, out string label) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; label = null; try { int num = key.IndexOf(':'); if (num < 0) { return false; } string text = key.Substring(0, num).ToLowerInvariant(); string text2 = key.Substring(num + 1).Trim(); switch (text) { case "player": if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ConfigEntry sharePlayerLocations = MercConfig.SharePlayerLocations; if (sharePlayerLocations == null || sharePlayerLocations.Value) { DateTime utcNow = DateTime.UtcNow; if (!KnowledgeRules.IsFresh(PlayerChronicle.PresenceObservedUtc, utcNow, 20.0)) { return false; } IReadOnlyList snapshot2 = PlayerChronicle.GetSnapshot(); List list = KnowledgeRules.MatchPlayers(text2, snapshot2.Select((PlayerKnowledgeRecord player) => player.Name).ToArray(), snapshot2.Select((PlayerKnowledgeRecord player) => player.PlayerId).ToArray()); if (list.Count != 1) { return false; } PlayerKnowledgeRecord playerKnowledgeRecord = snapshot2[list[0]]; if (!playerKnowledgeRecord.IsOnline || !playerKnowledgeRecord.HasKnownPosition || !KnowledgeRules.IsFresh(playerKnowledgeRecord.PositionObservedUtc, utcNow, 15.0)) { return false; } destination = new Vector3(playerKnowledgeRecord.PositionX, playerKnowledgeRecord.PositionY, playerKnowledgeRecord.PositionZ); label = KnowledgeRules.Display(playerKnowledgeRecord.Name); return true; } } return false; case "portal": { float num2 = float.MaxValue; Dictionary dictionary = PrefabNamesById(); foreach (ZDO item in BannerAuthority.AllZdosFromTable()) { if (item == null || !item.IsValid() || !dictionary.TryGetValue(item.GetPrefab(), out var value) || value.IndexOf("portal", StringComparison.OrdinalIgnoreCase) < 0) { continue; } string text3 = item.GetString(ZDOVars.s_tag, ""); if (!string.IsNullOrWhiteSpace(text3) && (text2.Length <= 0 || text3.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)) { float num3 = HorizontalMagnitude(item.GetPosition(), origin); if (!(num3 >= num2)) { num2 = num3; destination = item.GetPosition(); label = "'" + text3 + "' portal"; } } } return label != null; } case "pin": return snapshot?.TryFindNamedPin(text2, origin, out destination, out label) ?? false; } } catch (Exception ex) { MercPlugin.LogWarn("Dynamic guide target resolution failed: " + ex.Message); } return false; } internal static string Direction(Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_0022: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { return "nearby"; } float num = Mathf.Atan2(val.x, val.z) * 57.29578f; if (num < 0f) { num += 360f; } string[] array = new string[8] { "north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest" }; return array[Mathf.RoundToInt(num / 45f) % array.Length]; } internal static void ResetSceneCache() { _prefabNamesById = null; _locationRecords = null; _locationScene = null; } } public enum GuideTaskType { None = 0, Tombstone = 1, Cancel = 3, BossStone = 4, BossAltar = 5, Custom = 6 } public enum BossTarget { None, Eikthyr, Elder, Bonemass, Moder, Yagluth, Queen, Fader } public struct GuideCommand { public GuideTaskType Task; public BossTarget Boss; public string FindKey; public bool HasAction => Task != GuideTaskType.None; public GuideCommand(GuideTaskType task, BossTarget boss = BossTarget.None, string findKey = null) { Task = task; Boss = boss; FindKey = findKey; } } public sealed class GuideTaskResult { public bool Success; public string Message; public bool AdjustedForProgression; public static GuideTaskResult Ok(string message) { return new GuideTaskResult { Success = true, Message = message }; } public static GuideTaskResult Fail(string message) { return new GuideTaskResult { Success = false, Message = message }; } } public sealed class GuideClientSnapshot { public bool HasDeathPoint; public Vector3 DeathPoint; private readonly Dictionary _bossPins = new Dictionary(); private readonly List> _namedPins = new List>(); private readonly HashSet _knownGear = new HashSet(StringComparer.Ordinal); public int KnownGearCount => _knownGear.Count; public bool TryGetBoss(BossTarget boss, out Vector3 position) { return _bossPins.TryGetValue(boss, out position); } internal void AddNamedPin(string name, Vector3 position) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrWhiteSpace(name)) { name = name.Trim(); if (name.Length <= 32 && _namedPins.Count < 16) { _namedPins.Add(new KeyValuePair(name, position)); } } } public bool TryFindNamedPin(string needle, Vector3 origin, out Vector3 position, out string label) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_005b: 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_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) position = Vector3.zero; label = null; float num = float.MaxValue; string text = null; foreach (KeyValuePair namedPin in _namedPins) { if (string.IsNullOrEmpty(needle) || namedPin.Key.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0) { float num2 = namedPin.Value.x - origin.x; float num3 = namedPin.Value.z - origin.z; float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3); if (num4 < num) { num = num4; position = namedPin.Value; text = namedPin.Key; } } } if (text == null) { return false; } label = "'" + text + "' pin"; return true; } public bool KnowsGear(string prefabName) { if (!string.IsNullOrEmpty(prefabName)) { return _knownGear.Contains(prefabName); } return false; } internal void AddKnownGear(string prefabName) { if (!string.IsNullOrEmpty(prefabName)) { _knownGear.Add(prefabName); } } internal void SetBoss(BossTarget boss, Vector3 position) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (boss != BossTarget.None) { _bossPins[boss] = position; } } public string ToJson() { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\"d\":").Append(HasDeathPoint ? "true" : "false").Append(",\"x\":") .Append(DeathPoint.x.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"y\":") .Append(DeathPoint.y.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"z\":") .Append(DeathPoint.z.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"p\":["); bool flag = true; foreach (KeyValuePair bossPin in _bossPins) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"b\":").Append((int)bossPin.Key).Append(",\"x\":") .Append(bossPin.Value.x.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"y\":") .Append(bossPin.Value.y.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"z\":") .Append(bossPin.Value.z.ToString("R", CultureInfo.InvariantCulture)) .Append('}'); } stringBuilder.Append(']'); if (_knownGear.Count > 0) { stringBuilder.Append(",\"k\":["); bool flag2 = true; foreach (string item in _knownGear) { if (!flag2) { stringBuilder.Append(','); } flag2 = false; stringBuilder.Append('"').Append(MiniJson.Escape(item)).Append('"'); } stringBuilder.Append(']'); } if (_namedPins.Count > 0) { stringBuilder.Append(",\"n\":["); bool flag3 = true; foreach (KeyValuePair namedPin in _namedPins) { if (!flag3) { stringBuilder.Append(','); } flag3 = false; stringBuilder.Append("{\"name\":\"").Append(MiniJson.Escape(namedPin.Key)).Append("\",\"x\":") .Append(namedPin.Value.x.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"y\":") .Append(namedPin.Value.y.ToString("R", CultureInfo.InvariantCulture)) .Append(",\"z\":") .Append(namedPin.Value.z.ToString("R", CultureInfo.InvariantCulture)) .Append('}'); } stringBuilder.Append(']'); } return stringBuilder.Append('}').ToString(); } public static GuideClientSnapshot FromJson(string json) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) GuideClientSnapshot guideClientSnapshot = new GuideClientSnapshot(); if (string.IsNullOrEmpty(json) || json.Length > 8192) { return guideClientSnapshot; } if (!(MiniJson.Parse(json ?? "") is Dictionary dictionary)) { return guideClientSnapshot; } guideClientSnapshot.HasDeathPoint = ReadBool(dictionary, "d"); guideClientSnapshot.DeathPoint = ReadVector(dictionary); if (dictionary.TryGetValue("k", out var value) && value is List list) { foreach (object item in list) { if (item is string { Length: >0, Length: <=64 } text) { guideClientSnapshot.AddKnownGear(text); } } } if (dictionary.TryGetValue("p", out var value2) && value2 is List list2) { foreach (object item2 in list2) { if (item2 is Dictionary data) { int num = Mathf.RoundToInt(ReadNumber(data, "b")); if (num >= 1 && num <= 7) { guideClientSnapshot.SetBoss((BossTarget)num, ReadVector(data)); } } } } if (dictionary.TryGetValue("n", out var value3) && value3 is List list3) { foreach (object item3 in list3) { if (item3 is Dictionary dictionary2 && dictionary2.TryGetValue("name", out var value4) && value4 is string name) { guideClientSnapshot.AddNamedPin(name, ReadVector(dictionary2)); } } } return guideClientSnapshot; } private static Vector3 ReadVector(Dictionary data) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) return new Vector3(ReadNumber(data, "x"), ReadNumber(data, "y"), ReadNumber(data, "z")); } private static float ReadNumber(Dictionary data, string key) { if (data == null || !data.TryGetValue(key, out var value)) { return 0f; } try { return Convert.ToSingle(value, CultureInfo.InvariantCulture); } catch { return 0f; } } private static bool ReadBool(Dictionary data, string key) { bool flag = default(bool); int num; if (data != null && data.TryGetValue(key, out var value)) { if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } public static class GuideTasks { private sealed class RecentBossState { public BossTarget Boss; public GuideTaskType Task; public int At; } internal static readonly FieldInfo MinimapPinsField = AccessTools.Field(typeof(Minimap), "m_pins"); private const string TombstoneMarker = "[MERC_ACTION:GUIDE_TOMBSTONE]"; private const string CancelMarker = "[MERC_ACTION:CANCEL_GUIDE]"; private const int RecentBossMilliseconds = 120000; private static readonly Regex ActionMarker = new Regex("\\s*\\[MERC_ACTION:(GUIDE_TOMBSTONE|CANCEL_GUIDE|FIND_BOSS_STONE|GUIDE_BOSS_ALTAR)(?::(EIKTHYR|ELDER|BONEMASS|MODER|YAGLUTH|QUEEN|FADER))?\\]\\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly object RecentBossLock = new object(); private static readonly Dictionary RecentBossByPlayer = new Dictionary(); public static void ResetSceneMemory() { lock (RecentBossLock) { RecentBossByPlayer.Clear(); } } public static GuideCommand ClassifyPlayerRequest(string text) { return ClassifyPlayerRequest(text, 0L); } public static GuideCommand ClassifyPlayerRequest(string text, long memoryId) { if (string.IsNullOrWhiteSpace(text)) { return new GuideCommand(GuideTaskType.None); } string text2 = Normalize(text); if (GuideDialogueRules.ShouldCancel(text)) { return new GuideCommand(GuideTaskType.Cancel); } BossTarget bossTarget = ParseBoss(text2); if (bossTarget != BossTarget.None) { RememberBoss(bossTarget, GuideTaskType.BossAltar, memoryId); } if (!GuideDialogueRules.MayStartGuide(text)) { return new GuideCommand(GuideTaskType.None); } if (HasAny(text2, "tombstone", "grave", "corpse", "death marker", "death point", "my body", "my items", "my gear")) { return new GuideCommand(GuideTaskType.Tombstone); } GuideFindTarget[] targets = GuideFinder.Targets; foreach (GuideFindTarget guideFindTarget in targets) { if (GuideDialogueRules.MatchesFindTarget(text2, guideFindTarget.Key, guideFindTarget.Label)) { return new GuideCommand(GuideTaskType.Custom, BossTarget.None, guideFindTarget.Key); } } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { if (HasAny(text2, "portal", "portals")) { return new GuideCommand(GuideTaskType.Custom, BossTarget.None, "portal:" + ExtractTargetWord(text2)); } if (HasAny(text2, "pin", "pins", "marker", "waypoint")) { return new GuideCommand(GuideTaskType.Custom, BossTarget.None, "pin:" + ExtractTargetWord(text2)); } } bool flag = HasAny(text2, "boss stone", "boss stones", "boss runestone", "boss runestones", "vegvisir", "location stone", "location runestone", "rune stone", "rune stones"); bool flag2 = HasAny(text2, "boss altar", "boss altars", "summoning altar", "summon altar", "sacrifice altar", "forsaken altar", "boss spawn", "spawn for", "summon the boss"); bool flag3 = bossTarget != BossTarget.None || HasAny(text2, "boss", "forsaken"); bool flag4 = HasAny(text2, "where is he", "where is she", "where is it", "where is one", "show me where one is", "take me there", "lead me there", "where is that"); if (flag) { BossTarget boss = ((bossTarget != BossTarget.None) ? bossTarget : RecentOrNextBoss(memoryId)); RememberBoss(boss, GuideTaskType.BossStone, memoryId); return new GuideCommand(GuideTaskType.BossStone, boss); } if (flag2 || flag3) { BossTarget boss2 = ((bossTarget != BossTarget.None) ? bossTarget : RecentOrNextBoss(memoryId)); RememberBoss(boss2, GuideTaskType.BossAltar, memoryId); return new GuideCommand(GuideTaskType.BossAltar, boss2); } if (flag4 && TryGetRecentBoss(memoryId, out var boss3, out var task)) { return new GuideCommand(task, boss3); } return new GuideCommand(GuideTaskType.None); } public static GuideCommand ExtractReplyAction(ref string reply) { return ExtractReplyAction(ref reply, 0L); } public static GuideCommand ExtractReplyAction(ref string reply, long memoryId) { return ExtractReplyAction(ref reply, memoryId, remember: true); } internal static GuideCommand ExtractReplyAction(ref string reply, long memoryId, bool remember) { if (string.IsNullOrEmpty(reply)) { return new GuideCommand(GuideTaskType.None); } MatchCollection matchCollection = ActionMarker.Matches(reply); if (matchCollection.Count == 0) { return new GuideCommand(GuideTaskType.None); } GuideCommand result = new GuideCommand(GuideTaskType.None); bool flag = false; foreach (Match item in matchCollection) { GuideCommand guideCommand = FromMarker(item.Groups[1].Value, item.Groups[2].Value); if (!result.HasAction) { result = guideCommand; } else if (guideCommand.Task != result.Task || guideCommand.Boss != result.Boss) { flag = true; } } reply = ActionMarker.Replace(reply, " ").Trim(); if (flag) { MercPlugin.LogWarn("LLM returned conflicting guide-action markers; no task was started."); return new GuideCommand(GuideTaskType.None); } if (remember && (result.Task == GuideTaskType.BossStone || result.Task == GuideTaskType.BossAltar) && result.Boss != BossTarget.None) { RememberBoss(result.Boss, result.Task, memoryId); } return result; } public static string PromptRules() { return "Physical guidance is one general skill available to every mercenary; do not make tombstones their identity and do not list guide tools in introductions. Use only these safe hidden actions when the player clearly asks to be physically led: [MERC_ACTION:GUIDE_TOMBSTONE] for the latest tombstone, [MERC_ACTION:FIND_BOSS_STONE:BOSS] for a boss Vegvisir/runestone, and [MERC_ACTION:GUIDE_BOSS_ALTAR:BOSS] for the actual boss altar. Replace BOSS with exactly EIKTHYR, ELDER, BONEMASS, MODER, YAGLUTH, QUEEN, or FADER. Use the recent conversation to resolve short follow-ups such as 'where is he?' or 'show me where one is'. If the player asks to stop or cancel current guidance, use [MERC_ACTION:CANCEL_GUIDE]. If a destination request is genuinely unclear, ask which place they mean without reciting a menu of abilities. Do not use an action marker for ordinary informational questions. Never display, quote, or explain action markers; they are removed before the player sees your reply. Never claim any other physical ability or invent a location."; } public static GuideClientSnapshot CaptureClientSnapshot() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) GuideClientSnapshot guideClientSnapshot = new GuideClientSnapshot(); PlayerProfile val = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null); guideClientSnapshot.HasDeathPoint = val != null && val.HaveDeathPoint(); if (guideClientSnapshot.HasDeathPoint) { guideClientSnapshot.DeathPoint = val.GetDeathPoint(); } for (int i = 1; i <= 7; i++) { BossTarget boss = (BossTarget)i; if (TryGetKnownBossAltarPin(boss, out var position)) { guideClientSnapshot.SetBoss(boss, position); } } CaptureNamedPins(guideClientSnapshot); CaptureKnownGear(guideClientSnapshot); return guideClientSnapshot; } private static void CaptureNamedPins(GuideClientSnapshot snapshot) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) try { Minimap instance = Minimap.instance; List list = (((Object)(object)instance != (Object)null && MinimapPinsField != null) ? (MinimapPinsField.GetValue(instance) as List) : null); if (list == null) { return; } foreach (PinData item in list) { if (item != null && (int)item.m_type != 9 && !string.IsNullOrWhiteSpace(item.m_name)) { snapshot.AddNamedPin((Localization.instance != null) ? Localization.instance.Localize(item.m_name) : item.m_name, item.m_pos); } } } catch (Exception ex) { MercPlugin.LogWarn("Could not inspect named map pins: " + ex.Message); } } private static void CaptureKnownGear(GuideClientSnapshot snapshot) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Invalid comparison between Unknown and I4 //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Invalid comparison between Unknown and I4 //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Invalid comparison between Unknown and I4 //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Invalid comparison between Unknown and I4 //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Invalid comparison between Unknown and I4 //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Invalid comparison between Unknown and I4 Player localPlayer = Player.m_localPlayer; ObjectDB instance = ObjectDB.instance; if ((Object)(object)localPlayer == (Object)null || (Object)(object)instance == (Object)null || instance.m_recipes == null) { return; } try { foreach (Recipe recipe in instance.m_recipes) { if (snapshot.KnownGearCount >= 80) { break; } if (!((Object)(object)recipe == (Object)null) && recipe.m_enabled && !((Object)(object)recipe.m_item == (Object)null) && recipe.m_item.m_itemData != null && recipe.m_item.m_itemData.m_shared != null) { SharedData shared = recipe.m_item.m_itemData.m_shared; if ((recipe.m_item.m_itemData.IsWeapon() || (Object)(object)shared.m_buildPieces != (Object)null || (int)shared.m_itemType == 5 || (int)shared.m_itemType == 6 || (int)shared.m_itemType == 7 || (int)shared.m_itemType == 11 || (int)shared.m_itemType == 17 || (int)shared.m_itemType == 19) && localPlayer.IsRecipeKnown(shared.m_name)) { snapshot.AddKnownGear(Utils.GetPrefabName(((Object)((Component)recipe.m_item).gameObject).name)); } } } } catch { } } public static string BuildLiveContext(Mercenary merc) { return BuildLiveContext(merc, Player.m_localPlayer, CaptureClientSnapshot()); } public static string BuildLiveContext(Mercenary merc, Player player, GuideClientSnapshot snapshot) { snapshot = snapshot ?? new GuideClientSnapshot(); bool hasDeathPoint = snapshot.HasDeathPoint; string text = (((Object)(object)merc != (Object)null && (Object)(object)merc.Ai != (Object)null) ? merc.Ai.GuideStatus : "none"); List list = new List(); Vector3 position; for (int i = 1; i <= 7; i++) { BossTarget boss = (BossTarget)i; if (snapshot.TryGetBoss(boss, out position)) { list.Add(BossName(boss)); } } int stage = StageDirector.GetStage(); string text2; if (stage >= 7) { text2 = "All seven tracked Forsaken are defeated; do not recommend an earlier boss as the next progression step."; } else { BossTarget boss2 = (BossTarget)Mathf.Clamp(stage + 1, 1, 7); text2 = (snapshot.TryGetBoss(boss2, out position) ? ("Natural next boss: " + BossName(boss2) + ". Its altar is already revealed on the player's map, so do not recommend finding or rereading its Vegvisir; recommend preparation and altar travel instead.") : ("Natural next boss: " + BossName(boss2) + ". Its altar is not currently revealed on the player's map; a Vegvisir search is a valid discovery step.")); } string text3 = "Boss altars currently revealed on the requesting player's map: " + ((list.Count > 0) ? string.Join(", ", list.ToArray()) : "none") + "."; return "Guide skills available when requested: latest recorded tombstone, boss Vegvisir/runestone search, and exact generated boss altar guidance. Latest recorded death point: " + (hasDeathPoint ? "available" : "none") + ". Current guide task: " + text + ". " + text3 + " " + text2; } public static GuideTaskResult Execute(Mercenary merc, GuideCommand command, bool advanceKnownBossStep = false) { return Execute(merc, command, Player.m_localPlayer, CaptureClientSnapshot(), advanceKnownBossStep); } public static GuideTaskResult Execute(Mercenary merc, GuideCommand command, Player player, GuideClientSnapshot snapshot, bool advanceKnownBossStep = false) { return ExecuteCore(merc, null, command, player, null, snapshot, advanceKnownBossStep); } internal static GuideTaskResult Execute(Mercenary merc, GuideCommand command, ServerAuthority.SenderPlayerState requester, GuideClientSnapshot snapshot, bool advanceKnownBossStep = false) { return ExecuteCore(merc, null, command, requester?.LivePlayer, requester, snapshot, advanceKnownBossStep); } internal static GuideTaskResult Execute(ServerAuthority.MercenaryState mercState, GuideCommand command, ServerAuthority.SenderPlayerState requester, GuideClientSnapshot snapshot, bool advanceKnownBossStep = false) { return ExecuteCore(null, mercState, command, requester?.LivePlayer, requester, snapshot, advanceKnownBossStep); } private static string P(string token, params object[] arguments) { return MercLocalization.Phrase(token, arguments); } private static GuideTaskResult ExecuteCore(Mercenary merc, ServerAuthority.MercenaryState mercState, GuideCommand command, Player player, ServerAuthority.SenderPlayerState requester, GuideClientSnapshot snapshot, bool advanceKnownBossStep) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0650: Unknown result type (might be due to invalid IL or missing references) //IL_0655: Unknown result type (might be due to invalid IL or missing references) //IL_0656: Unknown result type (might be due to invalid IL or missing references) //IL_0657: Unknown result type (might be due to invalid IL or missing references) //IL_0684: Unknown result type (might be due to invalid IL or missing references) //IL_0685: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Unknown result type (might be due to invalid IL or missing references) //IL_0623: Unknown result type (might be due to invalid IL or missing references) //IL_074a: Unknown result type (might be due to invalid IL or missing references) //IL_074f: Unknown result type (might be due to invalid IL or missing references) //IL_0751: Unknown result type (might be due to invalid IL or missing references) //IL_0752: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_0783: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_08c1: Unknown result type (might be due to invalid IL or missing references) //IL_07c4: Unknown result type (might be due to invalid IL or missing references) //IL_07c9: Unknown result type (might be due to invalid IL or missing references) //IL_077b: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_01db: 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_0895: Unknown result type (might be due to invalid IL or missing references) //IL_0877: Unknown result type (might be due to invalid IL or missing references) //IL_0883: Unknown result type (might be due to invalid IL or missing references) //IL_041c: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_0483: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_05af: Unknown result type (might be due to invalid IL or missing references) //IL_059b: Unknown result type (might be due to invalid IL or missing references) //IL_05a2: Unknown result type (might be due to invalid IL or missing references) snapshot = snapshot ?? new GuideClientSnapshot(); if (((Object)(object)merc == (Object)null && mercState == null) || ((Object)(object)merc != (Object)null && ((Character)merc).IsDead()) || ((Object)(object)player == (Object)null && requester == null)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_guide")); } int num; if (requester != null) { num = (((mercState != null) ? ServerAuthority.IsAssignedTo(mercState, requester, requireFollowing: false) : merc.IsAssignedTo(requester, requireFollowing: false)) ? 1 : 0); } else { if (!((Object)(object)merc != (Object)null)) { goto IL_007f; } num = (merc.IsAssignedTo(player, requireFollowing: false) ? 1 : 0); } if (num != 0) { Vector3 val = (((Object)(object)player != (Object)null) ? ((Component)player).transform.position : requester.Position); long memoryId = (((Object)(object)player != (Object)null) ? player.GetPlayerID() : requester.PlayerId); if (command.Task == GuideTaskType.Cancel) { if (requester != null) { RequestCancelForAssignedMercenaries(requester); } else { RequestCancelForAssignedMercenaries(player); } return GuideTaskResult.Ok(P("dnpc_speech_guide_cancelled")); } ServerAuthority.EnsureGuideFollow(merc, mercState, player, requester); Vector3 position = val; BossTarget bossTarget = command.Boss; bool flag = false; if (command.Task == GuideTaskType.Custom && !string.IsNullOrEmpty(command.FindKey)) { GuideFindTarget guideFindTarget = GuideFinder.ByKey(command.FindKey); if (guideFindTarget == null) { string askerName = (((Object)(object)player != (Object)null) ? player.GetPlayerName() : requester?.PlayerName); if (command.FindKey != null && command.FindKey.IndexOf(':') >= 0 && GuideFinder.TryResolveDynamicTarget(command.FindKey, val, snapshot, askerName, out var destination, out var label)) { if (!GuideFinder.PlanRoute(val, destination, out var waypoints, out var blockage) && (!((Object)(object)merc != (Object)null) || !((Object)(object)merc.Ai != (Object)null) || !merc.Ai.TestLandPath(destination))) { return GuideTaskResult.Fail(P("dnpc_speech_water_route", MercLocalization.DirectionArgument(GuideFinder.Direction(val, blockage)))); } position = ((waypoints != null && waypoints.Count > 0) ? waypoints[0] : destination); string text = ((waypoints != null && waypoints.Count > 1) ? GuideFinder.SerializeRoute(waypoints) : ""); if (mercState != null && mercState.MercZdo != null) { mercState.MercZdo.Set("merc_guideFind", ""); mercState.MercZdo.Set("merc_guideRoute", text); } else if ((Object)(object)merc != (Object)null) { ZNetView component = ((Component)merc).GetComponent(); if (component != null && component.IsValid()) { component.GetZDO().Set("merc_guideFind", ""); component.GetZDO().Set("merc_guideRoute", text); } } if (requester != null) { RequestCancelForAssignedMercenaries(requester); if (mercState != null) { ServerAuthority.SendGuideTask(requester.SenderUid, mercState, requester.CharacterZdoId, GuideTaskType.Custom, BossTarget.None, position); } else if (!merc.RequestGuideTask(requester, GuideTaskType.Custom, BossTarget.None, position)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_begin_guide")); } } else { RequestCancelForAssignedMercenaries(player); if (!merc.RequestGuideTask(player, GuideTaskType.Custom, BossTarget.None, position)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_begin_guide")); } } return GuideTaskResult.Ok(P("dnpc_speech_target_this_way", label)); } return GuideTaskResult.Fail(P("dnpc_speech_unknown_find")); } if (!GuideFinder.GateAllows(guideFindTarget, player ?? requester?.LivePlayer, out var refusal)) { return GuideTaskResult.Fail(refusal); } bool flag2 = guideFindTarget.Kind == GuideFindKind.Creature; float maxDistance = ((guideFindTarget.Kind == GuideFindKind.Location) ? 1000000f : 2000f); string what; bool flag3 = GuideFinder.FindNearest(guideFindTarget, val, maxDistance, out position, out what); if (!flag3 && !flag2) { return GuideTaskResult.Fail(P("dnpc_speech_no_known_target", GuideFinder.TargetLabelArgument(guideFindTarget))); } float num2 = HorizontalDistance(val, position); string englishDirection = GuideFinder.Direction(val, position); List waypoints2 = null; Vector3 blockage2 = Vector3.zero; if (flag3 && !GuideFinder.PlanRoute(val, position, out waypoints2, out blockage2) && (!((Object)(object)merc != (Object)null) || !((Object)(object)merc.Ai != (Object)null) || !merc.Ai.TestLandPath(position))) { string englishDirection2 = GuideFinder.Direction(val, blockage2); return GuideTaskResult.Fail(P("dnpc_speech_water_route", MercLocalization.DirectionArgument(englishDirection2))); } bool flag4 = waypoints2 != null && waypoints2.Count > 1; string text2 = (flag4 ? GuideFinder.SerializeRoute(waypoints2) : ""); if (waypoints2 != null && waypoints2.Count > 0) { position = waypoints2[0]; } if (mercState != null && mercState.MercZdo != null) { mercState.MercZdo.Set("merc_guideFind", guideFindTarget.Key); mercState.MercZdo.Set("merc_guideRoute", text2); } else if ((Object)(object)merc != (Object)null) { ZNetView component2 = ((Component)merc).GetComponent(); if (component2 != null && component2.IsValid()) { component2.GetZDO().Set("merc_guideFind", guideFindTarget.Key); component2.GetZDO().Set("merc_guideRoute", text2); } } string message = ((!flag3) ? P("dnpc_speech_target_not_seen", GuideFinder.TargetLabelArgument(guideFindTarget)) : ((!flag4) ? ((num2 > 2000f) ? P("dnpc_speech_target_far", what, MercLocalization.DirectionArgument(englishDirection)) : P("dnpc_speech_target_nearest", what)) : P("dnpc_speech_target_land_detour", what))); if (requester != null) { RequestCancelForAssignedMercenaries(requester); if (mercState != null) { ServerAuthority.SendGuideTask(requester.SenderUid, mercState, requester.CharacterZdoId, GuideTaskType.Custom, BossTarget.None, position); } else if (!merc.RequestGuideTask(requester, GuideTaskType.Custom, BossTarget.None, position)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_begin_guide")); } } else { RequestCancelForAssignedMercenaries(player); if (!merc.RequestGuideTask(player, GuideTaskType.Custom, BossTarget.None, position)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_begin_guide")); } } return GuideTaskResult.Ok(message); } if (advanceKnownBossStep && command.Task == GuideTaskType.BossStone && bossTarget != BossTarget.None && snapshot.TryGetBoss(bossTarget, out var position2)) { command = new GuideCommand(GuideTaskType.BossAltar, bossTarget); position = position2; flag = true; } string message2; if (command.Task == GuideTaskType.Tombstone) { if (!snapshot.HasDeathPoint) { return GuideTaskResult.Fail(P("dnpc_speech_no_death_point")); } position = snapshot.DeathPoint; float num3 = HorizontalDistance(val, position); message2 = ((num3 <= 8f) ? P("dnpc_speech_death_point_here") : P("dnpc_speech_death_point_distance", Mathf.CeilToInt(num3), MercLocalization.DirectionArgument(CardinalDirection(val, position)))); } else { if (command.Task != GuideTaskType.BossStone && command.Task != GuideTaskType.BossAltar) { return GuideTaskResult.Fail(P("dnpc_speech_unknown_task")); } if (bossTarget == BossTarget.None) { bossTarget = NextBoss(); } RememberBoss(bossTarget, command.Task, memoryId); string text3 = BossArgument(bossTarget); message2 = (flag ? P("dnpc_speech_map_has_altar", text3) : ((command.Task == GuideTaskType.BossStone) ? P("dnpc_speech_search_runestone", text3) : P("dnpc_speech_lead_boss_altar", text3))); } List waypoints3 = null; Vector3 blockage3 = Vector3.zero; if (!GuideFinder.PlanRoute(val, position, out waypoints3, out blockage3) && (!((Object)(object)merc != (Object)null) || !((Object)(object)merc.Ai != (Object)null) || !merc.Ai.TestLandPath(position))) { string englishDirection3 = GuideFinder.Direction(val, blockage3); return GuideTaskResult.Fail(P("dnpc_speech_water_route", MercLocalization.DirectionArgument(englishDirection3))); } if (waypoints3 != null && waypoints3.Count > 0) { position = waypoints3[0]; string text4 = ((waypoints3.Count > 1) ? GuideFinder.SerializeRoute(waypoints3) : ""); if (mercState != null && mercState.MercZdo != null) { mercState.MercZdo.Set("merc_guideRoute", text4); mercState.MercZdo.Set("merc_guideFind", ""); } else if ((Object)(object)merc != (Object)null) { ZNetView component3 = ((Component)merc).GetComponent(); if (component3 != null && component3.IsValid()) { component3.GetZDO().Set("merc_guideRoute", text4); component3.GetZDO().Set("merc_guideFind", ""); } } } if (requester != null) { RequestCancelForAssignedMercenaries(requester); if (mercState != null) { ServerAuthority.SendGuideTask(requester.SenderUid, mercState, requester.CharacterZdoId, command.Task, bossTarget, position); } else if (!merc.RequestGuideTask(requester, command.Task, bossTarget, position)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_begin_guide")); } } else { RequestCancelForAssignedMercenaries(player); if (!merc.RequestGuideTask(player, command.Task, bossTarget, position)) { return GuideTaskResult.Fail(P("dnpc_speech_cannot_begin_guide")); } } GuideTaskResult guideTaskResult = GuideTaskResult.Ok(message2); guideTaskResult.AdjustedForProgression = flag; return guideTaskResult; } goto IL_007f; IL_007f: return GuideTaskResult.Fail(P("dnpc_speech_recruit_first")); } public static bool TryGetKnownBossAltarPin(BossTarget boss, out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; try { Minimap instance = Minimap.instance; List list = (((Object)(object)instance != (Object)null && MinimapPinsField != null) ? (MinimapPinsField.GetValue(instance) as List) : null); if (list == null) { return false; } foreach (PinData item in list) { if (item != null && (int)item.m_type == 9) { string text = item.m_name ?? ""; string pinName = ((Localization.instance != null) ? Localization.instance.Localize(text) : text); if (MatchesBossPinName(boss, text) || MatchesBossPinName(boss, pinName)) { position = item.m_pos; return true; } } } } catch (Exception ex) { MercPlugin.LogWarn("Could not inspect known boss map pins: " + ex.Message); } return false; } private static bool MatchesBossPinName(BossTarget boss, string pinName) { string text = Regex.Replace((pinName ?? "").ToLowerInvariant(), "[^a-z0-9]+", ""); switch (boss) { case BossTarget.Eikthyr: return text.Contains("eikthyr"); case BossTarget.Elder: if (!text.Contains("elder")) { return text.Contains("gdking"); } return true; case BossTarget.Bonemass: return text.Contains("bonemass"); case BossTarget.Moder: if (!text.Contains("moder")) { return text.Contains("dragonqueen"); } return true; case BossTarget.Yagluth: if (!text.Contains("yagluth")) { return text.Contains("goblinking"); } return true; case BossTarget.Queen: if (!text.Contains("queen") && !text.Contains("mistlandsboss")) { return text.Contains("dvergrboss"); } return true; case BossTarget.Fader: if (!text.Contains("fader")) { return text.Contains("ashlandsboss"); } return true; default: return false; } } private static void RequestCancelForAssignedMercenaries(Player player) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Character)instance).IsDead() && instance.IsAssignedTo(player, requireFollowing: false)) { instance.RequestGuideTask(player, GuideTaskType.Cancel, BossTarget.None, Vector3.zero); } } } private static void RequestCancelForAssignedMercenaries(ServerAuthority.SenderPlayerState requester) { ServerAuthority.CancelOwnedCompanyGuides(requester); } public static string BossName(BossTarget boss) { return boss switch { BossTarget.Eikthyr => "Eikthyr", BossTarget.Elder => "the Elder", BossTarget.Bonemass => "Bonemass", BossTarget.Moder => "Moder", BossTarget.Yagluth => "Yagluth", BossTarget.Queen => "the Queen", BossTarget.Fader => "Fader", _ => "the next Forsaken", }; } internal static string BossArgument(BossTarget boss) { return MercLocalization.TokenArgument(boss switch { BossTarget.Eikthyr => "dnpc_boss_eikthyr", BossTarget.Elder => "dnpc_boss_elder", BossTarget.Bonemass => "dnpc_boss_bonemass", BossTarget.Moder => "dnpc_boss_moder", BossTarget.Yagluth => "dnpc_boss_yagluth", BossTarget.Queen => "dnpc_boss_queen", BossTarget.Fader => "dnpc_boss_fader", _ => "dnpc_boss_next", }); } public static Biome BossBiome(BossTarget boss) { return (Biome)(boss switch { BossTarget.Eikthyr => 1, BossTarget.Elder => 8, BossTarget.Bonemass => 2, BossTarget.Moder => 4, BossTarget.Yagluth => 16, BossTarget.Queen => 512, BossTarget.Fader => 32, _ => 1, }); } public static bool MatchesBossLocation(BossTarget boss, string locationName) { string text = Regex.Replace((locationName ?? "").ToLowerInvariant(), "[^a-z0-9]+", ""); switch (boss) { case BossTarget.Eikthyr: return text.Contains("eikthyr"); case BossTarget.Elder: if (!text.Contains("gdking")) { return text.Contains("theelder"); } return true; case BossTarget.Bonemass: return text.Contains("bonemass"); case BossTarget.Moder: if (!text.Contains("dragonqueen") && !(text == "moder")) { return text.Contains("moderaltar"); } return true; case BossTarget.Yagluth: if (!text.Contains("goblinking")) { return text.Contains("yagluth"); } return true; case BossTarget.Queen: if (!text.Contains("mistlandsboss") && !text.Contains("dvergrboss")) { return text.Contains("thequeen"); } return true; case BossTarget.Fader: if (!text.Contains("fader")) { return text.Contains("ashlandsboss"); } return true; default: return false; } } public static string CardinalDirection(Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; if (Mathf.Abs(val.x) > Mathf.Abs(val.z)) { if (!(val.x >= 0f)) { return "west"; } return "east"; } if (!(val.z >= 0f)) { return "south"; } return "north"; } private static float HorizontalDistance(Vector3 left, Vector3 right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = left.x - right.x; float num2 = left.z - right.z; return Mathf.Sqrt(num * num + num2 * num2); } private static GuideCommand FromMarker(string marker, string bossName) { if (marker.Equals("GUIDE_TOMBSTONE", StringComparison.OrdinalIgnoreCase)) { return new GuideCommand(GuideTaskType.Tombstone); } if (marker.Equals("CANCEL_GUIDE", StringComparison.OrdinalIgnoreCase)) { return new GuideCommand(GuideTaskType.Cancel); } if (marker.Equals("FIND_BOSS_STONE", StringComparison.OrdinalIgnoreCase)) { return new GuideCommand(GuideTaskType.BossStone, ParseBossName(bossName)); } if (marker.Equals("GUIDE_BOSS_ALTAR", StringComparison.OrdinalIgnoreCase)) { return new GuideCommand(GuideTaskType.BossAltar, ParseBossName(bossName)); } return new GuideCommand(GuideTaskType.None); } private static BossTarget ParseBoss(string words) { if (HasAny(words, "eikthyr", "first boss", "1st boss", "boss 1")) { return BossTarget.Eikthyr; } if (HasAny(words, "the elder", "elder", "second boss", "2nd boss", "boss 2")) { return BossTarget.Elder; } if (HasAny(words, "bonemass", "third boss", "3rd boss", "boss 3")) { return BossTarget.Bonemass; } if (HasAny(words, "moder", "fourth boss", "4th boss", "boss 4")) { return BossTarget.Moder; } if (HasAny(words, "yagluth", "fifth boss", "5th boss", "boss 5")) { return BossTarget.Yagluth; } if (HasAny(words, "the queen", "queen", "sixth boss", "6th boss", "boss 6")) { return BossTarget.Queen; } if (HasAny(words, "fader", "seventh boss", "7th boss", "boss 7")) { return BossTarget.Fader; } return BossTarget.None; } private static BossTarget ParseBossName(string name) { if (Enum.TryParse(name, ignoreCase: true, out var result) && result != BossTarget.None) { return result; } return BossTarget.None; } private static BossTarget RecentOrNextBoss(long memoryId) { if (!TryGetRecentBoss(memoryId, out var boss, out var _)) { return NextBoss(); } return boss; } private static BossTarget NextBoss() { return (BossTarget)Mathf.Clamp(StageDirector.GetStage() + 1, 1, 7); } private static void RememberBoss(BossTarget boss, GuideTaskType task, long memoryId) { if (boss == BossTarget.None) { return; } lock (RecentBossLock) { if (!RecentBossByPlayer.TryGetValue(memoryId, out var value)) { value = new RecentBossState(); RecentBossByPlayer[memoryId] = value; } value.Boss = boss; if (task == GuideTaskType.BossStone || task == GuideTaskType.BossAltar) { value.Task = task; } value.At = Environment.TickCount; } } private static bool TryGetRecentBoss(long memoryId, out BossTarget boss, out GuideTaskType task) { lock (RecentBossLock) { if (RecentBossByPlayer.TryGetValue(memoryId, out var value)) { uint num = (uint)(Environment.TickCount - value.At); if (value.Boss != BossTarget.None && num <= 120000) { boss = value.Boss; task = ((value.Task == GuideTaskType.BossStone) ? GuideTaskType.BossStone : GuideTaskType.BossAltar); return true; } } } boss = BossTarget.None; task = GuideTaskType.BossAltar; return false; } private static string Normalize(string text) { string text2 = Regex.Replace((text ?? "").ToLowerInvariant(), "[^a-z0-9]+", " ").Trim(); return " " + text2 + " "; } private static string ExtractTargetWord(string words) { string text = " " + words.Trim() + " "; string[] array = new string[27] { " take ", " me ", " to ", " my ", " the ", " a ", " go ", " goto ", " find ", " show ", " lead ", " guide ", " bring ", " walk ", " please ", " lets ", " let ", " us ", " at ", " portal ", " portals ", " pin ", " pins ", " marker ", " markers ", " waypoint ", " named " }; foreach (string oldValue in array) { text = text.Replace(oldValue, " "); } text = Regex.Replace(text, "\\s+", " ").Trim(); if (text.Length == 0) { return ""; } return text.Split(new char[1] { ' ' })[0]; } private static bool HasAny(string words, params string[] choices) { for (int i = 0; i < choices.Length; i++) { string text = Normalize(choices[i]).Trim(); if (text.Length > 0 && words.Contains(" " + text + " ")) { return true; } } return false; } } internal enum KnowledgeIntent { None, OnlineRoster, KnownRoster, PlayerOnline, PlayerLocation, PlayerLastSeen, PlayerLastLeft, PlayerActivity, PlayerInfo, RecentActivity, ServerDateTime } internal sealed class KnowledgeQuery { internal KnowledgeIntent Intent; internal string Target = ""; internal bool ExplicitPlayer; } internal static class KnowledgeRules { internal const int MaximumQuestionLength = 512; internal const double PositionFreshSeconds = 15.0; internal const double PresenceFreshSeconds = 20.0; internal static bool TryConsumeRequestBudget(Queue accepted, int now, int limit) { limit = Math.Max(1, Math.Min(600, limit)); while (accepted.Count > 0) { int num = now - accepted.Peek(); if (num >= 0 && num < 60000) { break; } accepted.Dequeue(); } if (accepted.Count >= limit) { return false; } accepted.Enqueue(now); return true; } internal static KnowledgeQuery Parse(string question) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return new KnowledgeQuery(); } string text = Normalize(question); for (int i = 0; i < 3; i++) { string text2 = Regex.Replace(text, "^(?:please|hey|hello|hi|can you tell me|could you tell me|would you tell me|do you know|can you|could you)\\s+", ""); if (text2 == text) { break; } text = text2; } text = Regex.Replace(text, "^(who|where|what) s(?=\\s|$)", "$1 is"); text = Regex.Replace(text, "\\s+please$", ""); if (Matches(text, "^(?:don t|do not|never|not|stop|cancel)\\b")) { return new KnowledgeQuery(); } if (Matches(text, "^(?:who is online|who is on(?: the server)?|who is playing|who is currently online|which players are online|what players are online|anyone online|is anyone online|online players|players online|(?:list|show|show me)(?: the)? online players|who else is online)(?: now| right now)?$")) { return Query(KnowledgeIntent.OnlineRoster); } if (Matches(text, "^(?:known players|list(?: all)?(?: known)? players|who plays here|who has played here|who do you know(?: here)?)$")) { return Query(KnowledgeIntent.KnownRoster); } if (Matches(text, "^(?:recent player activity|recent activity|what happened recently|what have players been doing|what has everyone been doing)$")) { return Query(KnowledgeIntent.RecentActivity); } if (Matches(text, "^(?:what is (?:the )?(?:server time|server date|real time|date|current date|date today)|what date is it|what is today s date|server time|server date|today s date)$")) { return Query(KnowledgeIntent.ServerDateTime); } if (TryTarget(text, KnowledgeIntent.PlayerLastLeft, true, out var query, "^when did (.+?) (?:last )?(?:leave|log out|logout|disconnect)(?: the server| the world)?$")) { return query; } if (TryTarget(text, KnowledgeIntent.PlayerLastSeen, true, out query, "^(?:when was|when were) (.+?) (?:last seen|last online|last here|last on(?: the server)?)$", "^(?:when was|when were) (.+?) (?:on|online|here) last$", "^when did (.+?) last play$", "^(?:when did you last see|when have you last seen|have you seen|has anyone seen|last seen) (.+)$", "^(.+?) (?:was last seen when|last seen|last online)$")) { return query; } if (TryTarget(text, KnowledgeIntent.PlayerOnline, true, out query, "^is (.+?) (?:online|on the server|playing)(?: now| right now| today)?$", "^(?:check if|tell me if) (.+?) is (?:online|on the server|playing)$", "^(.+?) (?:online|on the server)$")) { return query; } if (TryTarget(text, KnowledgeIntent.PlayerActivity, true, out query, "^what (?:has|have) (.+?) (?:been doing|done lately|done recently|been up to)$", "^what did (.+?) (?:do|do recently)$", "^(?:recent activity for|activity for|what happened to) (.+)$", "^(.+?)(?: s)? recent activity$")) { return query; } if (TryTarget(text, KnowledgeIntent.PlayerInfo, false, out query, "^(?:tell me about|what do you know about|who is|player info for|information about) (.+)$")) { return query; } if (TryTarget(text, KnowledgeIntent.PlayerLocation, false, out query, "^where (?:is|are) (.+)$", "^where (.+?) (?:is|are)$", "^where (?:can i|do i) find (.+)$", "^how far (?:away )?is (.+)$", "^which (?:direction|way) is (.+)$", "^(?:find|locate|directions to|direction to|give me directions to|direct me to|point me to|point me toward|point me towards|take me to|lead me to|guide me to|how do i get to|how can i get to|help me find) (.+)$", "^tell me where (.+?) is$", "^show me (?:where|the location of) (.+?)(?: is)?$", "^(.+?)(?: s)? (?:location|position|coordinates)$")) { return query; } return new KnowledgeQuery(); } private static bool TryTarget(string text, KnowledgeIntent intent, bool explicitPlayer, out KnowledgeQuery query, params string[] patterns) { foreach (string pattern in patterns) { Match match = Regex.Match(text, pattern, RegexOptions.CultureInvariant); if (match.Success) { string text2 = match.Groups[1].Value.Trim(); if (text2.StartsWith("the player ", StringComparison.Ordinal)) { text2 = text2.Substring(11); explicitPlayer = true; } else if (text2.StartsWith("player ", StringComparison.Ordinal)) { text2 = text2.Substring(7); explicitPlayer = true; } if (text2.Length == 0 || text2.Length > 100) { break; } query = Query(intent, text2, explicitPlayer); return true; } } query = null; return false; } private static KnowledgeQuery Query(KnowledgeIntent intent, string target = "", bool explicitPlayer = false) { return new KnowledgeQuery { Intent = intent, Target = target, ExplicitPlayer = explicitPlayer }; } private static bool Matches(string value, string pattern) { return Regex.IsMatch(value, pattern, RegexOptions.CultureInvariant); } internal static string Normalize(string text) { if (string.IsNullOrEmpty(text)) { return ""; } StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 512)); bool flag = false; foreach (char c in text) { if (stringBuilder.Length >= 512) { break; } if (char.IsLetterOrDigit(c) || c == '#') { if (flag && stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(char.ToLowerInvariant(c)); flag = false; } else if (c == '-' && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] == '#') { stringBuilder.Append(c); } else { flag = true; } } return stringBuilder.ToString().Trim(); } internal static List MatchPlayers(string target, IReadOnlyList names, IReadOnlyList ids) { List list = new List(); if (names == null || ids == null || names.Count != ids.Count) { return list; } string text = Normalize(target); if (text.Length == 0) { return list; } for (int i = 0; i < names.Count; i++) { if (Normalize(names[i]) == text) { list.Add(i); } } if (list.Count > 0) { return list; } for (int j = 0; j < names.Count; j++) { string text2 = "#" + ids[j].ToString(CultureInfo.InvariantCulture); if (text == text2 || text == Normalize(names[j]) + " " + text2) { list.Add(j); } } if (list.Count == 0) { string text3 = Regex.Replace(text, "\\s+(?:right now|now|currently)$", ""); if (text3 != text) { for (int k = 0; k < names.Count; k++) { if (Normalize(names[k]) == text3) { list.Add(k); } } } } return list; } internal static bool IsSelf(string target) { switch (target) { default: return target == "my"; case "me": case "myself": case "i": return true; } } internal static HashSet MatchLongestPhrases(string question, IEnumerable phrases) { string text = " " + Normalize(question) + " "; HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (string phrase in phrases) { string text2 = Normalize(phrase); if (text2.Length > 0) { hashSet.Add(text2); } } List list = new List(hashSet); list.Sort(delegate(string a, string b) { int num5 = b.Length.CompareTo(a.Length); return (num5 == 0) ? string.CompareOrdinal(a, b) : num5; }); bool[] array = new bool[text.Length]; HashSet hashSet2 = new HashSet(StringComparer.Ordinal); foreach (string item in list) { string text3 = " " + item + " "; int num = 0; int num2; while (num < text.Length && (num2 = text.IndexOf(text3, num, StringComparison.Ordinal)) >= 0) { bool flag = false; for (int num3 = num2 + 1; num3 < num2 + text3.Length - 1; num3++) { flag |= array[num3]; } if (!flag) { hashSet2.Add(item); for (int num4 = num2 + 1; num4 < num2 + text3.Length - 1; num4++) { array[num4] = true; } } num = num2 + 1; } } return hashSet2; } internal static bool TryPosition(string value, out float x, out float y, out float z) { x = (y = (z = 0f)); if (string.IsNullOrWhiteSpace(value) || value.Length > 128) { return false; } string text = value.Trim(); if (text.StartsWith("(", StringComparison.Ordinal) && text.EndsWith(")", StringComparison.Ordinal)) { text = text.Substring(1, text.Length - 2); } string[] array = text.Split(new char[1] { ',' }); if (array.Length != 3 || !float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x) || !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y) || !float.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out z)) { return false; } if (FiniteCoordinate(x) && FiniteCoordinate(y) && FiniteCoordinate(z) && Math.Abs(x) <= 20000f && Math.Abs(y) <= 10000f) { return Math.Abs(z) <= 20000f; } return false; } internal static bool IsActivityQuestion(string question) { string value = Normalize(question); if (Matches(value, "^(?:don t|do not|never|not|stop|cancel|how (?:do|can|should|to)|where|what should|tell me how)\\b")) { return false; } bool num = Matches(value, "\\b(?:deaths?|died|die|harvests?|harvested|gather|gathered|collect|collected|chop|chopped|mine|mined|cut|activity)\\b") || Matches(value, "^what (?:did|has|have) .+? (?:do|done|been doing)\\b"); bool flag = Matches(value, "\\b(?:today|this day|valheim day|week|seven days|7 days|how many|how much|who|did|has|have|deaths?|activity|harvested|gathered|collected|chopped|mined)\\b"); return num && flag; } internal static string ActivityTarget(string question) { string input = Normalize(question); string[] array = new string[4] { "^(?:how (?:much|many)(?: .+?)? (?:did|has|have|does)|what (?:did|has|have)|did|has|have) (.+?) (?:cut|chop|chopped|mine|mined|harvest|harvested|gather|gathered|collect|collected|die|died|do|done|had|have)(?:\\s|$)", "^(?:deaths?|harvests?|activity)(?: (?:today|this week))? (?:for|by) (.+?)(?: today| this week| this day)?$", "^(.+?)(?: s)? (?:deaths?|harvests?|activity)(?: today| this week| this day)?$", "^(.+?) (?:died|harvested|gathered|collected|chopped|mined|cut)(?:\\s|$)" }; foreach (string pattern in array) { Match match = Regex.Match(input, pattern, RegexOptions.CultureInvariant); if (match.Success) { string text = match.Groups[1].Value.Trim(); if (text == "who") { return ""; } if (text.StartsWith("player ", StringComparison.Ordinal)) { text = text.Substring(7); } return text; } } return ""; } internal static bool IsFresh(DateTime observedUtc, DateTime nowUtc, double maximumSeconds) { if (observedUtc == DateTime.MinValue || observedUtc == DateTime.MaxValue) { return false; } double totalSeconds = (nowUtc - observedUtc).TotalSeconds; if (totalSeconds >= -2.0) { return totalSeconds <= maximumSeconds; } return false; } internal static string DescribeAge(DateTime observedUtc, DateTime nowUtc) { if (observedUtc == DateTime.MinValue) { return "time unknown"; } double totalSeconds = (nowUtc - observedUtc).TotalSeconds; if (totalSeconds < -2.0) { return "timestamp ahead of the server clock"; } if (totalSeconds < 2.0) { return "just now"; } if (totalSeconds < 60.0) { return Math.Floor(totalSeconds).ToString(CultureInfo.InvariantCulture) + " seconds ago"; } if (totalSeconds < 3600.0) { return Unit(Math.Floor(totalSeconds / 60.0), "minute") + " ago"; } if (totalSeconds < 86400.0) { return Unit(Math.Floor(totalSeconds / 3600.0), "hour") + " ago"; } return Unit(Math.Floor(totalSeconds / 86400.0), "day") + " ago"; } private static string Unit(double count, string unit) { return count.ToString(CultureInfo.InvariantCulture) + " " + unit + ((count == 1.0) ? "" : "s"); } internal static string Timestamp(DateTime utc) { return utc.ToString("yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture); } internal static bool TryDirection(double fromX, double fromZ, double toX, double toZ, out string direction, out string distance) { direction = ""; distance = ""; if (!FiniteCoordinate(fromX) || !FiniteCoordinate(fromZ) || !FiniteCoordinate(toX) || !FiniteCoordinate(toZ)) { return false; } double num = toX - fromX; double num2 = toZ - fromZ; double num3 = Math.Sqrt(num * num + num2 * num2); string[] array = new string[8] { "north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest" }; double num4 = (Math.Atan2(num, num2) * 180.0 / Math.PI + 360.0) % 360.0; direction = ((num3 < 5.0) ? "right nearby" : array[(int)Math.Floor((num4 + 22.5) / 45.0) % 8]); distance = ((num3 >= 1000.0) ? ((num3 / 1000.0).ToString("0.0", CultureInfo.InvariantCulture) + " km") : (Math.Round(num3).ToString(CultureInfo.InvariantCulture) + " m")); return true; } internal static bool FiniteCoordinate(double value) { if (!double.IsNaN(value) && !double.IsInfinity(value)) { return Math.Abs(value) <= 1000000.0; } return false; } internal static string Display(string text, int maximumLength = 100) { StringBuilder stringBuilder = new StringBuilder(); string text2 = text ?? ""; foreach (char c in text2) { if (stringBuilder.Length >= maximumLength) { break; } if (c != '<' && c != '>' && !char.IsControl(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString().Trim(); } } public static class LlmBrain { private sealed class IngredientFact { internal string Name; internal int Amount; } private sealed class RecipeFact { internal string ItemName; internal string PrefabName; internal string StationName; internal int StationLevel; internal int OutputAmount; internal bool IsBuildPiece; internal bool UsesAlternativeIngredients; internal string ToolName; internal string NormalizedItemName; internal string NormalizedPrefabName; internal readonly List Ingredients = new List(); } private const int MaxPlayerMessageChars = 1000; private const int MaxReplyChars = 1200; private const int MaxTrackedPlayers = 128; private const int MaxTrackedEventKeys = 128; private static readonly object EventLock = new object(); private static readonly List RecentEvents = new List(); private static readonly Dictionary RecentEventTimes = new Dictionary(); private static readonly Queue RecruitmentQueue = new Queue(); private static float _nextRecruitmentAt; private static ObjectDB _recipeObjectDb; private static int _recipeCount = -1; private static float _nextRecipeRefresh; private static List _recipes; private static readonly FieldInfo AlternativeIngredients = typeof(Recipe).GetField("m_requireOnlyOneIngredient"); public static bool IsEnabled => false; public static void Ask(Mercenary merc, string question) { merc?.RequestServerConversation(question); } public static void AskAgent(string question) { string text = BoundSingleLineText(question, 1000); if (text.Length > 0) { ServerAuthority.SendAgentQuestion(text); } } internal static void SayAsAgent(string text) { SayAsAgent(text, MercConfig.AgentName.Value); } internal static void SayAsAgent(string text, string speaker) { string text2 = BoundSingleLineText(MercLocalization.Resolve(text), 1200); if (text2.Length == 0) { return; } string text3 = BoundSingleLineText(speaker, 60); if (text3.Length == 0) { text3 = "Valheim Guide"; } if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString(text3, text2, (Type)1, false); return; } Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, text3 + ": " + text2, 0, (Sprite)null, false); } } public static void RequestProactiveComment(Mercenary merc) { } internal static void ProcessServerRequest(long sender, ServerAuthority.SenderPlayerState requester, Mercenary merc, string question, bool agentMode, GuideClientSnapshot guideSnapshot, bool proactive = false) { ProcessServerRequest(sender, requester, merc, null, question, agentMode, guideSnapshot, proactive); } internal static void ProcessServerRequest(long sender, ServerAuthority.SenderPlayerState requester, ServerAuthority.MercenaryState mercState, string question, bool agentMode, GuideClientSnapshot guideSnapshot, bool proactive = false) { ProcessServerRequest(sender, requester, null, mercState, question, agentMode, guideSnapshot, proactive); } private static void ProcessServerRequest(long sender, ServerAuthority.SenderPlayerState requester, Mercenary merc, ServerAuthority.MercenaryState mercState, string question, bool agentMode, GuideClientSnapshot guideSnapshot, bool proactive) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || requester == null) { ServerAuthority.AbortAi(sender); return; } if (proactive) { ServerAuthority.CompleteAiSilent(sender); return; } string text = StripSpeakerAddress(BoundSingleLineText(question, 1000), merc, mercState); bool flag = false; GuideTaskResult guideTaskResult = null; try { MercDialogue.RecordPlayerConversation(requester.PlayerId); KnowledgeQuery knowledgeQuery = KnowledgeRules.Parse(text); IReadOnlyList snapshot = PlayerChronicle.GetSnapshot(); if (knowledgeQuery.Intent != KnowledgeIntent.None && (knowledgeQuery.ExplicitPlayer || KnowledgeRules.IsSelf(knowledgeQuery.Target) || KnowledgeRules.MatchPlayers(knowledgeQuery.Target, snapshot.Select((PlayerKnowledgeRecord record) => record.Name).ToArray(), snapshot.Select((PlayerKnowledgeRecord record) => record.PlayerId).ToArray()).Count > 0)) { string text2 = (agentMode ? NpcSupport.Answer(text, requester, DialogueRole.Guide, "private") : CannedReply(merc, mercState, text, requester.LivePlayer, requester)); ServerAuthority.CompleteAi(sender, merc, mercState, agentMode, text2); return; } GuideCommand command = (agentMode ? new GuideCommand(GuideTaskType.None) : GuideTasks.ClassifyPlayerRequest(text, requester.PlayerId)); string text3; if (command.HasAction) { DialogueRole role = (DialogueRole)SpeakerClass(merc, mercState); NpcSupport.Forget(requester, role, (mercState != null) ? ((object)Unsafe.As(ref mercState.MercId)/*cast due to .constrained prefix*/).ToString() : role.ToString()); flag = true; guideTaskResult = ExecuteGuide(merc, mercState, command, requester, guideSnapshot ?? new GuideClientSnapshot()); text3 = ((guideTaskResult != null) ? guideTaskResult.Message : MercLocalization.Phrase("dnpc_dialogue_action_uncertain")); } else { text3 = (agentMode ? NpcSupport.Answer(text, requester, DialogueRole.Guide, "private") : CannedReply(merc, mercState, text, requester.LivePlayer, requester)); } ServerAuthority.CompleteAi(sender, merc, mercState, agentMode, text3); } catch (Exception ex) { MercPlugin.LogWarn("NPC conversation could not complete: " + ex.GetType().Name); try { string text4 = ((!flag) ? MercLocalization.Phrase("dnpc_dialogue_reply_unknown_tank_0") : (guideTaskResult?.Message ?? MercLocalization.Phrase("dnpc_dialogue_action_uncertain"))); ServerAuthority.CompleteAi(sender, merc, mercState, agentMode, text4); } catch { ServerAuthority.AbortAi(sender); } } } private static GuideTaskResult ExecuteGuide(Mercenary merc, ServerAuthority.MercenaryState mercState, GuideCommand command, ServerAuthority.SenderPlayerState requester, GuideClientSnapshot snapshot) { if (mercState == null) { return GuideTasks.Execute(merc, command, requester, snapshot); } return GuideTasks.Execute(mercState, command, requester, snapshot); } public static void RecordEvent(string key, string description, float suppressSeconds = 0f) { string text = BoundSingleLineText(description, 240); if (text.Length == 0) { return; } string text2 = BoundSingleLineText(key, 80); int tickCount = Environment.TickCount; lock (EventLock) { if (text2.Length > 0 && suppressSeconds > 0f && RecentEventTimes.TryGetValue(text2, out var value) && tickCount - value >= 0 && (float)(tickCount - value) < suppressSeconds * 1000f) { return; } if (text2.Length > 0) { if (!RecentEventTimes.ContainsKey(text2) && RecentEventTimes.Count >= 128) { string text3 = null; int num = -1; foreach (KeyValuePair recentEventTime in RecentEventTimes) { int num2 = tickCount - recentEventTime.Value; if (text3 == null || num2 > num) { text3 = recentEventTime.Key; num = num2; } } if (text3 != null) { RecentEventTimes.Remove(text3); } } RecentEventTimes[text2] = tickCount; } RecentEvents.Add(text); while (RecentEvents.Count > 10) { RecentEvents.RemoveAt(0); } } } internal static string[] RecentContextEvents() { lock (EventLock) { return RecentEvents.ToArray(); } } public static void ResetSceneMemory() { NpcSupport.ResetSceneMemory(); RecruitmentQueue.Clear(); _nextRecruitmentAt = 0f; _recipeObjectDb = null; _recipeCount = -1; _recipes = null; _nextRecipeRefresh = 0f; lock (EventLock) { RecentEvents.Clear(); RecentEventTimes.Clear(); } MercDialogue.ResetSceneMemory(); } public static void ObservePlayerState() { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } RefreshRecipeIndex(); PlayerChronicle.Update(); if (Time.unscaledTime < _nextRecruitmentAt) { return; } while (RecruitmentQueue.Count > 0) { Mercenary mercenary = RecruitmentQueue.Dequeue(); if (!((Object)(object)mercenary == (Object)null) && !((Character)mercenary).IsDead()) { ZNetView component = ((Component)mercenary).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { mercenary.Say(ServiceOffer(mercenary)); _nextRecruitmentAt = Time.unscaledTime + 1.2f; break; } } } } public static void BeginRecruitmentConversation(Mercenary merc) { if (!((Object)(object)merc == (Object)null) && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && RecruitmentQueue.Count < 24 && !RecruitmentQueue.Contains(merc)) { RecruitmentQueue.Enqueue(merc); } } private static string ServiceOffer(Mercenary merc) { MercClass mercClass = (((Object)(object)merc != (Object)null) ? merc.Class : MercClass.Tank); string text = (((Object)(object)merc != (Object)null) ? merc.GetName() : Mercenary.DisplayName(mercClass)); return MercLocalization.Phrase(mercClass switch { MercClass.Archer => "dnpc_dialogue_service_archer", MercClass.Healer => "dnpc_dialogue_service_healer", _ => "dnpc_dialogue_service_tank", }, text); } private static string CannedReply(Mercenary merc, ServerAuthority.MercenaryState mercState, string question, Player player, ServerAuthority.SenderPlayerState requester) { string question2 = StripSpeakerAddress(question, merc, mercState); DialogueRole role = (DialogueRole)SpeakerClass(merc, mercState); return NpcSupport.Answer(context: MercDialogue.CaptureContext(merc, mercState, requester), question: question2, requester: requester, role: role, speakerKey: (mercState != null) ? ((object)Unsafe.As(ref mercState.MercId)/*cast due to .constrained prefix*/).ToString() : role.ToString()); } internal static bool TryRecipeReply(string question, out string reply) { reply = null; RecipeQuestion recipeQuestion = SupportQueryRules.Recipe(question); if (recipeQuestion == null || DialogueRules.IsProcessedMaterialQuestion(question)) { return false; } List recipes = _recipes; if (recipes == null || recipes.Count == 0) { reply = MercLocalization.Phrase("dnpc_knowledge_recipe_unavailable"); return true; } List list = new List(); foreach (RecipeFact item in recipes) { if (recipeQuestion.Target == item.NormalizedItemName || recipeQuestion.Target == item.NormalizedPrefabName) { list.Add(item); } } if (list.Count == 0) { reply = MercLocalization.Phrase("dnpc_knowledge_recipe_unknown"); return true; } if (list.Count > 1) { List list2 = new List(); foreach (RecipeFact item2 in list) { if (!list2.Contains(item2.ItemName)) { list2.Add(item2.ItemName); } if (list2.Count == 3) { break; } } reply = MercLocalization.Phrase("dnpc_knowledge_recipe_ambiguous", string.Join(", ", list2.ToArray())); return true; } RecipeFact recipeFact = list[0]; if (recipeQuestion.Upgrade) { reply = MercLocalization.Phrase("dnpc_knowledge_recipe_upgrade"); return true; } if (recipeFact.UsesAlternativeIngredients) { reply = MercLocalization.Phrase("dnpc_knowledge_recipe_choice"); return true; } List list3 = new List(); foreach (IngredientFact ingredient in recipeFact.Ingredients) { list3.Add("(" + ingredient.Amount + ") " + ingredient.Name); } string text = ((list3.Count == 0) ? MercLocalization.Text("dnpc_knowledge_recipe_no_materials") : BoundSingleLineText(string.Join(", ", list3.ToArray()), 400)); string text2 = recipeFact.StationName + ((recipeFact.StationLevel > 1) ? (" level " + recipeFact.StationLevel) : ""); reply = (recipeFact.IsBuildPiece ? MercLocalization.Phrase("dnpc_knowledge_recipe_build", recipeFact.ItemName, recipeFact.ToolName, text2, text) : MercLocalization.Phrase("dnpc_knowledge_recipe_craft", recipeFact.ItemName, recipeFact.OutputAmount, text2, text)); return true; } private static List RefreshRecipeIndex() { ObjectDB instance = ObjectDB.instance; int num = (((Object)(object)instance != (Object)null && instance.m_recipes != null) ? instance.m_recipes.Count : 0); if ((Object)(object)instance == (Object)null || instance.m_recipes == null) { return null; } if (_recipes != null && (Object)(object)_recipeObjectDb == (Object)(object)instance && _recipeCount == num && Time.unscaledTime < _nextRecipeRefresh) { return _recipes; } List list = new List(); bool flag = default(bool); foreach (Recipe recipe in instance.m_recipes) { if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null || recipe.m_item.m_itemData?.m_shared == null) { continue; } RecipeFact obj = new RecipeFact { ItemName = LocalizedName(recipe.m_item.m_itemData.m_shared.m_name, ((Object)((Component)recipe.m_item).gameObject).name), PrefabName = Utils.GetPrefabName(((Object)((Component)recipe.m_item).gameObject).name), StationName = (((Object)(object)recipe.m_craftingStation != (Object)null) ? LocalizedName(recipe.m_craftingStation.m_name, ((Object)((Component)recipe.m_craftingStation).gameObject).name) : MercLocalization.Text("dnpc_knowledge_recipe_no_station")), StationLevel = Math.Max(1, recipe.m_minStationLevel), OutputAmount = Math.Max(1, recipe.m_amount) }; int num2; if (AlternativeIngredients != null) { object value = AlternativeIngredients.GetValue(recipe); if (value is bool) { flag = (bool)value; num2 = 1; } else { num2 = 0; } } else { num2 = 0; } obj.UsesAlternativeIngredients = (byte)((uint)num2 & (flag ? 1u : 0u)) != 0; RecipeFact recipeFact = obj; AddRequirements(recipeFact, recipe.m_resources); IndexName(recipeFact); list.Add(recipeFact); } HashSet hashSet = new HashSet(); if (instance.m_items != null) { foreach (GameObject item in instance.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent() : null); PieceTable val2 = val?.m_itemData?.m_shared?.m_buildPieces; if (val2?.m_pieces == null) { continue; } string toolName = LocalizedName(val.m_itemData.m_shared.m_name, ((Object)item).name); foreach (GameObject piece in val2.m_pieces) { if (!((Object)(object)piece == (Object)null) && hashSet.Add(((Object)piece).GetInstanceID())) { Piece component = piece.GetComponent(); if (!((Object)(object)component == (Object)null) && component.m_enabled && !component.m_repairPiece && !component.m_removePiece) { RecipeFact recipeFact2 = new RecipeFact { ItemName = LocalizedName(component.m_name, ((Object)piece).name), PrefabName = Utils.GetPrefabName(((Object)piece).name), StationName = (((Object)(object)component.m_craftingStation != (Object)null) ? ("requires nearby " + LocalizedName(component.m_craftingStation.m_name, ((Object)((Component)component.m_craftingStation).gameObject).name)) : MercLocalization.Text("dnpc_knowledge_recipe_build_no_station")), StationLevel = 1, OutputAmount = 1, IsBuildPiece = true, ToolName = toolName }; AddRequirements(recipeFact2, component.m_resources); IndexName(recipeFact2); list.Add(recipeFact2); } } } } } _recipeObjectDb = instance; _recipeCount = num; _recipes = list; _nextRecipeRefresh = Time.unscaledTime + 60f; return list; } private static void AddRequirements(RecipeFact fact, Requirement[] requirements) { if (requirements == null) { return; } foreach (Requirement val in requirements) { if (val?.m_resItem?.m_itemData?.m_shared != null) { int amount = val.GetAmount(1); if (amount > 0) { fact.Ingredients.Add(new IngredientFact { Name = LocalizedName(val.m_resItem.m_itemData.m_shared.m_name, ((Object)((Component)val.m_resItem).gameObject).name), Amount = amount }); } } } } private static void IndexName(RecipeFact fact) { fact.NormalizedItemName = NormalizeWords(fact.ItemName).Trim(); fact.NormalizedPrefabName = NormalizeWords(Regex.Replace(fact.PrefabName ?? "", "([a-z0-9])([A-Z])", "$1 $2")).Trim(); } private static string LocalizedName(string token, string fallback) { string text = ((Localization.instance != null && !string.IsNullOrWhiteSpace(token)) ? Localization.instance.Localize(token) : token); if (string.IsNullOrWhiteSpace(text) || text.StartsWith("$", StringComparison.Ordinal)) { text = Utils.GetPrefabName(fallback ?? ""); } return BoundSingleLineText(text, 100); } private static string StripSpeakerAddress(string question, Mercenary merc, ServerAuthority.MercenaryState state) { if ((Object)(object)merc == (Object)null && state == null) { return question; } string text = SpeakerName(merc, state); int num = text.IndexOf(" the ", StringComparison.OrdinalIgnoreCase); string text2 = ((num > 0) ? text.Substring(0, num) : text); MercClass mercClass = SpeakerClass(merc, state); return DialogueRules.StripAddress(question, text, text2, mercClass switch { MercClass.Archer => "fletcher", MercClass.Healer => "mender", _ => "bulwark", }, mercClass switch { MercClass.Archer => "archer", MercClass.Healer => "healer", _ => "tank", }); } private static MercClass SpeakerClass(Mercenary merc, ServerAuthority.MercenaryState state) { if (!((Object)(object)merc != (Object)null)) { return state?.Class ?? MercClass.Tank; } return merc.Class; } private static string SpeakerName(Mercenary merc, ServerAuthority.MercenaryState state) { if ((Object)(object)merc != (Object)null) { return BoundSingleLineText(merc.GetName(), 60); } if (state != null && !string.IsNullOrWhiteSpace(state.Name)) { return BoundSingleLineText(state.Name, 60); } return Mercenary.DisplayName(SpeakerClass(merc, state)); } private static string BoundSingleLineText(string text, int limit) { if (string.IsNullOrWhiteSpace(text) || limit <= 0) { return ""; } string input = Regex.Replace(text, "[\\u0000-\\u001f\\u007f]+", " "); input = Regex.Replace(input, "\\s+", " ").Trim(); if (input.Length > limit) { return input.Substring(0, Math.Max(0, limit - 3)).TrimEnd(Array.Empty()) + "..."; } return input; } private static string NormalizeWords(string text) { return " " + Regex.Replace((text ?? "").ToLowerInvariant(), "[^a-z0-9]+", " ").Trim() + " "; } private static bool ContainsTerm(string words, string term) { string text = Regex.Replace((term ?? "").ToLowerInvariant(), "[^a-z0-9]+", " ").Trim(); if (text.Length > 0) { return words.Contains(" " + text + " "); } return false; } private static bool ContainsAnyTerm(string words, params string[] terms) { foreach (string term in terms) { if (ContainsTerm(words, term)) { return true; } } return false; } } internal static class LoreKnowledge { private sealed class LoreCard { public readonly string[] Terms; public readonly string Text; public LoreCard(string[] terms, string text) { Terms = terms; Text = text; } } private static readonly LoreCard[] Cards = new LoreCard[5] { new LoreCard(new string[10] { "valheim", "tenth world", "odin", "valkyrie", "yggdrasil", "forsaken", "world lore", "valheim lore", "lore", "story" }, "VALHEIM'S PREMISE: In the game's canon, the player is a slain warrior carried by a Valkyrie to Valheim, a wild tenth world held apart from the familiar Nine Worlds. Odin has sent the warrior against the Forsaken, enemies exiled here who have grown powerful. The huge branches overhead belong to Yggdrasil. Hugin, and later Munin, act as raven guides. Treat this as Valheim's fictional setting, not a direct statement of surviving Norse religious sources."), new LoreCard(new string[13] { "eir", "tyr", "týr", "ullr", "skadi", "skaði", "odin", "thor", "freyr", "god", "gods", "norse myth", "mythology" }, "NORSE INSPIRATION: Odin is associated with wisdom, war, death, poetry, magic, and the slain; his ravens are Huginn and Muninn in Old Norse tradition. Týr is strongly associated with law, oaths, and courage, especially through the binding of Fenrir. Eir is a healing figure named among Menglöð's attendants and is commonly described as an excellent healer. Ullr is associated with bows, skis, hunting, and winter travel. Skaði is a jötunn associated with mountains, skiing, hunting, and winter. Valheim borrows names and themes freely, so distinguish attested Norse mythology from details invented or adapted by the game and this mod."), new LoreCard(new string[9] { "eikthyr", "elder", "bonemass", "moder", "yagluth", "queen", "fader", "boss lore", "forsaken lore" }, "THE FORSAKEN: Eikthyr is a supernatural stag and the first Forsaken. The Elder is an ancient tree-like ruler of the greydwarfs. Bonemass is an immense undead mass of the Swamp. Moder is the dragon matriarch of the Mountains; her name means 'mother' in Swedish. Yagluth is the skeletal former king of the fulings. The Queen is the imprisoned matriarch of the seekers. Fader rules the Charred in the Ashlands. Runestones, altar texts, item descriptions, dreams, and environmental remains provide much of their story; when the game leaves a connection uncertain, describe it as interpretation rather than settled canon."), new LoreCard(new string[9] { "greydwarf", "draugr", "fuling", "dvergr", "seeker", "charred", "people", "creature lore", "enemy lore" }, "PEOPLES AND CREATURES: Greydwarfs are forest beings tied in Valheim's lore to corrupt or wicked dead. Draugr are undead remnants of a people ruined after challenging the gods. Fulings are the goblin-like inhabitants of the Plains who served Yagluth. Dvergr are intelligent Mistlands inhabitants skilled in magic and extraction; damaging their wards or property can turn them hostile. Seekers are the insectoid brood ruled by the Queen. The Charred are Ashlands undead bound to Fader's realm. Avoid claiming every gameplay creature is identical to a being in historical Norse myth."), new LoreCard(new string[7] { "runestone", "vegvisir", "rune", "dream", "canon", "history", "backstory" }, "SOURCES INSIDE THE GAME: Ordinary runestones tell fragments of travelers, vanished cultures, creatures, and biomes. A Vegvisir is primarily a gameplay runestone that reveals a boss altar; it is not the altar itself. Dreams, item descriptions, boss stones, Hugin/Munin dialogue, and environmental storytelling also contribute. These sources can be partial or poetic, so do not turn an implication or player theory into a confirmed fact.") }; internal static string BuildLookup(string question) { string text = NormalizeWords(question); if (text.Trim().Length == 0) { return ""; } bool flag = ContainsAny(text, "lore", "story", "history", "backstory", "myth", "mythology", "legend", "who is", "who are", "what is valheim", "what are the forsaken", "tell me about"); List> list = new List>(); LoreCard[] cards = Cards; foreach (LoreCard loreCard in cards) { int num = 0; string[] terms = loreCard.Terms; foreach (string text2 in terms) { if (ContainsTerm(text, text2)) { num += (text2.Contains(" ") ? 8 : 4); } } if (num > 0 && (flag || num >= 4)) { list.Add(new KeyValuePair(loreCard, num)); } } if (list.Count == 0) { return ""; } list.Sort((KeyValuePair left, KeyValuePair right) => right.Value.CompareTo(left.Value)); StringBuilder stringBuilder = new StringBuilder("[LOCAL VALHEIM / NORSE LORE LOOKUP - relevant reference cards]\n"); int num2 = ((!flag) ? 1 : 2); for (int num3 = 0; num3 < list.Count && num3 < num2; num3++) { stringBuilder.AppendLine(list[num3].Key.Text); } stringBuilder.AppendLine("[END LOCAL LORE LOOKUP]"); return stringBuilder.ToString(); } private static string NormalizeWords(string text) { string text2 = Regex.Replace((text ?? "").ToLowerInvariant(), "[^a-z0-9ýðþæöáéíóúåäø]+", " ").Trim(); return " " + text2 + " "; } private static bool ContainsAny(string words, params string[] terms) { foreach (string term in terms) { if (ContainsTerm(words, term)) { return true; } } return false; } private static bool ContainsTerm(string words, string term) { string text = Regex.Replace((term ?? "").ToLowerInvariant(), "[^a-z0-9ýðþæöáéíóúåäø]+", " ").Trim(); if (text.Length > 0) { return words.Contains(" " + text + " "); } return false; } } internal static class MenderCombatRules { internal const float EngagementRange = 18f; internal const float RetreatHealthFraction = 0.45f; internal const float RetreatTriggerDistance = 5.5f; internal const float RunToEngageDistance = 8f; internal static bool ShouldRetreat(bool threatTargetsMender, float healthFraction, float threatDistance) { if (threatTargetsMender && healthFraction >= 0f && healthFraction <= 0.45f && threatDistance >= 0f) { return threatDistance <= 5.5f; } return false; } internal static bool ShouldRunToEngage(float threatDistance) { return threatDistance > 8f; } } public static class MenderHealingStatus { private const string RenewalEffectName = "SE_MercRenewal"; private const string GreaterHealEffectName = "SE_MercGreaterHeal"; private static readonly int RenewalEffectHash = StringExtensionMethods.GetStableHashCode("SE_MercRenewal"); private static readonly int GreaterHealEffectHash = StringExtensionMethods.GetStableHashCode("SE_MercGreaterHeal"); private static bool _hooked; private static bool _registered; private static bool _missingEffectLogged; private static int _failedAttempts; public static void Register() { if (!_hooked) { _hooked = true; PrefabManager.OnVanillaPrefabsAvailable += RegisterWhenReady; } } private static void RegisterWhenReady() { if (_registered) { return; } try { Sprite val = FindHolyHealingIcon(); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("no vanilla stamina-mead icon was available"); } float duration = Mathf.Max(0.25f, MercConfig.RenewalTickInterval.Value) * (float)Mathf.Max(1, MercConfig.RenewalTickCount.Value); int num = Mathf.Max(1, MercConfig.BurstHealDurationSeconds.Value); RegisterEffect("SE_MercRenewal", "Renewal", "Mira's renewing light is restoring health over time.", duration, val); RegisterEffect("SE_MercGreaterHeal", "Greater Heal", "Mira's greater healing light is restoring health each second.", num, val); _registered = true; PrefabManager.OnVanillaPrefabsAvailable -= RegisterWhenReady; MercPlugin.Log("Registered visible Renewal and Greater Heal status effects"); } catch (Exception ex) { if (++_failedAttempts >= 3) { PrefabManager.OnVanillaPrefabsAvailable -= RegisterWhenReady; MercPlugin.LogWarn("Mender healing status registration gave up after " + $"{_failedAttempts} attempts; last error: {ex.Message}"); } else { MercPlugin.LogWarn("Mender healing status registration failed: " + ex.Message); } } } private static void RegisterEffect(string effectName, string displayName, string tooltip, float duration, Sprite icon) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown StatusEffect val = ScriptableObject.CreateInstance(); ((Object)val).name = effectName; val.m_name = displayName; val.m_tooltip = tooltip; val.m_icon = icon; val.m_ttl = duration; val.m_flashIcon = false; val.m_startEffects = new EffectList(); val.m_stopEffects = new EffectList(); if (!ItemManager.Instance.AddStatusEffect(new CustomStatusEffect(val, false))) { throw new InvalidOperationException("Jotunn rejected the " + displayName + " status effect"); } } private static Sprite FindHolyHealingIcon() { string[] array = new string[4] { "MeadStaminaMedium", "MeadStaminaLingering", "MeadStaminaMinor", "MeadHealthMedium" }; foreach (string text in array) { GameObject prefab = PrefabManager.Instance.GetPrefab(text); Sprite[] array2 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null)?.m_itemData?.m_shared?.m_icons; if (array2 != null && array2.Length != 0 && (Object)(object)array2[0] != (Object)null) { return array2[0]; } } return null; } public static void ApplyRenewal(Character target, float duration) { Apply(target, RenewalEffectHash, duration); } public static void ApplyGreaterHeal(Character target, float duration) { Apply(target, GreaterHealEffectHash, duration); } private static void Apply(Character target, int effectHash, float duration) { if ((Object)(object)target == (Object)null || target.IsDead()) { return; } if (!_registered) { if (!_missingEffectLogged) { _missingEffectLogged = true; MercPlugin.LogWarn("Mender healing effects were not registered; healing still works but their HUD buffs are unavailable"); } return; } SEMan sEMan = target.GetSEMan(); StatusEffect val = ((sEMan != null) ? sEMan.AddStatusEffect(effectHash, true, 0, 0f, (short)(-1)) : null); if ((Object)(object)val != (Object)null) { val.m_ttl = Mathf.Max(0.25f, duration); val.ResetTime(); } } } public class MercAI : MonsterAI { private sealed class Renewal { public Character Target; public float TickTimer; public int TicksLeft; public float Amount; public float DurationRemaining; public float TickInterval; } private sealed class GreaterHealOverTime { public Character Target; public float TickTimer; public int TicksLeft; public float Amount; } private sealed class ProbeResult { public float ExpiresAt; public bool Value; } private Mercenary _ownerMerc; private float _dialogueObservationTimer; private const float MaxShortSwimWidth = 10f; private const float RunStaminaPerSecond = 8f; private const float ArcherRollStamina = 15f; private const float ArcherRollCooldown = 4f; private const float ArcherSightGrace = 0.65f; private const float ArcherRepositionCommit = 0.9f; private const float TargetScanInterval = 0.4f; private const float TargetStickiness = 8f; private const float MenderRetreatDistance = 4f; private const float MenderRetreatCommit = 0.8f; private const float MenderSupportRadius = 7f; private const float FormationTurnRate = 4f; private const float FormationEnterPadding = 0.75f; private const float FormationPersonalSpace = 1.25f; private const float RemoteRunSpeed = 5.2f; private const float NavigationGoalResetDistance = 2.5f; private const float NavigationSampleSeconds = 0.5f; private const float NavigationProgressDistance = 0.35f; private const float NavigationStallSeconds = 1.6f; private const float NavigationRecoverySeconds = 2.2f; private const float NavigationDoorScanSeconds = 0.25f; private const float HealScanInterval = 0.4f; private const float ProbeCacheSeconds = 0.3f; private const float ResurrectionSnapMaxDistance = 40f; private static readonly int AmbientGroundMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "terrain", "blocker", "vehicle" }); internal static readonly int ZdoRenewalReady = StringExtensionMethods.GetStableHashCode("merc_renewalReadyAt"); internal static readonly int ZdoBurstReady = StringExtensionMethods.GetStableHashCode("merc_burstReadyAt"); internal static readonly int ZdoGuideTask = StringExtensionMethods.GetStableHashCode("merc_guideTaskV1"); internal readonly MercResourceWorker ResourceWorker = new MercResourceWorker(); private float _resourceThreatScanAt; private bool _resourceThreatPresent; private readonly List _renewals = new List(); private readonly List _greaterHeals = new List(); private Character _combatTarget; private ZDOID _contextTargetId; private Character _contextTarget; private float _contextTargetUntil; private bool _contextTargetIsHunt; private ZDOID _protectedHuntTargetId; private float _protectedHuntTargetUntil; private float _targetScanTimer; private float _healScanTimer; private float _attackTimer; private Character _bowDrawTarget; private float _archerRollCooldown; private float _archerLostSightTimer; private float _archerRepositionTimer; private Vector3 _archerRepositionPoint; private bool _archerHasRepositionPoint; private int _archerFlankSign = 1; private Character _menderRetreatThreat; private float _menderRetreatTimer; private Vector3 _menderRetreatPoint; private Vector3 _formationForward; private bool _formationForwardReady; private bool _formationCorrecting; private float _employerSettledClock; private MercMovementMode _movementMode; private Vector3 _baseRoamAnchor; private Vector3 _baseRoamTarget; private float _baseRoamPause = -1f; private bool _baseRoamActive; private bool _baseRoamSuppressedByRecall; private bool _recallGathering; private bool _followingSneak; private ZSyncAnimation _stealthAnimation; private static readonly FieldInfo CurrentAttackField = typeof(Humanoid).GetField("m_currentAttack", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(typeof(Humanoid).FullName, "m_currentAttack"); private static readonly FieldInfo PreviousAttackField = typeof(Humanoid).GetField("m_previousAttack", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(typeof(Humanoid).FullName, "m_previousAttack"); private Vector3 _remoteFormationPosition; private Vector3 _remoteFormationVelocity; private float _remoteFormationSampleTime; private bool _remoteFormationReady; private readonly Collider[] _navigationDoorColliders = (Collider[])(object)new Collider[24]; private Vector3 _navigationGoal; private Vector3 _navigationSamplePosition; private Vector3 _navigationRecoveryPoint; private Vector3 _navigationRecoveryOrigin; private Vector3 _navigationRecoveryGoal; private Vector3 _navigationLastSharedDetour; private float _navigationSampleDistance; private float _navigationSampleClock; private float _navigationStallClock; private float _navigationRecoveryClock; private float _navigationDoorScanClock; private float _navigationLastUseTime; private float _navigationLastSharedDetourTime; private int _navigationRecoveryAttempt; private bool _navigationReady; private bool _navigationRecovering; private bool _navigationRecoveryDirect; private bool _navigationShareOnSuccess; private float _renewalCooldown; private float _burstCooldown; private float _healingPoseTimer; private readonly MercCampLife _campLife = new MercCampLife(); private Player _resurrectionTarget; private ZDOID _resurrectionTargetId; private float _resurrectionApproachTimer; private float _resurrectionTeleportRetryTimer; private float _resurrectionCastTimer; private float _resurrectionPulseTimer; private bool _resurrectionCasting; private GuideTaskType _guideTask; private Player _guidePlayer; private ZDOID _guidePlayerId; private long _guidePlayerPersistentId; private string _guidePlayerName; private Vector3 _guidePlayerPosition; private Vector3 _guidePlayerForward = Vector3.forward; private Biome _guidePlayerBiome; private Vector3 _guideDestination; private List _guideRoute; private int _guideRouteIndex; private string _guideCreatureKey; private string _guideCreatureLabel; private bool _guideCreatureAnnounced; private Character _guideCreatureTarget; private bool _guideCreatureHoldSaid; internal static string ActiveCreatureHuntKey; internal static MercAI ActiveCreatureHuntOwner; private Vegvisir _guideBossStone; private BossTarget _guideBoss; private float _guideScanTimer; private float _guideCalloutTimer; private bool _guideWaitNagSaid; private int _bossStoneSearchStep; private float _nextAiErrorLogTime; private float _followWaterBlockedTimer; private float _guideWaterBlockedTimer; private Vector3 _combatShorePoint; private float _combatShoreRefreshTimer; private bool _combatShoreRecoveryActive; private float _guidePlayerSeparatedTimer; private float _guideWaypointTimer; private Vector3 _guideLandWaypoint; private bool _guideHasLandWaypoint; private bool _guideWaterWarningGiven; private static readonly Func BaseUpdateAI = BuildBaseUpdateAi(); private float _guideProgressDistance = -1f; private float _guideProgressClock; private readonly Dictionary _probeCache = new Dictionary(); private const float GuideMaxSwimWidth = 60f; private static float _generatedWaterLevel = float.MinValue; private static float _nextWaterDiagTime; private const float CombatLeashRange = 20f; private const float GuideCombatLeashRange = 15f; private const float HomeTeleportRange = 40f; private float _returnHomeTimer; private bool _stayExpiryLogged; private Vector3 _idleRepositionAnchor; private Vector3 _idleRepositionTarget; private float _idleRepositionPause = -1f; private bool _idleRepositionActive; public Mercenary Merc { get; private set; } public bool IsSneakingWithEmployer => MercStealth.IsConcealed((Character)(object)Merc); public string GuideStatus { get { switch (_guideTask) { case GuideTaskType.Tombstone: return "guiding to the latest tombstone"; case GuideTaskType.BossStone: if (!((Object)(object)_guideBossStone != (Object)null)) { return "searching for a runestone for " + GuideTasks.BossName(_guideBoss); } return "guiding to a runestone for " + GuideTasks.BossName(_guideBoss); case GuideTaskType.BossAltar: return "guiding to " + GuideTasks.BossName(_guideBoss) + "'s altar"; case GuideTaskType.Custom: return "guiding to a discovery"; default: return "none"; } } } public bool IsGuiding { get { if (_guideTask != GuideTaskType.None) { return true; } ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); if ((Object)(object)val != (Object)null && val.IsValid()) { return !string.IsNullOrEmpty(val.GetZDO().GetString(ZdoGuideTask, "")); } return false; } } internal bool IsResurrectionActive { get { if (!((Object)(object)_resurrectionTarget != (Object)null)) { return !((ZDOID)(ref _resurrectionTargetId)).IsNone(); } return true; } } internal static float MaxGuideSwimWidth => 60f; internal bool EngagedInCombat { get { if (!((Object)(object)_combatTarget != (Object)null)) { if ((Object)(object)Merc != (Object)null) { return ((Character)Merc).InAttack(); } return false; } return true; } } private int GetOwnerEffectiveStage() { if ((Object)(object)_ownerMerc == (Object)null) { _ownerMerc = ((Component)this).GetComponent(); } if (!((Object)(object)_ownerMerc != (Object)null)) { return StageDirector.GetStage(); } return _ownerMerc.GetEffectiveStage(); } private static Func BuildBaseUpdateAi() { MethodInfo method = typeof(BaseAI).GetMethod("UpdateAI", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return null; } DynamicMethod dynamicMethod = new DynamicMethod("MercBaseAI_UpdateAI", typeof(bool), new Type[2] { typeof(MercAI), typeof(float) }, restrictedSkipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Call, method); iLGenerator.Emit(OpCodes.Ret); return (Func)dynamicMethod.CreateDelegate(typeof(Func)); } public override void Awake() { ((MonsterAI)this).Awake(); Merc = ((Component)this).GetComponent(); } private bool OwnerGuardFallback() { ZNetView component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { return component.IsOwner(); } return false; } private void TickHealingPose(float dt) { if (!(_healingPoseTimer <= 0f)) { _healingPoseTimer -= dt; if (_healingPoseTimer <= 0f) { Merc.EndBareHandCast(); } } } public void ResetTransientSessionState() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) ResourceWorker.Cancel(this); SetFollowingSneak(active: false); _recallGathering = false; _dialogueObservationTimer = 0f; MercDialogue.ClearCombatObservation(Merc); if (_healingPoseTimer > 0f && (Object)(object)Merc != (Object)null) { Merc.EndBareHandCast(); } _renewals.Clear(); _greaterHeals.Clear(); _combatTarget = null; ClearContextTarget(); _targetScanTimer = 0f; _attackTimer = 0f; Merc?.CancelAiBowDraw(); _bowDrawTarget = null; _archerRollCooldown = 0f; _archerLostSightTimer = 0f; _archerRepositionTimer = 0f; _archerRepositionPoint = Vector3.zero; _archerHasRepositionPoint = false; _archerFlankSign = 1; ResetMenderPositioning(); _combatShorePoint = Vector3.zero; _combatShoreRefreshTimer = 0f; _combatShoreRecoveryActive = false; ResetFormationState(); ResetNavigationRecovery(); _renewalCooldown = 0f; _burstCooldown = 0f; _healingPoseTimer = 0f; _campLife.Reset(Merc); ClearResurrection(); CancelGuideTask(null, announce: false); _guideTask = GuideTaskType.None; _guidePlayer = null; _guidePlayerId = default(ZDOID); _guidePlayerPersistentId = 0L; _guidePlayerName = ""; _guideDestination = Vector3.zero; _guideRoute = null; _guideRouteIndex = 0; _guideCreatureKey = null; _guideCreatureLabel = null; _guideCreatureAnnounced = false; _guideCreatureTarget = null; _guideCreatureHoldSaid = false; if ((Object)(object)ActiveCreatureHuntOwner == (Object)(object)this) { ActiveCreatureHuntKey = null; ActiveCreatureHuntOwner = null; } _guideBossStone = null; _guideBoss = BossTarget.None; _guideScanTimer = 0f; _guideCalloutTimer = 0f; _bossStoneSearchStep = 0; _followWaterBlockedTimer = 0f; _guideWaterBlockedTimer = 0f; _guidePlayerSeparatedTimer = 0f; _guideWaypointTimer = 0f; _guideHasLandWaypoint = false; _guideWaterWarningGiven = false; _guideProgressDistance = -1f; _guideProgressClock = 0f; try { ((BaseAI)this).SetAlerted(false); ((MonsterAI)this).SetFollowTarget((GameObject)null); ((BaseAI)this).ResetPatrolPoint(); ((BaseAI)this).ResetRandomMovement(); } catch { } ClearLatchedMovement(); } public override bool UpdateAI(float dt) { if ((Object)(object)Merc == (Object)null) { Merc = ((Component)this).GetComponent(); } if ((Object)(object)Merc == (Object)null) { return false; } try { if (BaseUpdateAI != null && !BaseUpdateAI(this, dt)) { ResourceWorker.OwnershipLost(this); _followingSneak = false; return false; } if (BaseUpdateAI == null && !OwnerGuardFallback()) { ResourceWorker.OwnershipLost(this); _followingSneak = false; return false; } MercNavigationMemory.UpdateDoors(Merc); _attackTimer = Mathf.Max(0f, _attackTimer - dt); _archerRollCooldown = Mathf.Max(0f, _archerRollCooldown - dt); _renewalCooldown = Mathf.Max(0f, _renewalCooldown - dt); _burstCooldown = Mathf.Max(0f, _burstCooldown - dt); _resurrectionTeleportRetryTimer = Mathf.Max(0f, _resurrectionTeleportRetryTimer - dt); _targetScanTimer -= dt; _healScanTimer -= dt; if (Merc.Class == MercClass.Healer) { UpdateHealingOverTime(dt); } if (UpdateResurrection(dt)) { ResourceWorker.Cancel(this); SetFollowingSneak(active: false); return true; } if (Merc.UpdateShipPassengerState()) { ResourceWorker.Cancel(this); SetFollowingSneak(active: false); _combatTarget = null; CancelBowDraw(); ResetArcherPositioning(); TickHealingPose(dt); ((BaseAI)this).StopMoving(); return true; } SetFollowingSneak(MercStealth.ShouldFollowSneak(Merc)); if (_followingSneak) { ResourceWorker.Cancel(this); InterruptCombatForOrder(); TickHealingPose(dt); UpdateFormation(dt); return true; } if (((Character)Merc).InWater() || ((Character)Merc).IsSwimming()) { ResourceWorker.Cancel(this); } if (_combatShoreRecoveryActive && RecoverToShoreAfterWaterCombat(dt, _combatTarget)) { return true; } if (HoldPositionForPlayerWaterTravel()) { ResourceWorker.Cancel(this); return true; } if (UpdateRecallGathering(dt) || ReturnToPreciseHold(dt)) { ResourceWorker.Cancel(this); return true; } if (ResourceWorker.Tick(this, dt)) { return true; } switch (Merc.Class) { case MercClass.Tank: UpdateTank(dt); break; case MercClass.Archer: UpdateArcher(dt); break; default: UpdateHealer(dt); break; } return true; } catch (Exception ex) { ResourceWorker.Cancel(this); SetFollowingSneak(active: false); ClearLatchedMovement(); if (_guideTask != GuideTaskType.None) { CancelGuideTask(null, announce: false); } if (Time.time >= _nextAiErrorLogTime) { _nextAiErrorLogTime = Time.time + 10f; MercPlugin.LogWarn(Merc.GetName() + " AI recovered from " + ex.GetType().Name + ": " + ex.Message); } return true; } finally { try { UpdateDialogueObservation(dt); } catch { } } } private void UpdateDialogueObservation(float dt) { if ((Object)(object)Merc == (Object)null || ((Character)Merc).IsDead() || !((Component)Merc).gameObject.activeInHierarchy || !OwnerGuardFallback()) { _dialogueObservationTimer = 0f; } else if (!float.IsNaN(dt) && !float.IsInfinity(dt) && !(dt < 0f)) { _dialogueObservationTimer -= dt; if (!(_dialogueObservationTimer > 0f)) { _dialogueObservationTimer = 2f; MercDialogue.PublishCombatObservation(Merc, ((BaseAI)this).GetTargetCreature()); } } } public bool CanBeginResurrection() { if ((Object)(object)Merc != (Object)null && Merc.Class == MercClass.Healer && (Object)(object)_resurrectionTarget == (Object)null) { return ((ZDOID)(ref _resurrectionTargetId)).IsNone(); } return false; } public bool BeginResurrection(Player player) { //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) if ((Object)(object)player == (Object)null || !CanBeginResurrection()) { return false; } _resurrectionTarget = player; _resurrectionTargetId = ((Character)player).GetZDOID(); InitializeResurrection(); return true; } internal bool BeginResurrection(ServerAuthority.SenderPlayerState requester) { //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) if (requester == null || ((ZDOID)(ref requester.CharacterZdoId)).IsNone() || !CanBeginResurrection()) { return false; } _resurrectionTarget = requester.LivePlayer; _resurrectionTargetId = requester.CharacterZdoId; InitializeResurrection(); return true; } private void InitializeResurrection() { CancelAmbientCamp(); _resurrectionApproachTimer = 0f; _resurrectionCastTimer = 0f; _resurrectionPulseTimer = 0f; _resurrectionCasting = false; _combatTarget = null; } public void CancelResurrection(bool refundCooldown) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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) if (!((Object)(object)_resurrectionTarget == (Object)null) || !((ZDOID)(ref _resurrectionTargetId)).IsNone()) { ZDOID resurrectionTargetId = _resurrectionTargetId; ClearResurrection(); ((BaseAI)this).StopMoving(); if (refundCooldown) { Merc.ResetResurrectionCooldown(); } Merc.BroadcastResurrectionDenied(resurrectionTargetId); } } private bool UpdateResurrection(float dt) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: 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_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_resurrectionTarget == (Object)null && ((ZDOID)(ref _resurrectionTargetId)).IsNone()) { return false; } if (!TryGetResurrectionTarget(out var player, out var position, out var forward, out var dead) || dead || ((Character)Merc).IsDead()) { CancelResurrection(refundCooldown: true); return true; } Vector3 val = position + forward * 2.2f; val.y = position.y; float num = Vector3.Distance(((Component)this).transform.position, val); if (!_resurrectionCasting && num > 1.4f) { _resurrectionApproachTimer += dt; if (!(_resurrectionApproachTimer >= 7f)) { MoveToWithStamina(dt, val, 1f, requestRun: true); AimAt(((Object)(object)player != (Object)null) ? ((Character)player).GetCenterPoint() : (position + Vector3.up)); return true; } if (Vector3.Distance(((Component)this).transform.position, val) > 40f) { CancelResurrection(refundCooldown: true); return true; } if (_resurrectionTeleportRetryTimer <= 0f) { _resurrectionTeleportRetryTimer = 1.5f; Vector3 val2 = position - val; Vector3 normalized = ((Vector3)(ref val2)).normalized; Quaternion val3 = ((((Vector3)(ref normalized)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(normalized) : ((Component)this).transform.rotation); ((Character)Merc).TeleportTo(val, val3, false); } } ((BaseAI)this).StopMoving(); AimAt(((Object)(object)player != (Object)null) ? ((Character)player).GetCenterPoint() : (position + Vector3.up)); if (!_resurrectionCasting) { _resurrectionCasting = true; _resurrectionCastTimer = Mathf.Max(1f, MercConfig.ResurrectionCastSeconds.Value); _resurrectionPulseTimer = 0f; Merc.BroadcastResurrectionCast(_resurrectionTargetId); } _resurrectionCastTimer -= dt; _resurrectionPulseTimer -= dt; if (_resurrectionPulseTimer <= 0f) { _resurrectionPulseTimer = 0.8f; Merc.BroadcastResurrectionPulse(_resurrectionTargetId); } if (_resurrectionCastTimer <= 0f) { ZDOID resurrectionTargetId = _resurrectionTargetId; ClearResurrection(); Merc.BroadcastResurrectionComplete(resurrectionTargetId); } return true; } private bool TryGetResurrectionTarget(out Player player, out Vector3 position, out Vector3 forward, out bool dead) { //IL_0078: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //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) player = _resurrectionTarget; if ((Object)(object)player == (Object)null && !((ZDOID)(ref _resurrectionTargetId)).IsNone()) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(_resurrectionTargetId) : null); player = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)player != (Object)null) { _resurrectionTarget = player; } } if ((Object)(object)player != (Object)null) { position = ((Component)player).transform.position; forward = ((Component)player).transform.forward; dead = ((Character)player).IsDead(); return true; } ZDO val2 = ((ZDOMan.instance != null && !((ZDOID)(ref _resurrectionTargetId)).IsNone()) ? ZDOMan.instance.GetZDO(_resurrectionTargetId) : null); if (val2 == null || !val2.IsValid()) { position = Vector3.zero; forward = Vector3.forward; dead = true; return false; } position = val2.GetPosition(); forward = val2.GetRotation() * Vector3.forward; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } dead = val2.GetBool(ZDOVars.s_dead, false); return true; } private void ClearResurrection() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) _resurrectionTarget = null; _resurrectionTargetId = default(ZDOID); _resurrectionApproachTimer = 0f; _resurrectionTeleportRetryTimer = 0f; _resurrectionCastTimer = 0f; _resurrectionPulseTimer = 0f; _resurrectionCasting = false; } public override Character GetTargetCreature() { if (!IsActionableCombatTarget(_combatTarget)) { return ((MonsterAI)this).GetTargetCreature(); } return _combatTarget; } private void UpdateTank(float dt) { //IL_0046: 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_0093: 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_009c: Unknown result type (might be due to invalid IL or missing references) Character val = AcquireCombatTarget(15f); if ((Object)(object)val == (Object)null) { if (!UpdateGuideTask(dt)) { UpdateFormation(dt); } } else { if (RecoverToShoreAfterWaterCombat(dt, val)) { return; } _combatTarget = val; AimAt(val); float num = Vector3.Distance(((Component)this).transform.position, ((Component)val).transform.position); ItemData val2 = Merc.EnsureClassWeapon(); if (val2 == null) { ((BaseAI)this).StopMoving(); return; } float num2 = Mathf.Clamp(val2.m_shared.m_aiAttackRange, 1.5f, 3.5f); if (num > num2) { Vector3 point = TankInterceptPoint(val); MoveToWithStamina(dt, point, num2 * 0.8f, num > 6f); } else { ((BaseAI)this).StopMoving(); TryStartAttack(val, val2, requireBow: false); } } } private Vector3 TankInterceptPoint(Character target) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)target).transform.position; Character val2 = CurrentThreatVictim(target); if (IsProtectedCompanyMember(val2) && (Object)(object)val2 != (Object)(object)Merc) { Vector3 val3 = ((Component)val2).transform.position - val; val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude > 1f) { val += ((Vector3)(ref val3)).normalized * Mathf.Min(2.5f, ((Vector3)(ref val3)).magnitude * 0.35f); } } Vector3 velocity = target.GetVelocity(); velocity.y = 0f; val += Vector3.ClampMagnitude(velocity * 0.25f, 1.5f); return GroundedPoint(val); } private void UpdateArcher(float dt) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Invalid comparison between Unknown and I4 float num = Mathf.Max(3f, MercConfig.ArcherRetreatRange.Value); float num2 = Mathf.Max(num + 2f, MercConfig.ArcherPreferredRange.Value); float range = Mathf.Max(num2 + 3f, MercConfig.ArcherTargetRange.Value); Character val = AcquireCombatTarget(range); if ((Object)(object)val == (Object)null) { CancelBowDraw(); ResetArcherPositioning(); if (!UpdateGuideTask(dt)) { UpdateFormation(dt); } } else { if (RecoverToShoreAfterWaterCombat(dt, val)) { return; } _combatTarget = val; AimAt(val); float num3 = HorizontalDistance(((Component)this).transform.position, ((Component)val).transform.position); float num4 = num + 0.75f; float num5 = num2 + 2.5f; bool flag = ((BaseAI)this).CanSeeTarget(val); if (((Character)Merc).InDodge()) { CancelBowDraw(); ((BaseAI)this).StopMoving(); return; } if (num3 < num) { CancelBowDraw(); _archerLostSightTimer = 0f; if (TryGetPreciseHoldAnchor(out var _) || !(_archerRollCooldown <= 0f) || !TryArcherRoll(val)) { UpdateArcherReposition(dt, val, num2, seekLineOfSight: false); } return; } if (((Character)Merc).IsDrawingBow()) { if (num3 > num5 + 2f) { CancelBowDraw(); UpdateArcherReposition(dt, val, num2, seekLineOfSight: false); return; } if (!flag) { _archerLostSightTimer += dt; if (_archerLostSightTimer > 0.65f) { CancelBowDraw(); UpdateArcherReposition(dt, val, num2, seekLineOfSight: true); return; } } else { _archerLostSightTimer = 0f; } ((BaseAI)this).StopMoving(); AdvanceAndReleaseBow(val, dt, flag); return; } _archerLostSightTimer = 0f; if (!flag || num3 > num5 || num3 < num4) { UpdateArcherReposition(dt, val, num2, !flag); return; } _archerHasRepositionPoint = false; _archerRepositionTimer = 0f; ((BaseAI)this).StopMoving(); ItemData val2 = Merc.EnsureClassWeapon(); if (val2 == null || (int)val2.m_shared.m_itemType != 4) { CancelBowDraw(); return; } if ((Object)(object)_bowDrawTarget != (Object)null && (Object)(object)_bowDrawTarget != (Object)(object)val) { CancelBowDraw(); } if (!((Character)Merc).IsDrawingBow() && !(_attackTimer > 0f) && !((Character)Merc).InAttack()) { AimAt(val); if (Merc.BeginAiBowDraw(val2)) { _bowDrawTarget = val; } else { _attackTimer = 0.5f; } } } } private void AdvanceAndReleaseBow(Character target, float dt, bool canSeeTarget) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 ItemData val = Merc.EnsureClassWeapon(); if (val == null || (int)val.m_shared.m_itemType != 4) { CancelBowDraw(); return; } AimAt(target); if (!((Character)Merc).IsDrawingBow()) { return; } if (!Merc.AdvanceAiBowDraw(val, dt, out var ready)) { CancelBowDraw(); _attackTimer = 0.5f; } else if (ready && canSeeTarget) { bool num = Merc.ReleaseAiBowAttack(target, val); _bowDrawTarget = null; if (!num) { _attackTimer = 0.5f; } else { SetAttackCooldown(val); } } } private bool TryArcherRoll(Character target) { //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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_0046: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)this).transform.position - ((Component)target).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = -((Component)target).transform.forward; val.y = 0f; } ((Vector3)(ref val)).Normalize(); Vector3 val2 = new Vector3(val.z, 0f, 0f - val.x) * (0.25f * (float)_archerFlankSign); Vector3 val3 = val + val2; Vector3 normalized = ((Vector3)(ref val3)).normalized; if (!Merc.TryStartAiRoll(normalized, 15f)) { return false; } _archerRollCooldown = 4f; _archerFlankSign = -_archerFlankSign; _archerHasRepositionPoint = false; _archerRepositionTimer = 0f; return true; } private void UpdateArcherReposition(float dt, Character target, float preferredRange, bool seekLineOfSight) { //IL_0050: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) _archerRepositionTimer -= dt; if (!_archerHasRepositionPoint || _archerRepositionTimer <= 0f || HorizontalDistance(((Component)this).transform.position, _archerRepositionPoint) < 1.25f) { SelectArcherRepositionPoint(target, preferredRange, seekLineOfSight); } float num = HorizontalDistance(((Component)this).transform.position, _archerRepositionPoint); MoveToWithStamina(dt, _archerRepositionPoint, 1.1f, num > 6f); } private void SelectArcherRepositionPoint(Character target, float preferredRange, bool seekLineOfSight) { //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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)this).transform.position - ((Component)target).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = -((Component)target).transform.forward; } val.y = 0f; ((Vector3)(ref val)).Normalize(); if (seekLineOfSight) { val = Quaternion.Euler(0f, 35f * (float)_archerFlankSign, 0f) * val; _archerFlankSign = -_archerFlankSign; } Vector3 val2 = ((Component)target).transform.position + val * preferredRange; if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.IsZoneLoaded(val2)) { float solidHeight = ZoneSystem.instance.GetSolidHeight(val2); if (solidHeight > -1000f) { val2.y = solidHeight + 0.1f; } } _archerRepositionPoint = val2; _archerHasRepositionPoint = true; _archerRepositionTimer = 0.9f; } private void ResetArcherPositioning() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) _archerLostSightTimer = 0f; _archerRepositionTimer = 0f; _archerHasRepositionPoint = false; _archerRepositionPoint = Vector3.zero; } private void CancelBowDraw() { if ((Object)(object)Merc != (Object)null && (((Character)Merc).IsDrawingBow() || (Object)(object)_bowDrawTarget != (Object)null)) { Merc.CancelAiBowDraw(); } _bowDrawTarget = null; } private void TryStartAttack(Character target, ItemData weapon, bool requireBow) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 if (_attackTimer > 0f || ((Character)Merc).InAttack() || weapon == null || weapon.m_shared == null) { return; } bool flag = (int)weapon.m_shared.m_itemType == 4; if (requireBow == flag) { AimAt(target); if (((Character)Merc).StartAttack(target, false)) { SetAttackCooldown(weapon); } } } private void SetAttackCooldown(ItemData weapon) { float valueOrDefault = (weapon?.m_shared?.m_aiAttackInterval).GetValueOrDefault(); _attackTimer = Mathf.Clamp((valueOrDefault > 0f) ? valueOrDefault : 1.5f, 0.8f, 4f); } private void AimAt(Character target) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null)) { AimAt(target.GetTopPoint()); } } private void AimAt(Vector3 aimPoint) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) ((BaseAI)this).LookAt(aimPoint); Vector3 val = aimPoint - ((Component)Merc).transform.position; Vector3 normalized = ((Vector3)(ref val)).normalized; if (((Vector3)(ref normalized)).sqrMagnitude > 0.001f) { ((Character)Merc).SetLookDir(normalized, 0f); } } private void UpdateHealer(float dt) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) if (_healingPoseTimer > 0f) { _healingPoseTimer -= dt; ((BaseAI)this).StopMoving(); if (_healingPoseTimer <= 0f) { Merc.EndBareHandCast(); } return; } bool num = _healScanTimer <= 0f; if (num) { _healScanTimer = 0.4f; } if (num && (TryCastGreaterHeal() || TryCastRenewal())) { ((BaseAI)this).StopMoving(); return; } Character val = AcquireCombatTarget(18f); if ((Object)(object)val == (Object)null) { _combatTarget = null; ResetMenderPositioning(); if (!UpdateGuideTask(dt)) { UpdateFormation(dt); } } else { if (RecoverToShoreAfterWaterCombat(dt, val)) { return; } _combatTarget = val; AimAt(val); float num2 = HorizontalDistance(((Component)this).transform.position, ((Component)val).transform.position); ItemData val2 = Merc.EnsureClassWeapon(); float num3 = ((val2 != null) ? Mathf.Clamp(val2.m_shared.m_aiAttackRange, 1.5f, 3.5f) : 2.2f); if (MenderCombatRules.ShouldRetreat((Object)(object)CurrentThreatVictim(val) == (Object)(object)Merc, ((Character)Merc).GetHealthPercentage(), num2)) { UpdateMenderRetreat(dt, val); } else if (num2 <= num3 + 0.25f) { ResetMenderPositioning(); ((BaseAI)this).StopMoving(); if (val2 != null) { TryStartAttack(val, val2, requireBow: false); } } else { ResetMenderPositioning(); bool flag = MenderCombatRules.ShouldRunToEngage(num2); MoveToWithStamina(dt, ((Component)val).transform.position, Mathf.Max(1.1f, num3 * 0.8f), flag, !flag); } } } private void UpdateMenderRetreat(float dt, Character threat) { //IL_0050: 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_006f: 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_0035: Unknown result type (might be due to invalid IL or missing references) _menderRetreatTimer -= dt; if ((Object)(object)_menderRetreatThreat != (Object)(object)threat || _menderRetreatTimer <= 0f || HorizontalDistance(((Component)this).transform.position, _menderRetreatPoint) < 0.8f) { _menderRetreatThreat = threat; _menderRetreatPoint = SelectMenderRetreatPoint(threat); _menderRetreatTimer = 0.8f; } AimAt(threat); MoveToWithStamina(dt, _menderRetreatPoint, 0.5f, requestRun: false); } private Vector3 SelectMenderRetreatPoint(Character threat) { //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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: 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_00d5: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)this).transform.position - ((Component)threat).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = -((Component)threat).transform.forward; } val.y = 0f; ((Vector3)(ref val)).Normalize(); Vector3 val2 = ((Component)this).transform.position + val * 4f; GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); if ((Object)(object)followTarget != (Object)null && HorizontalDistance(val2, followTarget.transform.position) > 7f) { Vector3 val3 = followTarget.transform.position - ((Component)threat).transform.position; val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = val; } val2 = followTarget.transform.position + ((Vector3)(ref val3)).normalized * 4.9f; } return GroundedPoint(val2); } private void ResetMenderPositioning() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) _menderRetreatThreat = null; _menderRetreatTimer = 0f; _menderRetreatPoint = Vector3.zero; } private static Vector3 GroundedPoint(Vector3 point) { //IL_003c: 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_001f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.IsZoneLoaded(point)) { float solidHeight = ZoneSystem.instance.GetSolidHeight(point); if (solidHeight > -1000f) { point.y = solidHeight + 0.1f; } } return point; } private bool TryCastGreaterHeal() { if (_burstCooldown > 0f) { return false; } Character val = FindWoundedAlly(MercConfig.HealRange.Value, MercConfig.BurstHealThreshold.Value, skipRenewed: false); if ((Object)(object)val == (Object)null) { return false; } AimAt(val); BeginHealingPose(); int ownerEffectiveStage = GetOwnerEffectiveStage(); float num = MercConfig.BurstHealAmount.Value + (float)ownerEffectiveStage * MercConfig.BurstHealPerStage.Value; float num2 = MercConfig.BurstHealOverTimeAmount.Value + (float)ownerEffectiveStage * MercConfig.BurstHealOverTimePerStage.Value; int num3 = Mathf.Max(1, MercConfig.BurstHealDurationSeconds.Value); Merc.HealBurst(val, num, num3); _greaterHeals.Add(new GreaterHealOverTime { Target = val, TickTimer = 1f, TicksLeft = num3, Amount = num2 }); Merc.Callout(MercLocalization.Phrase("dnpc_speech_greater_heal")); LlmBrain.RecordEvent("greater-heal", Merc.GetName() + " used Greater Heal on " + val.GetHoverName() + ".", 90f); MercPlugin.Log($"{Merc.GetName()} cast Greater Heal for {num:0} initial HP plus " + $"{num2:0} HP/s for {num3}s (stage {ownerEffectiveStage})"); _burstCooldown = Mathf.Max(0.1f, MercConfig.BurstHealCooldown.Value); _healScanTimer = 0.4f; PersistHealCooldowns(); return true; } private bool TryCastRenewal() { if (_renewalCooldown > 0f) { return false; } Character val = FindWoundedAlly(MercConfig.HealRange.Value, MercConfig.HealTriggerFraction.Value, skipRenewed: true); if ((Object)(object)val == (Object)null) { return false; } AimAt(val); BeginHealingPose(); int ownerEffectiveStage = GetOwnerEffectiveStage(); int num = Mathf.Max(1, MercConfig.RenewalTickCount.Value); float num2 = Mathf.Max(0.25f, MercConfig.RenewalTickInterval.Value); float num3 = (float)num * num2; _renewals.Add(new Renewal { Target = val, TickTimer = 0.1f, TicksLeft = num, Amount = MercConfig.RenewalTickAmount.Value + (float)ownerEffectiveStage * MercConfig.RenewalTickPerStage.Value, DurationRemaining = num3, TickInterval = num2 }); Merc.BeginRenewalEffect(val, num3); Merc.Callout(MercLocalization.Phrase("dnpc_speech_renewal")); LlmBrain.RecordEvent("renewal", Merc.GetName() + " placed Renewal on " + val.GetHoverName() + ".", 90f); MercPlugin.Log($"{Merc.GetName()} cast Renewal for {_renewals[_renewals.Count - 1].Amount:0} HP x " + $"{_renewals[_renewals.Count - 1].TicksLeft} ticks (stage {ownerEffectiveStage})"); _renewalCooldown = Mathf.Max(0.1f, MercConfig.RenewalCooldown.Value); _healScanTimer = 0.4f; PersistHealCooldowns(); return true; } private void PersistHealCooldowns() { try { ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null && !((Object)(object)ZNet.instance == (Object)null)) { double timeSeconds = ZNet.instance.GetTimeSeconds(); val2.Set(ZdoRenewalReady, (long)(timeSeconds + (double)_renewalCooldown)); val2.Set(ZdoBurstReady, (long)(timeSeconds + (double)_burstCooldown)); } } catch { } } internal void RecoverPersistedStateFromZdo() { _campLife.Reset(Merc); try { ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null && !((Object)(object)ZNet.instance == (Object)null)) { double timeSeconds = ZNet.instance.GetTimeSeconds(); _renewalCooldown = Mathf.Max(0f, (float)((double)val2.GetLong(ZdoRenewalReady, 0L) - timeSeconds)); _burstCooldown = Mathf.Max(0f, (float)((double)val2.GetLong(ZdoBurstReady, 0L) - timeSeconds)); _healScanTimer = 0f; } } catch { } } private void BeginHealingPose() { Merc.BeginBareHandCast(); _healingPoseTimer = 1.25f; } private void UpdateHealingOverTime(float dt) { for (int num = _renewals.Count - 1; num >= 0; num--) { Renewal renewal = _renewals[num]; if ((Object)(object)renewal.Target == (Object)null || renewal.Target.IsDead()) { _renewals.RemoveAt(num); } else { renewal.DurationRemaining -= dt; renewal.TickTimer -= dt; while (renewal.TickTimer <= 0f && renewal.TicksLeft > 0) { Merc.HealRenewalTick(renewal.Target, renewal.Amount); renewal.TicksLeft--; renewal.TickTimer += renewal.TickInterval; } if (renewal.DurationRemaining <= 0f) { _renewals.RemoveAt(num); } } } for (int num2 = _greaterHeals.Count - 1; num2 >= 0; num2--) { GreaterHealOverTime greaterHealOverTime = _greaterHeals[num2]; if ((Object)(object)greaterHealOverTime.Target == (Object)null || greaterHealOverTime.Target.IsDead() || greaterHealOverTime.TicksLeft <= 0) { _greaterHeals.RemoveAt(num2); } else { greaterHealOverTime.TickTimer -= dt; while (greaterHealOverTime.TickTimer <= 0f && greaterHealOverTime.TicksLeft > 0) { Merc.HealGreaterTick(greaterHealOverTime.Target, greaterHealOverTime.Amount); greaterHealOverTime.TicksLeft--; greaterHealOverTime.TickTimer += 1f; } if (greaterHealOverTime.TicksLeft <= 0) { _greaterHeals.RemoveAt(num2); } } } } private Character FindWoundedAlly(float range, float threshold, bool skipRenewed) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) Character result = null; float num = Mathf.Clamp01(threshold); float num2 = float.MinValue; GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); Player employer = (((Object)(object)followTarget != (Object)null) ? followTarget.GetComponent() : null); foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || (!(allCharacter is Player) && !(allCharacter is Mercenary)) || Vector3.Distance(((Component)this).transform.position, ((Component)allCharacter).transform.position) > range || (allCharacter is Mercenary companion && !IsSameCompany(companion))) { continue; } Player val = (Player)(object)((allCharacter is Player) ? allCharacter : null); if ((val != null && !IsSupportedPlayer(val, employer)) || (skipRenewed && HasRenewal(allCharacter))) { continue; } float healthPercentage = allCharacter.GetHealthPercentage(); if (!(healthPercentage >= num)) { float num3 = HealingPriority(allCharacter, healthPercentage, employer); if (num3 > num2) { num2 = num3; result = allCharacter; } } } return result; } private bool IsSupportedPlayer(Player player, Player employer) { //IL_0023: 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) if ((Object)(object)player == (Object)null) { return false; } if ((Object)(object)employer != (Object)null) { if (!((Object)(object)player == (Object)(object)employer)) { return HorizontalDistance(((Component)player).transform.position, ((Component)employer).transform.position) <= 10f; } return true; } if ((Object)(object)Merc != (Object)null) { return Merc.IsAssignedTo(player, requireFollowing: false); } return false; } private float HealingPriority(Character candidate, float healthFraction, Player employer) { float num = (1f - Mathf.Clamp01(healthFraction)) * 100f; if ((Object)(object)candidate == (Object)(object)employer) { num += 12f; } if ((Object)(object)candidate == (Object)(object)Merc) { num += 8f; } if (candidate is Mercenary { Class: MercClass.Tank }) { num += 6f; } return num; } private bool HasRenewal(Character target) { foreach (Renewal renewal in _renewals) { if ((Object)(object)renewal.Target == (Object)(object)target && renewal.DurationRemaining > 0f) { return true; } } return false; } public bool BeginGuideTask(Player player, GuideTaskType task, BossTarget boss, Vector3 destination) { //IL_0029: 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_004e: 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_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Merc == (Object)null || (Object)(object)player == (Object)null || task == GuideTaskType.None || task == GuideTaskType.Cancel) { return false; } _guidePlayer = player; _guidePlayerId = ((Character)player).GetZDOID(); _guidePlayerPersistentId = player.GetPlayerID(); _guidePlayerName = player.GetPlayerName(); return BeginGuideTask(task, boss, destination, ((Component)player).transform.position, ((Component)player).transform.forward, player.GetPlayerName()); } internal bool BeginGuideTask(ServerAuthority.SenderPlayerState requester, GuideTaskType task, BossTarget boss, Vector3 destination) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0086: 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) //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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (requester == null || ((ZDOID)(ref requester.CharacterZdoId)).IsNone()) { return false; } _guidePlayer = requester.LivePlayer; _guidePlayerId = requester.CharacterZdoId; _guidePlayerPersistentId = requester.PlayerId; _guidePlayerName = requester.PlayerName; Vector3 playerForward = ((requester.PlayerZdo != null) ? (requester.PlayerZdo.GetRotation() * Vector3.forward) : (((Object)(object)requester.LivePlayer != (Object)null) ? ((Component)requester.LivePlayer).transform.forward : Vector3.forward)); return BeginGuideTask(task, boss, destination, requester.Position, playerForward, requester.PlayerName); } private bool BeginGuideTask(GuideTaskType task, BossTarget boss, Vector3 destination, Vector3 playerPosition, Vector3 playerForward, string playerName) { //IL_0042: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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) //IL_00a8: 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) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Merc == (Object)null || task == GuideTaskType.None || task == GuideTaskType.Cancel) { return false; } ResourceWorker.Cancel(this); if (_guideTask != GuideTaskType.None) { CancelGuideTask(null, announce: false); } CancelAmbientCamp(); _guideTask = task; _guidePlayerPosition = playerPosition; _guidePlayerForward = ((((Vector3)(ref playerForward)).sqrMagnitude > 0.01f) ? ((Vector3)(ref playerForward)).normalized : Vector3.forward); _guideDestination = destination; _guideRoute = ReadGuideRoute(); _guideRouteIndex = 0; if (_guideRoute != null && _guideRoute.Count > 0) { _guideDestination = _guideRoute[0]; } _guideCreatureKey = null; _guideCreatureLabel = null; _guideCreatureAnnounced = false; _guideCreatureKey = ((task == GuideTaskType.Custom) ? ReadGuideFindKey() : null); if (_guideCreatureKey != null && GuideFinder.ByKey(_guideCreatureKey) == null) { _guideCreatureKey = null; } _guideCreatureLabel = ((_guideCreatureKey != null && GuideFinder.ByKey(_guideCreatureKey) != null) ? GuideFinder.TargetLabelArgument(GuideFinder.ByKey(_guideCreatureKey)) : null); _guideCreatureAnnounced = false; _guideCreatureTarget = null; _guideCreatureHoldSaid = false; GuideFindTarget guideFindTarget = ((_guideCreatureKey != null) ? GuideFinder.ByKey(_guideCreatureKey) : null); if (guideFindTarget != null && guideFindTarget.Kind == GuideFindKind.Creature) { ActiveCreatureHuntKey = _guideCreatureKey; ActiveCreatureHuntOwner = this; } _guideBossStone = null; _guideBoss = boss; _guideScanTimer = 0f; _guideCalloutTimer = 18f; _guideWaitNagSaid = false; _bossStoneSearchStep = 0; _guideWaterBlockedTimer = 0f; _guidePlayerSeparatedTimer = 0f; _guideWaypointTimer = 0f; _guideHasLandWaypoint = false; _guideWaterWarningGiven = false; _guideProgressDistance = -1f; _guideProgressClock = 0f; _combatTarget = null; ((BaseAI)this).ResetPatrolPoint(); switch (task) { case GuideTaskType.BossStone: SelectNextBossStoneSearchPoint(playerPosition); break; case GuideTaskType.BossAltar: { if (GuideTasks.TryGetKnownBossAltarPin(_guideBoss, out var position)) { _guideDestination = position; } else if (!TryFindBossAltar(_guideBoss, playerPosition, out _guideDestination)) { Merc.Say(MercLocalization.Phrase("dnpc_speech_altar_not_ready", GuideTasks.BossArgument(_guideBoss))); CancelGuideTask(null, announce: false); return false; } break; } } MercPlugin.Log($"{Merc.GetName()} began guide task {task} ({boss}) for {playerName}"); LlmBrain.RecordEvent("guide", Merc.GetName() + " began " + GuideStatus + " for the player.", 5f); PersistGuideTask(); return true; } private void PersistGuideTask() { try { ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null && val.IsOwner()) { if (_guideTask == GuideTaskType.None) { val2.Set(ZdoGuideTask, ""); return; } string[] array = new string[17]; int guideTask = (int)_guideTask; array[0] = guideTask.ToString(CultureInfo.InvariantCulture); array[1] = "|"; guideTask = (int)_guideBoss; array[2] = guideTask.ToString(CultureInfo.InvariantCulture); array[3] = "|"; array[4] = _guidePlayerPersistentId.ToString(CultureInfo.InvariantCulture); array[5] = "|"; array[6] = ((ZDOID)(ref _guidePlayerId)).UserID.ToString(CultureInfo.InvariantCulture); array[7] = ":"; array[8] = ((ZDOID)(ref _guidePlayerId)).ID.ToString(CultureInfo.InvariantCulture); array[9] = "|"; array[10] = (_guidePlayerName ?? "").Replace('|', '/'); array[11] = "|"; array[12] = _guideDestination.x.ToString(CultureInfo.InvariantCulture); array[13] = ","; array[14] = _guideDestination.y.ToString(CultureInfo.InvariantCulture); array[15] = ","; array[16] = _guideDestination.z.ToString(CultureInfo.InvariantCulture); string text = string.Concat(array); val2.Set(ZdoGuideTask, text); } } catch { } } internal void RecoverPersistedGuideTask() { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) if (_guideTask != GuideTaskType.None) { return; } try { ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null) { return; } string text = val2.GetString(ZdoGuideTask, ""); if (string.IsNullOrEmpty(text)) { return; } string[] array = text.Split(new char[1] { '|' }); if (array.Length < 6 || !int.TryParse(array[0], out var result) || !Enum.IsDefined(typeof(GuideTaskType), result) || result == 0 || !int.TryParse(array[1], out var result2) || !Enum.IsDefined(typeof(BossTarget), result2) || !long.TryParse(array[2], out var result3) || result3 == 0L) { return; } string[] array2 = array[3].Split(new char[1] { ':' }); if (array2.Length == 2 && long.TryParse(array2[0], out var result4) && uint.TryParse(array2[1], out var result5)) { string[] array3 = array[5].Split(new char[1] { ',' }); if (array3.Length == 3 && float.TryParse(array3[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result6) && float.TryParse(array3[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result7) && float.TryParse(array3[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result8)) { _guideTask = (GuideTaskType)result; _guideBoss = (BossTarget)result2; _guidePlayerPersistentId = result3; _guidePlayerId = new ZDOID(result4, result5); _guidePlayerName = array[4]; _guidePlayer = null; _guideDestination = new Vector3(result6, result7, result8); _guideCreatureKey = null; _guideCreatureLabel = null; _guideCreatureAnnounced = false; _guideCreatureTarget = null; _guideCreatureHoldSaid = false; _guideBossStone = null; _guideScanTimer = 0f; _guideCalloutTimer = 12f; _guideWaterBlockedTimer = 0f; _guidePlayerSeparatedTimer = 0f; _guideWaypointTimer = 0f; _guideHasLandWaypoint = false; _guideWaterWarningGiven = false; _guideProgressDistance = -1f; _guideProgressClock = 0f; _combatTarget = null; MercPlugin.Log($"{Merc.GetName()} resumed persisted guide task {_guideTask} for {_guidePlayerName}."); } } } catch { } } public void CancelGuideTask(Player requestingPlayer, bool announce) { //IL_005e: 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_007d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)requestingPlayer != (Object)null && ((Object)(object)Merc == (Object)null || !Merc.IsAssignedTo(requestingPlayer, requireFollowing: false))) { return; } ResourceWorker.Cancel(this); if (_guideTask == GuideTaskType.None) { PersistGuideTask(); return; } string guideStatus = GuideStatus; _guideTask = GuideTaskType.None; _guidePlayer = null; _guidePlayerId = default(ZDOID); _guidePlayerPersistentId = 0L; _guidePlayerName = ""; _guideDestination = Vector3.zero; _guideRoute = null; _guideRouteIndex = 0; _guideCreatureKey = null; _guideCreatureLabel = null; _guideCreatureAnnounced = false; _guideCreatureTarget = null; _guideCreatureHoldSaid = false; if ((Object)(object)ActiveCreatureHuntOwner == (Object)(object)this) { ActiveCreatureHuntKey = null; ActiveCreatureHuntOwner = null; } _guideBossStone = null; _guideBoss = BossTarget.None; _guideScanTimer = 0f; _guideCalloutTimer = 0f; _bossStoneSearchStep = 0; _guideWaterBlockedTimer = 0f; _guidePlayerSeparatedTimer = 0f; _guideWaypointTimer = 0f; _guideHasLandWaypoint = false; _guideWaterWarningGiven = false; _guideProgressDistance = -1f; _guideProgressClock = 0f; ResetNavigationRecovery(); ClearLatchedMovement(); PersistGuideTask(); if (announce && (Object)(object)Merc != (Object)null) { Merc.Say(MercLocalization.Phrase("dnpc_speech_stop_guiding")); } if ((Object)(object)Merc != (Object)null) { MercPlugin.Log(Merc.GetName() + " stopped " + guideStatus); } } private bool TryGetGuidePlayer(out Player player, out Vector3 position, out Vector3 forward, out Biome biome, out bool dead) { //IL_0078: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_009e: Expected I4, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: 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_0163: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) player = _guidePlayer; if ((Object)(object)player == (Object)null && !((ZDOID)(ref _guidePlayerId)).IsNone()) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(_guidePlayerId) : null); player = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)player != (Object)null) { _guidePlayer = player; } } if ((Object)(object)player != (Object)null) { position = ((Component)player).transform.position; forward = ((Component)player).transform.forward; biome = (Biome)(int)player.GetCurrentBiome(); dead = ((Character)player).IsDead(); return true; } if (!ServerAuthority.TryFindConnectedPeerByPlayerId(_guidePlayerPersistentId, out var _, out var _)) { position = Vector3.zero; forward = Vector3.forward; biome = (Biome)0; dead = true; return false; } ZDO val2 = ((ZDOMan.instance != null && !((ZDOID)(ref _guidePlayerId)).IsNone()) ? ZDOMan.instance.GetZDO(_guidePlayerId) : null); if (val2 == null || !val2.IsValid()) { position = Vector3.zero; forward = Vector3.forward; biome = (Biome)0; dead = true; return false; } position = val2.GetPosition(); forward = val2.GetRotation() * Vector3.forward; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } biome = (Biome)((WorldGenerator.instance != null) ? ((int)WorldGenerator.instance.GetBiome(position)) : 0); dead = val2.GetBool(ZDOVars.s_dead, false) || val2.GetFloat(ZDOVars.s_health, 1f) <= 0f; return true; } private bool UpdateGuideTask(float dt) { //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04b7: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_03f7: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_049c: Unknown result type (might be due to invalid IL or missing references) //IL_04a1: Unknown result type (might be due to invalid IL or missing references) //IL_0484: Unknown result type (might be due to invalid IL or missing references) //IL_0438: Unknown result type (might be due to invalid IL or missing references) //IL_043e: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0677: Unknown result type (might be due to invalid IL or missing references) //IL_067d: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_070e: Unknown result type (might be due to invalid IL or missing references) //IL_0713: Unknown result type (might be due to invalid IL or missing references) //IL_071d: Unknown result type (might be due to invalid IL or missing references) //IL_0722: Unknown result type (might be due to invalid IL or missing references) //IL_06a9: Unknown result type (might be due to invalid IL or missing references) //IL_06af: Unknown result type (might be due to invalid IL or missing references) //IL_0853: Unknown result type (might be due to invalid IL or missing references) //IL_0859: Unknown result type (might be due to invalid IL or missing references) //IL_07e4: Unknown result type (might be due to invalid IL or missing references) //IL_07ea: Unknown result type (might be due to invalid IL or missing references) //IL_0890: Unknown result type (might be due to invalid IL or missing references) //IL_0896: Unknown result type (might be due to invalid IL or missing references) //IL_086b: Unknown result type (might be due to invalid IL or missing references) //IL_0871: Unknown result type (might be due to invalid IL or missing references) //IL_064b: Unknown result type (might be due to invalid IL or missing references) //IL_08d4: Unknown result type (might be due to invalid IL or missing references) //IL_0660: Unknown result type (might be due to invalid IL or missing references) //IL_0666: Unknown result type (might be due to invalid IL or missing references) //IL_05c2: Unknown result type (might be due to invalid IL or missing references) //IL_08fa: Unknown result type (might be due to invalid IL or missing references) //IL_0900: Unknown result type (might be due to invalid IL or missing references) //IL_092f: Unknown result type (might be due to invalid IL or missing references) //IL_05d7: Unknown result type (might be due to invalid IL or missing references) //IL_05dd: Unknown result type (might be due to invalid IL or missing references) //IL_09e7: Unknown result type (might be due to invalid IL or missing references) //IL_09ed: Unknown result type (might be due to invalid IL or missing references) //IL_0a15: Unknown result type (might be due to invalid IL or missing references) //IL_0a00: Unknown result type (might be due to invalid IL or missing references) //IL_0a06: Unknown result type (might be due to invalid IL or missing references) //IL_0957: Unknown result type (might be due to invalid IL or missing references) //IL_095d: Unknown result type (might be due to invalid IL or missing references) //IL_0a2c: Unknown result type (might be due to invalid IL or missing references) if (_guideTask == GuideTaskType.None) { return false; } if (!TryGetGuidePlayer(out var player, out _guidePlayerPosition, out _guidePlayerForward, out _guidePlayerBiome, out var dead) || dead || (Object)(object)Merc == (Object)null || ((Character)Merc).IsDead() || !Merc.IsAssignedTo(_guidePlayerPersistentId, _guidePlayerName, requireFollowing: false)) { CancelGuideTask(null, announce: false); return false; } GuideFindTarget guideFindTarget = ((_guideTask == GuideTaskType.Custom && !string.IsNullOrEmpty(_guideCreatureKey)) ? GuideFinder.ByKey(_guideCreatureKey) : null); if (_guideTask == GuideTaskType.Custom && guideFindTarget != null && guideFindTarget.Kind == GuideFindKind.Creature) { _guideScanTimer -= dt; if (_guideScanTimer <= 0f) { _guideScanTimer = 3f; if ((Object)(object)_guideCreatureTarget != (Object)null) { if (!Object.op_Implicit((Object)(object)_guideCreatureTarget) || _guideCreatureTarget.IsDead()) { if (Object.op_Implicit((Object)(object)_guideCreatureTarget) && _guideCreatureTarget.IsDead()) { Merc.Say(_guideCreatureAnnounced ? MercLocalization.Phrase("dnpc_speech_hunt_complete_seen", _guideCreatureLabel) : MercLocalization.Phrase("dnpc_speech_hunt_complete", _guideCreatureLabel)); try { ((BaseAI)this).SetAlerted(false); } catch { } CancelGuideTask(null, announce: false); return true; } _guideCreatureTarget = null; } else { _guideDestination = ((Component)_guideCreatureTarget).transform.position; } } if ((Object)(object)_guideCreatureTarget == (Object)null) { Character val = GuideFinder.FindNearestLoadedCreature(_guideCreatureKey, _guidePlayerPosition, 250f); if ((Object)(object)val != (Object)null) { _guideCreatureTarget = val; _guideDestination = ((Component)val).transform.position; if (!_guideCreatureAnnounced) { _guideCreatureAnnounced = true; Merc.Say(MercLocalization.Phrase("dnpc_speech_hunt_spotted", _guideCreatureLabel)); } } } } if (HorizontalDistance(_guidePlayerPosition, _guideDestination) <= 10f && (Object)(object)_guideCreatureTarget == (Object)null) { SelectNextCreatureSearchPoint(_guidePlayerPosition); } if ((Object)(object)_guideCreatureTarget != (Object)null && Merc.Class != MercClass.Archer && HorizontalDistance(((Component)this).transform.position, ((Component)_guideCreatureTarget).transform.position) <= 30f) { if (!_guideCreatureHoldSaid) { _guideCreatureHoldSaid = true; Merc.Say(MercLocalization.Phrase("dnpc_speech_hunt_archer", _guideCreatureLabel)); } ((BaseAI)this).StopMoving(); ((BaseAI)this).LookAt(((Component)_guideCreatureTarget).transform.position + Vector3.up * 1.5f); return true; } } else if (_guideTask == GuideTaskType.BossStone) { if (GuideTasks.TryGetKnownBossAltarPin(_guideBoss, out var position)) { _guideTask = GuideTaskType.BossAltar; _guideBossStone = null; _guideDestination = position; Merc.Say(MercLocalization.Phrase("dnpc_speech_altar_marked", GuideTasks.BossArgument(_guideBoss))); LlmBrain.RecordEvent("boss-altar-revealed", "The player's map shows " + GuideTasks.BossName(_guideBoss) + "'s altar; the guide advanced from the runestone to the altar.", 10f); } if (_guideTask == GuideTaskType.BossStone) { _guideScanTimer -= dt; if (_guideScanTimer <= 0f) { _guideScanTimer = 4f; Vegvisir val2 = FindClosestLoadedBossStone(_guideBoss, _guidePlayerPosition, 260f); if ((Object)(object)val2 != (Object)null) { bool num = (Object)(object)_guideBossStone != (Object)(object)val2; _guideBossStone = val2; _guideDestination = ((Component)val2).transform.position; if (num) { Merc.Say(MercLocalization.Phrase("dnpc_speech_runestone_found", GuideTasks.BossArgument(_guideBoss))); } } else if ((Object)(object)_guideBossStone == (Object)null && HorizontalDistance(_guidePlayerPosition, _guideDestination) <= 12f) { SelectNextBossStoneSearchPoint(_guidePlayerPosition); } } if ((Object)(object)_guideBossStone != (Object)null) { if (!((Component)_guideBossStone).gameObject.activeInHierarchy) { _guideBossStone = null; SelectNextBossStoneSearchPoint(_guidePlayerPosition); } else { _guideDestination = ((Component)_guideBossStone).transform.position; } } } } if (_guideTask == GuideTaskType.None) { return true; } float remaining = HorizontalDistance(_guidePlayerPosition, _guideDestination); float num2 = ((_guideTask == GuideTaskType.BossStone && (Object)(object)_guideBossStone == (Object)null) ? 10f : 8f); if (remaining <= num2 && !TryAdvanceRouteWaymark(ref remaining)) { if (_guideTask == GuideTaskType.Tombstone) { Merc.Say(MercLocalization.Phrase("dnpc_speech_tombstone_arrival")); CancelGuideTask(null, announce: false); return true; } if (_guideTask == GuideTaskType.BossAltar) { Merc.Say(MercLocalization.Phrase("dnpc_speech_altar_arrival", GuideTasks.BossArgument(_guideBoss))); CancelGuideTask(null, announce: false); return true; } if ((Object)(object)_guideBossStone != (Object)null) { Merc.Say(MercLocalization.Phrase("dnpc_speech_runestone_arrival", GuideTasks.BossArgument(_guideBoss))); CancelGuideTask(null, announce: false); return true; } if (_guideTask == GuideTaskType.Custom) { if (guideFindTarget == null || guideFindTarget.Kind != GuideFindKind.Creature) { Merc.Say((_guideCreatureLabel != null) ? MercLocalization.Phrase("dnpc_speech_resource_arrival", _guideCreatureLabel) : MercLocalization.Phrase("dnpc_speech_arrived")); try { ((BaseAI)this).SetAlerted(false); } catch { } CancelGuideTask(null, announce: false); return true; } SelectNextCreatureSearchPoint(_guidePlayerPosition); if (_guideTask == GuideTaskType.None) { return true; } remaining = HorizontalDistance(_guidePlayerPosition, _guideDestination); } else if (_guideTask == GuideTaskType.BossStone) { SelectNextBossStoneSearchPoint(_guidePlayerPosition); if (_guideTask == GuideTaskType.None) { return true; } remaining = HorizontalDistance(_guidePlayerPosition, _guideDestination); } } if (HorizontalDistance(((Component)this).transform.position, _guidePlayerPosition) > 24f) { if ((Object)(object)player != (Object)null && !Mercenary.IsPlayerWaterborne(player) && CachedHasWaterBarrierBetween(((Component)this).transform.position, _guidePlayerPosition)) { _guidePlayerSeparatedTimer += dt; if (_guidePlayerSeparatedTimer >= 2f && Merc.TryCatchUpToPlayer(player, "guide crossed water")) { _guidePlayerSeparatedTimer = 0f; return true; } } else { _guidePlayerSeparatedTimer = 0f; } ((BaseAI)this).StopMoving(); ((BaseAI)this).LookAt(_guidePlayerPosition + Vector3.up * 1.5f); _guideCalloutTimer -= dt; if (_guideCalloutTimer <= 0f && !_guideWaitNagSaid) { _guideWaitNagSaid = true; Merc.Callout(MercLocalization.Phrase("dnpc_speech_wait_for_player")); } return true; } _guidePlayerSeparatedTimer = 0f; _guideWaitNagSaid = false; if (_guideProgressDistance < 0f || remaining <= _guideProgressDistance - 12f) { _guideProgressDistance = remaining; _guideProgressClock = 0f; } else if ((Object)(object)_guideCreatureTarget == (Object)null && (_guideProgressClock += dt) >= 40f) { Merc.Say(CachedHasWaterBarrierBetween(_guidePlayerPosition, _guideDestination, 18f) ? MercLocalization.Phrase("dnpc_speech_water_defeat") : MercLocalization.Phrase("dnpc_speech_route_failed")); CancelGuideTask(null, announce: false); return true; } _guideWaypointTimer -= dt; if (!_guideHasLandWaypoint || _guideWaypointTimer <= 0f || HorizontalDistance(((Component)this).transform.position, _guideLandWaypoint) < 4f || HorizontalDistance(_guidePlayerPosition, _guideLandWaypoint) > MaxGuideSwimWidth + 10f) { _guideHasLandWaypoint = TrySelectLandLeadPoint(_guidePlayerPosition, _guideDestination, Mathf.Clamp(remaining, 12f, 20f), out _guideLandWaypoint); _guideWaypointTimer = 1f; } if (!_guideHasLandWaypoint) { if (TryMoveWithNavigationRecovery(dt, _guideDestination, 0.75f, requestRun: false, requestWalk: false)) { _guideWaterBlockedTimer = 0f; return true; } LogWaterDiag($"guide found no land lead point, destination {HorizontalDistance(_guidePlayerPosition, _guideDestination):F0}m out"); _guideWaterBlockedTimer += dt; ((BaseAI)this).StopMoving(); ((BaseAI)this).LookAt(_guideDestination); if (_guideWaterBlockedTimer >= 2f && !_guideWaterWarningGiven) { _guideWaterWarningGiven = true; string text = (CachedHasWaterBarrierBetween(_guidePlayerPosition, _guideDestination, 18f) ? MercLocalization.Phrase("dnpc_speech_water_blocks") : MercLocalization.Phrase("dnpc_speech_no_safe_path")); Merc.Say(text); LlmBrain.RecordEvent("guide-water", MercLocalization.Resolve(text), 30f); CancelGuideTask(null, announce: false); } return true; } _guideWaterBlockedTimer = 0f; bool requestRun = _guideCreatureAnnounced && remaining <= 40f; if (CachedCanUseShortWaterCrossing(((Component)this).transform.position, _guideLandWaypoint, allowWetDestination: false) || CanUseGuideSwimCrossing(((Component)this).transform.position, _guideLandWaypoint)) { MoveAcrossWaterWithNavigationRecovery(dt, _guideLandWaypoint, 1.5f, requestRun); } else { MoveToWithNavigationRecovery(dt, _guideLandWaypoint, 1.5f, requestRun); } return true; } private string ReadGuideFindKey() { try { ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); return (val2 != null) ? val2.GetString("merc_guideFind", "") : null; } catch { return null; } } private List ReadGuideRoute() { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) try { ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); string text = ((val2 != null) ? val2.GetString("merc_guideRoute", "") : null); if (string.IsNullOrEmpty(text)) { return null; } List list = new List(); string[] array = text.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ',' }); if (array2.Length >= 2 && float.TryParse(array2[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetSolidHeight(new Vector3(result, 0f, result2)) : 0f); if (num <= -1000f) { num = 0f; } list.Add(new Vector3(result, num + 0.5f, result2)); } } return (list.Count > 1) ? list : null; } catch { return null; } } private bool TryAdvanceRouteWaymark(ref float remaining) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (_guideRoute == null || _guideRouteIndex >= _guideRoute.Count - 1) { return false; } _guideRouteIndex++; _guideDestination = _guideRoute[_guideRouteIndex]; _guideProgressDistance = -1f; _guideProgressClock = 0f; _guideWaypointTimer = 0f; _guideHasLandWaypoint = false; ResetNavigationRecovery(); remaining = HorizontalDistance(_guidePlayerPosition, _guideDestination); return true; } private void SelectNextCreatureSearchPoint(Vector3 playerPosition) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 12; i++) { float num = Random.Range(0f, (float)Math.PI * 2f); float num2 = Random.Range(50f, 140f); Vector3 val = playerPosition + new Vector3(Mathf.Sin(num) * num2, 0f, Mathf.Cos(num) * num2); float num3 = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetSolidHeight(val) : (-10000f)); if (num3 > -10000f) { val.y = num3 + 0.5f; _guideDestination = val; return; } } _guideDestination = playerPosition + new Vector3(Random.insideUnitCircle.x, 0f, Random.insideUnitCircle.y) * 60f; } private void SelectNextBossStoneSearchPoint(Vector3 playerPosition) { //IL_0039: 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_0067: 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_006a: 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_0055: 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_0044: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //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_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_012a: 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_0143: 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) if (_guideBoss == BossTarget.None) { Merc.Say(MercLocalization.Phrase("dnpc_speech_choose_forsaken")); CancelGuideTask(null, announce: false); return; } if (_guideBoss == BossTarget.Eikthyr && TryFindGeneratedLocation("StartTemple", playerPosition, out var destination)) { _guideDestination = destination; return; } WorldGenerator instance = WorldGenerator.instance; if (instance == null) { _guideDestination = playerPosition; return; } Biome val = GuideTasks.BossBiome(_guideBoss); if (instance.GetBiome(playerPosition) != val) { if (TryFindNearestBiome(instance, playerPosition, val, out var destination2)) { _guideDestination = destination2; return; } Merc.Say(MercLocalization.Phrase("dnpc_speech_biome_not_found", val)); CancelGuideTask(null, announce: false); return; } for (int i = 0; i < 24; i++) { int num = _bossStoneSearchStep++; float num2 = (float)num * 137.5f * ((float)Math.PI / 180f); float num3 = 55f + (float)(num % 5) * 24f; Vector3 val2 = playerPosition + new Vector3(Mathf.Sin(num2), 0f, Mathf.Cos(num2)) * num3; if (instance.GetBiome(val2) == val) { val2.y = instance.GetHeight(val2.x, val2.z); _guideDestination = val2; return; } } _guideDestination = playerPosition + _guidePlayerForward * 55f; } private static Vegvisir FindClosestLoadedBossStone(BossTarget boss, Vector3 origin, float range) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Vegvisir result = null; float num = range; Vegvisir[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Vegvisir val in array) { if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy || val.m_locations == null) { continue; } bool flag = false; foreach (VegvisrLocation location in val.m_locations) { if (location != null && GuideTasks.MatchesBossLocation(boss, location.m_locationName)) { flag = true; break; } } if (flag) { float num2 = HorizontalDistance(origin, ((Component)val).transform.position); if (!(num2 >= num)) { num = num2; result = val; } } } return result; } private static bool TryFindBossAltar(BossTarget boss, Vector3 origin, out Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || boss == BossTarget.None || !instance.LocationsGenerated) { return false; } float num = float.MaxValue; foreach (LocationInstance location in instance.GetLocationList()) { string generatedLocationName = GetGeneratedLocationName(location); if (GuideTasks.MatchesBossLocation(boss, generatedLocationName)) { float num2 = HorizontalDistance(origin, location.m_position); if (!(num2 >= num)) { num = num2; destination = location.m_position; } } } return num < float.MaxValue; } private static bool TryFindGeneratedLocation(string requiredName, Vector3 origin, out Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !instance.LocationsGenerated) { return false; } float num = float.MaxValue; foreach (LocationInstance location in instance.GetLocationList()) { if (string.Equals(GetGeneratedLocationName(location), requiredName, StringComparison.OrdinalIgnoreCase)) { float num2 = HorizontalDistance(origin, location.m_position); if (!(num2 >= num)) { num = num2; destination = location.m_position; } } } return num < float.MaxValue; } private static string GetGeneratedLocationName(LocationInstance instance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (instance.m_location == null) { return ""; } try { string name = instance.m_location.m_prefab.Name; if (!string.IsNullOrEmpty(name)) { return name; } } catch { } if (!string.IsNullOrEmpty(instance.m_location.m_prefabName)) { return instance.m_location.m_prefabName; } if (!string.IsNullOrEmpty(instance.m_location.m_name)) { return instance.m_location.m_name; } return ""; } private static bool TryFindNearestBiome(WorldGenerator generator, Vector3 origin, Biome biome, out Vector3 destination) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0032: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) for (float num = 100f; num <= 3000f; num += 100f) { for (int i = 0; i < 16; i++) { float num2 = (float)i * ((float)Math.PI / 8f); Vector3 val = origin + new Vector3(Mathf.Sin(num2), 0f, Mathf.Cos(num2)) * num; if (generator.GetBiome(val) == biome) { val.y = generator.GetHeight(val.x, val.z); destination = val; return true; } } } destination = Vector3.zero; return false; } private void ClearLatchedMovement() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) try { ((BaseAI)this).StopMoving(); } catch { } if (!((Object)(object)Merc == (Object)null)) { CancelBowDraw(); ((Character)Merc).SetMoveDir(Vector3.zero); ((Character)Merc).SetRun(false); } } public void ResetMovementAfterTeleport() { ClearLatchedMovement(); _followWaterBlockedTimer = 0f; _guideWaterBlockedTimer = 0f; _guidePlayerSeparatedTimer = 0f; _guideWaypointTimer = 0f; _guideHasLandWaypoint = false; ResetNavigationRecovery(); } private static float HorizontalDistance(Vector3 left, Vector3 right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = left.x - right.x; float num2 = left.z - right.z; return Mathf.Sqrt(num * num + num2 * num2); } private bool TrySelectLandLeadPoint(Vector3 origin, Vector3 destination, float step, out Vector3 leadPoint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_00b2: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) leadPoint = Vector3.zero; Vector3 val = destination - origin; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.1f) { return false; } val /= magnitude; step = Mathf.Clamp(step, 2f, 20f); if (!TryGetDryGround(origin + val * Mathf.Clamp(magnitude, 12f, 30f), out var grounded)) { grounded = FindFarBankAlong(origin, val, magnitude); } if (grounded != Vector3.zero && (CachedCanUseShortWaterCrossing(((Component)this).transform.position, grounded, allowWetDestination: false) || CanUseGuideSwimCrossing(((Component)this).transform.position, grounded))) { leadPoint = grounded; return true; } float num = float.MinValue; float[] obj = new float[9] { 0f, -30f, 30f, -60f, 60f, -90f, 90f, -120f, 120f }; int num2 = 0; float[] array = obj; foreach (float num3 in array) { Vector3 val2 = Quaternion.Euler(0f, num3, 0f) * val; Vector3 grounded2 = origin + val2 * step; if (!TryGetDryGround(grounded2, out grounded2) || num2 >= 4) { continue; } num2++; if (!HavePathSafe(grounded2) && !CachedCanUseShortWaterCrossing(((Component)this).transform.position, grounded2, allowWetDestination: false)) { continue; } float num4 = magnitude - HorizontalDistance(grounded2, destination); if (!(num4 < -1.5f)) { float num5 = num4 * 5f - Mathf.Abs(num3) * 0.02f - HorizontalDistance(((Component)this).transform.position, grounded2) * 0.05f; if (!(num5 <= num)) { num = num5; leadPoint = grounded2; } } } return num > float.MinValue; } private static Vector3 FindFarBankAlong(Vector3 origin, Vector3 direction, float remaining) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(remaining, MaxGuideSwimWidth + 8f); for (float num2 = 12f; num2 <= num; num2 += 4f) { Vector3 val = origin + direction * num2; if (!IsWetAtAnyRange(val) && TryGetDryGround(val, out var grounded)) { return grounded; } } return Vector3.zero; } private static bool TryGetDryGround(Vector3 candidate, out Vector3 grounded) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) grounded = candidate; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !instance.IsZoneLoaded(candidate)) { return false; } float solidHeight = instance.GetSolidHeight(candidate); if (solidHeight <= -1000f) { return false; } WaterVolume val = null; float waterLevel = Floating.GetWaterLevel(candidate, ref val); if ((Object)(object)val != (Object)null && waterLevel > solidHeight + 0.35f) { return false; } grounded.y = solidHeight + 0.1f; return true; } private static bool HasWaterBarrierBetween(Vector3 start, Vector3 end, float maxCheckDistance = 40f) { //IL_0011: 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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0067: 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_0086: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return false; } float num = HorizontalDistance(start, end); if (num < 2f) { return false; } float num2 = Mathf.Min(num, Mathf.Max(2f, maxCheckDistance)); int num3 = Mathf.Clamp(Mathf.CeilToInt(num2 / 2f), 1, 20); for (int i = 1; i <= num3; i++) { float num4 = num2 * (float)i / (float)num3 / num; Vector3 val = Vector3.Lerp(start, end, num4); if (!instance.IsZoneLoaded(val)) { continue; } float solidHeight = instance.GetSolidHeight(val); if (!(solidHeight <= -1000f)) { WaterVolume val2 = null; float waterLevel = Floating.GetWaterLevel(val, ref val2); if ((Object)(object)val2 != (Object)null && waterLevel > solidHeight + 0.45f) { return true; } } } return false; } private bool CachedProbe(string key, Func evaluate) { float time = Time.time; if (_probeCache.TryGetValue(key, out var value) && value.ExpiresAt > time) { return value.Value; } if (_probeCache.Count > 48) { _probeCache.Clear(); } bool flag = evaluate(); _probeCache[key] = new ProbeResult { ExpiresAt = time + 0.3f, Value = flag }; return flag; } private static string ProbeKey(string prefix, Vector3 a, Vector3 b, float extra) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0075: Unknown result type (might be due to invalid IL or missing references) return prefix + "|" + (long)(a.x * 2f) + "," + (long)(a.z * 2f) + "|" + (long)(b.x * 2f) + "," + (long)(b.z * 2f) + "|" + extra.ToString("0"); } private bool CachedHasWaterBarrierBetween(Vector3 start, Vector3 end, float maxCheckDistance = 40f) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) return CachedProbe(ProbeKey("water", start, end, maxCheckDistance), () => HasWaterBarrierBetween(start, end, maxCheckDistance)); } private bool CachedCanUseShortWaterCrossing(Vector3 start, Vector3 end, bool allowWetDestination) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) return CachedProbe(ProbeKey("cross", start, end, allowWetDestination ? 1f : 0f), () => CanUseShortWaterCrossing(start, end, allowWetDestination)); } private static bool CanUseShortWaterCrossing(Vector3 start, Vector3 end, bool allowWetDestination) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_00f2: Unknown result type (might be due to invalid IL or missing references) float num = HorizontalDistance(start, end); if (num < 1f || num > 30f) { return false; } int num2 = Mathf.Clamp(Mathf.CeilToInt(num / 0.5f), 2, 48); float num3 = 0f; float num4 = 0f; float num5 = 0f; float num6 = 0f; bool flag = false; bool flag2 = false; for (int i = 0; i <= num2; i++) { Vector3 point = Vector3.Lerp(start, end, (float)i / (float)num2); if (IsDeepWaterAt(point)) { flag = true; flag2 = true; num5 += num / (float)num2; num6 += num / (float)num2; num3 = Mathf.Max(num3, num5); num4 = Mathf.Max(num4, num6); } else if (IsWetAt(point)) { flag = true; num6 = 0f; num5 += num / (float)num2; num3 = Mathf.Max(num3, num5); } else { num5 = 0f; num6 = 0f; } } if (!flag) { return false; } if (flag2 && num4 > 10.5f) { return false; } if (!allowWetDestination) { return !IsDeepWaterAt(end); } return true; } internal static float GeneratedWaterLevel() { if (_generatedWaterLevel > float.MinValue) { return _generatedWaterLevel; } float num = 30f; try { FieldInfo field = typeof(ZoneSystem).GetField("m_waterLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((Object)(object)ZoneSystem.instance != (Object)null && field != null && field.GetValue(ZoneSystem.instance) is float num2) { num = num2; } } catch { } _generatedWaterLevel = num; return num; } internal static bool IsWetGeneratedAt(Vector3 point) { //IL_000c: 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) WorldGenerator instance = WorldGenerator.instance; if (instance == null) { return false; } return instance.GetHeight(point.x, point.z) < GeneratedWaterLevel() - 0.1f; } internal static bool IsWetAtAnyRange(Vector3 point) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance != (Object)null && instance.IsZoneLoaded(point)) { return IsWetAt(point); } return IsWetGeneratedAt(point); } private static bool CanUseGuideSwimCrossing(Vector3 start, Vector3 end) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0052: Unknown result type (might be due to invalid IL or missing references) float num = HorizontalDistance(start, end); if (num < 1f || num > 75f) { return false; } if (IsDeepWaterAt(end)) { return false; } int num2 = Mathf.CeilToInt(num / 0.5f); if (num2 < 2) { return false; } float num3 = num / (float)num2; float num4 = 0f; bool flag = false; for (int i = 1; i < num2; i++) { if (IsWetAt(Vector3.Lerp(start, end, (float)i / (float)num2))) { flag = true; num4 += num3; } } if (flag) { return num4 <= 60f; } return false; } internal static bool IsWetAt(Vector3 point) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !instance.IsZoneLoaded(point)) { return false; } float solidHeight = instance.GetSolidHeight(point); if (solidHeight <= -1000f) { return false; } WaterVolume val = null; float waterLevel = Floating.GetWaterLevel(point, ref val); if ((Object)(object)val != (Object)null) { return waterLevel > solidHeight + 0.1f; } return false; } private static void LogWaterDiag(string message) { if (!(Time.time < _nextWaterDiagTime)) { _nextWaterDiagTime = Time.time + 5f; MercPlugin.Log("Water diag: " + message); } } internal static bool IsDeepWaterAt(Vector3 point) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !instance.IsZoneLoaded(point)) { return false; } float solidHeight = instance.GetSolidHeight(point); if (solidHeight <= -1000f) { return false; } WaterVolume val = null; float waterLevel = Floating.GetWaterLevel(point, ref val); if ((Object)(object)val != (Object)null) { return waterLevel > solidHeight + 0.45f; } return false; } internal bool TestLandPath(Vector3 target) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return HavePathSafe(target); } private bool HavePathSafe(Vector3 target) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) return CachedProbe(ProbeKey("path", ((Component)this).transform.position, target, 0f), delegate { //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { return ((BaseAI)this).HavePath(target); } catch { return false; } }); } private bool HoldPositionForPlayerWaterTravel() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_00a7: 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_00b6: 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) GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); Player val = (((Object)(object)followTarget != (Object)null) ? followTarget.GetComponent() : null); if ((Object)(object)val == (Object)null || !Mercenary.IsPlayerWaterborne(val)) { return false; } if ((Object)(object)Mercenary.GetPlayerShip(val) == (Object)null && CachedCanUseShortWaterCrossing(((Component)this).transform.position, ((Component)val).transform.position, allowWetDestination: true)) { return false; } ((BaseAI)this).StopMoving(); ((Character)Merc).SetMoveDir(Vector3.zero); ((Character)Merc).SetRun(false); if (Vector3.Distance(((Component)this).transform.position, ((Component)val).transform.position) < 12f) { ((BaseAI)this).LookAt(((Component)val).transform.position + Vector3.up * 1.5f); } return true; } private bool WithinPlayerLeash(Character target) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (TryGetPreciseHoldAnchor(out var anchor)) { if ((Object)(object)target != (Object)null) { return HorizontalDistance(anchor, ((Component)target).transform.position) <= 20f; } return false; } GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); if ((Object)(object)followTarget == (Object)null) { return true; } float num = ((_guideTask != GuideTaskType.None) ? 15f : 20f); if ((Object)(object)target != (Object)null && Vector3.Distance(followTarget.transform.position, ((Component)target).transform.position) > num) { return false; } return Vector3.Distance(followTarget.transform.position, ((Component)this).transform.position) <= 26f; } private Character AcquireCombatTarget(float range) { Character val = ResolveContextTarget(); if ((Object)(object)val != (Object)null && WithinPlayerLeash(val)) { _combatTarget = val; CancelAmbientCamp(); return val; } bool flag = IsValidTarget(_combatTarget) && CombatAwarenessDistance(_combatTarget) <= range && WithinPlayerLeash(_combatTarget); if (flag && Merc.Class == MercClass.Archer && ((Character)Merc).IsDrawingBow()) { return _combatTarget; } if (_targetScanTimer > 0f) { if (!flag) { _combatTarget = null; } if ((Object)(object)_combatTarget != (Object)null) { CancelAmbientCamp(); } return _combatTarget; } _targetScanTimer = 0.4f; _combatTarget = FindBestThreat(range, flag ? _combatTarget : null); if ((Object)(object)_combatTarget != (Object)null) { CancelAmbientCamp(); } return _combatTarget; } private bool IsValidTarget(Character target) { if ((Object)(object)target != (Object)null && !target.IsDead() && !(target is Mercenary) && !target.IsPlayer() && !target.IsTamed()) { return ((BaseAI)this).IsEnemy(target); } return false; } private bool IsActionableCombatTarget(Character target) { if (!IsValidTarget(target)) { if (_contextTargetIsHunt && (Object)(object)target != (Object)null && (Object)(object)target == (Object)(object)_contextTarget && (Object)(object)Merc != (Object)null && Merc.Class == MercClass.Archer) { return MercContextOrders.IsHuntableAnimal(target); } return false; } return true; } private float CombatAwarenessDistance(Character target) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_006d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { return float.MaxValue; } float num = Vector3.Distance(((Component)this).transform.position, ((Component)target).transform.position); if ((Object)(object)Merc == (Object)null || Merc.Class != MercClass.Healer) { return num; } GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); if (!((Object)(object)followTarget != (Object)null)) { return num; } return Mathf.Min(num, Vector3.Distance(followTarget.transform.position, ((Component)target).transform.position)); } internal void ApplyContextTarget(MercContextTargetMode mode, ZDOID targetId) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00b2: Unknown result type (might be due to invalid IL or missing references) ResourceWorker.Cancel(this); Character contextTarget = _contextTarget; ClearContextTarget(); if ((Object)(object)_combatTarget == (Object)(object)contextTarget) { _combatTarget = null; } switch (mode) { case MercContextTargetMode.Clear: return; case MercContextTargetMode.ProtectHunt: if (!((ZDOID)(ref targetId)).IsNone()) { _protectedHuntTargetId = targetId; _protectedHuntTargetUntil = Time.time + 20f; } return; } if (!((ZDOID)(ref targetId)).IsNone() && (mode != MercContextTargetMode.Hunt || (!((Object)(object)Merc == (Object)null) && Merc.Class == MercClass.Archer))) { _contextTargetId = targetId; _contextTargetIsHunt = mode == MercContextTargetMode.Hunt; _contextTargetUntil = Time.time + 20f; _targetScanTimer = 0f; _contextTarget = FindCharacter(targetId); if ((Object)(object)_contextTarget != (Object)null && IsActionableCombatTarget(_contextTarget)) { _combatTarget = _contextTarget; CancelAmbientCamp(); } } } private Character ResolveContextTarget() { //IL_0034: 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) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref _contextTargetId)).IsNone()) { return null; } if (Time.time > _contextTargetUntil) { ClearContextTarget(); return null; } if ((Object)(object)_contextTarget == (Object)null) { _contextTarget = FindCharacter(_contextTargetId); } if (!IsActionableCombatTarget(_contextTarget)) { if ((Object)(object)_contextTarget != (Object)null && _contextTarget.IsDead()) { ClearContextTarget(); } return null; } GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); if ((Object)(object)followTarget != (Object)null && HorizontalDistance(followTarget.transform.position, ((Component)_contextTarget).transform.position) > 55f) { ClearContextTarget(); return null; } return _contextTarget; } private void ClearContextTarget() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) _contextTargetId = default(ZDOID); _contextTarget = null; _contextTargetUntil = 0f; _contextTargetIsHunt = false; _protectedHuntTargetId = default(ZDOID); _protectedHuntTargetUntil = 0f; } private static Character FindCharacter(ZDOID targetId) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)ZNetScene.instance != (Object)null && !((ZDOID)(ref targetId)).IsNone()) ? ZNetScene.instance.FindInstance(targetId) : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent(); } private Character FindBestThreat(float range, Character current) { Character result = null; float num = float.MinValue; foreach (Character allCharacter in Character.GetAllCharacters()) { if (!IsValidTarget(allCharacter) || (Object)(object)allCharacter == (Object)(object)((BaseAI)this).m_character || IsProtectedHuntGame(allCharacter)) { continue; } float num2 = CombatAwarenessDistance(allCharacter); if (!(num2 > range) && WithinPlayerLeash(allCharacter)) { float num3 = TacticalTargetScore(allCharacter, num2, range); if ((Object)(object)allCharacter == (Object)(object)current) { num3 += 8f; } if (num3 > num) { result = allCharacter; num = num3; } } } return result; } private float TacticalTargetScore(Character candidate, float distance, float range) { float num = Mathf.Max(0f, range - distance); Character val = CurrentThreatVictim(candidate); switch (Merc.Class) { case MercClass.Tank: if (val is Player && IsProtectedCompanyMember(val)) { num += 70f; } else if (val is Mercenary mercenary2 && IsSameCompany(mercenary2)) { num += ((mercenary2.Class == MercClass.Healer) ? 65f : ((mercenary2.Class == MercClass.Archer) ? 55f : 35f)); } if (candidate.IsBoss()) { num += 20f; } break; case MercClass.Healer: if ((Object)(object)val == (Object)(object)Merc) { num += 70f; } else if (IsProtectedCompanyMember(val)) { num += 20f; } break; case MercClass.Archer: if (candidate.IsFlying()) { num += 55f; } if (val is Mercenary mercenary && IsSameCompany(mercenary) && mercenary.Class == MercClass.Healer) { num += 35f; } else if (val is Player && IsProtectedCompanyMember(val)) { num += 20f; } if (candidate.IsBoss()) { num += 12f; } num += (1f - Mathf.Clamp01(candidate.GetHealthPercentage())) * 18f; break; } return num; } private static Character CurrentThreatVictim(Character threat) { BaseAI val = (((Object)(object)threat != (Object)null) ? threat.GetBaseAI() : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetTargetCreature(); } private bool IsProtectedCompanyMember(Character candidate) { if ((Object)(object)candidate == (Object)null) { return false; } if ((Object)(object)candidate == (Object)(object)Merc) { return true; } if (candidate is Mercenary companion) { return IsSameCompany(companion); } Player val = (Player)(object)((candidate is Player) ? candidate : null); if (val != null) { GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); Player val2 = (((Object)(object)followTarget != (Object)null) ? followTarget.GetComponent() : null); if (!((Object)(object)val == (Object)(object)val2)) { if ((Object)(object)Merc != (Object)null) { return Merc.IsAssignedTo(val, requireFollowing: false); } return false; } return true; } return false; } private bool IsSameCompany(Mercenary companion) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)companion == (Object)null || (Object)(object)Merc == (Object)null) { return false; } if ((Object)(object)companion == (Object)(object)Merc) { return true; } ZDO bannerZdo = Merc.GetBannerZdo(); ZDO bannerZdo2 = companion.GetBannerZdo(); if (bannerZdo != null && bannerZdo2 != null) { return bannerZdo.m_uid == bannerZdo2.m_uid; } GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); Player val = (((Object)(object)followTarget != (Object)null) ? followTarget.GetComponent() : null); if ((Object)(object)val != (Object)null) { return companion.IsAssignedTo(val, requireFollowing: false); } return false; } private bool IsProtectedHuntGame(Character candidate) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)candidate != (Object)null && Time.time <= _protectedHuntTargetUntil && !((ZDOID)(ref _protectedHuntTargetId)).IsNone()) { ZNetView component = ((Component)candidate).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && component.GetZDO().m_uid == _protectedHuntTargetId) { return true; } } if (ActiveCreatureHuntKey == null || (Object)(object)Merc == (Object)null || Merc.Class == MercClass.Archer) { return false; } GuideFindTarget guideFindTarget = GuideFinder.ByKey(ActiveCreatureHuntKey); if (guideFindTarget != null && guideFindTarget.Kind == GuideFindKind.Creature) { return GuideFinder.NameMatches(guideFindTarget, Utils.GetPrefabName(((Component)candidate).gameObject)); } return false; } private void UpdateFormation(float dt) { //IL_00aa: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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) GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); Vector3 position; Vector3 forward; if ((Object)(object)followTarget != (Object)null) { ResetIdleRepositioning(); Player component = followTarget.GetComponent(); Vector3 motion = (((Object)(object)component != (Object)null) ? ((Character)component).GetVelocity() : Vector3.zero); bool mirrorRun = (Object)(object)component != (Object)null && ((Character)component).IsRunning(); bool mirrorWalk = (Object)(object)component != (Object)null && ((Character)component).IsWalking(); _remoteFormationReady = false; _remoteFormationSampleTime = 0f; UpdateRoleFormation(dt, followTarget.transform.position, followTarget.transform.forward, motion, mirrorRun, mirrorWalk, component); } else if (TryGetRemoteFollowTarget(out position, out forward)) { ResetIdleRepositioning(); Vector3 motion2 = UpdateRemoteFormationVelocity(position, dt); bool mirrorRun2 = ((Vector3)(ref motion2)).magnitude >= 5.2f; bool mirrorWalk2 = ((Vector3)(ref motion2)).magnitude > 0.2f && ((Vector3)(ref motion2)).magnitude < 2.7f; UpdateRoleFormation(dt, position, forward, motion2, mirrorRun2, mirrorWalk2, null); } else { ResetFormationState(); UpdateIdleAtAnchor(dt); } } private void UpdateRoleFormation(float dt, Vector3 employerPosition, Vector3 facing, Vector3 motion, bool mirrorRun, bool mirrorWalk, Player livePlayer) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_014f: 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_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: 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_01d8: 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_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_034b: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_03c5: Unknown result type (might be due to invalid IL or missing references) //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_0513: Unknown result type (might be due to invalid IL or missing references) //IL_0518: Unknown result type (might be due to invalid IL or missing references) //IL_054a: Unknown result type (might be due to invalid IL or missing references) //IL_052f: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_0490: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) motion.y = 0f; float magnitude = ((Vector3)(ref motion)).magnitude; bool flag = MercMovementRules.IsEmployerMoving(magnitude); float num = HorizontalDistance(((Component)this).transform.position, employerPosition); bool flag2 = MercMovementRules.ShouldHoldAmbientActivity(_campLife.IsEngaged, num); _employerSettledClock = MercMovementRules.AdvanceSettledClock(_employerSettledClock, magnitude, dt); ZDO val = (((Object)(object)Merc != (Object)null) ? Merc.GetBannerZdo() : null); bool flag3 = val != null && val.IsValid(); float employerBannerDistance = (flag3 ? HorizontalDistance(employerPosition, val.GetPosition()) : (-1f)); if (_baseRoamSuppressedByRecall && MercMovementRules.ShouldReleaseRecallSuppression(flag3, employerBannerDistance)) { _baseRoamSuppressedByRecall = false; } MercMovementMode mercMovementMode = MercMovementRules.SelectMode(_movementMode == MercMovementMode.BaseRoam, flag3, employerBannerDistance, _employerSettledClock, _baseRoamSuppressedByRecall || _followingSneak); if (mercMovementMode != _movementMode) { if ((_movementMode == MercMovementMode.BaseRoam || mercMovementMode == MercMovementMode.Travel) && !flag2) { CancelAmbientCamp(); } _movementMode = mercMovementMode; _formationCorrecting = true; _formationForwardReady = false; } if (_movementMode == MercMovementMode.BaseRoam) { _followWaterBlockedTimer = 0f; if (_campLife.IsEngaged && !flag2) { CancelAmbientCamp(); } if (!_campLife.Tick(Merc, this, dt, employerPosition)) { UpdateBaseRoaming(dt, val.GetPosition(), val.GetRotation(), employerPosition); } return; } ResetBaseRoaming(); int num2; if (!_followingSneak) { if (flag) { num2 = ((!flag2) ? 1 : 0); if (num2 != 0) { goto IL_018d; } } else { num2 = 0; } goto IL_0193; } num2 = 1; goto IL_018d; IL_018d: CancelAmbientCamp(); goto IL_0193; IL_0193: Vector3 val2; if (flag) { val2 = ((Vector3)(ref motion)).normalized; } else if (_formationForwardReady) { val2 = _formationForward; } else { val2 = facing; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = Vector3.forward; } ((Vector3)(ref val2)).Normalize(); } if (!_formationForwardReady) { _formationForward = val2; _formationForwardReady = true; } else if (flag) { Vector3 val3 = Vector3.Slerp(_formationForward, val2, Mathf.Clamp01(dt * 4f)); _formationForward = ((Vector3)(ref val3)).normalized; } float num3 = (mirrorRun ? 1.25f : 0.75f); Vector3 val4 = (flag ? Vector3.ClampMagnitude(motion * 0.18f, num3) : Vector3.zero); Vector3 val5 = GroundedPoint(MercFormation.Anchor(employerPosition + val4, _formationForward, Merc.Class, _movementMode, Merc.PresetFormation)); MercFormationSlot mercFormationSlot = MercFormation.For(Merc.Class, _movementMode, Merc.PresetFormation); float num4 = HorizontalDistance(((Component)this).transform.position, val5); float num5 = HorizontalDistance(((Component)this).transform.position, employerPosition); float num6 = mercFormationSlot.SoftRadius + 0.75f; if (num4 > num6 || num5 > mercFormationSlot.MaxPlayerDistance || num < 1.25f) { _formationCorrecting = true; } else if (_formationCorrecting && !flag && num4 <= mercFormationSlot.SoftRadius && num >= 1.25f) { _formationCorrecting = false; } if (num2 == 0 && _campLife.IsEngaged && _campLife.Tick(Merc, this, dt, employerPosition)) { return; } if (!_formationCorrecting) { _followWaterBlockedTimer = 0f; ((BaseAI)this).StopMoving(); ((Character)Merc).SetWalk(false); ((Character)Merc).SetRun(false); if (_followingSneak || !_campLife.Tick(Merc, this, dt, employerPosition)) { ((BaseAI)this).LookAt(employerPosition + _formationForward * 4f + Vector3.up * 1.5f); } return; } bool flag4 = num4 > num6 + 4f || num > mercFormationSlot.MaxPlayerDistance + 2f; bool flag5 = (mirrorRun && flag) || flag4; bool requestWalk = (mirrorWalk && flag && !flag5) || _movementMode == MercMovementMode.Settled; float stopDistance = Mathf.Max(0.75f, mercFormationSlot.SoftRadius * 0.55f); if ((Object)(object)livePlayer != (Object)null) { bool allowWetDestination = ((Character)livePlayer).InWater() || ((Character)livePlayer).IsSwimming(); if (CachedCanUseShortWaterCrossing(((Component)this).transform.position, val5, allowWetDestination)) { _followWaterBlockedTimer = 0f; MoveAcrossWaterWithNavigationRecovery(dt, val5, stopDistance, flag5, requestWalk); return; } if (CachedHasWaterBarrierBetween(((Component)this).transform.position, employerPosition) && !HavePathSafe(employerPosition)) { LogWaterDiag($"formation blocked by uncrossable water, {num:F0}m to {((Object)livePlayer).name}"); _followWaterBlockedTimer += dt; ((BaseAI)this).StopMoving(); if (_followWaterBlockedTimer >= 2f && Merc.TryCatchUpToPlayer(livePlayer, "water-blocked formation route")) { _followWaterBlockedTimer = 0f; } return; } } else if (CachedCanUseShortWaterCrossing(((Component)this).transform.position, val5, allowWetDestination: false)) { _followWaterBlockedTimer = 0f; MoveAcrossWaterWithNavigationRecovery(dt, val5, stopDistance, flag5, requestWalk); return; } _followWaterBlockedTimer = 0f; MoveToWithNavigationRecovery(dt, val5, stopDistance, flag5, requestWalk); } private Vector3 UpdateRemoteFormationVelocity(Vector3 position, float dt) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00b7: Unknown result type (might be due to invalid IL or missing references) if (!_remoteFormationReady || HorizontalDistance(position, _remoteFormationPosition) > 20f) { _remoteFormationPosition = position; _remoteFormationVelocity = Vector3.zero; _remoteFormationSampleTime = 0f; _remoteFormationReady = true; return Vector3.zero; } _remoteFormationSampleTime += dt; Vector3 val = position - _remoteFormationPosition; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.0004f && _remoteFormationSampleTime > 0.01f) { Vector3 val2 = Vector3.ClampMagnitude(val / _remoteFormationSampleTime, 10f); _remoteFormationVelocity = Vector3.Lerp(_remoteFormationVelocity, val2, 0.65f); _remoteFormationPosition = position; _remoteFormationSampleTime = 0f; } else if (_remoteFormationSampleTime > 0.5f) { _remoteFormationVelocity = Vector3.MoveTowards(_remoteFormationVelocity, Vector3.zero, dt * 4f); } return _remoteFormationVelocity; } private void ResetFormationState() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) _formationForward = Vector3.zero; _formationForwardReady = false; _formationCorrecting = false; _employerSettledClock = 0f; _movementMode = MercMovementMode.Travel; _baseRoamSuppressedByRecall = false; ResetBaseRoaming(); _remoteFormationPosition = Vector3.zero; _remoteFormationVelocity = Vector3.zero; _remoteFormationSampleTime = 0f; _remoteFormationReady = false; } private static Vector3 ClassOffset(MercClass @class) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) return (Vector3)(@class switch { MercClass.Healer => new Vector3(-2f, 0f, 1f), MercClass.Tank => new Vector3(2f, 0f, 1f), _ => new Vector3(0f, 0f, -2f), }); } private void UpdateIdleAtAnchor(float dt) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: 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_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: 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_01e1: Unknown result type (might be due to invalid IL or missing references) ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); int num = ((val2 != null) ? val2.GetInt(Mercenary.ZdoStayDay, 0) : 0); int currentDay = WorldDataService.CurrentDay; bool num2 = num > 0 && currentDay > 0 && currentDay - num < 1; ZDO val3 = (((Object)(object)Merc != (Object)null) ? Merc.GetBannerZdo() : null); bool flag = false; Vector3 val4; if (num2 && val2 != null) { val4 = val2.GetVec3(Mercenary.ZdoStayAnchor, ((Component)this).transform.position); } else if (val3 != null) { if (num > 0 && val2 != null && val2.GetOwner() == ZNet.GetUID()) { if (!_stayExpiryLogged) { _stayExpiryLogged = true; MercPlugin.Log(Merc.GetName() + "'s stay order expired after an in-game day; returning to the banner."); } val2.Set(Mercenary.ZdoStayDay, 0, false); } val4 = val3.GetPosition(); flag = true; } else { val4 = ((Component)this).transform.position; } float num3 = Vector3.Distance(((Component)this).transform.position, val4); if (num2 && val2 != null && val2.GetBool(Mercenary.ZdoPreciseHold, false)) { CancelAmbientCamp(); if (num3 > 1.5f) { MoveToWithStamina(dt, val4, 0.8f, num3 > 8f); } else { ((BaseAI)this).StopMoving(); } } else if (num3 > 6f) { ResetIdleRepositioning(); if (flag && num3 > 40f && !EngagedInCombat) { _returnHomeTimer -= dt; if (_returnHomeTimer <= 0f) { _returnHomeTimer = 5f; Vector3 val5 = ClassOffset(Merc.Class); ((Character)Merc).TeleportTo(val4 + val5, Quaternion.LookRotation(-val5), true); MercPlugin.Log($"{Merc.GetName()} was {num3:0}m from the banner and returned home."); } } else { _returnHomeTimer = 5f; MoveToWithStamina(dt, val4, 3f, num3 > 16f, num3 <= 16f); } } else { _stayExpiryLogged = false; if (!_campLife.Tick(Merc, this, dt, val4)) { UpdatePurposefulIdle(dt, val4); } } } private void UpdatePurposefulIdle(float dt, Vector3 anchor) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_00c8: 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) if (_idleRepositionPause < 0f || HorizontalDistance(anchor, _idleRepositionAnchor) > 1f) { _idleRepositionAnchor = anchor; _idleRepositionTarget = Vector3.zero; _idleRepositionActive = false; _idleRepositionPause = Random.Range(10f, 20f); } if (_idleRepositionActive) { if (HorizontalDistance(((Component)this).transform.position, _idleRepositionTarget) > 0.8f) { MoveToWithNavigationRecovery(dt, _idleRepositionTarget, 0.65f, requestRun: false, requestWalk: true); return; } _idleRepositionActive = false; _idleRepositionPause = Random.Range(10f, 20f); } StopForAmbient(); _idleRepositionPause -= dt; if (!(_idleRepositionPause > 0f)) { if (TrySelectIdleReposition(anchor, out _idleRepositionTarget)) { _idleRepositionActive = true; } else { _idleRepositionPause = 5f; } } } private void UpdateBaseRoaming(float dt, Vector3 bannerPosition, Quaternion bannerRotation, Vector3 employerPosition) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: 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) if (_baseRoamPause < 0f || HorizontalDistance(bannerPosition, _baseRoamAnchor) > 1f) { _baseRoamAnchor = bannerPosition; _baseRoamTarget = Vector3.zero; _baseRoamActive = false; _baseRoamPause = Random.Range(8f, 16f); } if (_baseRoamActive) { if (HorizontalDistance(((Component)this).transform.position, _baseRoamTarget) > 0.8f) { MoveToWithNavigationRecovery(dt, _baseRoamTarget, 0.65f, requestRun: false, requestWalk: true); return; } _baseRoamActive = false; _baseRoamPause = Random.Range(8f, 16f); } StopForAmbient(); ((BaseAI)this).LookAt(employerPosition + Vector3.up * 1.5f); _baseRoamPause -= dt; if (!(_baseRoamPause > 0f)) { if (TrySelectBaseRoamPoint(bannerPosition, bannerRotation, out _baseRoamTarget)) { _baseRoamActive = true; } else { _baseRoamPause = 4f; } } } private bool TrySelectBaseRoamPoint(Vector3 bannerPosition, Quaternion bannerRotation, out Vector3 target) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) target = Vector3.zero; Vector3 val = bannerRotation * Vector3.forward; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); Vector3 val2 = new Vector3(val.z, 0f, 0f - val.x); MercMovementSlot mercMovementSlot = MercMovementRules.Slot(Merc.Class, MercMovementMode.BaseRoam); Vector3 val3 = val2 * mercMovementSlot.Side + val * mercMovementSlot.Forward; if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = val; } ((Vector3)(ref val3)).Normalize(); float num = Random.Range(-20f, 20f); float[] array = new float[8] { 0f, 35f, -35f, 70f, -70f, 110f, -110f, 180f }; float[] array2 = new float[3] { 7f, 11f, 15f }; for (int i = 0; i < array2.Length; i++) { for (int j = 0; j < array.Length; j++) { Vector3 val4 = Quaternion.Euler(0f, num + array[j], 0f) * val3; Vector3 val5 = bannerPosition + val4 * array2[i]; float num2 = HorizontalDistance(val5, bannerPosition); if (!(num2 < 5f) && !(num2 > 15.5f) && !((Object)(object)Player.GetClosestPlayer(val5, 2.25f) != (Object)null) && !IsIdlePointOccupied(val5) && !IsNearDoorway(val5) && TryGetAmbientApproach(val5, out var grounded) && !(HorizontalDistance(grounded, bannerPosition) > 15.5f)) { target = grounded; return true; } } } return false; } private void ResetBaseRoaming() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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) _baseRoamAnchor = Vector3.zero; _baseRoamTarget = Vector3.zero; _baseRoamPause = -1f; _baseRoamActive = false; } internal void ForceRecallFromBase() { ResourceWorker.Cancel(this); _recallGathering = true; InterruptCombatForOrder(); _baseRoamSuppressedByRecall = true; _movementMode = MercMovementMode.Travel; _employerSettledClock = 0f; _formationCorrecting = true; _formationForwardReady = false; CancelAmbientCamp(); ResetBaseRoaming(); ResetNavigationRecovery(); } private void SetFollowingSneak(bool active) { if ((Object)(object)Merc == (Object)null) { return; } bool flag = active && !_followingSneak; _followingSneak = active; if (OwnerGuardFallback()) { if ((Object)(object)_stealthAnimation == (Object)null) { _stealthAnimation = ((Component)Merc).GetComponent(); } ZSyncAnimation stealthAnimation = _stealthAnimation; if (stealthAnimation != null) { stealthAnimation.SetBool(MercStealth.Crouching, active && !((Character)Merc).InAttack() && !((Character)Merc).InDodge()); } if (flag) { CancelAmbientCamp(); ResetBaseRoaming(); _movementMode = MercMovementMode.Travel; _formationCorrecting = true; _formationForwardReady = false; } } } private void InterruptCombatForOrder() { _combatTarget = null; ClearContextTarget(); CancelBowDraw(); ResetArcherPositioning(); ResetMenderPositioning(); ((BaseAI)this).SetAlerted(false); Attack val = (Attack)(((Object)(object)Merc != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); if (val != null) { val.Stop(); PreviousAttackField.SetValue(Merc, val); CurrentAttackField.SetValue(Merc, null); } } private bool UpdateRecallGathering(float dt) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (!_recallGathering) { return false; } GameObject followTarget = ((MonsterAI)this).GetFollowTarget(); Vector3 position; Vector3 forward; if ((Object)(object)followTarget != (Object)null) { position = followTarget.transform.position; } else if (!TryGetRemoteFollowTarget(out position, out forward)) { _recallGathering = false; return false; } if (!MercOrderRules.ShouldGather(HorizontalDistance(((Component)this).transform.position, position))) { _recallGathering = false; return false; } InterruptCombatForOrder(); TickHealingPose(dt); UpdateFormation(dt); return true; } private bool TryGetPreciseHoldAnchor(out Vector3 anchor) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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) anchor = ((Component)this).transform.position; ZNetView val = (((Object)(object)Merc != (Object)null) ? ((Component)Merc).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null) { return false; } int stayDay = val2.GetInt(Mercenary.ZdoStayDay, 0); int currentDay = WorldDataService.CurrentDay; if (!MercOrderRules.IsPreciseHoldActive(!string.IsNullOrEmpty(Mercenary.FollowNameOf(val2)), val2.GetBool(Mercenary.ZdoPreciseHold, false), stayDay, currentDay)) { return false; } anchor = val2.GetVec3(Mercenary.ZdoStayAnchor, ((Component)this).transform.position); return true; } private bool ReturnToPreciseHold(float dt) { //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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (((Character)Merc).InWater() || ((Character)Merc).IsSwimming() || !TryGetPreciseHoldAnchor(out var anchor) || HorizontalDistance(((Component)this).transform.position, anchor) <= 3f) { return false; } InterruptCombatForOrder(); TickHealingPose(dt); MoveToWithNavigationRecovery(dt, anchor, 0.8f, HorizontalDistance(((Component)this).transform.position, anchor) > 8f); return true; } private Vector3 ConstrainToPreciseHold(Vector3 point) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0058: Unknown result type (might be due to invalid IL or missing references) if (_combatShoreRecoveryActive || IsResurrectionActive || ((Character)Merc).InWater() || ((Character)Merc).IsSwimming() || !TryGetPreciseHoldAnchor(out var anchor)) { return point; } Vector3 val = point - anchor; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude <= 9f) { return point; } return GroundedPoint(anchor + ((Vector3)(ref val)).normalized * 3f); } private bool TrySelectIdleReposition(Vector3 anchor, out Vector3 target) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) target = Vector3.zero; Vector3 val = ClassOffset(Merc.Class); val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); float num = Random.Range(-30f, 30f); float[] array = new float[6] { 0f, 60f, -60f, 120f, -120f, 180f }; float[] array2 = new float[2] { 3.5f, 5.5f }; for (int i = 0; i < array2.Length; i++) { for (int j = 0; j < array.Length; j++) { Vector3 val2 = Quaternion.Euler(0f, num + array[j], 0f) * val; Vector3 val3 = anchor + val2 * array2[i]; if (!((Object)(object)Player.GetClosestPlayer(val3, 2.25f) != (Object)null) && !IsIdlePointOccupied(val3) && !IsNearDoorway(val3) && TryGetAmbientApproach(val3, out var grounded) && !(HorizontalDistance(grounded, anchor) > 6.5f)) { target = grounded; return true; } } } return false; } private bool IsIdlePointOccupied(Vector3 point) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance == (Object)(object)Merc) && !((Character)instance).IsDead() && HorizontalDistance(((Component)instance).transform.position, point) < 1.8f) { return true; } } return false; } private static bool IsNearDoorway(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Collider[] array = Physics.OverlapSphere(point, 1.5f, -1, (QueryTriggerInteraction)2); foreach (Collider val in array) { if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).GetComponentInParent() != (Object)null) { return true; } } return false; } private void ResetIdleRepositioning() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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) _idleRepositionAnchor = Vector3.zero; _idleRepositionTarget = Vector3.zero; _idleRepositionPause = -1f; _idleRepositionActive = false; } private bool TryGetRemoteFollowTarget(out Vector3 position, out Vector3 forward) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; forward = Vector3.forward; if ((Object)(object)Merc == (Object)null || (Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return false; } ZNetView component = ((Component)Merc).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null) { return false; } string text = Mercenary.FollowNameOf(val); long num = Mercenary.EmployerIdOf(val); if (num == 0L || string.IsNullOrWhiteSpace(text)) { return false; } if (!ServerAuthority.TryFindConnectedPeerByPlayerId(num, out var peerUid, out var playerName) || (!string.IsNullOrWhiteSpace(playerName) && playerName != text)) { return false; } ZNetPeer peer = ZNet.instance.GetPeer(peerUid); ZDO val2 = ((peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) ? ZDOMan.instance.GetZDO(peer.m_characterID) : null); if (val2 == null || !val2.IsValid() || val2.GetBool(ZDOVars.s_dead, false)) { return false; } position = val2.GetPosition(); forward = val2.GetRotation() * Vector3.forward; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } return true; } private bool MoveToWithNavigationRecovery(float dt, Vector3 point, float stopDistance, bool requestRun, bool requestWalk = false) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (TryMoveWithNavigationRecovery(dt, point, stopDistance, requestRun, requestWalk)) { return false; } return MoveToWithStamina(dt, point, stopDistance, requestRun, requestWalk); } private bool TryMoveWithNavigationRecovery(float dt, Vector3 point, float stopDistance, bool requestRun, bool requestWalk) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (!TryGetNavigationRecoveryTarget(dt, point, stopDistance, out var recoveryPoint, out var direct)) { return false; } if (direct) { MoveDirectWithStamina(dt, recoveryPoint, 0.55f, requestRun, requestWalk); } else { MoveToWithStamina(dt, recoveryPoint, 0.7f, requestRun, requestWalk); } return true; } private bool TryGetNavigationRecoveryTarget(float dt, Vector3 goal, float stopDistance, out Vector3 recoveryPoint, out bool direct) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_012b: 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) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) float realtimeSinceStartup = Time.realtimeSinceStartup; bool num = !_navigationReady || realtimeSinceStartup - _navigationLastUseTime > 0.75f; bool flag = _navigationReady && HorizontalDistance(goal, _navigationGoal) > 2.5f; if (num || flag) { InitializeNavigationProgress(goal, realtimeSinceStartup); } else { _navigationGoal = goal; } _navigationLastUseTime = realtimeSinceStartup; if (HorizontalDistance(((Component)this).transform.position, goal) <= Mathf.Max(0.75f, stopDistance)) { _navigationRecovering = false; _navigationStallClock = 0f; recoveryPoint = Vector3.zero; direct = false; return false; } if (_navigationRecovering) { _navigationRecoveryClock -= dt; bool flag2 = HorizontalDistance(((Component)this).transform.position, _navigationRecoveryPoint) <= 0.7f; if (_navigationRecoveryClock > 0f && !flag2) { recoveryPoint = _navigationRecoveryPoint; direct = _navigationRecoveryDirect; return true; } if (flag2 && _navigationShareOnSuccess) { MercNavigationMemory.RememberDetour(_navigationRecoveryOrigin, _navigationRecoveryGoal, _navigationRecoveryPoint, ((Object)this).GetInstanceID()); } FinishNavigationRecovery(goal); } _navigationDoorScanClock -= dt; if (_navigationDoorScanClock <= 0f) { _navigationDoorScanClock = 0.25f; if (TryFindDoorPassage(goal, out var passage)) { BeginNavigationRecovery(passage, goal, directMovement: true, shareOnSuccess: false); recoveryPoint = _navigationRecoveryPoint; direct = true; return true; } } _navigationSampleClock += dt; if (_navigationSampleClock >= 0.5f) { float num2 = HorizontalDistance(((Component)this).transform.position, _navigationSamplePosition); float num3 = HorizontalDistance(((Component)this).transform.position, goal); if (num2 >= 0.35f || num3 <= _navigationSampleDistance - 0.35f) { _navigationStallClock = 0f; } else { _navigationStallClock += _navigationSampleClock; } _navigationSamplePosition = ((Component)this).transform.position; _navigationSampleDistance = num3; _navigationSampleClock = 0f; } if (_navigationStallClock >= 1.6f && TrySelectNavigationDetour(goal, out var detour, out var shareOnSuccess)) { BeginNavigationRecovery(detour, goal, directMovement: false, shareOnSuccess); recoveryPoint = _navigationRecoveryPoint; direct = false; return true; } recoveryPoint = Vector3.zero; direct = false; return false; } private void InitializeNavigationProgress(Vector3 goal, float now) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) _navigationGoal = goal; _navigationSamplePosition = ((Component)this).transform.position; _navigationSampleDistance = HorizontalDistance(((Component)this).transform.position, goal); _navigationSampleClock = 0f; _navigationStallClock = 0f; _navigationRecoveryClock = 0f; _navigationDoorScanClock = 0f; _navigationLastUseTime = now; _navigationRecoveryAttempt = 0; _navigationReady = true; _navigationRecovering = false; _navigationRecoveryDirect = false; } private void BeginNavigationRecovery(Vector3 point, Vector3 goal, bool directMovement, bool shareOnSuccess) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_001a: Unknown result type (might be due to invalid IL or missing references) _navigationRecoveryPoint = point; _navigationRecoveryOrigin = ((Component)this).transform.position; _navigationRecoveryGoal = goal; _navigationRecoveryClock = 2.2f; _navigationSampleClock = 0f; _navigationStallClock = 0f; _navigationRecovering = true; _navigationRecoveryDirect = directMovement; _navigationShareOnSuccess = shareOnSuccess; } private void FinishNavigationRecovery(Vector3 goal) { //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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) _navigationRecovering = false; _navigationRecoveryDirect = false; _navigationShareOnSuccess = false; _navigationRecoveryClock = 0f; _navigationSamplePosition = ((Component)this).transform.position; _navigationSampleDistance = HorizontalDistance(((Component)this).transform.position, goal); _navigationSampleClock = 0f; _navigationStallClock = 0f; } private void ResetNavigationRecovery() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0017: 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) _navigationGoal = Vector3.zero; _navigationSamplePosition = Vector3.zero; _navigationRecoveryPoint = Vector3.zero; _navigationRecoveryOrigin = Vector3.zero; _navigationRecoveryGoal = Vector3.zero; _navigationLastSharedDetour = Vector3.zero; _navigationSampleDistance = 0f; _navigationSampleClock = 0f; _navigationStallClock = 0f; _navigationRecoveryClock = 0f; _navigationDoorScanClock = 0f; _navigationLastUseTime = 0f; _navigationLastSharedDetourTime = 0f; _navigationRecoveryAttempt = 0; _navigationReady = false; _navigationRecovering = false; _navigationRecoveryDirect = false; _navigationShareOnSuccess = false; } private bool TryFindDoorPassage(Vector3 goal, out Vector3 passage) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_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_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0112: 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_012e: 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_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) if (MercNavigationMemory.TryGetDoorPassage(((Component)this).transform.position, goal, out passage)) { return true; } passage = Vector3.zero; if ((Object)(object)Merc == (Object)null) { return false; } Vector3 val = goal - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.25f) { return false; } ((Vector3)(ref val)).Normalize(); int num = Physics.OverlapSphereNonAlloc(((Component)this).transform.position + val * 1.25f, 2.4f, _navigationDoorColliders, -1, (QueryTriggerInteraction)2); Door val2 = null; float num2 = float.MaxValue; for (int i = 0; i < num; i++) { Collider val3 = _navigationDoorColliders[i]; _navigationDoorColliders[i] = null; Door val4 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponentInParent() : null); if ((Object)(object)val4 == (Object)null || !CanOpenNavigationDoor(val4)) { continue; } Vector3 val5 = ((Component)val4).transform.position - ((Component)this).transform.position; val5.y = 0f; float num3 = Vector3.Dot(val5, val); if (!(num3 < -0.25f) && !(num3 > 4f)) { Vector3 val6 = val5 - val * num3; float magnitude = ((Vector3)(ref val6)).magnitude; float sqrMagnitude = ((Vector3)(ref val5)).sqrMagnitude; if (!(magnitude > 2.2f) && !(sqrMagnitude >= num2)) { val2 = val4; num2 = sqrMagnitude; } } } if ((Object)(object)val2 == (Object)null || !MercNavigationMemory.TryReserveDoor(((Object)val2).GetInstanceID())) { return false; } Vector3 val7 = ((Component)val2).transform.position - ((Component)this).transform.position; val7.y = 0f; if (((Vector3)(ref val7)).sqrMagnitude < 0.1f) { val7 = val; } ((Vector3)(ref val7)).Normalize(); passage = GroundedPoint(((Component)val2).transform.position + val7 * 1.8f); bool flag; try { flag = val2.Interact((Humanoid)(object)Merc, false, false); } catch { flag = false; } if (!flag) { passage = Vector3.zero; return false; } MercNavigationMemory.RememberDoorPassage(((Object)val2).GetInstanceID(), val2, ((Component)val2).transform.position, passage, val7, Merc.EffectiveEmployerId(), Merc); MercPlugin.Log(Merc.GetName() + " opened a door blocking the company route."); return true; } private bool CanOpenNavigationDoor(Door door) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)door == (Object)null || !((Behaviour)door).isActiveAndEnabled) { return false; } ZNetView component = ((Component)door).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null || val.GetInt(ZDOVars.s_state, 0) != 0) { return false; } if (door.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)door).transform.position, 0f, false, false)) { return false; } if ((Object)(object)door.m_keyItem == (Object)null) { return true; } string text = door.m_keyItem.m_itemData?.m_shared?.m_name; Inventory val2 = (((Object)(object)Merc != (Object)null) ? ((Humanoid)Merc).GetInventory() : null); if (val2 != null && !string.IsNullOrEmpty(text)) { return val2.HaveItem(text, true); } return false; } private bool TrySelectNavigationDetour(Vector3 goal, out Vector3 detour, out bool shareOnSuccess) { //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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: 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_0126: 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_0136: 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_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: 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_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) int instanceID = ((Object)this).GetInstanceID(); if (MercNavigationMemory.TryGetDetour(((Component)this).transform.position, goal, instanceID, out var point) && (Time.realtimeSinceStartup - _navigationLastSharedDetourTime >= 6f || HorizontalDistance(point, _navigationLastSharedDetour) > 0.5f) && TryValidateNavigationDetour(point, out detour)) { _navigationLastSharedDetour = point; _navigationLastSharedDetourTime = Time.realtimeSinceStartup; shareOnSuccess = false; return true; } Vector3 val = goal - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.25f) { detour = Vector3.zero; shareOnSuccess = false; return false; } ((Vector3)(ref val)).Normalize(); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.z, 0f, 0f - val.x); int num = ((!((Object)(object)Merc != (Object)null) || Merc.Class != MercClass.Tank) ? 1 : (-1)); int num2 = (((_navigationRecoveryAttempt & 1) == 0) ? num : (-num)); float num3 = 2.25f + (float)Mathf.Min(_navigationRecoveryAttempt, 2) * 0.75f; Vector3 position = ((Component)this).transform.position; Vector3[] array = (Vector3[])(object)new Vector3[3] { position + val * 1.25f + val2 * (num3 * (float)num2), position + val * 1.25f - val2 * (num3 * (float)num2), position - val + val2 * (num3 * (float)num2) }; foreach (Vector3 candidate in array) { if (TryValidateNavigationDetour(candidate, out detour)) { _navigationRecoveryAttempt++; shareOnSuccess = true; return true; } } _navigationRecoveryAttempt++; _navigationStallClock = 0.8f; detour = Vector3.zero; shareOnSuccess = false; return false; } private bool TryValidateNavigationDetour(Vector3 candidate, out Vector3 grounded) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) grounded = Vector3.zero; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !instance.IsZoneLoaded(candidate)) { return false; } float solidHeight = instance.GetSolidHeight(candidate); if (solidHeight <= -1000f || Mathf.Abs(solidHeight - ((Component)this).transform.position.y) > 2.5f) { return false; } candidate.y = solidHeight + 0.1f; if (IsWetAt(candidate) || !HavePathSafe(candidate)) { return false; } grounded = candidate; return true; } private bool MoveToWithStamina(float dt, Vector3 point, float stopDistance, bool requestRun, bool requestWalk = false) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) CancelAmbientCamp(); point = ConstrainToPreciseHold(point); bool flag = !_followingSneak && requestRun && ((Character)Merc).GetStaminaPercentage() > 0.15f; ((Character)Merc).SetWalk(requestWalk && !flag); if (flag) { ((Character)Merc).UseStamina(8f * dt); } return ((BaseAI)this).MoveTo(dt, point, stopDistance, flag); } private void MoveAcrossWaterWithNavigationRecovery(float dt, Vector3 point, float stopDistance, bool requestRun, bool requestWalk = false) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (!((Character)Merc).InWater() && TryFindDryWaterApproach(((Component)this).transform.position, point, out var approach)) { LogWaterDiag($"pathing {HorizontalDistance(((Component)this).transform.position, approach):F0}m " + "to the dry water entry before crossing"); MoveToWithNavigationRecovery(dt, approach, 0.6f, requestRun, requestWalk); } else if (!TryMoveWithNavigationRecovery(dt, point, stopDistance, requestRun, requestWalk)) { MoveDirectWithStamina(dt, point, stopDistance, requestRun, requestWalk); } } private bool RecoverToShoreAfterWaterCombat(float dt, Character threat) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Merc != (Object)null) || (!((Character)Merc).InWater() && !((Character)Merc).IsSwimming() && !IsWetAt(((Component)this).transform.position))) { _combatShorePoint = Vector3.zero; _combatShoreRefreshTimer = 0f; _combatShoreRecoveryActive = false; return false; } _combatShoreRecoveryActive = true; if (IsActionableCombatTarget(threat)) { _combatTarget = threat; } CancelBowDraw(); ResetArcherPositioning(); ResetMenderPositioning(); ((Character)Merc).SetRun(false); _combatShoreRefreshTimer -= dt; if (_combatShorePoint == Vector3.zero || IsWetAt(_combatShorePoint) || HorizontalDistance(((Component)this).transform.position, _combatShorePoint) < 0.7f || _combatShoreRefreshTimer <= 0f) { _combatShoreRefreshTimer = 1f; if (!TrySelectNearestDryShore(out _combatShorePoint)) { ((BaseAI)this).StopMoving(); return true; } LogWaterDiag(Merc.GetName() + " broke off water combat and is swimming " + $"{HorizontalDistance(((Component)this).transform.position, _combatShorePoint):F0}m to shore"); } MoveAcrossWaterWithNavigationRecovery(dt, _combatShorePoint, 0.6f, requestRun: false); return true; } private bool TrySelectNearestDryShore(out Vector3 shore) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_007f: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) shore = Vector3.zero; Vector3 position = ((Component)this).transform.position; for (float num = 2f; num <= 24f; num += 2f) { float num2 = float.MaxValue; Vector3 val = Vector3.zero; for (int i = 0; i < 16; i++) { float num3 = (float)i * 22.5f; Vector3 val2 = Quaternion.Euler(0f, num3, 0f) * Vector3.forward; Vector3 val3 = position + val2 * num; if (!IsWetAt(val3) && TryGetDryGround(val3, out var grounded)) { float num4 = Mathf.Abs(grounded.y - position.y); if (!(num4 >= num2)) { num2 = num4; val = grounded; } } } if (!(val == Vector3.zero)) { shore = val; return true; } } return false; } private static bool TryFindDryWaterApproach(Vector3 start, Vector3 end, out Vector3 approach) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) approach = Vector3.zero; if (IsWetAt(start)) { return false; } Vector3 val = end - start; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 1.5f) { return false; } val /= magnitude; int num = Mathf.Max(2, Mathf.CeilToInt(magnitude / 0.5f)); for (int i = 1; i < num; i++) { float num2 = magnitude * (float)i / (float)num; if (!IsWetAt(start + val * num2)) { continue; } for (float num3 = 1f; num3 <= 2.5f; num3 += 0.5f) { float num4 = num2 - num3; if (num4 <= 1.25f) { return false; } if (TryGetDryGround(start + val * num4, out approach)) { return true; } } return false; } return false; } private bool MoveDirectWithStamina(float dt, Vector3 point, float stopDistance, bool requestRun, bool requestWalk = false) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) CancelAmbientCamp(); point = ConstrainToPreciseHold(point); if (HorizontalDistance(((Component)this).transform.position, point) <= Mathf.Max(0.5f, stopDistance)) { ((BaseAI)this).StopMoving(); ((Character)Merc).SetWalk(false); ((Character)Merc).SetRun(false); return true; } bool flag = !_followingSneak && requestRun && ((Character)Merc).GetStaminaPercentage() > 0.15f; ((Character)Merc).SetWalk(requestWalk && !flag); if (flag) { ((Character)Merc).UseStamina(8f * dt); } if (CachedCanUseShortWaterCrossing(((Component)this).transform.position, point, allowWetDestination: false)) { LogWaterDiag($"crossing water directly to a waypoint {HorizontalDistance(((Component)this).transform.position, point):F0}m away"); Vector3 val = point - ((Component)this).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { ((Character)Merc).SetMoveDir(((Vector3)(ref val)).normalized); ((Character)Merc).SetRun(flag && !((Character)Merc).InWater()); ((BaseAI)this).LookAt(point); } return false; } ((BaseAI)this).MoveTowards(point, flag); return false; } internal bool TryGetAmbientApproach(Vector3 candidate, out Vector3 grounded) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_0088: 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) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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) //IL_00ed: 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) grounded = Vector3.zero; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || !instance.IsZoneLoaded(candidate)) { return false; } Vector3 grounded2 = candidate; RaycastHit val = default(RaycastHit); if (Physics.Raycast(candidate + Vector3.up * 1.5f, Vector3.down, ref val, 4f, AmbientGroundMask, (QueryTriggerInteraction)1)) { grounded2.y = ((RaycastHit)(ref val)).point.y + 0.1f; } else if (!TryGetDryGround(candidate, out grounded2)) { return false; } if (Mathf.Abs(grounded2.y - ((Component)this).transform.position.y) > 2.5f) { return false; } WaterVolume val2 = null; float waterLevel = Floating.GetWaterLevel(grounded2, ref val2); if ((Object)(object)val2 != (Object)null && waterLevel > grounded2.y + 0.25f) { return false; } if (HorizontalDistance(((Component)this).transform.position, grounded2) > 1.25f && !HavePathSafe(grounded2)) { return false; } grounded = grounded2; return true; } internal bool MoveForAmbient(float dt, Vector3 point, float stopDistance) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) ((Character)Merc).SetWalk(true); ((Character)Merc).SetRun(false); return ((BaseAI)this).MoveTo(dt, point, stopDistance, false); } internal void StopForAmbient() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ((BaseAI)this).StopMoving(); ((Character)Merc).SetMoveDir(Vector3.zero); ((Character)Merc).SetWalk(false); ((Character)Merc).SetRun(false); } internal void CancelAmbientCamp() { _campLife.Interrupt(Merc); } internal void PrepareResourceWork() { CancelGuideTask(null, announce: false); InterruptCombatForOrder(); CancelAmbientCamp(); ResetBaseRoaming(); ResetNavigationRecovery(); _recallGathering = false; } internal bool HasResourceWorkThreat() { if (IsActionableCombatTarget(_combatTarget)) { return true; } if (Time.time >= _resourceThreatScanAt) { _resourceThreatScanAt = Time.time + 0.4f; _resourceThreatPresent = (Object)(object)FindBestThreat(18f, null) != (Object)null; } return _resourceThreatPresent; } internal void MoveForResourceWork(float dt, Vector3 point) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) MoveToWithNavigationRecovery(dt, point, 0.5f, requestRun: false, requestWalk: true); } } public static class MercBanner { public const string HammerItemId = "MercHammer"; public const string BannerPieceId = "MercBanner"; public const string VanillaHammerId = "Hammer"; private static bool _missingVisualLogged; private static GameObject _bannerPrefab; private static ZNetScene _bannerScene; private static readonly Dictionary IconGlyphs = new Dictionary { ['M'] = new string[7] { "10001", "11011", "10101", "10101", "10001", "10001", "10001" }, ['E'] = new string[7] { "11111", "10000", "10000", "11110", "10000", "10000", "11111" }, ['R'] = new string[7] { "11110", "10001", "10001", "11110", "10100", "10010", "10001" }, ['C'] = new string[7] { "01110", "10001", "10000", "10000", "10000", "10001", "01110" }, ['N'] = new string[7] { "10001", "11001", "11001", "10101", "10011", "10011", "10001" }, ['A'] = new string[7] { "01110", "10001", "10001", "11111", "10001", "10001", "10001" }, ['Y'] = new string[7] { "10001", "10001", "10001", "01010", "00100", "00100", "00100" }, ['B'] = new string[7] { "11110", "10001", "10001", "11110", "10001", "10001", "11110" } }; private static Sprite _pieceIcon; public static bool IsRegistered { get { if ((Object)(object)_bannerScene != (Object)null) { return (Object)(object)_bannerScene == (Object)(object)ZNetScene.instance; } return false; } } public static void RegisterItems() { } public static void RegisterBannerPiece() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || (Object)(object)_bannerScene == (Object)(object)instance) { return; } try { PieceTable val = FindVanillaHammerTable(); if ((Object)(object)val == (Object)null) { MercPlugin.LogWarn("Vanilla Hammer piece table not found; banner piece will retry."); return; } if ((Object)(object)_bannerPrefab == (Object)null) { GameObject val2 = FindVisualBase(); if ((Object)(object)val2 == (Object)null) { if (!_missingVisualLogged) { _missingVisualLogged = true; MercPlugin.LogWarn("No base piece found for the banner visual."); } return; } GameObject val3 = new GameObject("MercBanner_Prefab"); val3.SetActive(false); Object.DontDestroyOnLoad((Object)(object)val3); _bannerPrefab = Object.Instantiate(val2, val3.transform, false); ((Object)_bannerPrefab).name = "MercBanner"; if (((Object)val2).name != "piece_banner01" && ((Object)val2).name != "piece_banner") { CraftingStation[] componentsInChildren = _bannerPrefab.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } } Piece val4 = _bannerPrefab.GetComponent(); if ((Object)(object)val4 == (Object)null) { val4 = _bannerPrefab.AddComponent(); } val4.m_name = "$dnpc_banner_name"; val4.m_description = "$dnpc_banner_description"; val4.m_enabled = true; val4.m_canBeRemoved = true; val4.m_allowAltGroundPlacement = true; val4.m_resources = Array.Empty(); val4.m_category = (PieceCategory)0; val4.m_craftingStation = null; Sprite val5 = BuildPieceIcon(); if ((Object)(object)val5 != (Object)null) { val4.m_icon = val5; } if ((Object)(object)_bannerPrefab.GetComponent() == (Object)null) { _bannerPrefab.AddComponent(); } PrepareInteractionVolume(_bannerPrefab); ConfigureInvulnerableBanner(_bannerPrefab); } ConfigureInvulnerableBanner(_bannerPrefab); MercPrefabs.RegisterPrefab(instance, _bannerPrefab); if (!val.m_pieces.Contains(_bannerPrefab)) { val.m_pieces.Add(_bannerPrefab); } _bannerScene = instance; MercPlugin.Log("Registered MercBanner piece in the vanilla Hammer Misc tab"); } catch (Exception arg) { MercPlugin.LogWarn($"Banner piece registration failed: {arg}"); } } private static Sprite BuildPieceIcon() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_pieceIcon != (Object)null) { return _pieceIcon; } Color32 color = default(Color32); ((Color32)(ref color))..ctor((byte)245, (byte)205, (byte)80, byte.MaxValue); Color32 color2 = default(Color32); ((Color32)(ref color2))..ctor((byte)20, (byte)24, (byte)28, (byte)238); Color32 color3 = default(Color32); ((Color32)(ref color3))..ctor((byte)245, (byte)238, (byte)214, byte.MaxValue); Color32[] array = (Color32[])(object)new Color32[6400]; FillIconRect(array, 80, 2, 2, 76, 76, color2); FillIconRect(array, 80, 2, 2, 76, 3, color); FillIconRect(array, 80, 2, 75, 76, 3, color); FillIconRect(array, 80, 2, 2, 3, 76, color); FillIconRect(array, 80, 75, 2, 3, 76, color); DrawIconWord(array, 80, "MERCENARY", 1, 44, color3); DrawIconWord(array, 80, "BANNER", 1, 30, color3); Texture2D val = new Texture2D(80, 80, (TextureFormat)4, false) { name = "DynamicNPCs_Banner_Icon", filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1 }; val.SetPixels32(array); val.Apply(false, true); _pieceIcon = Sprite.Create(val, new Rect(0f, 0f, 80f, 80f), new Vector2(0.5f, 0.5f), 80f); ((Object)_pieceIcon).name = ((Object)val).name; return _pieceIcon; } private static void DrawIconWord(Color32[] pixels, int size, string word, int scale, int bottomY, Color32 color) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) int num = word.Length * 5 * scale + (word.Length - 1) * scale; int num2 = (size - num) / 2; for (int i = 0; i < word.Length; i++) { DrawIconGlyph(pixels, size, word[i], num2 + i * 6 * scale, bottomY, scale, color); } } private static void DrawIconGlyph(Color32[] pixels, int size, char glyph, int x, int y, int scale, Color32 color) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!IconGlyphs.TryGetValue(glyph, out var value)) { return; } for (int i = 0; i < value.Length; i++) { for (int j = 0; j < value[i].Length; j++) { if (value[i][j] == '1') { FillIconRect(pixels, size, x + j * scale, y + (value.Length - 1 - i) * scale, scale, scale, color); } } } } private static void FillIconRect(Color32[] pixels, int size, int x, int y, int width, int height, Color32 color) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) for (int i = y; i < y + height; i++) { for (int j = x; j < x + width; j++) { pixels[i * size + j] = color; } } } private static PieceTable FindVanillaHammerTable() { try { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Hammer") : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); return ((Object)(object)val2 != (Object)null && val2.m_itemData != null) ? val2.m_itemData.m_shared.m_buildPieces : null; } catch { return null; } } private static void PrepareInteractionVolume(GameObject banner) { //IL_002a: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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) try { BoxCollider component = banner.GetComponent(); if ((Object)(object)component == (Object)null) { component = banner.AddComponent(); component.center = new Vector3(0f, -1.41f, 0f); component.size = new Vector3(0.9f, 3.28f, 1.2f); ((Collider)component).isTrigger = true; MercPlugin.Log("Prepared full-banner interaction volume: center=" + ((object)component.center/*cast due to .constrained prefix*/).ToString() + ", size=" + ((object)component.size/*cast due to .constrained prefix*/).ToString() + "."); } } catch (Exception ex) { MercPlugin.LogWarn("Could not prepare banner interaction volume: " + ex.Message); } } private static GameObject FindVisualBase() { string[] array = new string[4] { "piece_banner01", "piece_banner", "piece_sign", "piece_workbench" }; for (int i = 0; i < array.Length; i++) { GameObject prefabSafe = MercUtil.GetPrefabSafe(array[i]); if ((Object)(object)prefabSafe != (Object)null && (Object)(object)prefabSafe.GetComponent() != (Object)null) { return prefabSafe; } } return null; } internal static bool LocalPlayerIsAdminOrHost() { try { if (SynchronizationManager.Instance != null) { return SynchronizationManager.Instance.PlayerIsAdmin; } return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.LocalPlayerIsAdminOrHost(); } catch { return false; } } internal static bool IsBanner(Component component) { if ((Object)(object)component == (Object)null) { return false; } Piece val = (Piece)(((object)((component is Piece) ? component : null)) ?? ((object)component.GetComponent())); if ((Object)(object)val == (Object)null) { return false; } if (!((Object)(object)((Component)val).GetComponent() != (Object)null)) { return ((Object)((Component)val).gameObject).name.StartsWith("MercBanner", StringComparison.Ordinal); } return true; } internal static void EnsureRuntimeComponent(Piece piece) { if (!((Object)(object)piece == (Object)null) && !((Object)(object)((Component)piece).gameObject == (Object)null) && ((Object)((Component)piece).gameObject).name.StartsWith("MercBanner", StringComparison.Ordinal)) { if ((Object)(object)((Component)piece).GetComponent() == (Object)null) { ((Component)piece).gameObject.AddComponent(); MercPlugin.Log("Repaired missing banner runtime component on " + ((Object)((Component)piece).gameObject).name + "."); } ConfigureInvulnerableBanner(((Component)piece).gameObject); } } internal static bool PlayerHasBanner(Player player) { if ((Object)(object)player == (Object)null) { return false; } long playerID = player.GetPlayerID(); if (playerID == 0L || ZDOMan.instance == null) { return false; } int stableHashCode = StringExtensionMethods.GetStableHashCode("MercBanner"); try { foreach (ZDO item in BannerAuthority.AllZdosFromTable()) { if (item != null && item.IsValid() && item.GetPrefab() == stableHashCode && (item.GetLong(ZDOVars.s_creator, 0L) == playerID || item.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == playerID)) { return true; } } } catch { } return false; } internal static bool IsMercenaryHammerEquipped(Player player) { return HasBuildHammerEquipped(player); } internal static bool HasBuildHammerEquipped(Player player) { if ((Object)(object)player == (Object)null) { return false; } try { ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon(); if ((Object)(object)currentWeapon?.m_dropPrefab != (Object)null) { string prefabName = Utils.GetPrefabName(currentWeapon.m_dropPrefab); if (prefabName == "Hammer" || prefabName == "MercHammer") { return true; } } ZNetView component = ((Component)player).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null) { return false; } int num = val.GetInt(ZDOVars.s_rightItem, 0); return num == StringExtensionMethods.GetStableHashCode("Hammer") || num == StringExtensionMethods.GetStableHashCode("MercHammer") || num == StringExtensionMethods.GetStableHashCode("Mercenary Hammer"); } catch { return false; } } internal static int GetLocalRemovalToolHash(Player player) { try { ItemData val = ((player != null) ? ((Humanoid)player).GetCurrentWeapon() : null); if ((Object)(object)val?.m_dropPrefab != (Object)null) { string prefabName = Utils.GetPrefabName(val.m_dropPrefab); if (prefabName == "Hammer" || prefabName == "MercHammer") { return StringExtensionMethods.GetStableHashCode(prefabName); } } foreach (ItemData equippedItem in ((Humanoid)player).GetInventory().GetEquippedItems()) { if (!((Object)(object)equippedItem?.m_dropPrefab == (Object)null)) { string prefabName2 = Utils.GetPrefabName(equippedItem.m_dropPrefab); if (prefabName2 == "Hammer" || prefabName2 == "MercHammer") { return StringExtensionMethods.GetStableHashCode(prefabName2); } } } ZNetView val2 = (((Object)(object)player != (Object)null) ? ((Component)player).GetComponent() : null); ZDO val3 = (((Object)(object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO() : null); if (val3 != null) { int num = val3.GetInt(ZDOVars.s_rightItem, 0); if (num == StringExtensionMethods.GetStableHashCode("Hammer") || num == StringExtensionMethods.GetStableHashCode("MercHammer") || num == StringExtensionMethods.GetStableHashCode("Mercenary Hammer")) { return num; } } } catch { } return 0; } internal static void ConfigureInvulnerableBanner(GameObject banner) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown if (!((Object)(object)banner == (Object)null)) { Piece component = banner.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_canBeRemoved = true; } WearNTear component2 = banner.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.m_health = 255f; component2.m_noSupportWear = true; component2.m_destroyedEffect = new EffectList(); component2.m_hitEffect = new EffectList(); } } } } public class MercBannerSpawn : MonoBehaviour, Hoverable, Interactable { internal static readonly List Instances = new List(); internal static readonly int ClaimOwnerIdKey = StringExtensionMethods.GetStableHashCode("merc_bannerClaimOwnerId"); internal static readonly int ClaimOwnerNameKey = StringExtensionMethods.GetStableHashCode("merc_bannerClaimOwnerName"); internal static readonly int[] CustomNameKeys = new int[3] { StringExtensionMethods.GetStableHashCode("merc_tankCustomName"), StringExtensionMethods.GetStableHashCode("merc_healerCustomName"), StringExtensionMethods.GetStableHashCode("merc_archerCustomName") }; internal static readonly int[] RecruitedKeys = new int[3] { StringExtensionMethods.GetStableHashCode("merc_tankRecruited"), StringExtensionMethods.GetStableHashCode("merc_healerRecruited"), StringExtensionMethods.GetStableHashCode("merc_archerRecruited") }; internal static bool ServerRemovalInProgress; internal static readonly int PlacementAuthKey = StringExtensionMethods.GetStableHashCode("merc_bannerPlacementAuth"); internal static readonly int CompanyStabledKey = StringExtensionMethods.GetStableHashCode("merc_companyStabled"); internal static readonly int[] LastSeenKeys = new int[3] { StringExtensionMethods.GetStableHashCode("merc_lastSeen0"), StringExtensionMethods.GetStableHashCode("merc_lastSeen1"), StringExtensionMethods.GetStableHashCode("merc_lastSeen2") }; internal static readonly string[] MemberIdKeys = new string[3] { "merc_tankMemberZdo", "merc_healerMemberZdo", "merc_archerMemberZdo" }; internal static readonly MercClass[] Classes = new MercClass[3] { MercClass.Tank, MercClass.Healer, MercClass.Archer }; private ZNetView _nview; private float _lastRemoveRequestAt = -10f; private float _lastViewRetryAt = -10f; public void Start() { EnsureClientView(); } public void Update() { if (((Object)(object)_nview == (Object)null || !_nview.IsValid()) && Time.unscaledTime - _lastViewRetryAt >= 0.5f) { _lastViewRetryAt = Time.unscaledTime; EnsureClientView(); } } private void EnsureClientView() { if (!Instances.Contains(this)) { Instances.Add(this); } MercBanner.ConfigureInvulnerableBanner(((Component)this).gameObject); if ((Object)(object)_nview == (Object)null) { _nview = ((Component)this).GetComponent(); } } public void OnDestroy() { Instances.Remove(this); } public string GetHoverName() { return MercLocalization.Text("dnpc_banner_name"); } public float GetHoverOffset() { return 0f; } public string GetHoverText() { string text = MercLocalization.Text("dnpc_banner_name"); if ((Object)(object)_nview == (Object)null || !_nview.IsValid()) { return text; } ZDO zDO = _nview.GetZDO(); long num = zDO.GetLong(ClaimOwnerIdKey, 0L); string text2 = zDO.GetString(ClaimOwnerNameKey, ""); if (num != 0L) { Player localPlayer = Player.m_localPlayer; bool flag = (Object)(object)localPlayer != (Object)null && num == localPlayer.GetPlayerID(); string text3 = (flag ? MercLocalization.Text("dnpc_banner_claimed_you") : MercLocalization.Text("dnpc_banner_claimed_player", string.IsNullOrWhiteSpace(text2) ? MercLocalization.Text("dnpc_banner_another_player") : text2)); if (!flag) { return text + "\n" + text3; } bool flag2 = zDO.GetInt(CompanyStabledKey, 0) != 0; return text + "\n" + text3 + "\n[" + UseBinding() + "] " + (flag2 ? MercLocalization.Text("dnpc_banner_summon") : MercLocalization.Text("dnpc_banner_dismiss")); } return text + "\n[" + UseBinding() + "] " + MercLocalization.Text("dnpc_banner_claim"); } private static string UseBinding() { return MercLocalization.Binding("Use", "E"); } public bool Interact(Humanoid user, bool hold, bool alt) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) if (!hold) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null) { EnsureClientView(); if ((Object)(object)_nview == (Object)null || !_nview.IsValid()) { ((Character)val).Message((MessageType)2, MercLocalization.Text("dnpc_banner_loading"), 0, (Sprite)null, false); return true; } ZDO zDO = _nview.GetZDO(); long num = zDO.GetLong(ClaimOwnerIdKey, 0L); if (num == 0L) { BannerAuthority.RequestClaim(_nview.GetZDO().m_uid, PlayerProgression.LocalSeedFromGear()); } else if (num != val.GetPlayerID()) { ((Character)val).Message((MessageType)2, MercLocalization.Text("dnpc_banner_owned_by", zDO.GetString(ClaimOwnerNameKey, MercLocalization.Text("dnpc_banner_another_player"))), 0, (Sprite)null, false); } else if (zDO.GetInt(CompanyStabledKey, 0) != 0) { BannerAuthority.RequestSummon(_nview.GetZDO().m_uid); } else { BannerAuthority.RequestUnsummon(_nview.GetZDO().m_uid); } return true; } } return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } internal void RequestServerRemoval() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) EnsureClientView(); if (!((Object)(object)_nview == (Object)null) && _nview.IsValid() && !(Time.unscaledTime - _lastRemoveRequestAt < 0.5f)) { _lastRemoveRequestAt = Time.unscaledTime; BannerAuthority.RequestRemoval(_nview.GetZDO().m_uid, MercBanner.GetLocalRemovalToolHash(Player.m_localPlayer)); } } internal static string DefaultFirstName(MercClass @class) { return @class switch { MercClass.Healer => MercConfig.HealerName.Value, MercClass.Tank => MercConfig.TankName.Value, _ => MercConfig.ArcherName.Value, }; } internal static string SanitizeName(string value) { string input = Regex.Replace(value ?? "", "[\\u0000-\\u001f\\u007f<>]", ""); input = Regex.Replace(input, "\\s+", " ").Trim(); if (input.Length > 24) { input = input.Substring(0, 24).Trim(); } return input; } internal static void PrepareDismissedRespawn(ZDO banner, MercClass @class) { if (banner != null) { int num = Mathf.Clamp((int)@class, 0, MemberIdKeys.Length - 1); banner.RemoveZDOID(MemberIdKeys[num]); banner.Set(LastSeenKeys[num], 0L); } } } internal sealed class MercCampLife { private enum ActivityState { Idle, ApproachingChair, UsingChair, ApproachingStation, UsingStation, Posing } private const float FurnitureSearchRadius = 16f; private const float HeatSearchRadius = 8f; private const float ApproachTimeout = 18f; private const float ApproachStallTimeout = 4f; private const float PositionInterruptDistance = 0.85f; private static readonly Dictionary Reservations = new Dictionary(); private Mercenary _merc; private ActivityState _state; private Component _target; private int _reservedTargetId; private int _lastTargetId; private bool _hasFurnitureHistory; private bool _lastFurnitureWasChair; private float _idleClock; private float _idleDelay; private float _activityClock; private float _cooldownClock; private float _approachClock; private float _approachStallClock; private float _lastApproachDistance; private float _stationPulseClock; private Vector3 _approachPoint; private Vector3 _activityPosition; private Vector3 _focusPoint; internal bool IsEngaged => _state != ActivityState.Idle; internal bool Tick(Mercenary merc, MercAI ai, float dt, Vector3 fallbackFocus) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) _merc = merc; bool allowAttachment = _state == ActivityState.UsingChair; if (!FeatureEnabled() || !IsSafe(merc, allowAttachment)) { Interrupt(merc); return false; } _cooldownClock = Mathf.Max(0f, _cooldownClock - dt); switch (_state) { case ActivityState.ApproachingChair: case ActivityState.ApproachingStation: return TickApproach(merc, ai, dt); case ActivityState.UsingChair: return TickChair(merc, dt); case ActivityState.UsingStation: return TickStation(merc, dt); case ActivityState.Posing: return TickPose(merc, dt); default: { if (_cooldownClock > 0f) { return false; } if (_idleDelay <= 0f) { _idleDelay = NextIdleDelay(merc.Class); } Vector3 velocity = ((Character)merc).GetVelocity(); velocity.y = 0f; if (((Vector3)(ref velocity)).sqrMagnitude > 0.04f) { _idleClock = 0f; return false; } _idleClock += dt; if (_idleClock < _idleDelay) { return false; } if (TryBeginFurnitureActivity(merc, ai)) { return true; } BeginFallbackPose(merc, fallbackFocus); return IsEngaged; } } } internal void Interrupt(Mercenary merc) { bool isEngaged = IsEngaged; merc?.EndAmbientActivity(); ReleaseReservation(); _state = ActivityState.Idle; _target = null; _activityClock = 0f; _approachClock = 0f; _approachStallClock = 0f; _idleClock = 0f; _idleDelay = 0f; if (isEngaged) { _cooldownClock = Mathf.Max(_cooldownClock, Random.Range(12f, 20f)); } } internal void Reset(Mercenary merc) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_0097: 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) merc?.ResetAmbientPose(); ReleaseReservation(); _merc = merc; _state = ActivityState.Idle; _target = null; _lastTargetId = 0; _hasFurnitureHistory = false; _lastFurnitureWasChair = false; _idleClock = 0f; _idleDelay = 0f; _activityClock = 0f; _cooldownClock = 0f; _approachClock = 0f; _approachStallClock = 0f; _stationPulseClock = 0f; _approachPoint = Vector3.zero; _activityPosition = Vector3.zero; _focusPoint = Vector3.zero; } internal static void YieldChair(Chair chair) { if (!((Object)(object)chair == (Object)null)) { int instanceID = ((Object)chair).GetInstanceID(); if (Reservations.TryGetValue(instanceID, out var value)) { value?.Interrupt(value._merc); } } } private bool TickApproach(Mercenary merc, MercAI ai, float dt) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_target == (Object)null || !_target.gameObject.activeInHierarchy || (Object)(object)ai == (Object)null) { Interrupt(merc); return false; } Component target = _target; Chair val = (Chair)(object)((target is Chair) ? target : null); if ((Object)(object)val != (Object)null && ((Object)(object)val.m_attachPoint == (Object)null || IsPlayerUsing(val))) { Interrupt(merc); return false; } _approachClock -= dt; float num = HorizontalDistance(((Component)merc).transform.position, _approachPoint); if (num + 0.15f < _lastApproachDistance) { _lastApproachDistance = num; _approachStallClock = 0f; } else { _approachStallClock += dt; } if (_approachClock <= 0f || _approachStallClock >= 4f) { Interrupt(merc); return false; } if (num > 0.55f) { ai.MoveForAmbient(dt, _approachPoint, 0.35f); return true; } ai.StopForAmbient(); if (_state == ActivityState.ApproachingChair) { Component target2 = _target; val = (Chair)(object)((target2 is Chair) ? target2 : null); if ((Object)(object)val == (Object)null || (Object)(object)val.m_attachPoint == (Object)null || IsPlayerUsing(val) || !merc.BeginAmbientAttachment(val.m_attachPoint, string.IsNullOrEmpty(val.m_attachAnimation) ? "attach_chair" : val.m_attachAnimation, val.m_detachOffset)) { Interrupt(merc); return false; } _state = ActivityState.UsingChair; _activityClock = Random.Range(45f, 75f); MercPlugin.Log("Camp life: " + merc.GetName() + " is seated at " + $"{Utils.GetPrefabName(((Component)val).gameObject)} for {_activityClock:0}s."); return true; } Component target3 = _target; CraftingStation val2 = (CraftingStation)(object)((target3 is CraftingStation) ? target3 : null); if ((Object)(object)val2 == (Object)null || !merc.BeginAmbientCrafting(Mathf.Max(1, val2.m_useAnimation))) { Interrupt(merc); return false; } _state = ActivityState.UsingStation; _activityPosition = ((Component)merc).transform.position; _focusPoint = ((Component)val2).transform.position; _activityClock = Random.Range(45f, 75f); _stationPulseClock = 0f; MercPlugin.Log("Camp life: " + merc.GetName() + " is using " + $"{Utils.GetPrefabName(((Component)val2).gameObject)} for {_activityClock:0}s."); HoldStill(merc, _focusPoint); return true; } private bool TickChair(Mercenary merc, float dt) { Component target = _target; Chair val = (Chair)(object)((target is Chair) ? target : null); if ((Object)(object)val == (Object)null || (Object)(object)val.m_attachPoint == (Object)null || IsPlayerUsing(val) || !merc.MaintainAmbientAttachment()) { Interrupt(merc); return false; } _activityClock -= dt; if (_activityClock > 0f) { return true; } Finish(merc); return false; } private bool TickStation(Mercenary merc, float dt) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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) Component target = _target; CraftingStation val = (CraftingStation)(object)((target is CraftingStation) ? target : null); if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy || HorizontalDistance(((Component)merc).transform.position, _activityPosition) > 0.85f) { Interrupt(merc); return false; } _stationPulseClock -= dt; if (_stationPulseClock <= 0f) { _stationPulseClock = 0.75f; try { val.PokeInUse(); } catch { } } _activityClock -= dt; HoldStill(merc, ((Component)val).transform.position); if (_activityClock > 0f) { return true; } Finish(merc); return false; } private bool TickPose(Mercenary merc, float dt) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (HorizontalDistance(((Component)merc).transform.position, _activityPosition) > 0.85f) { Interrupt(merc); return false; } _activityClock -= dt; HoldStill(merc, _focusPoint); if (_activityClock > 0f) { return true; } Finish(merc); return false; } private bool TryBeginFurnitureActivity(Mercenary merc, MercAI ai) { if (_hasFurnitureHistory ? (!_lastFurnitureWasChair) : ((byte)merc.Class != 0)) { if (TryBeginChair(merc, ai)) { return true; } if (TryBeginStation(merc, ai)) { return true; } } else { if (TryBeginStation(merc, ai)) { return true; } if (TryBeginChair(merc, ai)) { return true; } } return false; } private bool TryBeginChair(Mercenary merc, MercAI ai) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) Chair val = null; Vector3 point = Vector3.zero; float num = float.MaxValue; Chair[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Chair val2 in array) { if ((Object)(object)val2 == (Object)null || val2.m_inShip || (Object)(object)val2.m_attachPoint == (Object)null || !((Component)val2).gameObject.activeInHierarchy || IsPlayerUsing(val2) || !ReservationAvailable((Component)(object)val2)) { continue; } float num2 = HorizontalDistance(((Component)merc).transform.position, val2.m_attachPoint.position); if (!(num2 > 16f) && TryFindChairApproach(merc, ai, val2, out var approach)) { float num3 = ((((Object)val2).GetInstanceID() == _lastTargetId) ? 32f : 0f); float num4 = num2 + num3 + Random.Range(0f, 4f); if (!(num4 >= num)) { val = val2; point = approach; num = num4; } } } if ((Object)(object)val == (Object)null || !Reserve((Component)(object)val)) { return false; } _lastTargetId = ((Object)val).GetInstanceID(); _hasFurnitureHistory = true; _lastFurnitureWasChair = true; ai.StopForAmbient(); BeginApproach((Component)(object)val, point, ActivityState.ApproachingChair, merc); return true; } private bool TryBeginStation(Mercenary merc, MercAI ai) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0107: 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_005d: 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_00bd: Unknown result type (might be due to invalid IL or missing references) CraftingStation val = null; Vector3 point = Vector3.zero; float num = float.MaxValue; CraftingStation[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (CraftingStation val2 in array) { if ((Object)(object)val2 == (Object)null || !((Component)val2).gameObject.activeInHierarchy || !ReservationAvailable((Component)(object)val2)) { continue; } float num2 = HorizontalDistance(((Component)merc).transform.position, ((Component)val2).transform.position); if (!(num2 > 16f) && TryFindStationApproach(merc, ai, val2, out var approach)) { float num3 = ((((Object)val2).GetInstanceID() == _lastTargetId) ? 32f : 0f); float num4 = num2 + num3 + Random.Range(0f, 4f); if (!(num4 >= num)) { val = val2; point = approach; num = num4; } } } if ((Object)(object)val == (Object)null || !Reserve((Component)(object)val)) { return false; } _lastTargetId = ((Object)val).GetInstanceID(); _hasFurnitureHistory = true; _lastFurnitureWasChair = false; ai.StopForAmbient(); BeginApproach((Component)(object)val, point, ActivityState.ApproachingStation, merc); return true; } private void BeginApproach(Component target, Vector3 point, ActivityState state, Mercenary merc) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) _target = target; _approachPoint = point; _state = state; _approachClock = 18f; _approachStallClock = 0f; _lastApproachDistance = HorizontalDistance(((Component)merc).transform.position, point); _idleClock = 0f; _idleDelay = 0f; } private static bool TryFindChairApproach(Mercenary merc, MercAI ai, Chair chair, out Vector3 approach) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) approach = Vector3.zero; Vector3 val = ((Component)merc).transform.position - chair.m_attachPoint.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = chair.m_attachPoint.forward; } ((Vector3)(ref val)).Normalize(); float[] array = new float[8] { 0f, 45f, -45f, 90f, -90f, 135f, -135f, 180f }; float num = float.MaxValue; for (int i = 0; i < array.Length; i++) { Vector3 val2 = Quaternion.Euler(0f, array[i], 0f) * val; Vector3 candidate = chair.m_attachPoint.position + val2 * 0.8f; if (ai.TryGetAmbientApproach(candidate, out var grounded)) { float num2 = HorizontalDistance(((Component)merc).transform.position, grounded); if (!(num2 >= num)) { num = num2; approach = grounded; } } } return num < float.MaxValue; } private static bool TryFindStationApproach(Mercenary merc, MercAI ai, CraftingStation station, out Vector3 approach) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) approach = Vector3.zero; Vector3 val = ((Component)merc).transform.position - ((Component)station).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = -((Component)station).transform.forward; } ((Vector3)(ref val)).Normalize(); float num = Mathf.Clamp(station.m_useDistance * 0.65f, 0.9f, 1.35f); float[] array = new float[8] { 0f, 45f, -45f, 90f, -90f, 135f, -135f, 180f }; float num2 = float.MaxValue; for (int i = 0; i < array.Length; i++) { Vector3 val2 = Quaternion.Euler(0f, array[i], 0f) * val; Vector3 candidate = ((Component)station).transform.position + val2 * num; if (ai.TryGetAmbientApproach(candidate, out var grounded)) { float num3 = HorizontalDistance(((Component)merc).transform.position, grounded); if (!(num3 >= num2)) { num2 = num3; approach = grounded; } } } return num2 < float.MaxValue; } private void BeginFallbackPose(Mercenary merc, Vector3 fallbackFocus) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) EffectArea val = EffectArea.IsPointInsideArea(((Component)merc).transform.position, (Type)1, 8f); bool flag = (Object)(object)val != (Object)null; string animation = (flag ? "emote_sit" : ((merc.Class == MercClass.Tank) ? "emote_challenge" : ((merc.Class == MercClass.Archer) ? "emote_point" : "emote_wave"))); bool flag2 = flag; _focusPoint = (flag ? ((Component)val).transform.position : fallbackFocus); if (HorizontalDistance(((Component)merc).transform.position, _focusPoint) < 0.25f) { _focusPoint = ((Component)merc).transform.position + ((Component)merc).transform.forward * 3f; } if (!merc.BeginAmbientPose(animation, flag2)) { Rearm(); return; } _state = ActivityState.Posing; _activityPosition = ((Component)merc).transform.position; _activityClock = (flag2 ? Random.Range(25f, 45f) : Random.Range(5f, 8f)); HoldStill(merc, _focusPoint); } private void Finish(Mercenary merc) { merc.EndAmbientActivity(); ReleaseReservation(); _state = ActivityState.Idle; _target = null; _activityClock = 0f; Rearm(); } private void Rearm() { _idleClock = 0f; _idleDelay = 0f; _cooldownClock = Random.Range(12f, 20f); } private bool Reserve(Component target) { if ((Object)(object)target == (Object)null || !ReservationAvailable(target)) { return false; } int instanceID = ((Object)target).GetInstanceID(); Reservations[instanceID] = this; _reservedTargetId = instanceID; return true; } private bool ReservationAvailable(Component target) { int instanceID = ((Object)target).GetInstanceID(); if (!Reservations.TryGetValue(instanceID, out var value)) { return true; } if (value == null || !value.IsEngaged) { Reservations.Remove(instanceID); return true; } return value == this; } private void ReleaseReservation() { if (_reservedTargetId != 0) { if (Reservations.TryGetValue(_reservedTargetId, out var value) && value == this) { Reservations.Remove(_reservedTargetId); } _reservedTargetId = 0; } } private static bool FeatureEnabled() { if (MercConfig.AmbientCampLifeEnabled != null) { return MercConfig.AmbientCampLifeEnabled.Value; } return false; } private static bool IsSafe(Mercenary merc, bool allowAttachment) { if ((Object)(object)merc == (Object)null || ((Character)merc).IsDead() || ((Character)merc).InAttack() || ((Character)merc).IsDrawingBow() || ((Character)merc).InDodge() || ((Character)merc).IsStaggering() || ((Character)merc).IsKnockedBack() || ((Character)merc).IsSwimming() || ((Character)merc).InWater() || (!allowAttachment && !((Character)merc).IsOnGround())) { return false; } ZNetView component = ((Component)merc).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { return component.IsOwner(); } return false; } private static float NextIdleDelay(MercClass mercClass) { return mercClass switch { MercClass.Tank => 5f, MercClass.Healer => 3f, _ => 7f, } + Random.Range(0f, 2f); } private static bool IsPlayerUsing(Chair chair) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)chair != (Object)null && (Object)(object)chair.m_attachPoint != (Object)null) { return (Object)(object)Player.GetClosestPlayer(chair.m_attachPoint.position, 0.45f) != (Object)null; } return false; } private static void HoldStill(Mercenary merc, Vector3 focus) { //IL_0001: 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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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) ((Character)merc).SetMoveDir(Vector3.zero); ((Character)merc).SetWalk(false); ((Character)merc).SetRun(false); Vector3 val = focus - ((Component)merc).transform.position; if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { ((Character)merc).SetLookDir(((Vector3)(ref val)).normalized, 0.2f); } } private static float HorizontalDistance(Vector3 left, Vector3 right) { //IL_0018: 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) left.y = 0f; right.y = 0f; return Vector3.Distance(left, right); } } internal enum MercCommandPressAction { None, Context, Menu } internal sealed class MercCommandPress { internal const float HoldSeconds = 1.5f; private float _started; internal bool IsPending { get; private set; } internal void Reset() { IsPending = false; } internal MercCommandPressAction Step(bool pressed, bool held, bool allowed, float now) { if (!allowed || float.IsNaN(now) || float.IsInfinity(now)) { Reset(); return MercCommandPressAction.None; } if (!IsPending) { if (!pressed) { return MercCommandPressAction.None; } IsPending = true; _started = now; } float num = now - _started; if (num < 0f) { Reset(); return MercCommandPressAction.None; } if (num >= 1.5f) { Reset(); return MercCommandPressAction.Menu; } if (!held) { Reset(); return MercCommandPressAction.Context; } return MercCommandPressAction.None; } } internal static class MercCommandRules { internal static bool MayReceiveTarget(bool employedByRequester, bool followingRequester, bool alive) { return employedByRequester && followingRequester && alive; } } internal static class MercCompanyHud { private sealed class RowView { public GameObject Root; public Image Accent; public Image HealthFill; public RectTransform HealthFillRect; public Text Name; public Text Status; public Text Health; public Text Distance; } private static readonly List Rows = new List(); private static readonly Dictionary Pins = new Dictionary(); private static readonly FieldInfo PinUpdateRequiredField = typeof(Minimap).GetField("m_pinUpdateRequired", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly Color PanelBackground = new Color(0.125f, 0.125f, 0.125f, 0.9f); private static readonly Color HeaderBlue = new Color(0.161f, 0.373f, 0.553f, 0.96f); private static readonly Color RowDark = new Color(0.153f, 0.153f, 0.153f, 0.94f); private static readonly Color RowLight = new Color(0.207f, 0.207f, 0.207f, 0.94f); private static readonly Color HealthBack = new Color(0.055f, 0.055f, 0.055f, 0.96f); private static readonly Color BodyText = new Color(0.988f, 0.988f, 0.988f); private static readonly Color MutedText = new Color(0.76f, 0.76f, 0.76f); private static readonly Color TankAccent = new Color(0.988f, 0.612f, 0.235f); private static readonly Color HealerAccent = new Color(0.498f, 1f, 0.831f); private static readonly Color ArcherAccent = new Color(1f, 0.843f, 0f); private static readonly Color32 CompanyPinBlue = new Color32((byte)55, (byte)145, (byte)235, byte.MaxValue); private const float PanelWidth = 304f; private const float HeaderHeight = 30f; private const float RowHeight = 42f; private const float InnerWidth = 294f; private const float HealthWidth = 202f; private const int CompanySize = 3; private static GameObject _panel; private static RectTransform _panelRect; private static Text _header; private static Minimap _pinMap; private static Sprite _companyPinSprite; private static float _nextRefresh; private static bool _pinRefreshFailureLogged; private static bool _pinSpriteFailureLogged; internal static void Tick() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { Reset(); } else if (!(Time.unscaledTime < _nextRefresh)) { _nextRefresh = Time.unscaledTime + 0.2f; List company = FindLoadedCompany(localPlayer); UpdateHud(localPlayer, company); UpdatePins(localPlayer, company); } } internal static void Reset() { if ((Object)(object)_panel != (Object)null) { Object.Destroy((Object)(object)_panel); } _panel = null; _panelRect = null; _header = null; Rows.Clear(); RemovePins(); _nextRefresh = 0f; _pinRefreshFailureLogged = false; _pinSpriteFailureLogged = false; } private static List FindLoadedCompany(Player player) { List list = new List(3); for (int i = 0; i < Mercenary.Instances.Count; i++) { Mercenary mercenary = Mercenary.Instances[i]; if (!((Object)(object)mercenary == (Object)null) && !((Object)(object)((Component)mercenary).gameObject == (Object)null) && mercenary.IsAssignedTo(player, requireFollowing: false)) { list.Add(mercenary); } } list.Sort((Mercenary left, Mercenary right) => ((int)left.Class).CompareTo((int)right.Class)); return list; } private static void UpdateHud(Player player, List company) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) bool num = MercConfig.CompanyHudEnabled != null && MercConfig.CompanyHudEnabled.Value; bool flag = GUIManager.Instance != null && (Object)(object)GUIManager.CustomGUIFront != (Object)null; bool flag2 = MercGuideMenu.IsOpen || Menu.IsVisible() || Console.IsVisible() || TextInput.IsVisible() || InventoryGui.IsVisible() || Minimap.IsOpen(); if (!num || !flag || flag2 || company.Count == 0) { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } return; } if ((Object)(object)_panel == (Object)null) { try { BuildHud(); } catch (Exception ex) { MercPlugin.LogWarn("Could not create the company HUD: " + ex.Message); Reset(); return; } } _panel.SetActive(true); _panelRect.anchoredPosition = new Vector2(MercConfig.CompanyHudOffsetX.Value, MercConfig.CompanyHudOffsetY.Value); float value = MercConfig.CompanyHudScale.Value; ((Transform)_panelRect).localScale = new Vector3(value, value, 1f); int num2 = Mathf.Min(company.Count, 3); string text = (ZInput.IsGamepadActive() ? MercLocalization.Binding("JoyRadial", "R3") : ((object)MercConfig.GuideMenuHotkey.Value/*cast due to .constrained prefix*/).ToString()); _header.text = MercLocalization.Text("dnpc_hud_company_controls", num2, text); float num3 = 30f + (float)num2 * 42f + 8f; _panelRect.sizeDelta = new Vector2(304f, num3); for (int i = 0; i < Rows.Count; i++) { bool flag3 = i < num2; Rows[i].Root.SetActive(flag3); if (flag3) { ((RectTransform)Rows[i].Root.transform).anchoredPosition = new Vector2(0f, -34f - (float)i * 42f); UpdateRow(Rows[i], company[i], player); } } } private static void BuildHud() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0119: 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_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) _panel = new GameObject("DynamicNPCs_CompanyHud", new Type[2] { typeof(RectTransform), typeof(Image) }); _panelRect = _panel.GetComponent(); ((Transform)_panelRect).SetParent(GUIManager.CustomGUIFront.transform, false); RectTransform panelRect = _panelRect; RectTransform panelRect2 = _panelRect; RectTransform panelRect3 = _panelRect; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0f, 1f); panelRect3.pivot = val; Vector2 anchorMin = (panelRect2.anchorMax = val); panelRect.anchorMin = anchorMin; Image component = _panel.GetComponent(); ((Graphic)component).color = PanelBackground; ((Graphic)component).raycastTarget = false; GameObject val3 = new GameObject("Header", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component2 = val3.GetComponent(); ((Transform)component2).SetParent(_panel.transform, false); component2.anchorMin = new Vector2(0f, 1f); component2.anchorMax = new Vector2(1f, 1f); component2.pivot = new Vector2(0.5f, 1f); component2.anchoredPosition = Vector2.zero; component2.sizeDelta = new Vector2(-10f, 30f); Image component3 = val3.GetComponent(); ((Graphic)component3).color = HeaderBlue; ((Graphic)component3).raycastTarget = false; _header = CreateText("", val3.transform, Vector2.zero, 280f, 30f, 15, Color.white, (TextAnchor)3, bold: true); for (int i = 0; i < 3; i++) { Rows.Add(BuildRow(i)); } } private static RowView BuildRow(int index) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0062: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012e: 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_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0358: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Member" + index, new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component = val.GetComponent(); ((Transform)component).SetParent(_panel.transform, false); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 1f); component.pivot = val2; Vector2 val3 = (component.anchorMin = (component.anchorMax = val2)); component.sizeDelta = new Vector2(294f, 40f); Image component2 = val.GetComponent(); ((Graphic)component2).color = ((index % 2 == 0) ? RowLight : RowDark); ((Graphic)component2).raycastTarget = false; GameObject val6 = new GameObject("RoleAccent", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component3 = val6.GetComponent(); ((Transform)component3).SetParent(val.transform, false); component3.anchorMin = new Vector2(0f, 0f); component3.anchorMax = new Vector2(0f, 1f); component3.pivot = new Vector2(0f, 0.5f); component3.anchoredPosition = Vector2.zero; component3.sizeDelta = new Vector2(4f, -4f); Image component4 = val6.GetComponent(); ((Graphic)component4).raycastTarget = false; GameObject val7 = new GameObject("HealthBack", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component5 = val7.GetComponent(); ((Transform)component5).SetParent(val.transform, false); ((Vector2)(ref val3))..ctor(0.5f, 0.5f); component5.anchorMax = val3; component5.anchorMin = val3; component5.pivot = new Vector2(0f, 0.5f); component5.anchoredPosition = new Vector2(-137f, -11f); component5.sizeDelta = new Vector2(202f, 9f); Image component6 = val7.GetComponent(); ((Graphic)component6).color = HealthBack; ((Graphic)component6).raycastTarget = false; GameObject val8 = new GameObject("HealthFill", new Type[2] { typeof(RectTransform), typeof(Image) }); RectTransform component7 = val8.GetComponent(); ((Transform)component7).SetParent(val7.transform, false); ((Vector2)(ref val3))..ctor(0f, 0.5f); component7.anchorMax = val3; component7.anchorMin = val3; component7.pivot = new Vector2(0f, 0.5f); component7.anchoredPosition = Vector2.zero; component7.sizeDelta = new Vector2(202f, 7f); Image component8 = val8.GetComponent(); ((Graphic)component8).raycastTarget = false; return new RowView { Root = val, Accent = component4, HealthFill = component8, HealthFillRect = component7, Name = CreateText("", val.transform, new Vector2(-45f, 8f), 180f, 20f, 13, BodyText, (TextAnchor)3, bold: true), Status = CreateText("", val.transform, new Vector2(94f, 8f), 92f, 20f, 12, MutedText, (TextAnchor)5, bold: false), Health = CreateText("", val.transform, new Vector2(-36f, -11f), 202f, 16f, 10, BodyText, (TextAnchor)4, bold: false), Distance = CreateText("", val.transform, new Vector2(112f, -11f), 54f, 16f, 10, MutedText, (TextAnchor)5, bold: false) }; } private static Text CreateText(string value, Transform parent, Vector2 position, float width, float height, int size, Color color, TextAnchor alignment, bool bold) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) Font val = (bold ? GUIManager.Instance.AveriaSerifBold : GUIManager.Instance.AveriaSerif); Text component = GUIManager.Instance.CreateText(value, parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), position, val, size, color, true, Color.black, width, height, false).GetComponent(); component.alignment = alignment; component.horizontalOverflow = (HorizontalWrapMode)0; component.verticalOverflow = (VerticalWrapMode)0; ((Graphic)component).raycastTarget = false; return component; } private static void UpdateRow(RowView row, Mercenary merc, Player player) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(1f, ((Character)merc).GetMaxHealth()); float num2 = Mathf.Clamp(((Character)merc).GetHealth(), 0f, num); float num3 = Mathf.Clamp01(num2 / num); row.Name.text = merc.GetName(); row.Status.text = MercLocalization.Text(StatusToken(merc, player)); row.Health.text = MercLocalization.Text("dnpc_hud_health", Mathf.CeilToInt(num2), Mathf.CeilToInt(num)); row.Distance.text = MercLocalization.Text("dnpc_hud_distance", Mathf.RoundToInt(Vector3.Distance(((Component)player).transform.position, ((Component)merc).transform.position))); ((Graphic)row.Accent).color = RoleColor(merc.Class); ((Graphic)row.HealthFill).color = HealthColor(num3); row.HealthFillRect.sizeDelta = new Vector2(202f * num3, 7f); } private static string StatusToken(Mercenary merc, Player player) { if (((Character)merc).IsDead()) { return "dnpc_hud_downed"; } if ((Object)(object)merc.Ai != (Object)null && merc.Ai.IsSneakingWithEmployer) { return "dnpc_hud_sneaking"; } if (MercResourceWork.IsWorking(merc)) { return "dnpc_hud_working"; } if ((Object)(object)merc.Ai != (Object)null && merc.Ai.IsGuiding) { return "dnpc_hud_guiding"; } if (!merc.IsAssignedTo(player, requireFollowing: true)) { return "dnpc_hud_guarding"; } return "dnpc_hud_following"; } private static Color RoleColor(MercClass mercClass) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return (Color)(mercClass switch { MercClass.Archer => ArcherAccent, MercClass.Healer => HealerAccent, _ => TankAccent, }); } private static Color HealthColor(float fraction) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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) if (fraction > 0.55f) { return new Color(0.35f, 0.82f, 0.4f); } if (fraction > 0.25f) { return new Color(0.94f, 0.72f, 0.2f); } return new Color(0.9f, 0.28f, 0.22f); } private static void UpdatePins(Player player, List company) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012d: 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_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) bool num = MercConfig.CompanyMapPinsEnabled != null && MercConfig.CompanyMapPinsEnabled.Value; Minimap instance = Minimap.instance; if (!num || (Object)(object)instance == (Object)null) { RemovePins(); return; } if ((Object)(object)_pinMap != (Object)(object)instance) { RemovePins(); _pinMap = instance; } HashSet hashSet = new HashSet(company); List list = new List(); foreach (KeyValuePair pin in Pins) { if ((Object)(object)pin.Key == (Object)null || !hashSet.Contains(pin.Key)) { list.Add(pin.Key); } } foreach (Mercenary item in list) { RemovePin(item); } foreach (Mercenary item2 in company) { if (item2.IsAssignedTo(player, requireFollowing: false)) { if (!Pins.TryGetValue(item2, out var value) || value == null) { value = instance.AddPin(((Component)item2).transform.position, (PinType)10, item2.GetName(), false, false, 0L, default(PlatformUserID)); Pins[item2] = value; } Sprite companyPinSprite = GetCompanyPinSprite(value.m_icon); value.m_pos = ((Component)item2).transform.position; value.m_name = item2.GetName(); value.m_icon = companyPinSprite; if ((Object)(object)value.m_iconElement != (Object)null) { value.m_iconElement.sprite = companyPinSprite; } } } RequestPinRefresh(instance); } private static Sprite GetCompanyPinSprite(Sprite playerSprite) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00a8: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01df: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_companyPinSprite != (Object)null) { return _companyPinSprite; } if ((Object)(object)playerSprite == (Object)null || (Object)(object)playerSprite.texture == (Object)null) { return playerSprite; } RenderTexture active = RenderTexture.active; RenderTexture val = null; try { Texture2D texture = playerSprite.texture; val = RenderTexture.GetTemporary(((Texture)texture).width, ((Texture)texture).height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2); Graphics.Blit((Texture)(object)texture, val); RenderTexture.active = val; Texture2D val2 = new Texture2D(((Texture)texture).width, ((Texture)texture).height, (TextureFormat)4, false) { name = "DynamicNPCs_BluePlayerPin", filterMode = ((Texture)texture).filterMode, wrapMode = (TextureWrapMode)1 }; val2.ReadPixels(new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), 0, 0, false); Color32[] pixels = val2.GetPixels32(); for (int i = 0; i < pixels.Length; i++) { Color32 val3 = pixels[i]; if (val3.a != 0 && !((float)(int)val3.r <= (float)(int)val3.g * 1.2f) && !((float)(int)val3.r <= (float)(int)val3.b * 1.2f)) { float num = (float)(int)val3.r / 255f; val3.r = (byte)Mathf.RoundToInt((float)(int)CompanyPinBlue.r * num); val3.g = (byte)Mathf.RoundToInt((float)(int)CompanyPinBlue.g * num); val3.b = (byte)Mathf.RoundToInt((float)(int)CompanyPinBlue.b * num); pixels[i] = val3; } } val2.SetPixels32(pixels); val2.Apply(false, true); Rect rect = playerSprite.rect; Vector2 val4 = default(Vector2); ((Vector2)(ref val4))..ctor(playerSprite.pivot.x / ((Rect)(ref rect)).width, playerSprite.pivot.y / ((Rect)(ref rect)).height); _companyPinSprite = Sprite.Create(val2, rect, val4, playerSprite.pixelsPerUnit, 0u, (SpriteMeshType)0, playerSprite.border); ((Object)_companyPinSprite).name = ((Object)val2).name; return _companyPinSprite; } catch (Exception ex) { if (!_pinSpriteFailureLogged) { _pinSpriteFailureLogged = true; MercPlugin.LogWarn("Could not recolor the company player pin: " + ex.Message); } return playerSprite; } finally { RenderTexture.active = active; if ((Object)(object)val != (Object)null) { RenderTexture.ReleaseTemporary(val); } } } private static void RequestPinRefresh(Minimap map) { try { PinUpdateRequiredField?.SetValue(map, true); } catch (Exception ex) { if (!_pinRefreshFailureLogged) { _pinRefreshFailureLogged = true; MercPlugin.LogWarn("Could not refresh company map pins: " + ex.Message); } } } private static void RemovePin(Mercenary merc) { if (Pins.TryGetValue(merc, out var value)) { if ((Object)(object)_pinMap != (Object)null && value != null) { _pinMap.RemovePin(value); } Pins.Remove(merc); } } private static void RemovePins() { if ((Object)(object)_pinMap != (Object)null) { foreach (PinData value in Pins.Values) { if (value != null) { _pinMap.RemovePin(value); } } } Pins.Clear(); _pinMap = null; } } public static class MercConfig { public static ConfigEntry ProgressionPerPlayer; public static ConfigEntry GuideMenuHotkey; public static ConfigEntry RespawnDelaySeconds; public static ConfigEntry FollowTeleportDistance; public static ConfigEntry PreventFriendlyFire; public static ConfigEntry PlayerPassThroughEnabled; public static ConfigEntry RenameHotkey; public static ConfigEntry AmbientCampLifeEnabled; public static ConfigEntry ContextualRemarksEnabled; public static ConfigEntry ContextCompanyOrdersEnabled; public static ConfigEntry ContextCompanyOrderHotkey; public static ConfigEntry HoldCompanyOrderHotkey; public static ConfigEntry RecallCompanyOrderHotkey; public static ConfigEntry CancelCompanyGuideHotkey; public static ConfigEntry CompanyHudEnabled; public static ConfigEntry CompanyHudScale; public static ConfigEntry CompanyHudOffsetX; public static ConfigEntry CompanyHudOffsetY; public static ConfigEntry CompanyMapPinsEnabled; public static ConfigEntry SkillBase; public static ConfigEntry SkillPerStage; public static ConfigEntry MaxSkill; public static ConfigEntry DamageDealtMultiplier; public static ConfigEntry HealRange; public static ConfigEntry HealTriggerFraction; public static ConfigEntry RenewalCooldown; public static ConfigEntry RenewalTickAmount; public static ConfigEntry RenewalTickPerStage; public static ConfigEntry RenewalTickInterval; public static ConfigEntry RenewalTickCount; public static ConfigEntry BurstHealCooldown; public static ConfigEntry BurstHealAmount; public static ConfigEntry BurstHealPerStage; public static ConfigEntry BurstHealOverTimeAmount; public static ConfigEntry BurstHealOverTimePerStage; public static ConfigEntry BurstHealDurationSeconds; public static ConfigEntry BurstHealThreshold; public static ConfigEntry ResurrectionEnabled; public static ConfigEntry ResurrectionCooldown; public static ConfigEntry ResurrectionRange; public static ConfigEntry ResurrectionCastSeconds; public static ConfigEntry ResurrectionHealthFraction; public static ConfigEntry ResurrectionGraceSeconds; public static ConfigEntry ArcherPreferredRange; public static ConfigEntry ArcherRetreatRange; public static ConfigEntry ArcherTargetRange; public static ConfigEntry LlmEnabled; public static ConfigEntry LlmBaseUrl; public static ConfigEntry LlmApiKey; public static ConfigEntry LlmModel; public static ConfigEntry LlmMaxTokens; public static ConfigEntry LlmTemperature; public static ConfigEntry LlmTimeoutSeconds; public static ConfigEntry LlmServerMaxConcurrent; public static ConfigEntry LlmServerRequestsPerMinute; public static ConfigEntry ProactiveTips; public static ConfigEntry TipIntervalMinutes; public static ConfigEntry CustomInstructions; public static ConfigEntry HearPlayerChat; public static ConfigEntry ChatHearingRange; public static ConfigEntry AgentChatEnabled; public static ConfigEntry AgentName; public static ConfigEntry AiRequestCooldownSeconds; public static ConfigEntry ServerDialogueRequestsPerMinute; public static ConfigEntry ShareOtherPlayersActivity; public static ConfigEntry SharePlayerLocations; public static ConfigEntry IndexWorldContainers; public static ConfigEntry SnikkEnabled; public static ConfigEntry SnikkMinimumVisitDays; public static ConfigEntry SnikkMaximumVisitDays; public static ConfigEntry SnikkForcedFindDays; public static ConfigEntry SnikkSchedulerPollSeconds; public static ConfigEntry SnikkRetrySeconds; public static ConfigEntry SnikkSpawnMinimumDistance; public static ConfigEntry SnikkSpawnMaximumDistance; public static ConfigEntry SnikkSightingDistance; public static ConfigEntry SnikkSightingSeconds; public static ConfigEntry SnikkIdleMinimumSeconds; public static ConfigEntry SnikkIdleMaximumSeconds; public static ConfigEntry SnikkHardTimeoutSeconds; public static ConfigEntry SnikkRecentVisitBlock; public static ConfigEntry TankName; public static ConfigEntry HealerName; public static ConfigEntry ArcherName; public static void Bind(ConfigFile config) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Expected O, but got Unknown ProgressionPerPlayer = BindSynced(config, "General", "ProgressionPerPlayer", defaultValue: true, "Mercenary gear, food and power follow each employer's server-authoritative, world-scoped PERSONAL progression (seeded from their equipment when they claim/recruit, raised when they help defeat a boss, and mirrored to every simulating peer) instead of the world's global boss keys."); RespawnDelaySeconds = BindSynced(config, "General", "RespawnDelaySeconds", 60f, 5f, 86400f, "Seconds before a dead mercenary respawns at its banner."); FollowTeleportDistance = BindSynced(config, "General", "FollowTeleportDistance", 30f, 5f, 200f, "If a following mercenary falls this far behind, it teleports to the owner."); PreventFriendlyFire = BindSynced(config, "General", "PreventFriendlyFire", defaultValue: true, "Mercenaries take no damage from players (prevents accidental kills)."); PlayerPassThroughEnabled = BindSynced(config, "General", "PlayerPassThroughEnabled", defaultValue: true, "Let players pass through recruited mercenaries so companions cannot physically block doors, stairs, crafting stations, or narrow base corridors. World and enemy collision are unchanged."); AmbientCampLifeEnabled = BindSynced(config, "General", "AmbientCampLifeEnabled", defaultValue: true, "Allow safe, idle mercenaries to use cosmetic camp and rest poses. This creates no objects, bonuses, production, or persistent ambient state."); ContextCompanyOrdersEnabled = BindSynced(config, "General", "ContextCompanyOrdersEnabled", defaultValue: true, "Enable server-authoritative company orders and contextual enemy/resource commands from the company binding."); GuideMenuHotkey = config.Bind("Controls", "GuideMenu", new KeyboardShortcut((KeyCode)103, Array.Empty()), "Tap and release to command living followers to attack the aimed enemy or work on the aimed tree/log/mineable rock. Hold for 1.5 seconds to open the rotating company wheel, regardless of your target; that hold sends no activity order. Tapping without a valid target does nothing. Controller uses the same tap/hold gesture on its radial binding. In the wheel, mouse/right stick aims; wheel/arrows/D-pad rotate; click/Enter/radial-interact/A selects; right-click/Escape/radial-back/B goes back."); RetireCommandBindings(config); CompanyHudEnabled = config.Bind("Interface", "CompanyHudEnabled", true, "Show a compact local HUD for your currently loaded mercenary company."); CompanyHudScale = config.Bind("Interface", "CompanyHudScale", 1f, new ConfigDescription("Local scale of the company HUD.", (AcceptableValueBase)(object)new AcceptableValueRange(0.65f, 1.5f), Array.Empty())); CompanyHudOffsetX = config.Bind("Interface", "CompanyHudOffsetX", 24f, new ConfigDescription("Horizontal company HUD offset in pixels from the top-left corner.", (AcceptableValueBase)(object)new AcceptableValueRange(-2000f, 4000f), Array.Empty())); CompanyHudOffsetY = config.Bind("Interface", "CompanyHudOffsetY", -260f, new ConfigDescription("Vertical company HUD offset in pixels from the top-left corner (negative moves down).", (AcceptableValueBase)(object)new AcceptableValueRange(-4000f, 2000f), Array.Empty())); CompanyMapPinsEnabled = config.Bind("Interface", "CompanyMapPinsEnabled", false, "Show unsaved, owner-only local map pins for your currently loaded mercenaries. Disabled by default."); RemoveLegacySurvivalSettings(config); SkillBase = BindSynced(config, "Stats", "SkillBase", 35f, 0f, 100f, "Weapon skill level (0-100) at stage 0."); SkillPerStage = BindSynced(config, "Stats", "SkillPerStage", 8f, 0f, 100f, "Extra weapon skill per stage."); MaxSkill = BindSynced(config, "Stats", "MaxSkill", 90f, 1f, 100f, "Weapon skill cap."); DamageDealtMultiplier = BindSynced(config, "Stats", "DamageDealtMultiplier", 1f, 0.05f, 20f, "Multiplier on all mercenary damage."); HealRange = BindSynced(config, "Healer", "HealRange", 15f, 1f, 100f, "Heal range in meters."); HealTriggerFraction = BindSynced(config, "Healer", "HealTriggerFraction", 0.7f, 0.05f, 1f, "Renewal targets allies below this health fraction (0.7 = below 70%)."); RenewalCooldown = BindSynced(config, "Healer - Renewal", "Cooldown", 45f, 1f, 3600f, "Seconds between Renewal casts."); RenewalTickAmount = BindSynced(config, "Healer - Renewal", "HealPerTick", 8f, 0f, 1000f, "Healing per Renewal tick at stage 0."); RenewalTickPerStage = BindSynced(config, "Healer - Renewal", "HealPerTickPerBossStage", 4f, 0f, 1000f, "Extra healing per Renewal tick for each progression stage."); RenewalTickInterval = BindSynced(config, "Healer - Renewal", "TickInterval", 2f, 0.1f, 60f, "Seconds between Renewal healing ticks."); RenewalTickCount = BindSynced(config, "Healer - Renewal", "TickCount", 5, 1, 100, "Number of healing ticks in one Renewal."); BurstHealCooldown = BindSynced(config, "Healer - Greater Heal", "Cooldown", 60f, 1f, 3600f, "Seconds between Greater Heal casts."); BurstHealAmount = BindSynced(config, "Healer - Greater Heal", "HealAmount", 40f, 0f, 10000f, "Initial healing at stage 0."); BurstHealPerStage = BindSynced(config, "Healer - Greater Heal", "HealPerBossStage", 20f, 0f, 10000f, "Extra initial healing for each defeated-boss progression stage."); BurstHealOverTimeAmount = BindSynced(config, "Healer - Greater Heal", "HealOverTimePerSecond", 5f, 0f, 10000f, "Healing applied once per second after Greater Heal's initial heal at stage 0."); BurstHealOverTimePerStage = BindSynced(config, "Healer - Greater Heal", "HealOverTimePerSecondPerBossStage", 5f, 0f, 10000f, "Extra per-second healing for each defeated-boss progression stage."); BurstHealDurationSeconds = BindSynced(config, "Healer - Greater Heal", "DurationSeconds", 4, 0, 60, "Number of one-second heal-over-time ticks after Greater Heal's initial heal."); BurstHealThreshold = BindSynced(config, "Healer - Greater Heal", "EmergencyThreshold", 0.4f, 0.01f, 1f, "Greater Heal targets allies below this health fraction (0.4 = below 40%)."); ResurrectionEnabled = BindSynced(config, "Healer - Resurrect", "Enabled", defaultValue: true, "When a recruited Mira is following nearby, she prevents a player's death and visibly resurrects them in place."); ResurrectionCooldown = BindSynced(config, "Healer - Resurrect", "CooldownSeconds", 300f, 10f, 86400f, "Seconds before Mira can resurrect another player (300 seconds = 5 minutes)."); ResurrectionRange = BindSynced(config, "Healer - Resurrect", "Range", 30f, 1f, 200f, "Maximum distance in meters between Mira and the player when the lethal hit occurs."); ResurrectionCastSeconds = BindSynced(config, "Healer - Resurrect", "CastSeconds", 4f, 0.5f, 60f, "Seconds Mira spends visibly casting once she reaches the downed player."); ResurrectionHealthFraction = BindSynced(config, "Healer - Resurrect", "RestoredHealthFraction", 0.5f, 0.05f, 1f, "Fraction of maximum health restored by Resurrect (0.5 = half health)."); ResurrectionGraceSeconds = BindSynced(config, "Healer - Resurrect", "ProtectionSeconds", 5f, 0f, 120f, "Brief lethal-damage protection after Resurrect so the player cannot be killed again before regaining control."); MigrateHealerDefaults(config); ArcherPreferredRange = BindSynced(config, "Archer", "PreferredRange", 12f, 2f, 100f, "Distance Fen tries to maintain from a target."); ArcherRetreatRange = BindSynced(config, "Archer", "RetreatRange", 6f, 1f, 100f, "Fen rolls or retreats when a target gets closer than this."); ArcherTargetRange = BindSynced(config, "Archer", "TargetRange", 15f, 4f, 100f, "Maximum range at which Fen acquires enemies."); MigrateArcherDefaults(config); RetireProviderSettings(config); ContextualRemarksEnabled = BindSynced(config, "Dialogue", "ContextualRemarksEnabled", defaultValue: true, "Allow brief private company remarks about observed threats and environment changes. No API key is needed; only the server chooses remarks."); HearPlayerChat = BindSynced(config, "Dialogue", "HearPlayerChat", LegacyDialogueValue(config, "HearPlayerChat", fallback: true), "Recruited mercenaries automatically listen to the owning player's normal chat and respond without a talk key."); ChatHearingRange = BindSynced(config, "Dialogue", "ChatHearingRange", LegacyDialogueValue(config, "ChatHearingRange", 60f), 1f, 300f, "Maximum distance in meters at which a recruited mercenary can hear player chat."); AgentChatEnabled = BindSynced(config, "Dialogue", "AgentChatEnabled", LegacyDialogueValue(config, "AgentChatEnabled", fallback: true), "Treat chat beginning with ! as a private question for the stored world knowledge. Both '!where am I' and '! where am I' work."); AgentName = BindSynced(config, "Dialogue", "AgentName", LegacyDialogueValue(config, "AgentName", "Valheim Agent"), "Speaker name shown for private ! knowledge replies."); AiRequestCooldownSeconds = BindSynced(config, "Dialogue", "RequestCooldownSeconds", LegacyDialogueValue(config, "RequestCooldownSeconds", 2f), 0.5f, 600f, "Minimum time between accepted questions from one player. Only one answer may be pending per player."); ServerDialogueRequestsPerMinute = BindSynced(config, "Dialogue", "ServerRequestsPerMinute", 120, 1, 600, "Server-wide limit on requested knowledge and dialogue answers per minute. Ambient remarks have separate cooldowns."); ShareOtherPlayersActivity = BindSynced(config, "Data", "ShareOtherPlayersActivity", defaultValue: true, "Let a player's question include summarized activity of other players (deaths, harvests) in NPC answers and Snikk gossip. Disable for stricter privacy; admins always retain access."); SharePlayerLocations = BindSynced(config, "Data", "SharePlayerLocations", defaultValue: true, "NPCs give directions to every online player, including those hidden on the map. Disable to withhold other players' positions. Online status and last-seen time remain available."); IndexWorldContainers = BindSynced(config, "Data", "IndexWorldContainers", defaultValue: true, "Let the server index container contents for storage-related answers. Disable for stricter privacy."); SnikkEnabled = BindSynced(config, "Snikk", "Enabled", defaultValue: false, "Enable Snikk the Gossip, a temporary server-wide visitor who uses recorded player activity for factual gossip. Off by default."); SnikkMinimumVisitDays = BindSynced(config, "Snikk", "MinimumVisitDays", 10, 1, 365, "Minimum Valheim days between successful Snikk visits."); SnikkMaximumVisitDays = BindSynced(config, "Snikk", "MaximumVisitDays", 20, 1, 365, "Maximum Valheim days between successful Snikk visits."); SnikkForcedFindDays = BindSynced(config, "Snikk", "ForcedFindAfterDays", 30, 1, 365, "After this many Valheim days without a successful sighting, Snikk may find a player outside a base."); SnikkSchedulerPollSeconds = BindSynced(config, "Snikk", "SchedulerPollSeconds", 15f, 5f, 300f, "Seconds between server-side visit eligibility checks."); SnikkRetrySeconds = BindSynced(config, "Snikk", "FailedAttemptRetrySeconds", 90f, 5f, 3600f, "Delay after a failed spawn/path attempt. Failed attempts never reset the visit schedule."); SnikkSpawnMinimumDistance = BindSynced(config, "Snikk", "SpawnMinimumDistance", 45f, 10f, 500f, "Minimum distance from the target used for a safe, out-of-sight spawn."); SnikkSpawnMaximumDistance = BindSynced(config, "Snikk", "SpawnMaximumDistance", 80f, 10f, 1000f, "Maximum distance from the target used for a safe, out-of-sight spawn."); SnikkSightingDistance = BindSynced(config, "Snikk", "SightingDistance", 25f, 2f, 100f, "Distance at which Snikk can become a successful sighting."); SnikkSightingSeconds = BindSynced(config, "Snikk", "SightingSeconds", 3f, 0.5f, 60f, "Seconds Snikk must remain near the target before the visit counts as successful."); SnikkIdleMinimumSeconds = BindSynced(config, "Snikk", "IdleMinimumSeconds", 60f, 5f, 3600f, "Minimum time Snikk remains after delivering his automatic gossip."); SnikkIdleMaximumSeconds = BindSynced(config, "Snikk", "IdleMaximumSeconds", 180f, 5f, 7200f, "Maximum time Snikk remains after delivering his automatic gossip."); SnikkHardTimeoutSeconds = BindSynced(config, "Snikk", "HardTimeoutSeconds", 600f, 30f, 7200f, "Maximum total encounter duration before the temporary Snikk instance is cleaned up."); SnikkRecentVisitBlock = BindSynced(config, "Snikk", "RecentFactVisitBlock", 3, 0, 20, "Number of completed visits for which a used gossip fact is deliberately avoided."); TankName = BindSynced(config, "Names", "TankName", "Bram", "Default tank mercenary first name."); HealerName = BindSynced(config, "Names", "HealerName", "Mira", "Default healer mercenary first name."); ArcherName = BindSynced(config, "Names", "ArcherName", "Fen", "Default archer mercenary first name."); } private static ConfigEntry BindSynced(ConfigFile config, string section, string key, T defaultValue, string description) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown return config.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } private static T LegacyDialogueValue(ConfigFile config, string key, T fallback) { ConfigEntry val = config.Bind("LLM", key, fallback, "Retired dialogue setting; migrated to Dialogue."); T value = val.Value; config.Remove(((ConfigEntryBase)val).Definition); return value; } private static void RetireProviderSettings(ConfigFile config) { string[] array = new string[12] { "Enabled", "BaseUrl", "ApiKey", "Model", "MaxTokens", "Temperature", "TimeoutSeconds", "ServerMaxConcurrentRequests", "ServerRequestsPerMinute", "ProactiveTips", "TipIntervalMinutes", "CustomInstructions" }; foreach (string text in array) { ConfigEntry val = config.Bind("LLM", text, "", "Removed external-provider setting."); config.Remove(((ConfigEntryBase)val).Definition); } } private static ConfigEntry BindSynced(ConfigFile config, string section, string key, float defaultValue, float minValue, float maxValue, string description) { //IL_0017: 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_0024: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown return config.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(minValue, maxValue), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } private static ConfigEntry BindSynced(ConfigFile config, string section, string key, int defaultValue, int minValue, int maxValue, string description) { //IL_0017: 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_0024: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown return config.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(minValue, maxValue), new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); } private static void RemoveLegacySurvivalSettings(ConfigFile config) { RemoveLegacy(config, "General", "HammerAutoGrant", defaultValue: true); RemoveLegacy(config, "Stats", "TankBaseHealth", 400f); RemoveLegacy(config, "Stats", "ArcherBaseHealth", 200f); RemoveLegacy(config, "Stats", "HealerBaseHealth", 150f); RemoveLegacy(config, "Stats", "HealthPerStage", 60f); RemoveLegacy(config, "Stamina", "Base", 100f); RemoveLegacy(config, "Stamina", "PerBossStage", 15f); RemoveLegacy(config, "Stamina", "RegenPerSecond", 12f); RemoveLegacy(config, "Stamina", "RegenDelay", 1.5f); RemoveLegacy(config, "Stamina", "RunCostPerSecond", 8f); RemoveLegacy(config, "Healer", "HealCooldown", 4f); RemoveLegacy(config, "Healer", "HealBaseAmount", 20f); RemoveLegacy(config, "Healer", "HealAmountPerStage", 6f); RemoveLegacy(config, "Healer - Renewal", "StaminaCost", 20f); RemoveLegacy(config, "Healer - Greater Heal", "StaminaCost", 35f); } private static void MigrateHealerDefaults(ConfigFile config) { ConfigEntry obj = config.Bind("Internal", "HealerThresholdDefaultsVersion", 0, "Internal healer-default migration marker. Do not edit."); int num = obj.Value; if (num < 1) { if (HealTriggerFraction.Value == 0.9f) { HealTriggerFraction.Value = 0.7f; } if (BurstHealThreshold.Value == 0.55f) { BurstHealThreshold.Value = 0.4f; } num = 1; } if (num < 2) { if (RenewalCooldown.Value == 8f) { RenewalCooldown.Value = 45f; } if (BurstHealCooldown.Value == 12f) { BurstHealCooldown.Value = 60f; } num = 2; } if (num < 3) { if (BurstHealAmount.Value == 60f) { BurstHealAmount.Value = 40f; } if (ResurrectionCooldown.Value == 600f) { ResurrectionCooldown.Value = 300f; } num = 3; } obj.Value = num; } private static void MigrateArcherDefaults(ConfigFile config) { ConfigEntry val = config.Bind("Internal", "ArcherCombatDefaultsVersion", 0, "Internal archer-default migration marker. Do not edit."); if (val.Value < 1) { if (ArcherPreferredRange.Value == 14f) { ArcherPreferredRange.Value = 12f; } if (ArcherRetreatRange.Value == 7f) { ArcherRetreatRange.Value = 6f; } if (ArcherTargetRange.Value == 35f) { ArcherTargetRange.Value = 24f; } val.Value = 1; } } private static void RetireCommandBindings(ConfigFile config) { string[] array = new string[5] { "ContextCompanyOrder", "HoldCompanyAtMark", "RecallCompany", "CancelCompanyGuide", "RenameMercenary" }; foreach (string key in array) { RemoveLegacy(config, "Controls", key, ""); } RemoveLegacy(config, "Internal", "ContextOrderBindingsVersion", 0); RenameHotkey = (ContextCompanyOrderHotkey = (HoldCompanyOrderHotkey = (RecallCompanyOrderHotkey = (CancelCompanyGuideHotkey = null)))); } private static void RemoveLegacy(ConfigFile config, string section, string key, T defaultValue) { ConfigEntry val = config.Bind(section, key, defaultValue, "Removed legacy setting."); config.Remove(((ConfigEntryBase)val).Definition); } } internal enum MercContextOrderType { None, Recall, Hold, GuidePoint, FocusEnemy, HuntAnimal, CancelGuide } internal enum MercContextTargetMode { Clear, Focus, Hunt, ProtectHunt } internal static class MercContextOrders { internal const float MaximumPointRange = 60f; internal const float MaximumTargetRange = 45f; internal const float TargetLifetimeSeconds = 20f; private static int _requestSequence = Environment.TickCount; internal static int NextSequence() { return ++_requestSequence; } internal static bool TryHandleContextInput(Player player) { //IL_0050: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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) if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || MercGuideMenu.IsOpen || MercConfig.ContextCompanyOrdersEnabled == null || !MercConfig.ContextCompanyOrdersEnabled.Value) { return false; } if (!TryGetPointedHit(player, out var selected)) { return false; } Character componentInParent = ((Component)((RaycastHit)(ref selected)).collider).GetComponentInParent(); if (IsHostileTo(player, componentInParent)) { ZDOID targetId = CharacterId(componentInParent); if (((ZDOID)(ref targetId)).IsNone()) { ((Character)player).Message((MessageType)2, MercLocalization.Text("dnpc_message_company_order_target_failed"), 0, (Sprite)null, false); } else { ServerAuthority.RequestContextCompanyOrder(MercContextOrderType.FocusEnemy, targetId, Vector3.zero, NextSequence()); } return true; } if ((Object)(object)componentInParent != (Object)null) { return false; } return MercResourceWork.TryRequest(player, selected); } internal static void RequestMenuOrder(MercContextOrderType order) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (MercConfig.ContextCompanyOrdersEnabled != null && MercConfig.ContextCompanyOrdersEnabled.Value) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Character)localPlayer).IsDead() && (order == MercContextOrderType.Recall || order == MercContextOrderType.Hold || order == MercContextOrderType.CancelGuide)) { Vector3 point = ((order == MercContextOrderType.Hold) ? ((Component)localPlayer).transform.position : Vector3.zero); ServerAuthority.RequestContextCompanyOrder(order, default(ZDOID), point, NextSequence()); } } } internal static bool IsFinite(Vector3 point) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(point.x) && !float.IsInfinity(point.x) && !float.IsNaN(point.y) && !float.IsInfinity(point.y) && !float.IsNaN(point.z)) { return !float.IsInfinity(point.z); } return false; } internal static bool IsHuntableAnimal(Character character) { if ((Object)(object)character == (Object)null || character.IsDead() || character.IsPlayer() || character is Mercenary || character.IsTamed()) { return false; } string text = (Utils.GetPrefabName(((Component)character).gameObject) ?? "").ToLowerInvariant(); if (!text.Contains("deer") && !text.Contains("hare") && !text.Contains("chicken")) { return text.Contains("hen"); } return true; } internal static string AnimalLabel(Character character) { string text = (((Object)(object)character != (Object)null) ? Utils.GetPrefabName(((Component)character).gameObject) : "")?.ToLowerInvariant() ?? ""; if (text.Contains("deer")) { return "deer"; } if (text.Contains("hare")) { return "hare"; } if (text.Contains("chicken")) { return "chicken"; } if (text.Contains("hen")) { return "hen"; } return "animal"; } internal static Vector3 HoldOffset(MercClass mercClass) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) return (Vector3)(mercClass switch { MercClass.Healer => new Vector3(-2f, 0f, 1f), MercClass.Tank => new Vector3(2f, 0f, 1f), _ => new Vector3(0f, 0f, -2f), }); } private static bool TryGetPointedHit(Player player, out RaycastHit selected) { //IL_0001: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) selected = default(RaycastHit); Camera main = Camera.main; ? val; if (!((Object)(object)main != (Object)null)) { Vector3 eyePoint = ((Character)player).GetEyePoint(); Vector3 lookDir = ((Character)player).GetLookDir(); val = new Ray(eyePoint, ((Vector3)(ref lookDir)).normalized); } else { val = main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f)); } Ray val2 = (Ray)val; float num = Vector3.Distance(((Ray)(ref val2)).origin, ((Character)player).GetEyePoint()); RaycastHit[] array = Physics.RaycastAll(val2, 45f + num, -5, (QueryTriggerInteraction)1); Array.Sort(array, (RaycastHit left, RaycastHit right) => ((RaycastHit)(ref left)).distance.CompareTo(((RaycastHit)(ref right)).distance)); RaycastHit[] array2 = array; for (int num2 = 0; num2 < array2.Length; num2++) { RaycastHit val3 = array2[num2]; if (!((Object)(object)((RaycastHit)(ref val3)).collider == (Object)null) && !((Component)((RaycastHit)(ref val3)).collider).transform.IsChildOf(((Component)player).transform)) { selected = val3; if (IsFinite(((RaycastHit)(ref val3)).point)) { return Vector3.Distance(((Component)player).transform.position, ((RaycastHit)(ref val3)).point) <= 45f; } return false; } } return false; } private static bool IsHostileTo(Player player, Character character) { if ((Object)(object)player != (Object)null && (Object)(object)character != (Object)null && !character.IsDead() && !character.IsPlayer() && !(character is Mercenary) && !character.IsTamed()) { return BaseAI.IsEnemy((Character)(object)player, character); } return false; } private static ZDOID CharacterId(Character character) { //IL_0026: 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_0034: Unknown result type (might be due to invalid IL or missing references) ZNetView val = (((Object)(object)character != (Object)null) ? ((Component)character).GetComponent() : null); if (!((Object)(object)val != (Object)null) || !val.IsValid()) { return default(ZDOID); } return val.GetZDO().m_uid; } } internal static class MercDialogue { private sealed class Observer { internal Mercenary Merc; internal ServerAuthority.MercenaryState State; internal DialogueObservation Observation; } private const float HearingRange = 24f; private const float ThreatRange = 24f; private const int MaximumRememberedCompanies = 128; private static readonly int CombatObservedKey = StringExtensionMethods.GetStableHashCode("jg224.dnpc.dialogue.combatV1"); private static readonly int CombatObservedAtKey = StringExtensionMethods.GetStableHashCode("jg224.dnpc.dialogue.observedAtV1"); private static readonly int CombatObserverKey = StringExtensionMethods.GetStableHashCode("jg224.dnpc.dialogue.observerV1"); private static readonly int SightMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "terrain", "blocker", "vehicle" }); private static readonly Dictionary Companies = new Dictionary(); private static readonly Dictionary NextSpeakers = new Dictionary(); private static readonly Dictionary LastCompanyUse = new Dictionary(); private static readonly Collider[] ThreatHits = (Collider[])(object)new Collider[256]; private static readonly HashSet ThreatCandidates = new HashSet(); private static double _evictedConversationQuietUntil; internal static void ResetSceneMemory() { Companies.Clear(); NextSpeakers.Clear(); LastCompanyUse.Clear(); _evictedConversationQuietUntil = 0.0; } internal static void RecordPlayerConversation(long playerId) { if (playerId != 0L && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { StateFor(playerId).ReserveConversation(Time.time); } } internal static bool CanSpeakProactive(long playerId) { if (playerId != 0L && (double)Time.time >= _evictedConversationQuietUntil) { if (Companies.TryGetValue(playerId, out var value)) { return value.MayOfferOptionalRemark(Time.time); } return true; } return false; } internal static void PublishCombatObservation(Mercenary merc, Character target) { try { ZNetView val = (((Object)(object)merc != (Object)null) ? ((Component)merc).GetComponent() : null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && !((Character)merc).IsDead() && ((Component)merc).gameObject.activeInHierarchy && !((Object)(object)ZNet.instance == (Object)null)) { double timeSeconds = ZNet.instance.GetTimeSeconds(); if (!double.IsNaN(timeSeconds) && !double.IsInfinity(timeSeconds) && !(timeSeconds <= 0.0) && !(timeSeconds > 9223372036854776.0)) { ZDO zDO = val.GetZDO(); zDO.Set(CombatObservedKey, IsHostile(merc, target)); zDO.Set(CombatObserverKey, zDO.GetOwner()); zDO.Set(CombatObservedAtKey, (long)(timeSeconds * 1000.0)); } } } catch { } } internal static void ClearCombatObservation(Mercenary merc) { try { ZNetView val = (((Object)(object)merc != (Object)null) ? ((Component)merc).GetComponent() : null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { val.GetZDO().Set(CombatObservedAtKey, 0L); val.GetZDO().Set(CombatObserverKey, 0L); } } catch { } } internal static void Poll() { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } bool remarksEnabled = MercConfig.ContextualRemarksEnabled != null && MercConfig.ContextualRemarksEnabled.Value; HashSet hashSet = new HashSet(); if ((Object)(object)Player.m_localPlayer != (Object)null) { ObservePeer(ZNet.GetUID(), hashSet, remarksEnabled); } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) { ObservePeer(peer.m_uid, hashSet, remarksEnabled); } } List list = new List(); foreach (long key in Companies.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (long item in list) { ForgetCompany(item); } } private static void ObservePeer(long peerUid, HashSet observed, bool remarksEnabled) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) if (!ServerAuthority.TryResolveSender(peerUid, out var state) || state.PlayerId == 0L || state.IsDead || observed.Contains(state.PlayerId) || !Finite(state.Position)) { return; } if (!remarksEnabled) { observed.Add(state.PlayerId); return; } List list = new List(3); foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Character)instance).IsDead() && ((Component)instance).gameObject.activeInHierarchy && !(Vector3.Distance(((Component)instance).transform.position, state.Position) > 24f)) { ZNetView component = ((Component)instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && ServerAuthority.TryResolveMercenary(component.GetZDO().m_uid, out var state2, out var _) && ServerAuthority.IsAssignedTo(state2, state, requireFollowing: false)) { list.Add(new Observer { Merc = instance, State = state2, Observation = CaptureObservation(instance, state2, state) }); } } } if (list.Count == 0) { return; } observed.Add(state.PlayerId); DialogueEventState dialogueEventState = StateFor(state.PlayerId); DialogueObservation observation = Combine(list); string text = dialogueEventState.Observe(observation, Time.time); if (text.Length == 0 || (double)Time.time < _evictedConversationQuietUntil) { return; } NextSpeakers.TryGetValue(state.PlayerId, out var value); DialogueRole dialogueRole = ((text == "low_health" || text == "recovered") ? DialogueRole.Healer : (text.StartsWith("threat_", StringComparison.Ordinal) ? DialogueRole.Archer : ((!(text == "combat")) ? ((DialogueRole)(value % 3)) : DialogueRole.Tank))); Observer observer = null; foreach (Observer item in list) { if (!text.StartsWith("threat_", StringComparison.Ordinal) || !("threat_" + item.Observation.Context.Threat != text)) { if (observer == null) { observer = item; } if (item.State.Class == (MercClass)dialogueRole) { observer = item; break; } } } if (observer != null) { DialogueRole role = (DialogueRole)observer.State.Class; int num = dialogueEventState.NextVariant(text, role); string token = "dnpc_dialogue_event_" + text + "_" + role.ToString().ToLowerInvariant() + "_" + num; ServerAuthority.SendMercSpeech(state.PeerUid, observer.State, MercLocalization.Phrase(token)); NextSpeakers[state.PlayerId] = (value + 1) % 3; } } internal static DialogueContext CaptureContext(Mercenary merc, ServerAuthority.MercenaryState state, ServerAuthority.SenderPlayerState requester) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if (requester == null || !ServerAuthority.TryResolveSender(requester.PeerUid, out var state2) || state2.PlayerId != requester.PlayerId || state2.CharacterZdoId != requester.CharacterZdoId) { return new DialogueContext(); } requester = state2; if ((Object)(object)merc == (Object)null && state != null && (Object)(object)ZNetScene.instance != (Object)null) { GameObject val = ZNetScene.instance.FindInstance(state.MercId); merc = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); } if (state == null && (Object)(object)merc != (Object)null) { ZNetView component = ((Component)merc).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { ServerAuthority.TryResolveMercenary(component.GetZDO().m_uid, out state, out var _); } } return CaptureObservation(merc, state, requester).Context; } private static DialogueObservation CaptureObservation(Mercenary merc, ServerAuthority.MercenaryState state, ServerAuthority.SenderPlayerState requester) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Unknown result type (might be due to invalid IL or missing references) //IL_056b: Unknown result type (might be due to invalid IL or missing references) //IL_0577: Unknown result type (might be due to invalid IL or missing references) //IL_0599: Unknown result type (might be due to invalid IL or missing references) //IL_059f: Unknown result type (might be due to invalid IL or missing references) //IL_05b1: Unknown result type (might be due to invalid IL or missing references) //IL_05b8: Unknown result type (might be due to invalid IL or missing references) DialogueObservation dialogueObservation = new DialogueObservation(); DialogueContext context = dialogueObservation.Context; if (requester == null || requester.IsDead || !Finite(requester.Position)) { return dialogueObservation; } if (Finite(requester.Position)) { context.IsInterior = Character.InInterior(requester.Position); context.InteriorKnown = true; } if (WorldGenerator.instance != null) { string text = ((object)WorldGenerator.instance.GetBiome(requester.Position)/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(); switch (text) { case "ocean": case "swamp": case "ashlands": case "mountain": case "deepnorth": case "mistlands": case "meadows": case "blackforest": case "plains": context.Biome = text; context.BiomeKnown = true; break; } } if ((Object)(object)EnvMan.instance != (Object)null) { context.IsNight = !EnvMan.IsDay(); context.NightKnown = (dialogueObservation.NightKnown = true); } Player livePlayer = requester.LivePlayer; float num = (((Object)(object)livePlayer != (Object)null) ? ((Character)livePlayer).GetHealth() : ((requester.PlayerZdo != null) ? requester.PlayerZdo.GetFloat(ZDOVars.s_health, float.NaN) : float.NaN)); float num2 = (((Object)(object)livePlayer != (Object)null) ? ((Character)livePlayer).GetMaxHealth() : ((requester.PlayerZdo != null) ? requester.PlayerZdo.GetFloat(ZDOVars.s_maxHealth, float.NaN) : float.NaN)); if (!float.IsNaN(num) && !float.IsInfinity(num) && !float.IsNaN(num2) && !float.IsInfinity(num2) && num2 > 0f && num > 0f && num <= num2) { dialogueObservation.HealthFraction = num / num2; context.HealthKnown = (dialogueObservation.HealthKnown = true); context.LowHealth = dialogueObservation.HealthFraction <= 0.35f; } ZNetView val = (((Object)(object)livePlayer != (Object)null) ? ((Component)livePlayer).GetComponent() : null); if ((Object)(object)livePlayer != (Object)null && (Object)(object)val != (Object)null && val.IsValid() && val.IsOwner() && ((Character)livePlayer).GetSEMan() != null) { context.IsWet = ((Character)livePlayer).GetSEMan().HaveStatusEffect(StringExtensionMethods.GetStableHashCode("Wet")); context.WetKnown = (dialogueObservation.WetKnown = true); } if ((Object)(object)merc == (Object)null || ((Character)merc).IsDead() || state == null || Vector3.Distance(((Component)merc).transform.position, requester.Position) > 24f || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.IsZoneLoaded(((Component)merc).transform.position)) { return dialogueObservation; } ZNetView component = ((Component)merc).GetComponent(); float health = ((Character)merc).GetHealth(); float maxHealth = ((Character)merc).GetMaxHealth(); if (!float.IsNaN(health) && !float.IsInfinity(health) && !float.IsNaN(maxHealth) && !float.IsInfinity(maxHealth) && maxHealth > 0f && health > 0f && health <= maxHealth) { context.SpeakerHealthKnown = true; context.SpeakerLowHealth = health / maxHealth <= 0.35f; } bool flag = (Object)(object)component != (Object)null && component.IsValid() && component.IsOwner() && (Object)(object)merc.Ai != (Object)null; Character candidate = (flag ? ((BaseAI)merc.Ai).GetTargetCreature() : null); context.InCombat = IsHostile(merc, candidate); context.CombatKnown = (dialogueObservation.CombatKnown = flag); if (!flag && (Object)(object)component != (Object)null && component.IsValid() && (Object)(object)ZNet.instance != (Object)null) { ZDO zDO = component.GetZDO(); if (DialogueObservationRules.IsFreshCombatObservation(zDO.GetOwner(), zDO.GetLong(CombatObserverKey, 0L), zDO.GetLong(CombatObservedAtKey, 0L), ZNet.instance.GetTimeSeconds())) { context.CombatKnown = (dialogueObservation.CombatKnown = true); context.InCombat = zDO.GetBool(CombatObservedKey, false); } } Character val2 = null; float num3 = float.MaxValue; int num4 = Physics.OverlapSphereNonAlloc(((Component)merc).transform.position, 24f, ThreatHits, -1, (QueryTriggerInteraction)1); ThreatCandidates.Clear(); for (int i = 0; i < num4; i++) { Collider val3 = ThreatHits[i]; ThreatHits[i] = null; Character val4 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponentInParent() : null); if (!((Object)(object)val4 == (Object)null) && ThreatCandidates.Add(((Object)val4).GetInstanceID()) && IsHostile(merc, val4)) { float num5 = Vector3.Distance(((Component)merc).transform.position, ((Component)val4).transform.position); if (!(num5 > 24f) && !(num5 >= num3) && !(Vector3.Distance(((Component)val4).transform.position, requester.Position) > 24f) && !Physics.Linecast(((Character)merc).GetEyePoint(), val4.GetCenterPoint(), SightMask, (QueryTriggerInteraction)1)) { val2 = val4; num3 = num5; } } } context.ThreatsKnown = (dialogueObservation.ThreatsKnown = (Object)(object)val2 != (Object)null || num4 < ThreatHits.Length); if ((Object)(object)val2 != (Object)null) { context.Threat = DialogueRules.ThreatKey(Utils.GetPrefabName(((Component)val2).gameObject)); } return dialogueObservation; } private static DialogueObservation Combine(List observers) { DialogueObservation observation = observers[0].Observation; DialogueContext context = observation.Context; DialogueObservation dialogueObservation = new DialogueObservation { HealthKnown = observation.HealthKnown, HealthFraction = observation.HealthFraction, NightKnown = observation.NightKnown, WetKnown = observation.WetKnown, CombatKnown = observation.CombatKnown, ThreatsKnown = observation.ThreatsKnown, Context = new DialogueContext { Biome = context.Biome, BiomeKnown = context.BiomeKnown, InteriorKnown = context.InteriorKnown, IsInterior = context.IsInterior, IsNight = context.IsNight, NightKnown = context.NightKnown, IsWet = context.IsWet, WetKnown = context.WetKnown, LowHealth = context.LowHealth, HealthKnown = context.HealthKnown, SpeakerHealthKnown = context.SpeakerHealthKnown, SpeakerLowHealth = context.SpeakerLowHealth, InCombat = context.InCombat, CombatKnown = context.CombatKnown, Threat = context.Threat, ThreatsKnown = context.ThreatsKnown } }; foreach (Observer observer in observers) { dialogueObservation.Context.InCombat |= observer.Observation.Context.InCombat; dialogueObservation.CombatKnown &= observer.Observation.CombatKnown; dialogueObservation.ThreatsKnown &= observer.Observation.ThreatsKnown; if (string.IsNullOrEmpty(dialogueObservation.Context.Threat) && !string.IsNullOrEmpty(observer.Observation.Context.Threat)) { dialogueObservation.Context.Threat = observer.Observation.Context.Threat; } } if (dialogueObservation.Context.InCombat) { dialogueObservation.CombatKnown = true; } dialogueObservation.Context.CombatKnown = dialogueObservation.CombatKnown; dialogueObservation.Context.ThreatsKnown = dialogueObservation.ThreatsKnown; return dialogueObservation; } private static bool IsHostile(Mercenary merc, Character candidate) { if ((Object)(object)candidate != (Object)null && (Object)(object)candidate != (Object)(object)merc && !candidate.IsDead() && !candidate.IsPlayer() && !(candidate is Mercenary) && !candidate.IsTamed()) { return BaseAI.IsEnemy((Character)(object)merc, candidate); } return false; } private static DialogueEventState StateFor(long playerId) { if (!Companies.TryGetValue(playerId, out var value)) { if (Companies.Count >= 128) { long num = 0L; double num2 = double.PositiveInfinity; foreach (KeyValuePair item in LastCompanyUse) { if (!(item.Value >= num2)) { num = item.Key; num2 = item.Value; } } if (Companies.TryGetValue(num, out var value2) && !value2.MayOfferOptionalRemark(Time.time)) { _evictedConversationQuietUntil = Math.Max(_evictedConversationQuietUntil, (double)Time.time + 35.0); } ForgetCompany(num); } value = new DialogueEventState(); Companies[playerId] = value; } LastCompanyUse[playerId] = Time.time; return value; } private static void ForgetCompany(long playerId) { Companies.Remove(playerId); NextSpeakers.Remove(playerId); LastCompanyUse.Remove(playerId); } private static bool Finite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } public class Mercenary : Humanoid, Interactable, TextReceiver { public static readonly List Instances = new List(); public static readonly int ZdoClass = StringExtensionMethods.GetStableHashCode("merc_class"); internal static readonly int ZdoEmployerId = StringExtensionMethods.GetStableHashCode("dynamicnpcs_employerId"); internal static readonly int ZdoEmployerName = StringExtensionMethods.GetStableHashCode("dynamicnpcs_employerName"); internal static readonly int ZdoFollowName = StringExtensionMethods.GetStableHashCode("dynamicnpcs_followName"); public static readonly int ZdoStamina = StringExtensionMethods.GetStableHashCode("merc_stamina"); public static readonly int ZdoMaxStamina = StringExtensionMethods.GetStableHashCode("merc_maxStamina"); public static readonly int ZdoIntroVersion = StringExtensionMethods.GetStableHashCode("merc_introVersion"); public static readonly int ZdoResurrectReady = StringExtensionMethods.GetStableHashCode("merc_resurrectReady"); public static readonly int ZdoResurrectCooldownVersion = StringExtensionMethods.GetStableHashCode("merc_resurrectCooldownVersion"); public static readonly int ZdoFoodSummary = StringExtensionMethods.GetStableHashCode("merc_foodSummary"); public static readonly int ZdoCustomName = StringExtensionMethods.GetStableHashCode("merc_customName"); public static readonly int ZdoStandalone = StringExtensionMethods.GetStableHashCode("dynamicnpcs_standalone"); public static readonly int ZdoProgressionStage = StringExtensionMethods.GetStableHashCode("dynamicnpcs_progressionStageV1"); public static readonly int ZdoStayDay = StringExtensionMethods.GetStableHashCode("merc_stayDay"); public static readonly int ZdoStayAnchor = StringExtensionMethods.GetStableHashCode("merc_stayAnchor"); public static readonly int ZdoPreciseHold = StringExtensionMethods.GetStableHashCode("merc_preciseHoldV1"); public const string ZdoBanner = "merc_banner"; internal static readonly int[] BannerEmployerIds = new int[3] { StringExtensionMethods.GetStableHashCode("merc_tankEmployerId"), StringExtensionMethods.GetStableHashCode("merc_healerEmployerId"), StringExtensionMethods.GetStableHashCode("merc_archerEmployerId") }; internal static readonly int[] BannerEmployerNames = new int[3] { StringExtensionMethods.GetStableHashCode("merc_tankEmployerName"), StringExtensionMethods.GetStableHashCode("merc_healerEmployerName"), StringExtensionMethods.GetStableHashCode("merc_archerEmployerName") }; internal static readonly int[] BannerFollowNames = new int[3] { StringExtensionMethods.GetStableHashCode("merc_tankFollowName"), StringExtensionMethods.GetStableHashCode("merc_healerFollowName"), StringExtensionMethods.GetStableHashCode("merc_archerFollowName") }; private static readonly int[] BannerIntroVersions = new int[3] { StringExtensionMethods.GetStableHashCode("merc_tankIntroVersion"), StringExtensionMethods.GetStableHashCode("merc_healerIntroVersion"), StringExtensionMethods.GetStableHashCode("merc_archerIntroVersion") }; private const int CurrentRecruitmentVersion = 4; private const int CurrentResurrectCooldownVersion = 2; private const float VanillaBaseHealth = 25f; private const float VanillaBaseStamina = 50f; private const float VanillaBaseStaminaRegen = 6f; private const float VanillaStaminaRegenDelay = 1f; private const float VanillaFoodRegenInterval = 10f; private const float AiRollDuration = 0.85f; private const float AiRollInvincibleDuration = 0.45f; private const string HealerDressVisual = "ArmorDress2"; private const string HealerCrownVisual = "HelmetMidsummerCrown"; private static readonly int PoisonResistanceEffectHash = StringExtensionMethods.GetStableHashCode("Potion_poisonresist"); public static bool IsDungeonTeleport; private int _appliedVisualStage = -1; private int _appliedLoadoutStage = -1; private float _visPollTimer; private float _staminaSyncTimer; private float _staminaRegenDelay; private float _stamina; private float _maxStamina; private float _foodPollTimer; private float _foodRegenTimer; private float _foodHealthRegen; private string _foodPlanKey = ""; private string _foodSummary = ""; private bool _missingWeaponLogged; private bool _introChecked; private float _lastCatchupTeleportTime = -10f; private bool _preserveNextTeleportHeight; private Ship _expectedShip; private Ship _passengerShip; private Transform _passengerAttachPoint; private Vector3 _passengerLocalPosition; private Quaternion _passengerLocalRotation = Quaternion.identity; private string _passengerAttachAnimation = ""; private bool _passengerOriginalGravity = true; private Collider[] _passengerIgnoredColliders = Array.Empty(); private Ship _controlledShipCache; private long _controlledShipPlayerId; private float _controlledShipPollTimer; private float _aiRollTimer; private float _aiRollInvincibleTimer; private bool _aiRollInvincible; private string _ambientLoopAnimation = ""; private bool _ambientPoseActive; private Transform _ambientAttachPoint; private string _ambientAttachAnimation = ""; private Vector3 _ambientDetachOffset; private bool _ambientAttachmentActive; private bool _ambientOriginalGravity = true; private bool _ambientCraftingActive; private bool _ambientHandsHidden; private long _lastObservedNetworkOwner = long.MinValue; private float _lastManagementRequestAt = -10f; private Player _pendingUsePlayer; private float _pendingUseStartedAt; private bool _ignoreUseUntilRelease; private readonly Dictionary _passThroughPlayerColliders = new Dictionary(); private float _playerCollisionRefreshTimer; private const float DismissHoldSeconds = 2f; internal CompanyFormation PresetFormation { get { int num = (((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO().GetInt("dnpc.company.formation.v1", 0) : 0); if (num < 0 || num > 2) { return CompanyFormation.Roles; } return (CompanyFormation)num; } } public MercClass Class { get; private set; } public MercAI Ai { get; private set; } public string FoodSummary { get { if (!string.IsNullOrEmpty(_foodSummary)) { return _foodSummary; } if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) { return ((Character)this).m_nview.GetZDO().GetString(ZdoFoodSummary, "food is still being selected"); } return "food is still being selected"; } } public static void SetBannerId(ZDO zdo, ZDOID banner) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) zdo.Set("merc_banner", banner); } public static ZDOID BannerIdOf(ZDO zdo) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return zdo.GetZDOID("merc_banner"); } internal static void RestoreEmploymentFromBanner(ZDO mercZdo, ZDO bannerZdo, MercClass @class) { if (mercZdo != null && bannerZdo != null) { mercZdo.Set("dnpc.company.formation.v1", bannerZdo.GetInt("dnpc.company.formation.v1", 0)); int num = Mathf.Clamp((int)@class, 0, BannerEmployerNames.Length - 1); mercZdo.Set(ZdoCustomName, bannerZdo.GetString(MercBannerSpawn.CustomNameKeys[num], DefaultFirstName(@class))); string text = bannerZdo.GetString(BannerEmployerNames[num], ""); if (string.IsNullOrEmpty(text)) { SetEmployment(mercZdo, 0L, "", ""); } else { SetEmployment(mercZdo, bannerZdo.GetLong(BannerEmployerIds[num], 0L), text, bannerZdo.GetString(BannerFollowNames[num], "")); } } } internal static long EmployerIdOf(ZDO zdo) { if (zdo == null) { return 0L; } long num = zdo.GetLong(ZdoEmployerId, long.MinValue); if (num == long.MinValue) { return zdo.GetLong(ZDOVars.s_owner, 0L); } return num; } internal static string EmployerNameOf(ZDO zdo) { if (zdo == null) { return ""; } string result = default(string); if (!zdo.GetString(ZdoEmployerName, ref result)) { return zdo.GetString(ZDOVars.s_ownerName, ""); } return result; } internal static string FollowNameOf(ZDO zdo) { if (zdo == null) { return ""; } string result = default(string); if (!zdo.GetString(ZdoFollowName, ref result)) { return zdo.GetString(ZDOVars.s_follow, ""); } return result; } internal static void SetEmployment(ZDO zdo, long employerId, string employerName, string followName) { if (zdo != null) { employerName = employerName ?? ""; followName = followName ?? ""; zdo.Set(ZdoEmployerId, employerId); zdo.Set(ZdoEmployerName, employerName); zdo.Set(ZdoFollowName, followName); zdo.Set(ZDOVars.s_owner, employerId); zdo.Set(ZDOVars.s_ownerName, employerName); zdo.Set(ZDOVars.s_follow, followName); } } internal static void SetFollowName(ZDO zdo, string followName) { if (zdo != null) { followName = followName ?? ""; zdo.Set(ZdoFollowName, followName); zdo.Set(ZDOVars.s_follow, followName); } } internal static int ProgressionStageOf(ZDO zdo) { if (zdo == null) { return -1; } return zdo.GetInt(ZdoProgressionStage, -1); } internal static bool SetProgressionStage(ZDO zdo, int stage) { if (zdo == null) { return false; } int num = MercProgressionRules.ResolveStage(stage, -1); if (ProgressionStageOf(zdo) == num) { return false; } zdo.Set(ZdoProgressionStage, num, false); return true; } private static string DefaultFirstName(MercClass @class) { return @class switch { MercClass.Healer => MercConfig.HealerName.Value, MercClass.Tank => MercConfig.TankName.Value, _ => MercConfig.ArcherName.Value, }; } public override void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) Class = ParseClass(Utils.GetPrefabName(((Object)this).name)); Ai = ((Component)this).GetComponent(); _maxStamina = 50f; _stamina = _maxStamina; ((Humanoid)this).Awake(); if (!Instances.Contains(this)) { Instances.Add(this); } ((Character)this).m_faction = (Faction)0; ((Character)this).m_health = 25f; if (((Character)this).m_nview.IsValid()) { ((Character)this).m_nview.Register("RPC_Command", (Action)RPC_Command); ((Character)this).m_nview.Register("RPC_RenameMercenary", (Action)RPC_RenameMercenary); ((Character)this).m_nview.Register("RPC_Dismiss", (Action)RPC_Dismiss); ((Character)this).m_nview.Register("RPC_RefreshAuthoritativeState", (Action)RPC_RefreshAuthoritativeState); ((Character)this).m_nview.Register("RPC_RecallNow", (Action)RPC_RecallNow); ((Character)this).m_nview.Register("RPC_ApplyDismiss", (Action)RPC_ApplyDismiss); ((Character)this).m_nview.Register("RPC_Say", (Action)RPC_Say); ((Character)this).m_nview.Register("RPC_RequestResurrection", (Action)RPC_RequestResurrection); ((Character)this).m_nview.Register("RPC_BeginResurrection", (Action)RPC_BeginResurrection); ((Character)this).m_nview.Register("RPC_ResurrectionCast", (Action)RPC_ResurrectionCast); ((Character)this).m_nview.Register("RPC_ResurrectionPulse", (Action)RPC_ResurrectionPulse); ((Character)this).m_nview.Register("RPC_ResurrectionComplete", (Action)RPC_ResurrectionComplete); ((Character)this).m_nview.Register("RPC_ResurrectionDenied", (Action)RPC_ResurrectionDenied); ((Character)this).m_nview.Register("RPC_HolyHealingVisual", (Action)RPC_HolyHealingVisual); ((Character)this).m_nview.Register("RPC_GuideTask", (Method)RPC_GuideTask); ((Character)this).m_nview.Register("RPC_ConversationRequest", (Action)RPC_ConversationRequest); ((Character)this).m_nview.GetZDO().Set(ZdoClass, (int)Class, false); PinGenderToClass(); } MercPrefabs.BindInteractionForwarders(this); } public override void OnDestroy() { ReleasePlayerPassThrough(); Instances.Remove(this); ((Humanoid)this).OnDestroy(); } public override void Start() { ((Humanoid)this).Start(); if (((Character)this).IsTamed()) { ((Character)this).SetTamed(false); } if ((Object)(object)base.m_visEquipment != (Object)null) { base.m_visEquipment.m_isPlayer = true; } int effectiveStage = GetEffectiveStage(); if (IsLoadoutOwner()) { ApplyLoadout(effectiveStage); } ApplyStageVisuals(effectiveStage); Ai?.ResetTransientSessionState(); } public static void ResetAllTransientSessionState(string reason) { Mercenary[] array = Instances.ToArray(); int num = 0; Mercenary[] array2 = array; foreach (Mercenary mercenary in array2) { if (!((Object)(object)mercenary == (Object)null)) { mercenary.ResetTransientSessionState(); num++; } } if (num > 0) { MercPlugin.Log($"Cleared transient AI state for {num} mercenaries ({reason})."); } } private void ResetTransientSessionState() { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) ResetAmbientPose(); EndShipPassenger(); _expectedShip = null; _controlledShipCache = null; _controlledShipPlayerId = 0L; _controlledShipPollTimer = 0f; _preserveNextTeleportHeight = false; _lastCatchupTeleportTime = -10f; _foodPlanKey = ""; _foodSummary = ""; _foodHealthRegen = 0f; _foodPollTimer = 0f; _foodRegenTimer = 10f; EndAiRoll(); Ai?.ResetTransientSessionState(); ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetRun(false); Rigidbody component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && !component.isKinematic) { component.linearVelocity = Vector3.zero; component.angularVelocity = Vector3.zero; } } public override float GetMaxStamina() { if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid() && !((Character)this).m_nview.IsOwner()) { return ((Character)this).m_nview.GetZDO().GetFloat(ZdoMaxStamina, Mathf.Max(50f, _maxStamina)); } return Mathf.Max(50f, _maxStamina); } public override float GetStaminaPercentage() { float maxStamina = ((Character)this).GetMaxStamina(); if (!(maxStamina > 0f)) { return 1f; } return Mathf.Clamp01(_stamina / maxStamina); } public override bool HaveStamina(float amount) { if (!(amount <= 0f)) { return _stamina + 0.001f >= amount; } return true; } public override void UseStamina(float amount) { if (!(amount <= 0f)) { _stamina = Mathf.Max(0f, _stamina - amount); _staminaRegenDelay = 1f; SyncStamina(); } } public override void AddStamina(float amount) { if (!(amount <= 0f)) { _stamina = Mathf.Min(((Character)this).GetMaxStamina(), _stamina + amount); } } private void UpdateFoodAndStamina(float dt) { if (_staminaRegenDelay > 0f) { _staminaRegenDelay = Mathf.Max(0f, _staminaRegenDelay - dt); } else { float num = ((((Character)this).GetMaxStamina() > 0f) ? Mathf.Clamp01(_stamina / ((Character)this).GetMaxStamina()) : 1f); float num2 = 6f + 6f * (1f - num); float num3 = 1f; if (((Character)this).m_seman != null) { ((Character)this).m_seman.ModifyStaminaRegen(ref num3); } float num4 = (((Object)(object)Game.instance != (Object)null) ? Mathf.Max(0f, Game.m_staminaRegenRate) : 1f); ((Character)this).AddStamina(num2 * Mathf.Max(0f, num3) * num4 * dt); } _foodRegenTimer -= dt; if (_foodRegenTimer <= 0f) { _foodRegenTimer = 10f; if (_foodHealthRegen > 0f && !((Character)this).IsDead() && ((Character)this).GetHealth() < ((Character)this).GetMaxHealth()) { float num5 = 1f; if (((Character)this).m_seman != null) { ((Character)this).m_seman.ModifyHealthRegen(ref num5); } ((Character)this).Heal(_foodHealthRegen * Mathf.Max(0f, num5), true); } } _foodPollTimer -= dt; if (_foodPollTimer <= 0f) { _foodPollTimer = 10f; ApplyFoodPlan(GetEffectiveStage(), FindAssignedPlayer(), refill: false); } _staminaSyncTimer -= dt; if (_staminaSyncTimer <= 0f) { _staminaSyncTimer = 0.5f; SyncStamina(); } } private void SyncStamina() { if (!((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { ((Character)this).m_nview.GetZDO().Set(ZdoStamina, _stamina); ((Character)this).m_nview.GetZDO().Set(ZdoMaxStamina, ((Character)this).GetMaxStamina()); } } private Player FindAssignedPlayer() { if ((Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid()) { return null; } string text = EffectiveEmployerName(); if (string.IsNullOrEmpty(text)) { return null; } foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && allPlayer.GetPlayerName() == text) { return allPlayer; } } return null; } private void ApplyFoodPlan(int stage, Player owner, bool refill) { if (!IsLoadoutOwner()) { return; } UpdateEmploymentResistance(); if (!IsEmployed()) { RemoveFoodPlan(refill); return; } ZDO zDO = ((Character)this).m_nview.GetZDO(); SaveEmploymentToBanner(EmployerIdOf(zDO), EmployerNameOf(zDO), FollowNameOf(zDO)); MercFoodPlan mercFoodPlan = MercFood.Select(stage, owner); if (mercFoodPlan.Foods.Count != 0 && (refill || !(mercFoodPlan.Key == _foodPlanKey))) { float num = Mathf.Max(1f, ((Character)this).GetMaxHealth()); float num2 = Mathf.Clamp01(((Character)this).GetHealth() / num); float num3 = Mathf.Max(1f, ((Character)this).GetMaxStamina()); float num4 = Mathf.Clamp01(_stamina / num3); _foodPlanKey = mercFoodPlan.Key; _foodSummary = mercFoodPlan.Summary; _foodHealthRegen = mercFoodPlan.HealthRegen; _maxStamina = 50f + mercFoodPlan.StaminaBonus; float num5 = 25f + mercFoodPlan.HealthBonus; ((Character)this).SetMaxHealth(num5); ((Character)this).SetHealth(refill ? num5 : Mathf.Clamp(num5 * num2, 1f, num5)); _stamina = (refill ? _maxStamina : Mathf.Clamp(_maxStamina * num4, 0f, _maxStamina)); _staminaRegenDelay = 0f; _foodRegenTimer = 10f; ((Character)this).m_nview.GetZDO().Set(ZdoFoodSummary, _foodSummary); SyncStamina(); if (mercFoodPlan.Foods.Count < 3) { MercPlugin.LogWarn($"{GetName()} found only {mercFoodPlan.Foods.Count} eligible foods at stage {stage}."); } MercPlugin.Log($"{GetName()} food plan: {_foodSummary}; max health {num5:0}, max stamina {_maxStamina:0}."); } } private bool IsEmployed() { if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) { return !string.IsNullOrEmpty(EffectiveEmployerName()); } return false; } private void RemoveFoodPlan(bool refill) { RemoveEmploymentResistance(); if (refill || !(_foodPlanKey == "unemployed")) { float num = Mathf.Max(1f, ((Character)this).GetMaxHealth()); float num2 = Mathf.Clamp01(((Character)this).GetHealth() / num); float num3 = Mathf.Max(1f, ((Character)this).GetMaxStamina()); float num4 = Mathf.Clamp01(_stamina / num3); _foodPlanKey = "unemployed"; _foodSummary = "no active meals (not yet employed)"; _foodHealthRegen = 0f; _maxStamina = 50f; ((Character)this).SetMaxHealth(25f); ((Character)this).SetHealth(refill ? 25f : Mathf.Clamp(25f * num2, 1f, 25f)); _stamina = (refill ? 50f : Mathf.Clamp(50f * num4, 0f, 50f)); ((Character)this).m_nview.GetZDO().Set(ZdoFoodSummary, _foodSummary); SyncStamina(); } } private void UpdateEmploymentResistance() { if (((Character)this).m_seman != null) { if (!IsEmployed()) { RemoveEmploymentResistance(); } else { ((Character)this).m_seman.AddStatusEffect(PoisonResistanceEffectHash, true, 0, 0f, (short)(-1)); } } } private void RemoveEmploymentResistance() { SEMan seman = ((Character)this).m_seman; if (seman != null) { seman.RemoveStatusEffect(PoisonResistanceEffectHash, false); } } private static MercClass ParseClass(string prefabName) { if (prefabName.Contains("Healer")) { return MercClass.Healer; } if (prefabName.Contains("Archer")) { return MercClass.Archer; } return MercClass.Tank; } internal bool IsLoadoutOwner() { if (((Character)this).m_nview.IsValid()) { return ((Character)this).m_nview.IsOwner(); } return false; } public static string DisplayName(MercClass @class) { return @class switch { MercClass.Healer => MercConfig.HealerName.Value, MercClass.Tank => MercConfig.TankName.Value, _ => MercConfig.ArcherName.Value, } + " " + RoleTitle(@class); } public string GetName() { return GetFirstName() + " " + RoleTitle(Class); } private static string RoleTitle(MercClass @class) { return MercLocalization.Text(RoleTitleToken(@class)); } private static string RoleTitleToken(MercClass @class) { return @class switch { MercClass.Healer => "dnpc_role_mender", MercClass.Tank => "dnpc_role_bulwark", _ => "dnpc_role_fletcher", }; } private string GetFirstName() { ZDO bannerZdo = GetBannerZdo(); int num = Mathf.Clamp((int)Class, 0, MercBannerSpawn.CustomNameKeys.Length - 1); string text = ((bannerZdo != null) ? bannerZdo.GetString(MercBannerSpawn.CustomNameKeys[num], "") : (((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO().GetString(ZdoCustomName, "") : "")); if (string.IsNullOrWhiteSpace(text)) { return DefaultFirstName(Class); } string text2 = " " + RoleTitle(Class); string text3 = " " + MercLocalization.EnglishText(RoleTitleToken(Class)); if (text.EndsWith(text2, StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - text2.Length).TrimEnd(Array.Empty()); } else if (text.EndsWith(text3, StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - text3.Length).TrimEnd(Array.Empty()); } if (!string.IsNullOrWhiteSpace(text)) { return text; } return DefaultFirstName(Class); } public bool Interact(Humanoid user, bool hold, bool alt) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((user is Player) ? user : null); if (val == null) { return true; } MercPlugin.Log($"Mercenary local interaction: merc={GetName()}, hold={hold}, alt={alt}, " + $"assigned={IsAssignedTo(val, requireFollowing: false)}, bannerPresent={GetBannerZdo() != null}."); ((Character)this).m_nview.GetZDO(); string text = EffectiveEmployerName(); bool flag = IsAssignedTo(val, requireFollowing: false); bool flag2 = IsBannerClaimedBy(val); if (alt) { if (hold) { return true; } TryBeginRename(val, showFailure: true); return true; } if (hold) { if (_ignoreUseUntilRelease || !flag) { return true; } if ((Object)(object)_pendingUsePlayer != (Object)(object)val) { BeginPendingUse(val); } return true; } if (flag) { BeginPendingUse(val); Command((Humanoid)(object)val); return true; } if (string.IsNullOrEmpty(text)) { if (flag2 || PlayerClaimsAnyBanner(val)) { if (Time.unscaledTime - _lastManagementRequestAt >= 0.75f) { _lastManagementRequestAt = Time.unscaledTime; ServerAuthority.RequestMercRehire(((Character)this).m_nview.GetZDO().m_uid, PlayerProgression.LocalSeedFromGear()); } } else { ((Character)val).Message((MessageType)2, MercLocalization.Text("dnpc_message_claim_banner_first"), 0, (Sprite)null, false); } return true; } if (!flag) { ((Character)val).Message((MessageType)2, MercLocalization.Text("dnpc_message_already_serves", GetName(), text), 0, (Sprite)null, false); return true; } return true; } private void BeginPendingUse(Player player) { _pendingUsePlayer = player; _pendingUseStartedAt = Time.unscaledTime; } private void ClearPendingUse() { _pendingUsePlayer = null; _pendingUseStartedAt = 0f; } private void UpdatePendingUse() { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (!_ignoreUseUntilRelease && (Object)(object)_pendingUsePlayer == (Object)null) { return; } bool flag = ZInput.GetButton("Use") || ZInput.GetButton("JoyUse"); if (_ignoreUseUntilRelease) { if (!flag) { _ignoreUseUntilRelease = false; } } else { if ((Object)(object)_pendingUsePlayer == (Object)null) { return; } if (!flag) { ClearPendingUse(); return; } Player pendingUsePlayer = _pendingUsePlayer; if (Time.unscaledTime - _pendingUseStartedAt < 2f || Time.unscaledTime - _lastManagementRequestAt < 0.75f) { return; } Mercenary mercenary = (((Object)(object)pendingUsePlayer != (Object)null) ? (pendingUsePlayer.GetHoverCreature() as Mercenary) : null); if ((Object)(object)mercenary == (Object)null && (Object)(object)pendingUsePlayer != (Object)null) { GameObject hoverObject = ((Humanoid)pendingUsePlayer).GetHoverObject(); if ((Object)(object)hoverObject != (Object)null) { mercenary = hoverObject.GetComponentInParent(); } } if ((Object)(object)pendingUsePlayer == (Object)null || ((Character)pendingUsePlayer).IsDead() || (Object)(object)mercenary != (Object)(object)this || Vector3.Distance(((Component)this).transform.position, ((Component)pendingUsePlayer).transform.position) > 8f) { ClearPendingUse(); return; } _lastManagementRequestAt = Time.unscaledTime; ServerAuthority.RequestMercDismiss(((Character)this).m_nview.GetZDO().m_uid); ClearPendingUse(); _ignoreUseUntilRelease = true; } } public bool UseItem(Humanoid user, ItemData item) { return true; } public string GetText() { return GetFirstName(); } internal bool TryBeginRename(Player player, bool showFailure) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid()) { return false; } if (!ValidateManager(player)) { if (showFailure) { ((Character)player).Message((MessageType)2, MercLocalization.Text("dnpc_message_claim_banner_first"), 0, (Sprite)null, false); } return false; } if (Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) > 8f) { if (showFailure) { ((Character)player).Message((MessageType)2, MercLocalization.Text("dnpc_message_move_closer_rename"), 0, (Sprite)null, false); } return false; } if ((Object)(object)TextInput.instance == (Object)null || TextInput.IsVisible()) { return false; } TextInput.instance.RequestText((TextReceiver)(object)this, MercLocalization.Text("dnpc_rename_title", RoleTitle(Class)), 24); return true; } public void SetText(string text) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid()) { string text2 = MercBannerSpawn.SanitizeName(text); if (string.IsNullOrWhiteSpace(text2)) { text2 = DefaultFirstName(Class); } ServerAuthority.RequestMercRename(((Character)this).m_nview.GetZDO().m_uid, text2); } } public void Command(Humanoid user, bool message = true) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (((Character)this).m_nview.IsValid()) { ServerAuthority.RequestMercCommand(((Character)this).m_nview.GetZDO().m_uid, message); } } internal void RequestServerConversation(string question) { RequestServerConversation(question, proactive: false); } internal void RequestServerProactiveComment(string instruction) { RequestServerConversation(instruction, proactive: true); } private void RequestServerConversation(string question, bool proactive) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && !string.IsNullOrWhiteSpace(question)) { string text = question.Trim(); if (text.Length > 1000) { text = text.Substring(0, 1000); } string s = GuideTasks.CaptureClientSnapshot().ToJson(); string payload = "{\"q\":\"" + MiniJson.Escape(text) + "\",\"g\":\"" + MiniJson.Escape(s) + "\",\"p\":" + (proactive ? "true" : "false") + "}"; ServerAuthority.RequestMercConversation(((Character)this).m_nview.GetZDO().m_uid, payload); } } private void RPC_ConversationRequest(long sender, string payload) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_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_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) ZDOID val = (ZDOID)(((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO().m_uid : default(ZDOID)); MercPlugin.Log($"Merc conversation request received: sender={sender}, merc={GetName()}, mercZdo={val}."); if (!IsAuthoritativeServer()) { MercPlugin.LogWarn($"Merc conversation request rejected: sender={sender}, merc={GetName()}, reason=handler is not the authoritative server."); return; } if (!ServerAuthority.TryResolveSender(sender, out var state)) { MercPlugin.LogWarn($"Merc conversation request rejected: sender={sender}, merc={GetName()}, reason=sender resolution failed."); return; } if (!MercConfig.HearPlayerChat.Value) { MercPlugin.LogWarn($"Merc conversation request rejected: sender={sender}, merc={GetName()}, reason=HearPlayerChat is disabled."); return; } if (!IsAssignedTo(state, requireFollowing: false)) { MercPlugin.LogWarn($"Merc conversation request rejected: sender={sender}, merc={GetName()}, reason=mercenary is not assigned to playerId={state.PlayerId}, playerName={state.PlayerName}."); return; } Vector3 position = ((Character)this).m_nview.GetZDO().GetPosition(); float num = Vector3.Distance(position, state.Position); if (num > Mathf.Max(1f, MercConfig.ChatHearingRange.Value)) { MercPlugin.LogWarn($"Merc conversation request rejected: sender={sender}, merc={GetName()}, reason=distance {num:F1}m exceeds hearing range, mercPosition={position}, playerPosition={state.Position}."); } else { if (string.IsNullOrEmpty(payload) || payload.Length > 10000 || !ServerAuthority.TryBeginAi(sender, state, this, agentMode: false)) { return; } Dictionary dictionary = MiniJson.Parse(payload) as Dictionary; string text = ((dictionary != null && dictionary.TryGetValue("q", out var value)) ? (value as string) : ""); object value2; string json = ((dictionary != null && dictionary.TryGetValue("g", out value2)) ? (value2 as string) : ""); bool flag = default(bool); int num2; if (dictionary != null && dictionary.TryGetValue("p", out var value3)) { if (value3 is bool) { flag = (bool)value3; num2 = 1; } else { num2 = 0; } } else { num2 = 0; } bool flag2 = (byte)((uint)num2 & (flag ? 1u : 0u)) != 0; if (string.IsNullOrWhiteSpace(text)) { ServerAuthority.AbortAi(sender); MercPlugin.LogWarn($"Merc conversation request rejected: sender={sender}, merc={GetName()}, reason=question payload was empty or invalid."); return; } if (flag2) { ServerAuthority.AbortAi(sender); return; } text = text.Trim(); if (text.Length > 1000) { text = text.Substring(0, 1000); } MercPlugin.Log($"Merc conversation request accepted: sender={sender}, merc={GetName()}, " + $"playerId={state.PlayerId}, playerName={state.PlayerName}, " + $"livePlayer={(Object)(object)state.LivePlayer != (Object)null}, distance={num:F1}m, " + $"questionLength={text.Length}, proactive={flag2}."); LlmBrain.ProcessServerRequest(sender, state, this, text, agentMode: false, GuideClientSnapshot.FromJson(json), flag2); } } private void RPC_Command(long sender, ZDOID characterID, bool message) { //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_0049: Unknown result type (might be due to invalid IL or missing references) if (IsAuthoritativeServer() && !((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && ServerAuthority.TryResolveSender(sender, out var state) && !(state.CharacterZdoId != characterID)) { ServerAuthority.OnMercCommandRequest(sender, ((Character)this).m_nview.GetZDO().m_uid, message); } } private void RPC_RenameMercenary(long sender, string requestedName) { //IL_0027: 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) if (!IsAuthoritativeServer()) { return; } if (!ServerAuthority.TryResolveSender(sender, out var state) || !ValidateManager(state) || Vector3.Distance(((Character)this).m_nview.GetZDO().GetPosition(), state.Position) > 8f) { MercPlugin.LogWarn($"Mercenary rename request rejected: sender={sender}, merc={GetName()}, reason=identity, management, death, or distance validation failed."); return; } string text = MercBannerSpawn.SanitizeName(requestedName); if (string.IsNullOrWhiteSpace(text)) { text = DefaultFirstName(Class); } ZDO bannerZdo = GetBannerZdo(); int num = Mathf.Clamp((int)Class, 0, MercBannerSpawn.CustomNameKeys.Length - 1); if (bannerZdo == null) { MercPlugin.LogWarn($"Mercenary rename request rejected: sender={sender}, merc={GetName()}, reason=banner ZDO is missing."); return; } bannerZdo.Set(MercBannerSpawn.CustomNameKeys[num], text); if (((Character)this).m_nview.IsOwner()) { ((Character)this).m_nview.GetZDO().Set(ZdoCustomName, text); } NotifySimulationOwnerOfStateChange(refillFood: false); ServerAuthority.SendPlayerMessage(sender, "Renamed to " + GetName() + "."); MercPlugin.Log(state.PlayerName + " renamed a mercenary to " + GetName() + "."); } private void RPC_Dismiss(long sender) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!IsAuthoritativeServer()) { return; } if (!ServerAuthority.TryResolveSender(sender, out var state) || state.IsDead || !IsAssignedTo(state, requireFollowing: false) || Vector3.Distance(((Character)this).m_nview.GetZDO().GetPosition(), state.Position) > 8f) { MercPlugin.LogWarn($"Mercenary dismiss request rejected: sender={sender}, merc={GetName()}, reason=identity, assignment, death, or distance validation failed."); return; } string name = GetName(); ZDO bannerZdo = GetBannerZdo(); ZDO zDO = ((Character)this).m_nview.GetZDO(); if (((Character)this).m_nview.IsOwner()) { SetEmployment(zDO, 0L, "", ""); } if (bannerZdo != null) { int num = Mathf.Clamp((int)Class, 0, BannerEmployerNames.Length - 1); bannerZdo.Set(BannerEmployerIds[num], 0L); bannerZdo.Set(BannerEmployerNames[num], ""); bannerZdo.Set(BannerFollowNames[num], ""); } ((Character)this).m_nview.InvokeRPC("RPC_ApplyDismiss", Array.Empty()); ServerAuthority.SendPlayerMessage(sender, name + " has been dismissed and returned to the banner. Press E to rehire them later."); LlmBrain.RecordEvent("dismiss:" + name, name + " was dismissed from active service.", 2f); MercPlugin.Log(state.PlayerName + " dismissed " + name + "."); } private void RPC_ApplyDismiss(long sender) { if (ServerAuthority.IsAuthoritativeServerSender(sender) && ((Character)this).m_nview.IsOwner()) { ZDO bannerZdo = GetBannerZdo(); Ai?.ResetTransientSessionState(); RemoveFoodPlan(refill: false); ((Character)this).m_nview.GetZDO().Set(ZdoStayDay, 0, false); TryReturnToBanner(bannerZdo, out var _); MercAI ai = Ai; if (ai != null) { ((BaseAI)ai).SetPatrolPoint(); } } } private bool TryReturnToBanner(ZDO banner, out bool respawnAtUnloadedBanner) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) respawnAtUnloadedBanner = false; if (banner == null) { return false; } Vector3 val = ((Class == MercClass.Tank) ? new Vector3(2f, 0f, 1f) : ((Class == MercClass.Healer) ? new Vector3(-2f, 0f, 1f) : new Vector3(0f, 0f, -2f))); Vector3 val2 = banner.GetPosition() + val; Quaternion val3 = ((((Vector3)(ref val)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(-val) : banner.GetRotation()); if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.IsZoneLoaded(val2)) { return ((Character)this).TeleportTo(val2, val3, false); } if ((Object)(object)ZNetScene.instance == (Object)null) { return false; } MercBannerSpawn.PrepareDismissedRespawn(banner, Class); respawnAtUnloadedBanner = true; return true; } private bool IsAuthoritativeServer() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && (Object)(object)((Character)this).m_nview != (Object)null) { return ((Character)this).m_nview.IsValid(); } return false; } internal ZDO GetBannerZdo() { //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_0043: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || ZDOMan.instance == null) { return null; } ZDOID val = BannerIdOf(((Character)this).m_nview.GetZDO()); if (!((ZDOID)(ref val)).IsNone()) { return ZDOMan.instance.GetZDO(val); } return null; } public int GetEffectiveStage() { if (MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value) { long num = EffectiveEmployerId(); if (num != 0L) { int firstCandidate = ProgressionStageOf(((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO() : null); int num2 = ProgressionStageOf(GetBannerZdo()); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { if (WorldDataService.IsWorldStateReady) { int stage = PlayerProgression.GetStage(num); int num3 = MercProgressionRules.SelectDurableStage(stage, num2, PlayerProgression.WasRecoveredFromBackup); if (num3 >= 0) { if (stage < 0 || num3 > stage) { int num4 = PlayerProgression.EnsureSeeded(num, num3, PlayerProgression.WasRecoveredFromBackup ? "newer banner mirror after atomic-backup recovery" : "recovered banner progression mirror"); if (num4 >= num3) { return num4; } return num3; } return num3; } int num5 = ServerAuthority.SeedStageFromLiveGear(num); if (num5 >= 0) { return num5; } } if (num2 >= 0) { return MercProgressionRules.ResolveStage(num2, -1); } return 0; } return MercProgressionRules.ResolveStage(firstCandidate, num2); } } return StageDirector.GetStage(); } internal long EffectiveEmployerId() { ZDO bannerZdo = GetBannerZdo(); int num = Mathf.Clamp((int)Class, 0, BannerEmployerIds.Length - 1); if (bannerZdo == null) { return EmployerIdOf(((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO() : null); } return bannerZdo.GetLong(BannerEmployerIds[num], 0L); } private string EffectiveEmployerName() { ZDO bannerZdo = GetBannerZdo(); int num = Mathf.Clamp((int)Class, 0, BannerEmployerNames.Length - 1); if (bannerZdo == null) { return EmployerNameOf(((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO() : null); } return bannerZdo.GetString(BannerEmployerNames[num], ""); } private string EffectiveFollowName() { ZDO bannerZdo = GetBannerZdo(); int num = Mathf.Clamp((int)Class, 0, BannerFollowNames.Length - 1); if (bannerZdo == null) { return FollowNameOf(((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) ? ((Character)this).m_nview.GetZDO() : null); } return bannerZdo.GetString(BannerFollowNames[num], ""); } private bool IsBannerClaimedBy(Player player) { ZDO bannerZdo = GetBannerZdo(); if ((Object)(object)player != (Object)null && bannerZdo != null) { return bannerZdo.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == player.GetPlayerID(); } return false; } private static bool PlayerClaimsAnyBanner(Player player) { if ((Object)(object)player == (Object)null) { return false; } foreach (MercBannerSpawn instance in MercBannerSpawn.Instances) { if (!((Object)(object)instance == (Object)null)) { ZNetView component = ((Component)instance).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val != null && val.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == player.GetPlayerID()) { return true; } } } return false; } private bool IsBannerClaimedBy(ServerAuthority.SenderPlayerState requester) { ZDO bannerZdo = GetBannerZdo(); if (requester != null && bannerZdo != null) { return bannerZdo.GetLong(MercBannerSpawn.ClaimOwnerIdKey, 0L) == requester.PlayerId; } return false; } private bool ValidateManager(Player player) { if ((Object)(object)player == (Object)null || ((Character)player).IsDead()) { return false; } if (!IsAssignedTo(player, requireFollowing: false)) { return IsBannerClaimedBy(player); } return true; } private bool ValidateManager(ServerAuthority.SenderPlayerState requester) { if (requester != null && !requester.IsDead) { if (!IsAssignedTo(requester, requireFollowing: false)) { return IsBannerClaimedBy(requester); } return true; } return false; } private static Player FindPlayer(ZDOID characterID) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(characterID) : null); if (!Object.op_Implicit((Object)(object)val)) { return null; } return val.GetComponent(); } private static Character FindCharacter(ZDOID characterID) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(characterID) : null); if (!Object.op_Implicit((Object)(object)val)) { return null; } return val.GetComponent(); } public void Follow(Player player) { if (!((Object)(object)Ai == (Object)null)) { Ai.CancelAmbientCamp(); ApplyLocalFollowTarget(player); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { SetFollowName(((Character)this).m_nview.GetZDO(), player.GetPlayerName()); SaveEmploymentToBanner(player.GetPlayerID(), player.GetPlayerName(), player.GetPlayerName()); } ApplyFoodPlan(GetEffectiveStage(), player, refill: false); } } private void ApplyLocalFollowTarget(Player player) { if (!((Object)(object)Ai == (Object)null) && !((Object)(object)player == (Object)null)) { ((BaseAI)Ai).ResetPatrolPoint(); ((MonsterAI)Ai).SetFollowTarget(((Component)player).gameObject); } } private void Follow(ServerAuthority.SenderPlayerState requester) { if (requester != null && !((Object)(object)Ai == (Object)null)) { Ai.CancelAmbientCamp(); ((BaseAI)Ai).ResetPatrolPoint(); if ((Object)(object)requester.LivePlayer != (Object)null) { ((MonsterAI)Ai).SetFollowTarget(((Component)requester.LivePlayer).gameObject); } if (((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { SetFollowName(((Character)this).m_nview.GetZDO(), requester.PlayerName); } SaveEmploymentToBanner(requester.PlayerId, requester.PlayerName, requester.PlayerName); ApplyFoodPlan(GetEffectiveStage(), requester.LivePlayer, refill: false); } } internal void ApplyAuthoritativeEmployment(ServerAuthority.SenderPlayerState requester, bool refillFood) { if (requester == null || !IsAuthoritativeServer()) { return; } ZDO bannerZdo = GetBannerZdo(); if (bannerZdo != null) { int num = Mathf.Clamp((int)Class, 0, BannerEmployerNames.Length - 1); bannerZdo.Set(BannerEmployerIds[num], requester.PlayerId); bannerZdo.Set(BannerEmployerNames[num], requester.PlayerName); bannerZdo.Set(BannerFollowNames[num], requester.PlayerName); if (((Character)this).m_nview.IsOwner()) { SetEmployment(((Character)this).m_nview.GetZDO(), requester.PlayerId, requester.PlayerName, requester.PlayerName); } NotifySimulationOwnerOfStateChange(refillFood); } } internal void NotifySimulationOwnerOfStateChange(bool refillFood) { if (IsAuthoritativeServer()) { ((Character)this).m_nview.InvokeRPC("RPC_RefreshAuthoritativeState", new object[1] { refillFood }); } } internal void NotifySimulationOwnerOfRecall() { if (IsAuthoritativeServer()) { ((Character)this).m_nview.InvokeRPC("RPC_RecallNow", Array.Empty()); } } private void RPC_RefreshAuthoritativeState(long sender, bool refillFood) { if (ServerAuthority.IsAuthoritativeServerSender(sender) && ((Character)this).m_nview.IsOwner()) { Ai?.CancelAmbientCamp(); UpdateSavedFollowTarget(); if (IsEmployed()) { ApplyFoodPlan(GetEffectiveStage(), Player.m_localPlayer, refillFood); } else { RemoveFoodPlan(refill: false); } } } private void RPC_RecallNow(long sender) { if (ServerAuthority.IsAuthoritativeServerSender(sender) && ((Character)this).m_nview.IsOwner()) { UpdateSavedFollowTarget(); Ai?.ForceRecallFromBase(); } } public void UnFollow() { if (!((Object)(object)Ai == (Object)null)) { Ai.CancelAmbientCamp(); Ai.CancelGuideTask(null, announce: false); ((MonsterAI)Ai).SetFollowTarget((GameObject)null); ((BaseAI)Ai).SetPatrolPoint(); if (((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { SetFollowName(((Character)this).m_nview.GetZDO(), ""); SaveEmploymentToBanner(EffectiveEmployerId(), EffectiveEmployerName(), ""); } } } private void SaveEmploymentToBanner(long employerId, string employerName, string followName) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(employerName) && !((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && ZDOMan.instance != null) { ZDO zDO = ZDOMan.instance.GetZDO(BannerIdOf(((Character)this).m_nview.GetZDO())); if (zDO != null) { int num = Mathf.Clamp((int)Class, 0, BannerEmployerNames.Length - 1); zDO.Set(BannerEmployerIds[num], employerId); zDO.Set(BannerEmployerNames[num], employerName); zDO.Set(BannerFollowNames[num], followName ?? ""); } } } public bool IsFollowing() { if ((Object)(object)Ai != (Object)null) { return (Object)(object)((MonsterAI)Ai).GetFollowTarget() != (Object)null; } return false; } public bool RequestGuideTask(Player player, GuideTaskType task, BossTarget boss, Vector3 destination) { //IL_002c: 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) if ((Object)(object)player == (Object)null || !((Character)this).m_nview.IsValid()) { return false; } ((Character)this).m_nview.InvokeRPC("RPC_GuideTask", new object[4] { ((Character)player).GetZDOID(), (int)task, (int)boss, destination }); return true; } internal bool RequestGuideTask(ServerAuthority.SenderPlayerState requester, GuideTaskType task, BossTarget boss, Vector3 destination) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (requester == null || !IsAuthoritativeServer() || (Object)(object)Ai == (Object)null) { return false; } if (!IsAssignedTo(requester, requireFollowing: false)) { return false; } ((Character)this).m_nview.InvokeRPC("RPC_GuideTask", new object[4] { requester.CharacterZdoId, (int)task, (int)boss, destination }); return true; } private void RPC_GuideTask(long sender, ZDOID playerId, int taskValue, int bossValue, Vector3 destination) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (((Character)this).m_nview.IsOwner() && ServerAuthority.IsAuthoritativeServerSender(sender) && !((Object)(object)Ai == (Object)null) && IsAssignedCharacter(playerId, out var player) && Enum.IsDefined(typeof(GuideTaskType), taskValue) && Enum.IsDefined(typeof(BossTarget), bossValue)) { if (taskValue == 3 || taskValue == 0) { Ai.CancelGuideTask(player, announce: false); } else if (!((Object)(object)player == (Object)null)) { ApplyLocalFollowTarget(player); Ai.BeginGuideTask(player, (GuideTaskType)taskValue, (BossTarget)bossValue, destination); } } } private void UpdateSavedFollowTarget() { if ((Object)(object)Ai == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner()) { return; } string text = EffectiveFollowName(); GameObject followTarget = ((MonsterAI)Ai).GetFollowTarget(); if (string.IsNullOrEmpty(text)) { if ((Object)(object)followTarget != (Object)null) { Ai.CancelGuideTask(null, announce: false); ((MonsterAI)Ai).SetFollowTarget((GameObject)null); ((BaseAI)Ai).SetPatrolPoint(); } return; } Player val = (((Object)(object)followTarget != (Object)null) ? followTarget.GetComponent() : null); if ((Object)(object)val != (Object)null && val.GetPlayerName() == text) { return; } foreach (Player allPlayer in Player.GetAllPlayers()) { if (allPlayer.GetPlayerName() == text) { ApplyLocalFollowTarget(allPlayer); break; } } } private void TryStartIntroduction() { if (_introChecked || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } ZDO bannerZdo = GetBannerZdo(); if (bannerZdo == null) { return; } string text = EffectiveEmployerName(); if (!string.IsNullOrWhiteSpace(text) && !(EffectiveFollowName() != text)) { _introChecked = true; int num = Mathf.Clamp((int)Class, 0, BannerIntroVersions.Length - 1); if (bannerZdo.GetInt(BannerIntroVersions[num], ((Character)this).m_nview.GetZDO().GetInt(ZdoIntroVersion, 0)) < 4) { bannerZdo.Set(BannerIntroVersions[num], 4, false); LlmBrain.BeginRecruitmentConversation(this); } } } public override string GetHoverName() { return GetName(); } public override string GetHoverText() { //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) { ((Character)this).m_nview.GetZDO(); } string text = EffectiveEmployerName(); Player localPlayer = Player.m_localPlayer; bool flag = (Object)(object)localPlayer != (Object)null && IsAssignedTo(localPlayer, requireFollowing: false); bool flag2 = (Object)(object)localPlayer != (Object)null && IsBannerClaimedBy(localPlayer); ZDO bannerZdo = GetBannerZdo(); int num = Mathf.Clamp((int)Class, 0, MercBannerSpawn.RecruitedKeys.Length - 1); bool flag3 = bannerZdo != null && bannerZdo.GetBool(MercBannerSpawn.RecruitedKeys[num], false); string text2 = ((!string.IsNullOrEmpty(text)) ? (flag ? MercLocalization.Text(IsFollowing() ? "dnpc_action_stay_here" : "dnpc_action_follow_me") : MercLocalization.Text("dnpc_action_serves", text)) : (flag2 ? MercLocalization.Text(flag3 ? "dnpc_action_rehire" : "dnpc_action_recruit") : MercLocalization.Text("dnpc_action_claim_banner_first"))); string text3 = ((Class == MercClass.Healer) ? "#7fffd4" : ((Class == MercClass.Archer) ? "#ffd700" : "#ff9f43")); string text4 = ""; if (Class == MercClass.Healer && MercConfig.ResurrectionEnabled.Value) { float resurrectionCooldownRemaining = GetResurrectionCooldownRemaining(); text4 = ((resurrectionCooldownRemaining > 0f) ? ("\n" + MercLocalization.Text("dnpc_resurrect_cooldown", Mathf.CeilToInt(resurrectionCooldownRemaining / 60f))) : ((!((Object)(object)Player.m_localPlayer != (Object)null) || !IsAssignedTo(Player.m_localPlayer, requireFollowing: true)) ? ("\n" + MercLocalization.Text("dnpc_resurrect_following")) : ("\n" + MercLocalization.Text("dnpc_resurrect_ready")))); } string text5 = ""; if (flag || flag2) { string text6 = (ZInput.IsGamepadActive() ? MercLocalization.Binding("JoyRadial", "R3") : ((MercConfig.GuideMenuHotkey != null) ? ((object)MercConfig.GuideMenuHotkey.Value/*cast due to .constrained prefix*/).ToString() : "G")); if (string.IsNullOrWhiteSpace(text6)) { text6 = MercLocalization.Text("dnpc_action_unbound"); } if (flag) { text5 = text5 + "\n[" + text6 + "] " + MercLocalization.Text("dnpc_action_guide"); } } if (flag) { string text7 = (ZInput.IsGamepadActive() ? MercLocalization.Binding("JoyUse", "A") : MercLocalization.Binding("Use", "E")); text5 = text5 + "\n[" + MercLocalization.Text("dnpc_hold_action", "" + text7 + "") + "] " + MercLocalization.Text("dnpc_action_dismiss"); } string text8 = (ZInput.IsGamepadActive() ? MercLocalization.Binding("JoyUse", "A") : MercLocalization.Binding("Use", "E")); return "" + GetName() + " (" + MercLocalization.Text("dnpc_noun_mercenary") + ")" + text4 + "\n[" + text8 + "] " + text2 + text5; } public void Say(string text) { BroadcastSpeech(text, addToChat: true); } public void Callout(string text) { BroadcastSpeech(text, addToChat: false); } private void BroadcastSpeech(string text, bool addToChat) { if (!string.IsNullOrEmpty(text)) { if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) { ((Character)this).m_nview.InvokeRPC(ZNetView.Everybody, "RPC_Say", new object[2] { text, addToChat }); } else { ShowSpeech(text, addToChat); } } } private void RPC_Say(long sender, string text, bool addToChat) { if (sender == ((Character)this).GetOwner() || ServerAuthority.IsAuthoritativeServerSender(sender)) { ShowSpeech(text, addToChat); } } private void ShowSpeech(string text, bool addToChat = true) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(text)) { return; } text = MercLocalization.Resolve(text); if ((Object)(object)Chat.instance != (Object)null) { Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 2.2f, 25f, Mathf.Min(4f + (float)text.Length / 30f, 14f), "", text, false); if (addToChat) { ((Terminal)Chat.instance).AddString(GetName(), text, (Type)1, false); } } MercUtil.SpawnVfx("sfx_dverger_vo_idle", ((Component)this).transform.position); } internal void ShowServerSpeech(string text, bool addToChat) { ShowSpeech(text, addToChat); } internal void ApplyServerGuidePayload(ZDOID playerId, string payload) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Ai == (Object)null || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || !(MiniJson.Parse(payload ?? "") is Dictionary dictionary)) { return; } object value; int num = (dictionary.TryGetValue("task", out value) ? Convert.ToInt32(value) : 0); object value2; int num2 = (dictionary.TryGetValue("boss", out value2) ? Convert.ToInt32(value2) : 0); if (Enum.IsDefined(typeof(GuideTaskType), num) && Enum.IsDefined(typeof(BossTarget), num2) && IsAssignedCharacter(playerId, out var player)) { GuideTaskType guideTaskType = (GuideTaskType)num; if (guideTaskType == GuideTaskType.None || guideTaskType == GuideTaskType.Cancel) { Ai.CancelGuideTask(player, announce: false); } else if (!((Object)(object)player == (Object)null)) { object value3; float num3 = (dictionary.TryGetValue("x", out value3) ? Convert.ToSingle(value3) : 0f); object value4; float num4 = (dictionary.TryGetValue("y", out value4) ? Convert.ToSingle(value4) : 0f); object value5; float num5 = (dictionary.TryGetValue("z", out value5) ? Convert.ToSingle(value5) : 0f); ApplyLocalFollowTarget(player); Ai.BeginGuideTask(player, guideTaskType, (BossTarget)num2, new Vector3(num3, num4, num5)); } } } internal void ApplyServerContextTarget(ZDOID playerId, int modeValue, ZDOID targetId) { //IL_0050: 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) if (!((Object)(object)Ai == (Object)null) && !((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner() && Enum.IsDefined(typeof(MercContextTargetMode), modeValue) && IsAssignedCharacter(playerId, out var _)) { Ai.ApplyContextTarget((MercContextTargetMode)modeValue, targetId); } } private bool IsAssignedCharacter(ZDOID characterId, out Player player) { //IL_0001: 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) player = FindPlayer(characterId); if ((Object)(object)player != (Object)null) { return IsAssignedTo(player, requireFollowing: false); } ZDO val = ((!((ZDOID)(ref characterId)).IsNone() && ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(characterId) : null); if (val != null && val.IsValid()) { return IsAssignedTo(val.GetLong(ZDOVars.s_playerID, 0L), "", requireFollowing: false); } return false; } internal void ApplyServerResurrection(ZDOID playerId) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || (Object)(object)Ai == (Object)null) { return; } Player val = FindPlayer(playerId) ?? Player.m_localPlayer; if ((Object)(object)val == (Object)null || !CanOfferResurrection(val) || !Ai.CanBeginResurrection()) { BroadcastResurrectionDenied(playerId); return; } long num = (long)Mathf.Ceil(Mathf.Max(1f, MercConfig.ResurrectionCooldown.Value)); ((Character)this).m_nview.GetZDO().Set(ZdoResurrectReady, (long)Math.Ceiling(ResurrectionManager.NetworkTime) + num); ((Character)this).m_nview.GetZDO().Set(ZdoResurrectCooldownVersion, 2, false); if (!Ai.BeginResurrection(val)) { ResetResurrectionCooldown(); BroadcastResurrectionDenied(playerId); } } public bool IsAssignedTo(Player player, bool requireFollowing) { if ((Object)(object)player == (Object)null || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid()) { return false; } return IsAssignedTo(player.GetPlayerID(), player.GetPlayerName(), requireFollowing); } internal bool IsAssignedTo(long playerId, string playerName, bool requireFollowing) { if ((Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid()) { return false; } long num = EffectiveEmployerId(); if (num == 0L || playerId == 0L || num != playerId) { return false; } if (requireFollowing) { return EffectiveFollowName() == playerName; } return true; } internal bool IsAssignedTo(ServerAuthority.SenderPlayerState requester, bool requireFollowing) { if (requester != null) { return IsAssignedTo(requester.PlayerId, requester.PlayerName, requireFollowing); } return false; } public float GetResurrectionCooldownRemaining() { if (Class != MercClass.Healer || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid()) { return 0f; } ZDO zDO = ((Character)this).m_nview.GetZDO(); long num = zDO.GetLong(ZdoResurrectReady, 0L); double networkTime = ResurrectionManager.NetworkTime; if (zDO.GetInt(ZdoResurrectCooldownVersion, 0) < 2) { if (num > 0 && Mathf.Approximately(MercConfig.ResurrectionCooldown.Value, 300f)) { num -= 300; } if (((Character)this).m_nview.IsOwner()) { zDO.Set(ZdoResurrectReady, num); zDO.Set(ZdoResurrectCooldownVersion, 2, false); } } return Mathf.Max(0f, (float)((double)num - networkTime)); } public bool CanOfferResurrection(Player player) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (MercConfig.ResurrectionEnabled.Value && Class == MercClass.Healer && !((Character)this).IsDead() && IsAssignedTo(player, requireFollowing: true) && Vector3.Distance(((Component)this).transform.position, ((Component)player).transform.position) <= Mathf.Max(1f, MercConfig.ResurrectionRange.Value)) { return GetResurrectionCooldownRemaining() <= 0f; } return false; } private bool CanOfferResurrection(ServerAuthority.SenderPlayerState requester) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (requester != null && MercConfig.ResurrectionEnabled.Value && Class == MercClass.Healer && !((Character)this).IsDead() && IsAssignedTo(requester, requireFollowing: true) && Vector3.Distance(((Component)this).transform.position, requester.Position) <= Mathf.Max(1f, MercConfig.ResurrectionRange.Value)) { return GetResurrectionCooldownRemaining() <= 0f; } return false; } public void RequestResurrection(Player player) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid()) { ServerAuthority.RequestMercResurrection(((Character)this).m_nview.GetZDO().m_uid, ((Character)player).GetZDOID()); } } private void RPC_RequestResurrection(long sender, ZDOID playerId) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_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_00a7: Unknown result type (might be due to invalid IL or missing references) if (IsAuthoritativeServer()) { if (!ServerAuthority.TryResolveSender(sender, out var state) || state.CharacterZdoId != playerId || !CanOfferResurrection(state) || (Object)(object)Ai == (Object)null) { MercPlugin.LogWarn($"Resurrect request rejected: sender={sender}, playerZdo={playerId}, " + $"resolved={state != null}, merc={GetName()}."); BroadcastResurrectionDenied(playerId); return; } long num = (long)Mathf.Ceil(Mathf.Max(1f, MercConfig.ResurrectionCooldown.Value)); ((Character)this).m_nview.InvokeRPC("RPC_BeginResurrection", new object[1] { playerId }); MercPlugin.Log(GetName() + " accepted a Resurrect request for " + state.PlayerName + "; " + $"cooldown {num}s"); } } private void RPC_BeginResurrection(long sender, ZDOID playerId) { //IL_0024: 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_00bb: Unknown result type (might be due to invalid IL or missing references) if (!ServerAuthority.IsAuthoritativeServerSender(sender) || !((Character)this).m_nview.IsOwner() || (Object)(object)Ai == (Object)null) { return; } Player val = FindPlayer(playerId); if ((Object)(object)val == (Object)null || !CanOfferResurrection(val) || !Ai.CanBeginResurrection()) { BroadcastResurrectionDenied(playerId); return; } long num = (long)Mathf.Ceil(Mathf.Max(1f, MercConfig.ResurrectionCooldown.Value)); ((Character)this).m_nview.GetZDO().Set(ZdoResurrectReady, (long)Math.Ceiling(ResurrectionManager.NetworkTime) + num); ((Character)this).m_nview.GetZDO().Set(ZdoResurrectCooldownVersion, 2, false); if (!Ai.BeginResurrection(val)) { ResetResurrectionCooldown(); BroadcastResurrectionDenied(playerId); } } internal void BroadcastResurrectionCast(Player player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)null) { BroadcastResurrectionCast(((Character)player).GetZDOID()); } } internal void BroadcastResurrectionCast(ZDOID playerId) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (!((ZDOID)(ref playerId)).IsNone() && ((Character)this).m_nview.IsOwner()) { ZSyncAnimation zAnim = ((Character)this).GetZAnim(); if (zAnim != null) { zAnim.SetTrigger("gpower"); } ((Character)this).m_nview.InvokeRPC(ZNetView.Everybody, "RPC_ResurrectionCast", new object[1] { playerId }); } } internal void BroadcastResurrectionPulse(Player player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)null) { BroadcastResurrectionPulse(((Character)player).GetZDOID()); } } internal void BroadcastResurrectionPulse(ZDOID playerId) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (!((ZDOID)(ref playerId)).IsNone() && ((Character)this).m_nview.IsOwner()) { ((Character)this).m_nview.InvokeRPC(ZNetView.Everybody, "RPC_ResurrectionPulse", new object[1] { playerId }); } } internal void BroadcastResurrectionComplete(Player player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)null) { BroadcastResurrectionComplete(((Character)player).GetZDOID()); } } internal void BroadcastResurrectionComplete(ZDOID playerId) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (!((ZDOID)(ref playerId)).IsNone() && ((Character)this).m_nview.IsOwner()) { float num = Mathf.Clamp(MercConfig.ResurrectionHealthFraction.Value, 0.05f, 1f); ((Character)this).m_nview.InvokeRPC(ZNetView.Everybody, "RPC_ResurrectionComplete", new object[2] { playerId, num }); } } internal void BroadcastResurrectionDenied(Player player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)null) { BroadcastResurrectionDenied(((Character)player).GetZDOID()); } } internal void BroadcastResurrectionDenied(ZDOID playerId) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { ((Character)this).m_nview.InvokeRPC(ZNetView.Everybody, "RPC_ResurrectionDenied", new object[1] { playerId }); } } internal void ResetResurrectionCooldown() { if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { ((Character)this).m_nview.GetZDO().Set(ZdoResurrectReady, (long)Math.Floor(ResurrectionManager.NetworkTime)); ((Character)this).m_nview.GetZDO().Set(ZdoResurrectCooldownVersion, 2, false); } } private void RPC_ResurrectionCast(long sender, ZDOID playerId) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (sender == ((Character)this).GetOwner()) { Player player = FindPlayer(playerId); ShowSpeech(MercLocalization.Phrase("dnpc_speech_resurrect_cast")); MercUtil.SpawnVfx("sfx_dverger_heal_start", ((Component)this).transform.position); MercUtil.SpawnVfx("fx_guardstone_activate", ((Component)this).transform.position); MercUtil.SpawnVfx("vfx_StaminaUpgrade", ((Component)this).transform.position + Vector3.up); ResurrectionManager.OnCastStarted(this, player); } } private void RPC_ResurrectionPulse(long sender, ZDOID playerId) { //IL_000a: 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_0026: 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 (sender == ((Character)this).GetOwner()) { Player val = FindPlayer(playerId); MercUtil.SpawnVfx("vfx_Potion_stamina_medium", ((Component)this).transform.position + Vector3.up); if ((Object)(object)val != (Object)null) { MercUtil.SpawnVfx("vfx_Potion_stamina_medium", ((Character)val).GetCenterPoint()); } } } private void RPC_ResurrectionComplete(long sender, ZDOID playerId, float healthFraction) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (sender == ((Character)this).GetOwner()) { Player val = FindPlayer(playerId); ShowSpeech(MercLocalization.Phrase("dnpc_speech_resurrect_complete")); MercUtil.SpawnVfx("sfx_dverger_heal_finish", ((Component)this).transform.position); if ((Object)(object)val != (Object)null) { MercUtil.SpawnVfx("vfx_StaminaUpgrade", ((Character)val).GetCenterPoint()); MercUtil.SpawnVfx("fx_guardstone_permitted_add", ((Character)val).GetCenterPoint()); } ResurrectionManager.OnCompleted(this, val, healthFraction); } } private void RPC_ResurrectionDenied(long sender, ZDOID playerId) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (sender == ((Character)this).GetOwner()) { ResurrectionManager.OnDenied(this, FindPlayer(playerId)); } } public void ApplyLoadout(int stage) { if (IsLoadoutOwner()) { _appliedLoadoutStage = stage; ((Character)this).SetLevel(1); base.m_inventory.RemoveAll(); _missingWeaponLogged = false; MercLoadout mercLoadout = GearTable.Get(Class, stage); int quality = Mathf.Clamp(stage, 1, 4); TryAddEquip(mercLoadout.Chest, quality); TryAddEquip(mercLoadout.Legs, quality); TryAddEquip(mercLoadout.Helmet, quality); TryAddEquip(mercLoadout.Cape, quality); TryAddEquip(mercLoadout.Shield, quality); if (!string.IsNullOrEmpty(mercLoadout.Ammo) && base.m_inventory.AddItem(mercLoadout.Ammo, 200, 1, 0, 0L, GetName(), false, false) == null) { MercPlugin.LogWarn(GetName() + ": failed to add ammo " + mercLoadout.Ammo); } TryAddEquip(mercLoadout.Weapon, quality); ItemData val = EnsureClassWeapon(); if (!string.IsNullOrEmpty(mercLoadout.Weapon) && val != null) { MercPlugin.Log($"{GetName()} equipped {mercLoadout.Weapon} at stage {stage}; " + $"body armor {((Character)this).GetBodyArmor():0.#}"); } ApplyFoodPlan(stage, FindAssignedPlayer(), refill: true); } } private ItemData TryAddEquip(string itemId, int quality) { if (string.IsNullOrEmpty(itemId)) { return null; } ItemData val = base.m_inventory.AddItem(itemId, 1, quality, 0, 0L, GetName(), false, false); if (val == null) { MercPlugin.LogWarn(GetName() + ": failed to add loadout item " + itemId); return null; } if (!((Humanoid)this).EquipItem(val, false)) { MercPlugin.LogWarn(GetName() + ": failed to equip " + itemId); } return val; } public override void ApplyArmorDamageMods(ref DamageModifiers modifiers) { ApplyItemDamageModifiers(base.m_chestItem, ref modifiers); ApplyItemDamageModifiers(base.m_legItem, ref modifiers); ApplyItemDamageModifiers(base.m_helmetItem, ref modifiers); ApplyItemDamageModifiers(base.m_shoulderItem, ref modifiers); } private static void ApplyItemDamageModifiers(ItemData item, ref DamageModifiers modifiers) { if (item?.m_shared?.m_damageModifiers != null) { ((DamageModifiers)(ref modifiers)).Apply(item.m_shared.m_damageModifiers); } } public override float GetBodyArmor() { float num = 0f; num += EquippedArmor(base.m_chestItem); num += EquippedArmor(base.m_legItem); num += EquippedArmor(base.m_helmetItem); num += EquippedArmor(base.m_shoulderItem); SEMan seman = ((Character)this).m_seman; if (seman != null) { seman.ApplyArmorMods(ref num); } return num; } private static float EquippedArmor(ItemData item) { if (item == null) { return 0f; } return item.GetArmor(); } public ItemData EnsureClassWeapon() { string weapon = GearTable.Get(Class, GetEffectiveStage()).Weapon; if (string.IsNullOrEmpty(weapon)) { return null; } ItemData currentWeapon = ((Humanoid)this).GetCurrentWeapon(); if (IsPrefab(currentWeapon, weapon)) { return currentWeapon; } foreach (ItemData allItem in base.m_inventory.GetAllItems()) { if (IsPrefab(allItem, weapon) && ((Humanoid)this).EquipItem(allItem, false)) { ItemData currentWeapon2 = ((Humanoid)this).GetCurrentWeapon(); if (IsPrefab(currentWeapon2, weapon)) { return currentWeapon2; } } } if (!_missingWeaponLogged) { _missingWeaponLogged = true; MercPlugin.LogWarn(GetName() + ": configured weapon " + weapon + " is missing; refusing unarmed combat"); } return null; } public bool BeginAiBowDraw(ItemData bow) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) Attack val = bow?.m_shared?.m_attack; if (val == null || !val.m_bowDraw || ((Character)this).InAttack()) { return false; } if (!val.StartDraw((Humanoid)(object)this, bow)) { return false; } base.m_attackDrawTime = 0.001f; if (!string.IsNullOrEmpty(val.m_drawAnimationState)) { ((Character)this).m_zanim.SetBool(val.m_drawAnimationState, true); ((Character)this).m_zanim.SetFloat("drawpercent", 0f); } EffectList holdStartEffect = bow.m_shared.m_holdStartEffect; if (holdStartEffect != null) { holdStartEffect.Create(((Component)this).transform.position, Quaternion.identity, ((Component)this).transform, 1f, -1, default(ZDOID)); } return true; } public bool AdvanceAiBowDraw(ItemData bow, float dt, out bool ready) { ready = false; Attack val = bow?.m_shared?.m_attack; if (val == null || !val.m_bowDraw || base.m_attackDrawTime <= 0f) { return false; } float num = Mathf.Max(0f, bow.GetDrawStaminaDrain()) * Mathf.Max(0f, dt); if (num > 0f && !((Character)this).HaveStamina(num)) { CancelAiBowDraw(bow); return false; } ((Character)this).UseStamina(num); base.m_attackDrawTime += Mathf.Max(0f, dt); float attackDrawPercentage = ((Humanoid)this).GetAttackDrawPercentage(); if (!string.IsNullOrEmpty(val.m_drawAnimationState)) { ((Character)this).m_zanim.SetFloat("drawpercent", attackDrawPercentage); } ready = attackDrawPercentage >= 0.999f; return true; } public bool ReleaseAiBowAttack(Character target, ItemData bow) { if ((Object)(object)target == (Object)null || bow == null || ((Humanoid)this).GetAttackDrawPercentage() < 0.999f) { CancelAiBowDraw(bow); return false; } bool result = ((Character)this).StartAttack(target, false); FinishAiBowDraw(bow); return result; } public void CancelAiBowDraw(ItemData bow = null) { if (bow == null) { bow = ((Humanoid)this).GetCurrentWeapon(); } FinishAiBowDraw(bow); } public bool TryStartAiRoll(Vector3 direction, float staminaCost) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) if (Class != MercClass.Archer || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || ((Character)this).IsDead() || ((Character)this).InAttack() || ((Character)this).IsStaggering() || ((Character)this).InDodge() || !((Character)this).IsOnGround() || ((Character)this).IsSwimming()) { return false; } direction.y = 0f; if (((Vector3)(ref direction)).sqrMagnitude < 0.01f || !((Character)this).HaveStamina(staminaCost)) { return false; } ((Vector3)(ref direction)).Normalize(); ((Humanoid)this).ClearActionQueue(); CancelAiBowDraw(); ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetRun(false); ((Component)this).transform.rotation = Quaternion.LookRotation(direction); if ((Object)(object)((Character)this).m_body != (Object)null) { ((Character)this).m_body.rotation = ((Component)this).transform.rotation; } ZSyncAnimation zanim = ((Character)this).m_zanim; if (zanim != null) { zanim.SetTrigger("dodge"); } ((Character)this).AddNoise(5f); ((Character)this).UseStamina(staminaCost); _aiRollTimer = 0.85f; _aiRollInvincibleTimer = 0.45f; SetAiRollInvincible(value: true); return true; } public override bool InDodge() { return _aiRollTimer > 0f; } public override bool IsDodgeInvincible() { if ((Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid()) { return false; } if (((Character)this).m_nview.IsOwner()) { return _aiRollInvincible; } return ((Character)this).m_nview.GetZDO().GetBool(ZDOVars.s_dodgeinv, false); } public void OnDodgeMortal() { _aiRollInvincibleTimer = 0f; SetAiRollInvincible(value: false); } private void FinishAiBowDraw(ItemData bow) { Attack val = bow?.m_shared?.m_attack; if (val != null && !string.IsNullOrEmpty(val.m_drawAnimationState) && (Object)(object)((Character)this).m_zanim != (Object)null) { ((Character)this).m_zanim.SetBool(val.m_drawAnimationState, false); } base.m_attackDrawTime = 0f; } public void BeginBareHandCast() { if (Class == MercClass.Healer) { ItemData currentWeapon = ((Humanoid)this).GetCurrentWeapon(); if (currentWeapon != null) { ((Humanoid)this).UnequipItem(currentWeapon, false); } } } public void EndBareHandCast() { if (Class == MercClass.Healer && !((Character)this).IsDead()) { EnsureClassWeapon(); } } private static bool IsPrefab(ItemData item, string prefabId) { if (item != null && (Object)(object)item.m_dropPrefab != (Object)null) { return Utils.GetPrefabName(item.m_dropPrefab) == prefabId; } return false; } public void ApplyStageVisuals(int stage) { //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) PinGenderToClass(); if ((Object)(object)base.m_visEquipment == (Object)null || (_appliedVisualStage == stage && Class != MercClass.Healer)) { return; } _appliedVisualStage = stage; MercLoadout mercLoadout = GearTable.Get(Class, stage); int num = 0; int num2 = Mathf.Clamp(stage, 1, 4); if (Class == MercClass.Healer) { ApplyHealerCosmeticVisuals(base.m_visEquipment); } else { string text = Safe(mercLoadout.Chest); string text2 = Safe(mercLoadout.Legs); string text3 = Safe(mercLoadout.Helmet); string text4 = Safe(mercLoadout.Cape); if ((string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(mercLoadout.Chest)) || (string.IsNullOrEmpty(text2) && !string.IsNullOrEmpty(mercLoadout.Legs)) || (string.IsNullOrEmpty(text3) && !string.IsNullOrEmpty(mercLoadout.Helmet)) || (string.IsNullOrEmpty(text4) && !string.IsNullOrEmpty(mercLoadout.Cape))) { _appliedVisualStage = -1; return; } base.m_visEquipment.SetChestItem(MercVisuals.ItemHash(text)); base.m_visEquipment.SetLegItem(MercVisuals.ItemHash(text2)); base.m_visEquipment.SetHelmetItem(MercVisuals.ItemHash(text3)); base.m_visEquipment.SetShoulderItem(MercVisuals.ItemHash(text4), num, num2); } if (Class == MercClass.Tank) { base.m_visEquipment.SetLeftItem(MercVisuals.ItemHash(Safe(mercLoadout.Shield)), num, num2); base.m_visEquipment.SetRightItem(MercVisuals.ItemHash(Safe(mercLoadout.Weapon)), num2); } else if (Class == MercClass.Archer) { base.m_visEquipment.SetLeftBackItem(0, num, 0); } base.m_visEquipment.SetHairItem(MercVisuals.ItemHash((Class == MercClass.Healer) ? Safe("Hair6") : ((Class == MercClass.Archer) ? Safe("Hair5") : Safe("Hair1")))); base.m_visEquipment.SetBeardItem(MercVisuals.ItemHash((Class == MercClass.Tank) ? "Beard5" : "")); base.m_visEquipment.SetSkinColor(new Vector3(1f, 0.87f, 0.72f)); base.m_visEquipment.SetHairColor(new Vector3(0.28f, 0.18f, 0.09f)); } private void PinGenderToClass() { if (((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { int num = ((Class == MercClass.Healer) ? 1 : 0); if (((Character)this).m_nview.GetZDO().GetInt(ZDOVars.s_modelIndex, -1) != num) { ((Character)this).m_nview.GetZDO().Set(ZDOVars.s_modelIndex, num, false); } } } internal void ApplyHealerCosmeticVisuals(VisEquipment visuals) { if (Class == MercClass.Healer && !((Object)(object)visuals == (Object)null)) { visuals.SetChestItem(MercVisuals.ItemHash("ArmorDress2")); visuals.SetLegItem(0); visuals.SetHelmetItem(MercVisuals.ItemHash("HelmetMidsummerCrown")); visuals.SetShoulderItem(0, 0, 0); } } private static string Safe(string itemId) { if (!((Object)(object)MercUtil.GetItemPrefabSafe(itemId) != (Object)null)) { return ""; } return itemId; } public void Update() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (!((Character)this).m_nview.IsValid()) { return; } long owner = ((Character)this).m_nview.GetZDO().GetOwner(); if (owner != _lastObservedNetworkOwner) { _lastObservedNetworkOwner = owner; MercPlugin.Log($"Mercenary simulation owner: merc={GetName()}, mercZdo={((Character)this).m_nview.GetZDO().m_uid}, " + $"networkOwner={owner}, localUid={(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0)}, " + $"localSimulates={((Character)this).m_nview.IsOwner()}."); if (((Character)this).m_nview.IsOwner()) { if ((Object)(object)Ai != (Object)null && Ai.IsResurrectionActive) { Ai.CancelResurrection(refundCooldown: true); } int effectiveStage = GetEffectiveStage(); ApplyLoadout(effectiveStage); ApplyStageVisuals(effectiveStage); Ai?.RecoverPersistedStateFromZdo(); Ai?.RecoverPersistedGuideTask(); } } UpdatePendingUse(); UpdatePlayerPassThrough(Time.deltaTime); TryStartIntroduction(); if (((Character)this).m_nview.IsOwner()) { UpdateSavedFollowTarget(); UpdateFollowCatchup(); UpdateFoodAndStamina(Time.deltaTime); UpdateAiRoll(Time.deltaTime); } _visPollTimer += Time.deltaTime; if (_visPollTimer > 5f) { _visPollTimer = 0f; int effectiveStage2 = GetEffectiveStage(); ApplyStageVisuals(effectiveStage2); if (IsLoadoutOwner() && _appliedLoadoutStage != effectiveStage2) { ApplyLoadout(effectiveStage2); } if (IsLoadoutOwner()) { RefreshDurability(); } } } private void UpdatePlayerPassThrough(float dt) { _playerCollisionRefreshTimer -= dt; if (_playerCollisionRefreshTimer > 0f) { return; } _playerCollisionRefreshTimer = 1f; if (MercConfig.PlayerPassThroughEnabled == null || !MercConfig.PlayerPassThroughEnabled.Value || (Object)(object)((Character)this).m_collider == (Object)null) { ReleasePlayerPassThrough(); return; } HashSet hashSet = new HashSet(); foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer == (Object)null || ((Character)allPlayer).IsDead()) { continue; } Collider[] componentsInChildren = ((Component)allPlayer).GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)((Character)this).m_collider || val.isTrigger) { continue; } int instanceID = ((Object)val).GetInstanceID(); hashSet.Add(instanceID); if (!_passThroughPlayerColliders.ContainsKey(instanceID)) { try { Physics.IgnoreCollision((Collider)(object)((Character)this).m_collider, val, true); _passThroughPlayerColliders[instanceID] = val; } catch { } } } } List list = new List(); foreach (KeyValuePair passThroughPlayerCollider in _passThroughPlayerColliders) { if ((Object)(object)passThroughPlayerCollider.Value != (Object)null && hashSet.Contains(passThroughPlayerCollider.Key)) { continue; } if ((Object)(object)passThroughPlayerCollider.Value != (Object)null) { try { Physics.IgnoreCollision((Collider)(object)((Character)this).m_collider, passThroughPlayerCollider.Value, false); } catch { } } list.Add(passThroughPlayerCollider.Key); } foreach (int item in list) { _passThroughPlayerColliders.Remove(item); } } private void ReleasePlayerPassThrough() { if ((Object)(object)((Character)this).m_collider != (Object)null) { foreach (Collider value in _passThroughPlayerColliders.Values) { if (!((Object)(object)value == (Object)null)) { try { Physics.IgnoreCollision((Collider)(object)((Character)this).m_collider, value, false); } catch { } } } } _passThroughPlayerColliders.Clear(); } public override void CustomFixedUpdate(float fixedDeltaTime) { ((Humanoid)this).CustomFixedUpdate(fixedDeltaTime); if (!((Object)(object)((Character)this).m_nview == (Object)null) && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { if (_ambientAttachmentActive) { MaintainAmbientAttachment(); } else if ((Object)(object)_passengerShip != (Object)null) { MaintainShipPassenger(); } } } private void UpdateAiRoll(float dt) { if (!(_aiRollTimer <= 0f)) { _aiRollTimer = Mathf.Max(0f, _aiRollTimer - dt); _aiRollInvincibleTimer = Mathf.Max(0f, _aiRollInvincibleTimer - dt); if (_aiRollInvincibleTimer <= 0f) { SetAiRollInvincible(value: false); } if (_aiRollTimer <= 0f) { EndAiRoll(); } } } private void EndAiRoll() { _aiRollTimer = 0f; _aiRollInvincibleTimer = 0f; SetAiRollInvincible(value: false); } private void SetAiRollInvincible(bool value) { _aiRollInvincible = value; if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid() && ((Character)this).m_nview.IsOwner()) { ((Character)this).m_nview.GetZDO().Set(ZDOVars.s_dodgeinv, value); } } internal bool BeginAmbientPose(string animation, bool looping) { if (string.IsNullOrEmpty(animation) || (Object)(object)((Character)this).m_zanim == (Object)null || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || ((Character)this).IsDead()) { return false; } EndAmbientActivity(); _ambientPoseActive = true; if (looping) { _ambientLoopAnimation = animation; ((Character)this).m_zanim.SetBool(animation, true); } else { _ambientLoopAnimation = ""; ((Character)this).m_zanim.SetTrigger(animation); } return true; } internal bool BeginAmbientAttachment(Transform attachPoint, string animation, Vector3 detachOffset) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)attachPoint == (Object)null || string.IsNullOrEmpty(animation) || (Object)(object)((Character)this).m_zanim == (Object)null || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || ((Character)this).IsDead()) { return false; } EndAmbientActivity(); _ambientAttachPoint = attachPoint; _ambientAttachAnimation = animation; _ambientDetachOffset = detachOffset; _ambientOriginalGravity = (Object)(object)((Character)this).m_body == (Object)null || ((Character)this).m_body.useGravity; _ambientAttachmentActive = true; ((Humanoid)this).ClearActionQueue(); CancelAiBowDraw(); ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetWalk(false); ((Character)this).SetRun(false); if ((Object)(object)((Character)this).m_body != (Object)null) { ((Character)this).m_body.useGravity = false; } ((Character)this).m_zanim.SetBool(_ambientAttachAnimation, true); return MaintainAmbientAttachment(); } internal bool MaintainAmbientAttachment() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //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_0036: 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_0096: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (!_ambientAttachmentActive || (Object)(object)_ambientAttachPoint == (Object)null) { return false; } Vector3 position = _ambientAttachPoint.position; Quaternion rotation = _ambientAttachPoint.rotation; ((Component)this).transform.SetPositionAndRotation(position, rotation); if ((Object)(object)((Character)this).m_body != (Object)null) { Rigidbody componentInParent = ((Component)_ambientAttachPoint).GetComponentInParent(); ((Character)this).m_body.useGravity = false; ((Character)this).m_body.linearVelocity = (((Object)(object)componentInParent != (Object)null) ? componentInParent.GetPointVelocity(position) : Vector3.zero); ((Character)this).m_body.angularVelocity = Vector3.zero; } ((Character)this).m_maxAirAltitude = position.y; ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetWalk(false); ((Character)this).SetRun(false); return true; } internal bool BeginAmbientCrafting(int animation) { if ((Object)(object)((Character)this).m_zanim == (Object)null || (Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner() || ((Character)this).IsDead()) { return false; } EndAmbientActivity(); ((Humanoid)this).ClearActionQueue(); CancelAiBowDraw(); _ambientHandsHidden = ((Humanoid)this).HideHandItems(false, false); _ambientCraftingActive = true; ((Character)this).m_zanim.SetInt("crafting", Mathf.Max(1, animation)); return true; } internal void EndAmbientPose() { if (!_ambientPoseActive) { return; } if ((Object)(object)((Character)this).m_zanim != (Object)null) { if (!string.IsNullOrEmpty(_ambientLoopAnimation)) { ((Character)this).m_zanim.SetBool(_ambientLoopAnimation, false); } ((Character)this).m_zanim.SetTrigger("emote_stop"); } _ambientLoopAnimation = ""; _ambientPoseActive = false; } private void EndAmbientAttachment() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if (_ambientAttachmentActive) { Transform ambientAttachPoint = _ambientAttachPoint; Vector3 ambientDetachOffset = _ambientDetachOffset; if ((Object)(object)((Character)this).m_zanim != (Object)null && !string.IsNullOrEmpty(_ambientAttachAnimation)) { ((Character)this).m_zanim.SetBool(_ambientAttachAnimation, false); } _ambientAttachmentActive = false; _ambientAttachPoint = null; _ambientAttachAnimation = ""; _ambientDetachOffset = Vector3.zero; if ((Object)(object)((Character)this).m_body != (Object)null) { ((Character)this).m_body.useGravity = _ambientOriginalGravity; } if ((Object)(object)ambientAttachPoint != (Object)null) { Vector3 position = ambientAttachPoint.TransformPoint(ambientDetachOffset); ((Component)this).transform.position = position; } MercUtil.ResetCharacterGroundState((Character)(object)this); } } private void EndAmbientCrafting() { if (_ambientCraftingActive || _ambientHandsHidden) { if ((Object)(object)((Character)this).m_zanim != (Object)null) { ((Character)this).m_zanim.SetInt("crafting", 0); } if (_ambientHandsHidden) { ((Humanoid)this).ShowHandItems(false, false); } _ambientHandsHidden = false; _ambientCraftingActive = false; } } internal void EndAmbientActivity() { EndAmbientPose(); EndAmbientCrafting(); EndAmbientAttachment(); } internal void ResetAmbientPose() { EndAmbientActivity(); if ((Object)(object)((Character)this).m_zanim != (Object)null) { ((Character)this).m_zanim.SetBool("emote_sit", false); ((Character)this).m_zanim.SetBool("attach_chair", false); ((Character)this).m_zanim.SetInt("crafting", 0); ((Character)this).m_zanim.SetTrigger("emote_stop"); } _ambientLoopAnimation = ""; _ambientPoseActive = false; _ambientAttachmentActive = false; _ambientAttachPoint = null; _ambientAttachAnimation = ""; _ambientCraftingActive = false; _ambientHandsHidden = false; } public override bool InEmote() { if (!_ambientPoseActive && !_ambientAttachmentActive && !_ambientCraftingActive) { return ((Character)this).InEmote(); } return true; } public override bool IsAttached() { if (!_ambientAttachmentActive && !((Object)(object)_passengerShip != (Object)null)) { return ((Humanoid)this).IsAttached(); } return true; } public override bool IsAttachedToShip() { if (!((Object)(object)_passengerShip != (Object)null)) { return ((Character)this).IsAttachedToShip(); } return true; } public override bool GetRelativePosition(out ZDOID parent, out string attachJoint, out Vector3 relativePos, out Quaternion relativeRot, out Vector3 relativeVel) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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_006c: 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_0078: 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_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) if (_ambientAttachmentActive && (Object)(object)_ambientAttachPoint != (Object)null) { ZNetView componentInParent = ((Component)_ambientAttachPoint).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && componentInParent.IsValid()) { parent = componentInParent.GetZDO().m_uid; if ((Object)(object)((Component)componentInParent).GetComponent() != (Object)null) { attachJoint = ((Object)_ambientAttachPoint).name; relativePos = Vector3.zero; relativeRot = Quaternion.identity; } else { attachJoint = ""; relativePos = ((Component)componentInParent).transform.InverseTransformPoint(((Component)this).transform.position); relativeRot = Quaternion.Inverse(((Component)componentInParent).transform.rotation) * ((Component)this).transform.rotation; } relativeVel = Vector3.zero; return true; } } if ((Object)(object)_passengerShip != (Object)null) { ZNetView component = ((Component)_passengerShip).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { parent = component.GetZDO().m_uid; attachJoint = ""; relativePos = ((Component)component).transform.InverseTransformPoint(((Component)this).transform.position); relativeRot = Quaternion.Inverse(((Component)component).transform.rotation) * ((Component)this).transform.rotation; relativeVel = Vector3.zero; return true; } } return ((Humanoid)this).GetRelativePosition(ref parent, ref attachJoint, ref relativePos, ref relativeRot, ref relativeVel); } public override bool IsSitting() { if (!(_ambientLoopAnimation == "emote_sit") && !_ambientAttachmentActive) { return ((Humanoid)this).IsSitting(); } return true; } public override void StopEmote() { EndAmbientActivity(); ((Character)this).StopEmote(); } private void UpdateFollowCatchup() { //IL_0046: 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) GameObject val = (((Object)(object)Ai != (Object)null) ? ((MonsterAI)Ai).GetFollowTarget() : null); if ((Object)(object)val == (Object)null) { return; } Player component = val.GetComponent(); if ((Object)(object)component == (Object)null || ((Character)component).IsTeleporting()) { return; } float num = Vector3.Distance(((Component)this).transform.position, val.transform.position); Ship playerShip = GetPlayerShip(component); if ((Object)(object)playerShip != (Object)null) { if ((Object)(object)_passengerShip == (Object)(object)playerShip) { _expectedShip = playerShip; } else if (num > 4f || (Object)(object)_expectedShip != (Object)(object)playerShip) { TryBoardPlayerShip(component, playerShip); } } else { _expectedShip = null; if (!((Character)component).InWater() && !((Character)component).IsSwimming() && !(num < MercConfig.FollowTeleportDistance.Value)) { TryCatchUpToPlayer(component, "distance catch-up"); } } } public bool TryCatchUpToPlayer(Player player, string reason) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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) if ((Object)(object)player == (Object)null || ((Character)player).IsTeleporting() || IsPlayerWaterborne(player)) { return false; } if (Time.time - _lastCatchupTeleportTime < 2f) { return false; } if (!TryFindDryCatchupPoint(player, out var position)) { return false; } Quaternion rotation = ((Component)player).transform.rotation; if (!((Character)this).TeleportTo(position, rotation, false)) { return false; } _lastCatchupTeleportTime = Time.time; MercPlugin.Log(GetName() + " rejoined " + player.GetPlayerName() + " on dry ground (" + reason + ")."); return true; } public override float GetJogSpeedFactor() { return ((Character)this).GetJogSpeedFactor() / EnemySpeedCompensation(); } public override float GetRunSpeedFactor() { return ((Character)this).GetRunSpeedFactor() / EnemySpeedCompensation(); } private float EnemySpeedCompensation() { float num = (((Character)this).InInterior() ? 1f : Game.m_enemySpeedSize); if ((Object)(object)Game.instance != (Object)null) { num *= 1f + (float)Game.m_worldLevel * Game.instance.m_worldLevelEnemyMoveSpeedMultiplier; } return Mathf.Max(num, 0.01f); } public static bool IsPlayerWaterborne(Player player) { if ((Object)(object)player == (Object)null) { return false; } try { return ((Character)player).InWater() || ((Character)player).IsSwimming() || ((Character)player).IsAttachedToShip() || (Object)(object)((Character)player).GetStandingOnShip() != (Object)null || (Object)(object)player.GetControlledShip() != (Object)null; } catch { return ((Character)player).InWater() || ((Character)player).IsSwimming(); } } public static Ship GetPlayerShip(Player player) { if ((Object)(object)player == (Object)null) { return null; } try { Ship standingOnShip = ((Character)player).GetStandingOnShip(); return ((Object)(object)standingOnShip != (Object)null) ? standingOnShip : player.GetControlledShip(); } catch { return null; } } public static Ship GetPlayerControlledShip(Player player) { //IL_0085: 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) if ((Object)(object)player == (Object)null) { return null; } try { Ship controlledShip = player.GetControlledShip(); if ((Object)(object)controlledShip != (Object)null) { return controlledShip; } Ship standingOnShip = ((Character)player).GetStandingOnShip(); if (ShipIsControlledBy(standingOnShip, player.GetPlayerID())) { return standingOnShip; } Ship[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Ship val in array) { if (!ShipIsControlledBy(val, player.GetPlayerID())) { continue; } Rigidbody component = ((Component)val).GetComponent(); if ((!((Object)(object)component != (Object)null) || !component.isKinematic) && !((Object)(object)component == (Object)null)) { Vector3 linearVelocity = component.linearVelocity; if (!(((Vector3)(ref linearVelocity)).sqrMagnitude < 0.25f)) { return val; } } } } catch { } return null; } private static bool ShipIsControlledBy(Ship ship, long playerId) { ZNetView val = (((Object)(object)(((Object)(object)ship != (Object)null) ? ship.m_shipControlls : null) != (Object)null) ? ((Component)ship).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null) { return val2.GetLong(ZDOVars.s_user, 0L) == playerId; } return false; } public bool UpdateShipPassengerState() { if ((Object)(object)((Character)this).m_nview == (Object)null || !((Character)this).m_nview.IsValid() || !((Character)this).m_nview.IsOwner()) { return false; } GameObject val = (((Object)(object)Ai != (Object)null) ? ((MonsterAI)Ai).GetFollowTarget() : null); Player val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 == (Object)null) { _controlledShipCache = null; _controlledShipPlayerId = 0L; _controlledShipPollTimer = 0f; EndShipPassenger(); return false; } long playerID = val2.GetPlayerID(); _controlledShipPollTimer -= Time.deltaTime; if (_controlledShipPlayerId != playerID || _controlledShipPollTimer <= 0f) { _controlledShipPlayerId = playerID; _controlledShipPollTimer = 0.25f; _controlledShipCache = GetPlayerControlledShip(val2); } Ship controlledShipCache = _controlledShipCache; if ((Object)(object)controlledShipCache == (Object)null) { EndShipPassenger(); return false; } if ((Object)(object)_passengerShip != (Object)(object)controlledShipCache) { BeginShipPassenger(controlledShipCache, val2); } if ((Object)(object)_passengerShip == (Object)null) { return false; } MaintainShipPassenger(); return true; } private void BeginShipPassenger(Ship ship, Player player) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) Ai?.CancelAmbientCamp(); EndShipPassenger(); if ((Object)(object)ship == (Object)null || (Object)(object)player == (Object)null) { return; } _passengerShip = ship; SelectPassengerAnchor(ship, player); _passengerOriginalGravity = (Object)(object)((Character)this).m_body == (Object)null || ((Character)this).m_body.useGravity; ((Humanoid)this).ClearActionQueue(); CancelAiBowDraw(); ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetRun(false); if ((Object)(object)((Character)this).m_body != (Object)null) { ((Character)this).m_body.useGravity = false; } _passengerIgnoredColliders = ((Component)ship).GetComponentsInChildren(true); if ((Object)(object)((Character)this).m_collider != (Object)null) { Collider[] passengerIgnoredColliders = _passengerIgnoredColliders; foreach (Collider val in passengerIgnoredColliders) { if ((Object)(object)val != (Object)null) { Physics.IgnoreCollision((Collider)(object)((Character)this).m_collider, val, true); } } } ClearShipPassengerAnimations(); if (!string.IsNullOrEmpty(_passengerAttachAnimation)) { ZSyncAnimation zanim = ((Character)this).m_zanim; if (zanim != null) { zanim.SetBool(_passengerAttachAnimation, true); } } _expectedShip = ship; MaintainShipPassenger(); MercPlugin.Log(GetName() + " secured ship interaction '" + _passengerAttachAnimation + "' on " + Utils.GetPrefabName(((Component)ship).gameObject) + "."); } private void SelectPassengerAnchor(Ship ship, Player player) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) _passengerAttachPoint = null; _passengerAttachAnimation = ""; _passengerLocalRotation = Quaternion.identity; Chair val = null; List list = new List(); Chair[] componentsInChildren = ((Component)ship).GetComponentsInChildren(true); foreach (Chair val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || (Object)(object)val2.m_attachPoint == (Object)null) { continue; } string a = val2.m_attachAnimation ?? ""; if (string.Equals(a, "attach_mast", StringComparison.Ordinal)) { if ((Object)(object)val == (Object)null) { val = val2; } } else if (val2.m_inShip && (string.Equals(a, "attach_sitship", StringComparison.Ordinal) || string.Equals(a, "attach_chair", StringComparison.Ordinal))) { list.Add(val2); } } Chair val3 = null; if (Class == MercClass.Archer) { val3 = val; } else if (Class == MercClass.Tank && list.Count > 0) { val3 = list[0]; } else if (Class == MercClass.Healer && list.Count > 0) { val3 = list[Mathf.Min(1, list.Count - 1)]; } if ((Object)(object)val3 == (Object)null) { val3 = val ?? ((list.Count > 0) ? list[0] : null); } if ((Object)(object)val3 != (Object)null) { _passengerAttachPoint = val3.m_attachPoint; _passengerLocalPosition = ((Component)ship).transform.InverseTransformPoint(_passengerAttachPoint.position); _passengerLocalRotation = Quaternion.Inverse(((Component)ship).transform.rotation) * _passengerAttachPoint.rotation; _passengerAttachAnimation = val3.m_attachAnimation ?? ""; return; } Vector3 val4 = ((Component)ship).transform.InverseTransformPoint(((Component)player).transform.position); Vector3 val5 = Vector3.zero; Transform val6 = null; Transform[] componentsInChildren2 = ((Component)ship).GetComponentsInChildren(true); foreach (Transform val7 in componentsInChildren2) { if (!((Object)(object)val7 == (Object)null) && ((Object)val7).name.IndexOf("mast", StringComparison.OrdinalIgnoreCase) >= 0 && ((Object)(object)val6 == (Object)null || val7.position.y < val6.position.y)) { val6 = val7; } } if ((Object)(object)val6 != (Object)null) { val5 = ((Component)ship).transform.InverseTransformPoint(val6.position); } string prefabName = Utils.GetPrefabName(((Component)ship).gameObject); float num = ((prefabName.IndexOf("raft", StringComparison.OrdinalIgnoreCase) >= 0) ? 0.5f : ((prefabName.IndexOf("karve", StringComparison.OrdinalIgnoreCase) >= 0) ? 0.7f : 0.9f)); Vector3 val8 = ((Class == MercClass.Tank) ? new Vector3(0f - num, 0f, -0.45f) : ((Class == MercClass.Healer) ? new Vector3(num, 0f, -0.45f) : new Vector3(0f, 0f, 0.8f))); _passengerLocalPosition = new Vector3(val5.x + val8.x, val4.y, val5.z + val8.z); _passengerAttachAnimation = "attach_sitship"; } private void MaintainShipPassenger() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_passengerShip == (Object)null) { return; } Vector3 val = (((Object)(object)_passengerAttachPoint != (Object)null) ? _passengerAttachPoint.position : ((Component)_passengerShip).transform.TransformPoint(_passengerLocalPosition)); Quaternion val2 = (((Object)(object)_passengerAttachPoint != (Object)null) ? _passengerAttachPoint.rotation : (((Component)_passengerShip).transform.rotation * _passengerLocalRotation)); ((Component)this).transform.SetPositionAndRotation(val, val2); if ((Object)(object)((Character)this).m_body != (Object)null) { ((Character)this).m_body.useGravity = false; Rigidbody component = ((Component)_passengerShip).GetComponent(); if (!((Character)this).m_body.isKinematic) { ((Character)this).m_body.linearVelocity = (((Object)(object)component != (Object)null) ? component.GetPointVelocity(val) : Vector3.zero); ((Character)this).m_body.angularVelocity = Vector3.zero; } } ((Character)this).m_maxAirAltitude = val.y; ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetWalk(false); ((Character)this).SetRun(false); } private void EndShipPassenger() { //IL_0032: 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_0024: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_passengerShip == (Object)null) { return; } Rigidbody component = ((Component)_passengerShip).GetComponent(); Vector3 linearVelocity = (((Object)(object)component != (Object)null) ? component.GetPointVelocity(((Component)this).transform.position) : Vector3.zero); _passengerShip = null; _passengerAttachPoint = null; ClearShipPassengerAnimations(); _passengerAttachAnimation = ""; if ((Object)(object)((Character)this).m_collider != (Object)null) { Collider[] passengerIgnoredColliders = _passengerIgnoredColliders; foreach (Collider val in passengerIgnoredColliders) { if ((Object)(object)val != (Object)null) { Physics.IgnoreCollision((Collider)(object)((Character)this).m_collider, val, false); } } } _passengerIgnoredColliders = Array.Empty(); if ((Object)(object)((Character)this).m_body != (Object)null) { ((Character)this).m_body.useGravity = _passengerOriginalGravity; if (!((Character)this).m_body.isKinematic) { ((Character)this).m_body.linearVelocity = linearVelocity; ((Character)this).m_body.angularVelocity = Vector3.zero; } } MercUtil.ResetCharacterGroundState((Character)(object)this); } private void ClearShipPassengerAnimations() { if (!string.IsNullOrEmpty(_passengerAttachAnimation)) { ZSyncAnimation zanim = ((Character)this).m_zanim; if (zanim != null) { zanim.SetBool(_passengerAttachAnimation, false); } } ZSyncAnimation zanim2 = ((Character)this).m_zanim; if (zanim2 != null) { zanim2.SetBool("emote_sit", false); } ZSyncAnimation zanim3 = ((Character)this).m_zanim; if (zanim3 != null) { zanim3.SetBool("attach_mast", false); } ZSyncAnimation zanim4 = ((Character)this).m_zanim; if (zanim4 != null) { zanim4.SetBool("attach_sitship", false); } ZSyncAnimation zanim5 = ((Character)this).m_zanim; if (zanim5 != null) { zanim5.SetBool("attach_chair", false); } } private bool TryBoardPlayerShip(Player player, Ship ship) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: 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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)ship == (Object)null || Time.time - _lastCatchupTeleportTime < 2f) { return false; } Vector3 forward = ((Component)player).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = ((Component)ship).transform.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(forward.z, 0f, 0f - forward.x); float num = ((Class == MercClass.Tank) ? (-0.7f) : ((Class == MercClass.Healer) ? 0.7f : 0f)); float num2 = ((Class == MercClass.Archer) ? 1.5f : 0.8f); Vector3 val2 = ((Component)player).transform.position - forward * num2 + val * num + Vector3.up * 0.15f; if ((Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.IsZoneLoaded(val2)) { return false; } _preserveNextTeleportHeight = true; bool flag; try { flag = ((Character)this).TeleportTo(val2, ((Component)player).transform.rotation, false); } finally { _preserveNextTeleportHeight = false; } if (!flag) { return false; } _lastCatchupTeleportTime = Time.time; _expectedShip = ship; MercPlugin.Log(GetName() + " boarded the player's ship for water travel."); return true; } private bool TryFindDryCatchupPoint(Player player, out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_005c: 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) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: 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_00b7: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: 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) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0122: 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_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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0150: 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_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return false; } Vector3 forward = ((Component)player).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(forward.z, 0f, 0f - forward.x); Vector3 position2 = ((Component)player).transform.position; Vector3 val2 = MercFormation.Offset(Class, forward, MercMovementMode.Travel, PresetFormation); Vector3[] obj = new Vector3[8] { val2, -forward * 2.5f, val * 2.5f, -val * 2.5f, forward * 2.5f, default(Vector3), default(Vector3), default(Vector3) }; Vector3 val3 = -forward + val; obj[5] = ((Vector3)(ref val3)).normalized * 3.5f; val3 = -forward - val; obj[6] = ((Vector3)(ref val3)).normalized * 3.5f; obj[7] = Vector3.zero; Vector3[] array = (Vector3[])(object)obj; foreach (Vector3 val4 in array) { Vector3 val5 = position2 + val4; if (!instance.IsZoneLoaded(val5)) { continue; } float solidHeight = instance.GetSolidHeight(val5); if (!(solidHeight <= -1000f) && !(Mathf.Abs(solidHeight - position2.y) > 6f)) { WaterVolume val6 = null; float waterLevel = Floating.GetWaterLevel(val5, ref val6); if (!((Object)(object)val6 != (Object)null) || !(waterLevel > solidHeight + 0.35f)) { val5.y = solidHeight + 0.1f; position = val5; return true; } } } return false; } private void RefreshDurability() { foreach (ItemData allItem in base.m_inventory.GetAllItems()) { if (allItem != null) { allItem.m_durability = allItem.GetMaxDurability(); } } } public override void OnDeath() { Ai?.CancelAmbientCamp(); ResetAmbientPose(); EndShipPassenger(); Ai?.CancelResurrection(refundCooldown: true); Ai?.CancelGuideTask(null, announce: false); base.m_inventory.RemoveAll(); Say("I'm beaten. I'll return at the mercenary banner."); ((Character)this).OnDeath(); } public override float GetSkillFactor(SkillType skill) { return MercenarySkillFactor(); } public override float GetRandomSkillFactor(SkillType skill) { return MercenarySkillFactor(); } private float MercenarySkillFactor() { float num = Mathf.Clamp(MercConfig.SkillBase.Value + (float)GetEffectiveStage() * MercConfig.SkillPerStage.Value, 0f, MercConfig.MaxSkill.Value); return Mathf.Max(0.1f, num / 100f); } public override bool TeleportTo(Vector3 pos, Quaternion rot, bool distantTeleport) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!IsDungeonTeleport && !_preserveNextTeleportHeight) { float num = (((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetSolidHeight(pos) : (-1000f)); if (num > -1000f) { pos.y = num + 0.1f; } } if (distantTeleport) { ZDO zDO = ((Character)this).m_nview.GetZDO(); zDO.SetPosition(pos); zDO.SetRotation(rot); ZDOMan.instance.ForceSendZDO(zDO.m_uid); ((Component)this).transform.position = pos; ((Component)this).transform.rotation = rot; ResetMotionAfterTeleport(); return true; } if ((Object)(object)ZoneSystem.instance != (Object)null && !ZoneSystem.instance.IsZoneLoaded(pos)) { return false; } ((Component)this).transform.position = pos; ((Component)this).transform.rotation = rot; ResetMotionAfterTeleport(); return true; } private void ResetMotionAfterTeleport() { //IL_0012: 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_0047: Unknown result type (might be due to invalid IL or missing references) Ai?.ResetMovementAfterTeleport(); ((Character)this).SetMoveDir(Vector3.zero); ((Character)this).SetRun(false); Rigidbody component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && !component.isKinematic) { component.linearVelocity = Vector3.zero; component.angularVelocity = Vector3.zero; } MercUtil.ResetCharacterGroundState((Character)(object)this); } public void BeginRenewalEffect(Character target, float duration) { if (!((Object)(object)target == (Object)null) && !target.IsDead()) { MenderHealingStatus.ApplyRenewal(target, duration); BroadcastHolyHealingVisual(target, start: true); } } public void HealRenewalTick(Character target, float amount) { if (!((Object)(object)target == (Object)null) && !target.IsDead()) { target.Heal(amount, true); BroadcastHolyHealingVisual(target, start: false); } } public void HealBurst(Character target, float amount, float duration) { if (!((Object)(object)target == (Object)null) && !target.IsDead()) { target.Heal(amount, true); MenderHealingStatus.ApplyGreaterHeal(target, duration); BroadcastHolyHealingVisual(target, start: true); } } public void HealGreaterTick(Character target, float amount) { if (!((Object)(object)target == (Object)null) && !target.IsDead()) { target.Heal(amount, true); BroadcastHolyHealingVisual(target, start: false); } } private void BroadcastHolyHealingVisual(Character target, bool start) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null)) { if ((Object)(object)((Character)this).m_nview != (Object)null && ((Character)this).m_nview.IsValid()) { ((Character)this).m_nview.InvokeRPC(ZNetView.Everybody, "RPC_HolyHealingVisual", new object[2] { target.GetZDOID(), start }); } else { ShowHolyHealingVisual(target, start); } } } private void RPC_HolyHealingVisual(long sender, ZDOID targetId, bool start) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (sender == ((Character)this).GetOwner()) { ShowHolyHealingVisual(FindCharacter(targetId), start); } } private static void ShowHolyHealingVisual(Character target, bool start) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0024: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null)) { Vector3 centerPoint = target.GetCenterPoint(); if (start) { MercUtil.SpawnVfx("vfx_StaminaUpgrade", centerPoint); MercUtil.SpawnVfx("fx_guardstone_permitted_add", centerPoint); } else { MercUtil.SpawnVfx("vfx_Potion_stamina_medium", centerPoint); } } } } public sealed class MercenaryInteractionProxy : MonoBehaviour, Hoverable, Interactable { private Mercenary _mercenary; private Mercenary Target { get { if (!((Object)(object)_mercenary != (Object)null)) { return _mercenary = ((Component)this).GetComponentInParent(); } return _mercenary; } } internal void Bind(Mercenary mercenary) { _mercenary = mercenary; } public string GetHoverName() { if (!((Object)(object)Target != (Object)null)) { return ""; } return ((Character)Target).GetHoverName(); } public string GetHoverText() { if (!((Object)(object)Target != (Object)null)) { return ""; } return ((Character)Target).GetHoverText(); } public float GetHoverOffset() { if (!((Object)(object)Target != (Object)null)) { return 0f; } return ((Character)Target).GetHoverOffset(); } public bool Interact(Humanoid user, bool hold, bool alt) { if ((Object)(object)Target != (Object)null) { return Target.Interact(user, hold, alt); } return false; } public bool UseItem(Humanoid user, ItemData item) { if ((Object)(object)Target != (Object)null) { return Target.UseItem(user, item); } return false; } } internal static class MercenaryKnowledge { internal static string BuildCompanyReference() { return BuildCompanyReference(Player.m_localPlayer); } internal static string BuildCompanyReference(Player player) { StringBuilder stringBuilder = new StringBuilder(); long num = (((Object)(object)player != (Object)null) ? player.GetPlayerID() : 0); int num2 = ((MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value && num != 0L) ? PlayerProgression.GetStage(num) : (-1)); int num3 = ((num2 >= 0) ? num2 : StageDirector.GetStage()); int loadedCount; Mercenary mercenary = FindBestLoaded(MercClass.Tank, player, out loadedCount); Mercenary mercenary2 = FindBestLoaded(MercClass.Healer, player, out loadedCount); Mercenary mercenary3 = FindBestLoaded(MercClass.Archer, player, out loadedCount); string text = (((Object)(object)mercenary != (Object)null) ? mercenary.GetName() : DisplayName(MercClass.Tank)); string text2 = (((Object)(object)mercenary2 != (Object)null) ? mercenary2.GetName() : DisplayName(MercClass.Healer)); string text3 = (((Object)(object)mercenary3 != (Object)null) ? mercenary3.GetName() : DisplayName(MercClass.Archer)); int num4 = Mathf.Max(1, MercConfig.RenewalTickCount.Value); float num5 = MercConfig.RenewalTickAmount.Value + (float)num3 * MercConfig.RenewalTickPerStage.Value; float num6 = MercConfig.BurstHealAmount.Value + (float)num3 * MercConfig.BurstHealPerStage.Value; float num7 = MercConfig.BurstHealOverTimeAmount.Value + (float)num3 * MercConfig.BurstHealOverTimePerStage.Value; int num8 = Mathf.Max(1, MercConfig.BurstHealDurationSeconds.Value); stringBuilder.AppendLine("[DYNAMICNPCS MOD REFERENCE - authoritative]"); stringBuilder.AppendLine("Company: " + text + " is the close-range axe-and-shield tank; " + text2 + " is the female healer and defensive club fighter; " + text3 + " is the ranged bow scout. A placed Mercenary Banner arrives with the full company already hired, equipped for the placer's own progression; each Viking may keep exactly one (placed from the ordinary Hammer, Misc tab). Press E on a companion to have them follow; hold E to send them back to the banner. " + $"Each recruited companion hears their employer's ordinary chat within {Mathf.Max(1f, MercConfig.ChatHearingRange.Value):0}m, manages hidden stamina, and automatically maintains " + "two health foods plus one stamina food appropriate to defeated-boss progression."); stringBuilder.AppendLine("Shared actions: every recruited mercenary can guide the player to the latest tombstone, to nearby discoveries they know how to find, search for a real boss Vegvisir/runestone, or lead toward a known/generated boss altar. They can fight and guide, but cannot craft items for the player, place structures, gather resources, or magically reveal an undiscovered map marker."); stringBuilder.AppendLine($"{text2}'s healing at the current boss stage ({num3}): Renewal automatically targets the most wounded ally " + $"below {MercConfig.HealTriggerFraction.Value * 100f:0}% health within {MercConfig.HealRange.Value:0}m, heals " + $"{num5:0} HP per tick for {num4} ticks every {MercConfig.RenewalTickInterval.Value:0.#}s " + $"({num5 * (float)num4:0} HP total), and has a {MercConfig.RenewalCooldown.Value:0}s cooldown. " + $"Greater Heal takes priority on an ally below {MercConfig.BurstHealThreshold.Value * 100f:0}% health, instantly heals " + $"{num6:0} HP and then {num7:0} HP each second for {num8}s within {MercConfig.HealRange.Value:0}m " + $"({num6 + num7 * (float)num8:0} HP total), and has a {MercConfig.BurstHealCooldown.Value:0}s cooldown. " + "She stows her club and casts both bare-handed; both place named HUD buffs and golden/white light on the patient. Neither spell needs eitr, reagents, or inventory items."); stringBuilder.AppendLine("Resurrect: when enabled, an employed, following, living " + text2 + " must be within " + $"{MercConfig.ResurrectionRange.Value:0}m and off cooldown when lethal damage lands. The player is downed in place while she approaches, " + $"casts for {MercConfig.ResurrectionCastSeconds.Value:0.#}s with golden/white effects, restores " + $"{MercConfig.ResurrectionHealthFraction.Value * 100f:0}% health, and grants {MercConfig.ResurrectionGraceSeconds.Value:0.#}s lethal-damage protection. " + $"Cooldown is {MercConfig.ResurrectionCooldown.Value / 60f:0.#} minutes. It costs no item or eitr."); AppendLiveRoster(stringBuilder, player); stringBuilder.AppendLine("Interpretation rule: 'not loaded on this client' does not mean a companion does not exist. It means no live local network object can currently provide a position. Never invent a location; explain the loaded status and suggest returning to the banner area if needed."); stringBuilder.AppendLine("[END DYNAMICNPCS MOD REFERENCE]"); return stringBuilder.ToString(); } internal static bool TryBuildFallback(string question, out string answer) { return TryBuildFallback(question, Player.m_localPlayer, out answer); } internal static bool TryBuildFallback(string question, Player player, out string answer) { return TryBuildFallback(question, player, null, out answer); } internal static bool TryBuildFallback(string question, ServerAuthority.SenderPlayerState requester, out string answer) { return TryBuildFallback(question, requester?.LivePlayer, requester, out answer); } private static bool TryBuildFallback(string question, Player player, ServerAuthority.SenderPlayerState requester, out string answer) { //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) answer = null; string text = (question ?? "").ToLowerInvariant(); string term = MercConfig.HealerName.Value.ToLowerInvariant(); string term2 = MercConfig.TankName.Value.ToLowerInvariant(); string term3 = MercConfig.ArcherName.Value.ToLowerInvariant(); bool flag = text.Contains("where") || text.Contains("find") || text.Contains("location"); if (flag && requester != null) { MercClass? mercClass = ((ContainsTerm(text, term) || ContainsTerm(text, "healer") || ContainsTerm(text, "mender")) ? new MercClass?(MercClass.Healer) : ((ContainsTerm(text, term2) || ContainsTerm(text, "tank") || ContainsTerm(text, "bulwark")) ? new MercClass?(MercClass.Tank) : ((ContainsTerm(text, term3) || ContainsTerm(text, "archer") || ContainsTerm(text, "fletcher")) ? new MercClass?(MercClass.Archer) : ((MercClass?)null)))); if (!mercClass.HasValue) { answer = "Name the companion you want to locate: " + MercConfig.TankName.Value + ", " + MercConfig.HealerName.Value + " or " + MercConfig.ArcherName.Value + "."; return true; } foreach (Mercenary instance in Mercenary.Instances) { if ((Object)(object)instance == (Object)null || instance.Class != mercClass.Value || ((Character)instance).IsDead()) { continue; } ZNetView component = ((Component)instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && ServerAuthority.TryResolveMercenary(component.GetZDO().m_uid, out var state, out var _) && ServerAuthority.IsAssignedTo(state, requester, requireFollowing: false) && KnowledgeRules.TryDirection(requester.Position.x, requester.Position.z, state.Position.x, state.Position.z, out var direction, out var distance)) { if (Character.InInterior(state.Position) || Character.InInterior(requester.Position)) { answer = instance.GetName() + (Character.InInterior(state.Position) ? " is inside an interior" : " is outdoors") + ". I cannot confirm a route between interior and surface positions. Check the company HUD."; return true; } answer = instance.GetName() + " is about " + distance + " " + direction + ", " + (instance.IsFollowing() ? "following." : "holding position."); return true; } } answer = "I cannot confirm a loaded position for your " + DisplayName(mercClass.Value) + ". Check the company HUD or return to your Mercenary Banner."; return true; } if (ContainsTerm(text, term) || text.Contains("mender") || ContainsTerm(text, "healer") || text.Contains("renewal") || text.Contains("greater heal") || text.Contains("resurrect")) { if (flag) { int loadedCount; Mercenary mercenary = FindBestLoaded(MercClass.Healer, player, out loadedCount); answer = (((Object)(object)mercenary == (Object)null) ? (MercConfig.HealerName.Value + " is our Mender, but she is not loaded in the server's active area, so I cannot give you a false direction. Return to the Mercenary Banner area so its spawner can restore the trio.") : (mercenary.GetName() + " is " + DescribeRelativeLocation(player, mercenary) + " and is " + (mercenary.IsFollowing() ? "following" : "staying in place") + ".")); return true; } int num = (MercConfig.ProgressionPerPlayer.Value ? PlayerProgression.GetKnownStage(requester?.PlayerId ?? (((Object)(object)player != (Object)null) ? player.GetPlayerID() : 0)) : StageDirector.GetStage()); if (num < 0) { answer = MercConfig.HealerName.Value + " provides Renewal and Greater Heal automatically when their health thresholds and cooldowns allow. Spell strength depends on your company tier, which I cannot confirm yet. Resurrect also requires her to be following nearby and ready."; return true; } float num2 = MercConfig.RenewalTickAmount.Value + (float)num * MercConfig.RenewalTickPerStage.Value; float num3 = MercConfig.BurstHealAmount.Value + (float)num * MercConfig.BurstHealPerStage.Value; float num4 = MercConfig.BurstHealOverTimeAmount.Value + (float)num * MercConfig.BurstHealOverTimePerStage.Value; answer = $"{MercConfig.HealerName.Value} is our Mender: Renewal heals {num2:0} HP for {MercConfig.RenewalTickCount.Value} ticks, " + $"Greater Heal restores {num3:0} HP at once plus {num4:0} HP each second for {MercConfig.BurstHealDurationSeconds.Value}s, " + "and Resurrect can bring you back when she is following nearby and off cooldown. Her magic costs no items or eitr."; return true; } if (ContainsTerm(text, term2) || text.Contains("bulwark") || ContainsTerm(text, "tank")) { answer = MercConfig.TankName.Value + " is our Bulwark. He fights up close with an axe and shield, holds enemies away from the group, and can perform the same travel and search guidance as the other mercenaries."; return true; } if (ContainsTerm(text, term3) || text.Contains("fletcher") || ContainsTerm(text, "archer")) { answer = MercConfig.ArcherName.Value + " is our Fletcher. He fights at bow range, retreats when enemies close in, scouts, and can perform the same travel and search guidance as the other mercenaries."; return true; } if (ContainsTerm(text, "company") && !ContainsTerm(text, "banner")) { answer = "Your company has three roles: " + MercConfig.TankName.Value + " fights up close with an axe and shield, " + MercConfig.HealerName.Value + " provides healing and can resurrect under the required conditions, and " + MercConfig.ArcherName.Value + " fights at bow range. They support Follow and Stay orders, supported travel and search orders, and questions about the world. Ask about a named companion for details."; return true; } if (text.Contains("mercenary banner") || text.Contains("mercenary hammer") || text.Contains("mercenaries")) { answer = "The Mercenary Banner maintains " + MercConfig.TankName.Value + ", " + MercConfig.HealerName.Value + ", and " + MercConfig.ArcherName.Value + ". Claiming assigns the default-named trio without prompts. Rename one afterward by looking directly at that mercenary and using the rename hotkey; the banner itself has no rename interaction. Then tell each companion to Follow or Stay. All three hear only their employer's ordinary chat within the server-set range."; return true; } return false; } private static bool ContainsTerm(string text, string term) { string text2 = Regex.Replace(term ?? "", "[^a-z0-9]+", " ").Trim(); if (text2.Length == 0) { return false; } return (" " + Regex.Replace(text ?? "", "[^a-z0-9]+", " ").Trim() + " ").Contains(" " + text2 + " "); } private static List ConnectedPlayerNames() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null)) { string playerName = allPlayer.GetPlayerName(); if (!string.IsNullOrWhiteSpace(playerName) && !list.Contains(playerName)) { list.Add(playerName); } } } if ((Object)(object)ZNet.instance != (Object)null && ZDOMan.instance != null && ZNet.instance.IsServer()) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); string text = ((zDO != null && zDO.IsValid()) ? zDO.GetString(ZDOVars.s_playerName, peer.m_playerName ?? "") : (peer.m_playerName ?? "")); if (!string.IsNullOrWhiteSpace(text) && !list.Contains(text)) { list.Add(text); } } } } return list; } private static void AppendLiveRoster(StringBuilder sb, Player player) { sb.AppendLine("[LIVE MERCENARY ROSTER - current server view]"); MercClass[] array = new MercClass[3] { MercClass.Tank, MercClass.Healer, MercClass.Archer }; foreach (MercClass mercClass in array) { int loadedCount; Mercenary mercenary = FindBestLoaded(mercClass, player, out loadedCount); string text = DisplayName(mercClass); if ((Object)(object)mercenary == (Object)null) { sb.AppendLine("- " + text + ": not currently loaded in the server's active area; exact location and condition are unknown. A surviving banner's server-side spawner will maintain/respawn its trio when that banner area is active."); continue; } ZNetView component = ((Component)mercenary).GetComponent(); string text2 = Mercenary.EmployerNameOf(((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); string text3 = (string.IsNullOrEmpty(text2) ? "not yet recruited" : (((Object)(object)player != (Object)null && mercenary.IsAssignedTo(player, requireFollowing: false)) ? "employed by the requesting player" : ("serves " + text2))); string text4 = (mercenary.IsFollowing() ? "following" : "staying"); string text5 = (((Character)mercenary).IsDead() ? "dead/awaiting banner respawn" : "alive"); string text6 = (((Object)(object)player != (Object)null) ? DescribeRelativeLocation(player, mercenary) : "position loaded"); string text7 = (((Object)(object)mercenary.Ai != (Object)null && mercenary.Ai.GuideStatus != "none") ? ("; " + mercenary.Ai.GuideStatus) : ""); string text8 = ((loadedCount > 1) ? $"; {loadedCount} loaded instances of this class" : ""); string text9 = ((mercClass != MercClass.Healer) ? "" : ((mercenary.GetResurrectionCooldownRemaining() <= 0f) ? "; Resurrect ready" : $"; Resurrect cooldown {Mathf.CeilToInt(mercenary.GetResurrectionCooldownRemaining())}s remaining")); sb.AppendLine("- " + mercenary.GetName() + ": " + text5 + ", " + text3 + ", " + text4 + ", " + text6 + text7 + text9 + text8 + "."); } sb.AppendLine("[END LIVE MERCENARY ROSTER]"); } private static Mercenary FindBestLoaded(MercClass mercClass, Player player, out int loadedCount) { //IL_0096: 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) loadedCount = 0; Mercenary mercenary = null; int num = int.MinValue; float num2 = float.MaxValue; foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && instance.Class == mercClass) { loadedCount++; int num3 = (((Object)(object)player != (Object)null && instance.IsAssignedTo(player, requireFollowing: false)) ? 4 : 0) + ((!((Character)instance).IsDead()) ? 2 : 0) + (instance.IsFollowing() ? 1 : 0); float num4 = (((Object)(object)player != (Object)null) ? Vector3.Distance(((Component)player).transform.position, ((Component)instance).transform.position) : 0f); if ((Object)(object)mercenary == (Object)null || num3 > num || (num3 == num && num4 < num2)) { mercenary = instance; num = num3; num2 = num4; } } } return mercenary; } private static string DescribeRelativeLocation(Player player, Mercenary merc) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return "loaded, but the local player's position is unavailable"; } Vector3 val = ((Component)merc).transform.position - ((Component)player).transform.position; Vector2 val2 = new Vector2(val.x, val.z); float magnitude = ((Vector2)(ref val2)).magnitude; string arg = CompassDirection(val); Biome val3 = ((WorldGenerator.instance != null) ? WorldGenerator.instance.GetBiome(((Component)merc).transform.position) : player.GetCurrentBiome()); if (!(magnitude < 2f)) { return $"about {Mathf.RoundToInt(magnitude)}m {arg} of the player in {val3}"; } return $"beside the player in {val3}"; } private static string CompassDirection(Vector3 delta) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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) if (Mathf.Abs(delta.x) < 0.01f && Mathf.Abs(delta.z) < 0.01f) { return "nearby"; } float num = Mathf.Atan2(delta.x, delta.z) * 57.29578f; if (num < 0f) { num += 360f; } string[] array = new string[8] { "north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest" }; return array[Mathf.RoundToInt(num / 45f) % array.Length]; } private static string DisplayName(MercClass mercClass) { return mercClass switch { MercClass.Healer => MercConfig.HealerName.Value + " the Mender", MercClass.Archer => MercConfig.ArcherName.Value + " the Fletcher", _ => MercConfig.TankName.Value + " the Bulwark", }; } } internal sealed class MercFoodChoice { public string PrefabName; public string DisplayName; public float Health; public float Stamina; public float Regen; public int Tier; } internal sealed class MercFoodPlan { public readonly List Foods = new List(); public float HealthBonus; public float StaminaBonus; public float HealthRegen; public string Key { get { List list = new List(); foreach (MercFoodChoice food in Foods) { list.Add(food.PrefabName); } return string.Join("|", list.ToArray()); } } public string Summary { get { if (Foods.Count == 0) { return "no food selected"; } List list = new List(); for (int i = 0; i < Foods.Count; i++) { string text = ((i < 2) ? "health" : "stamina"); list.Add(Foods[i].DisplayName + " (" + text + ")"); } return string.Join(", ", list.ToArray()); } } } internal static class MercFood { private sealed class CachedPlan { internal MercFoodPlan Plan; internal float BuiltAt; } private static readonly Dictionary VanillaFoodTier = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "CookedDeerMeat", 0 }, { "CookedMeat", 0 }, { "NeckTailGrilled", 0 }, { "Honey", 0 }, { "Raspberry", 0 }, { "Mushroom", 0 }, { "DeerStew", 1 }, { "MincedMeatSauce", 1 }, { "CookedBjornMeat", 1 }, { "CarrotSoup", 1 }, { "QueensJam", 1 }, { "Blueberries", 1 }, { "MushroomYellow", 1 }, { "SerpentStew", 2 }, { "SerpentMeatCooked", 2 }, { "Sausages", 2 }, { "BlackSoup", 2 }, { "TurnipStew", 2 }, { "MuckShake", 2 }, { "WolfSkewer", 3 }, { "CookedWolfMeat", 3 }, { "WolfJerky", 3 }, { "Eyescream", 3 }, { "OnionSoup", 3 }, { "Onion", 3 }, { "LoxPie", 4 }, { "FishWraps", 4 }, { "BloodPudding", 4 }, { "Bread", 4 }, { "CookedLoxMeat", 4 }, { "Cloudberry", 4 }, { "MisthareSupreme", 5 }, { "MeatPlatter", 5 }, { "HoneyGlazedChicken", 5 }, { "FishAndBread", 5 }, { "MushroomOmelette", 5 }, { "Salad", 5 }, { "PiquantPie", 6 }, { "MashedMeat", 6 }, { "FierySvinstew", 6 }, { "RoastedCrustPie", 6 }, { "ScorchingMedley", 6 }, { "SpicyMarmalade", 6 } }; private const float PlanCacheSeconds = 10f; private static ObjectDB _cachedObjectDb; private static int _cachedItemCount = -1; private static readonly Dictionary PlansByKey = new Dictionary(); public static MercFoodPlan Select(int stage, Player owner) { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_items == null) { return new MercFoodPlan(); } int count = instance.m_items.Count; long key = (((Object)(object)owner != (Object)null) ? owner.GetPlayerID() : 0) * 16 + stage; float realtimeSinceStartup = Time.realtimeSinceStartup; if (_cachedObjectDb == instance && _cachedItemCount == count && PlansByKey.TryGetValue(key, out var value) && realtimeSinceStartup >= value.BuiltAt && realtimeSinceStartup - value.BuiltAt < 10f) { return value.Plan; } if (_cachedObjectDb != instance || _cachedItemCount != count) { _cachedObjectDb = instance; _cachedItemCount = count; PlansByKey.Clear(); } MercFoodPlan mercFoodPlan = BuildPlan(stage, owner, instance); PlansByKey[key] = new CachedPlan { Plan = mercFoodPlan, BuiltAt = realtimeSinceStartup }; return mercFoodPlan; } private static MercFoodPlan BuildPlan(int stage, Player owner, ObjectDB db) { MercFoodPlan mercFoodPlan = new MercFoodPlan(); List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { continue; } SharedData shared = component.m_itemData.m_shared; if ((shared.m_food <= 0f && shared.m_foodStamina <= 0f) || shared.m_foodEitr > 0f) { continue; } string prefabName = Utils.GetPrefabName(((Object)item).name); if (string.IsNullOrEmpty(prefabName) || !hashSet.Add(prefabName)) { continue; } int tier; if (VanillaFoodTier.TryGetValue(prefabName, out var value)) { if (value > stage) { continue; } tier = value; } else { if (!IsKnownToPlayer(owner, shared.m_name)) { continue; } tier = stage; } string text = shared.m_name; if (Localization.instance != null && !string.IsNullOrEmpty(text)) { text = Localization.instance.Localize(text); } if (string.IsNullOrWhiteSpace(text) || text.StartsWith("$")) { text = prefabName; } list.Add(new MercFoodChoice { PrefabName = prefabName, DisplayName = text, Health = shared.m_food, Stamina = shared.m_foodStamina, Regen = shared.m_foodRegen, Tier = tier }); } HashSet selected = new HashSet(StringComparer.OrdinalIgnoreCase); List list2 = list.FindAll((MercFoodChoice food) => food.Health > food.Stamina && food.Health > 0f); list2.Sort(CompareHealth); AddBest(mercFoodPlan, selected, list2, 2); if (mercFoodPlan.Foods.Count < 2) { list.Sort(CompareHealth); AddBest(mercFoodPlan, selected, list, 2 - mercFoodPlan.Foods.Count); } List list3 = list.FindAll((MercFoodChoice food) => food.Stamina > food.Health && food.Stamina > 0f); list3.Sort(CompareStamina); AddBest(mercFoodPlan, selected, list3, 1); if (mercFoodPlan.Foods.Count < 3) { list.Sort(CompareStamina); AddBest(mercFoodPlan, selected, list, 3 - mercFoodPlan.Foods.Count); } foreach (MercFoodChoice food in mercFoodPlan.Foods) { mercFoodPlan.HealthBonus += food.Health; mercFoodPlan.StaminaBonus += food.Stamina; mercFoodPlan.HealthRegen += food.Regen; } return mercFoodPlan; } private static bool IsKnownToPlayer(Player owner, string sharedName) { if ((Object)(object)owner == (Object)null || string.IsNullOrEmpty(sharedName)) { return false; } try { return owner.IsRecipeKnown(sharedName) || owner.IsKnownMaterial(sharedName); } catch { return false; } } private static void AddBest(MercFoodPlan plan, HashSet selected, List candidates, int count) { foreach (MercFoodChoice candidate in candidates) { if (count <= 0) { break; } if (selected.Add(candidate.PrefabName)) { plan.Foods.Add(candidate); count--; } } } private static int CompareHealth(MercFoodChoice left, MercFoodChoice right) { int num = right.Health.CompareTo(left.Health); if (num != 0) { return num; } num = right.Tier.CompareTo(left.Tier); if (num != 0) { return num; } num = right.Regen.CompareTo(left.Regen); if (num == 0) { return right.Stamina.CompareTo(left.Stamina); } return num; } private static int CompareStamina(MercFoodChoice left, MercFoodChoice right) { int num = right.Stamina.CompareTo(left.Stamina); if (num != 0) { return num; } num = right.Tier.CompareTo(left.Tier); if (num != 0) { return num; } num = right.Regen.CompareTo(left.Regen); if (num == 0) { return right.Health.CompareTo(left.Health); } return num; } } internal readonly struct MercFormationSlot { internal readonly float Side; internal readonly float Forward; internal readonly float SoftRadius; internal readonly float MaxPlayerDistance; internal MercFormationSlot(float side, float forward, float softRadius, float maxPlayerDistance) { Side = side; Forward = forward; SoftRadius = softRadius; MaxPlayerDistance = maxPlayerDistance; } } internal static class MercFormation { internal static MercFormationSlot For(MercClass mercClass, MercMovementMode mode = MercMovementMode.Travel, CompanyFormation formation = CompanyFormation.Roles) { MercMovementSlot mercMovementSlot = CompanyPresetRules.Slot(mercClass, mode, formation); return new MercFormationSlot(mercMovementSlot.Side, mercMovementSlot.Forward, mercMovementSlot.SoftRadius, mercMovementSlot.MaxReferenceDistance); } internal static Vector3 Offset(MercClass mercClass, Vector3 forward, MercMovementMode mode = MercMovementMode.Travel, CompanyFormation formation = CompanyFormation.Roles) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = Vector3.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val = new Vector3(forward.z, 0f, 0f - forward.x); MercFormationSlot mercFormationSlot = For(mercClass, mode, formation); return val * mercFormationSlot.Side + forward * mercFormationSlot.Forward; } internal static Vector3 Anchor(Vector3 employerPosition, Vector3 forward, MercClass mercClass, MercMovementMode mode = MercMovementMode.Travel, CompanyFormation formation = CompanyFormation.Roles) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) return employerPosition + Offset(mercClass, forward, mode, formation); } } public static class MercGuideMenu { private sealed class Choice { internal string Label; internal string Detail; internal string Scope; internal string Icon; internal Action Select; internal Text Text; internal Image Image; } internal const string GuideMenuRequestRpc = "DynamicNPCs_GuideMenuRequestV1"; private static readonly List Choices = new List(); private static readonly Stack Parents = new Stack(); private static GameObject _panel; private static RectTransform _rect; private static MercRadialWheel _wheel; private static Text _title; private static Text _center; private static Text _scope; private static Text _description; private static Text _back; private static Text _hint; private static Mercenary _merc; private static bool _memberPage; private static bool _blocked; private static int _selected = -1; private static float _rotation; private static float _targetRotation; private static bool _suppressRadialUntilRelease; private static Vector3 _lastMouse; private static bool _aimingMouse; private static bool _awaitNeutral; private static int _openedFrame; private static string _pageScope; private static string _emptyMessage; private static Action _currentPage; private static bool _lastGamepad; public static bool IsOpen => (Object)(object)_panel != (Object)null; public static void Toggle(Mercenary merc) { if (IsOpen) { Close(); } else { Open(merc); } } public static void Open(Mercenary merc) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) if (IsOpen || (Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead() || Hud.InRadial()) { return; } try { if (GUIManager.Instance != null && !((Object)(object)GUIManager.CustomGUIFront == (Object)null)) { MercRadialWheel.ReadNativePalette(); _merc = merc; _panel = new GameObject("DynamicNPCs_CompanyWheel", new Type[2] { typeof(RectTransform), typeof(Image) }); _rect = _panel.GetComponent(); ((Transform)_rect).SetParent(GUIManager.CustomGUIFront.transform, false); RectTransform rect = _rect; RectTransform rect2 = _rect; Vector2 val = (_rect.pivot = new Vector2(0.5f, 0.5f)); Vector2 anchorMin = (rect2.anchorMax = val); rect.anchorMin = anchorMin; _rect.sizeDelta = new Vector2(760f, 820f); ((Graphic)_panel.GetComponent()).color = Color.clear; _wheel = new GameObject("Segments", new Type[2] { typeof(RectTransform), typeof(MercRadialWheel) }).GetComponent(); ((Transform)((Graphic)_wheel).rectTransform).SetParent((Transform)(object)_rect, false); ((Graphic)_wheel).rectTransform.sizeDelta = new Vector2(610f, 610f); ((Graphic)_wheel).raycastTarget = false; _title = Label("Title", "", Vector2.up * 338f, 700f, 40f, 28, MercRadialWheel.Gold); _scope = Label("Scope", "", Vector2.up * 55f, 178f, 24f, 13, Color.white); _center = Label("Selected", "", Vector2.zero, 182f, 76f, 23, MercRadialWheel.Gold); _back = Label("Back", "", Vector2.down * 63f, 178f, 24f, 14, Color.white); _description = Label("Description", "", Vector2.down * 337f, 690f, 58f, 18, Color.white); _hint = Label("Controls", "", Vector2.down * 392f, 730f, 28f, 14, new Color(0.8f, 0.77f, 0.69f)); Parents.Clear(); _lastMouse = Input.mousePosition; _aimingMouse = false; _openedFrame = Time.frameCount; _suppressRadialUntilRelease = ZInput.GetButton("JoyRadial"); SetInputBlocked(block: true); ShowCompany(); DrawSelection(); } } catch (Exception ex) { MercPlugin.LogWarn("Could not open company wheel: " + ex.Message); Close(); } } public static void Close() { _suppressRadialUntilRelease |= _blocked && ZInput.GetButton("JoyRadial"); if ((Object)(object)_panel != (Object)null) { Object.Destroy((Object)(object)_panel); } _panel = null; _rect = null; _wheel = null; _merc = null; Choices.Clear(); Parents.Clear(); _currentPage = null; _selected = -1; SetInputBlocked(block: false); } private static void SetInputBlocked(bool block) { if (_blocked != block) { GUIManager.BlockInput(block); _blocked = block; } } internal static bool SuppressVanillaRadial() { if (ZInput.GetButtonDown("JoyRadial") && MercPlugin.CanReadCompanyInput()) { _suppressRadialUntilRelease = true; } if (!ZInput.GetButton("JoyRadial") && !ZInput.GetButtonUp("JoyRadial")) { _suppressRadialUntilRelease = false; } if (!IsOpen) { return _suppressRadialUntilRelease; } return true; } internal static void ConsumeContextRadialPress() { _suppressRadialUntilRelease |= ZInput.GetButton("JoyRadial"); } internal static void Tick() { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) if (!IsOpen) { if (_blocked) { Close(); } return; } try { if ((Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead() || (Object)(object)ZNetScene.instance == (Object)null || Menu.IsVisible() || Console.IsVisible() || InventoryGui.IsVisible() || Minimap.IsOpen() || ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())) { Close(); return; } if (_memberPage && ((Object)(object)_merc == (Object)null || ((Character)_merc).IsDead() || !_merc.IsAssignedTo(Player.m_localPlayer, requireFollowing: false))) { Parents.Clear(); ShowCompany(); } Transform parent = ((Transform)_rect).parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); float num; if (!((Object)(object)val != (Object)null)) { num = 1f; } else { float[] obj = new float[3] { 1f, 0f, 0f }; Rect rect = val.rect; obj[1] = ((Rect)(ref rect)).width / 790f; rect = val.rect; obj[2] = ((Rect)(ref rect)).height / 850f; num = Mathf.Min(obj); } float num2 = num; ((Transform)_rect).localScale = Vector3.one * Mathf.Max(0.25f, num2); bool flag = ZInput.IsGamepadActive(); if (_hint.text.Length == 0 || flag != _lastGamepad) { _lastGamepad = flag; _hint.text = (flag ? MercLocalization.Text("dnpc_radial_pad_hint", MercLocalization.Binding("JoyRadialInteract", "RT"), MercLocalization.Binding("JoyRadialBack", "LT")) : T("dnpc_radial_mouse_hint")); } _rotation = MercRadialRules.StepRotation(_rotation, _targetRotation, Time.unscaledDeltaTime); if (Time.frameCount > _openedFrame) { ReadInput(flag); } if (IsOpen) { DrawSelection(); } } catch (Exception ex) { MercPlugin.LogWarn("Company wheel closed after input failure: " + ex.Message); Close(); } } private static void ReadInput(bool gamepad) { //IL_0032: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: 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_01e0: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)27) || Input.GetMouseButtonDown(1) || (gamepad && (ZInput.GetButtonDown("JoyButtonB") || ZInput.GetButtonDown("JoyRadialBack")))) { Back(); return; } Vector3 mousePosition = Input.mousePosition; Vector3 val = mousePosition - _lastMouse; bool flag = ((Vector3)(ref val)).sqrMagnitude > 0.04f; _lastMouse = mousePosition; if (flag) { _aimingMouse = true; } float y = Input.mouseScrollDelta.y; bool flag2 = Input.GetKeyDown((KeyCode)275) || Input.GetKeyDown((KeyCode)274) || (gamepad && (ZInput.GetButtonDown("JoyDPadRight") || ZInput.GetButtonDown("JoyDPadDown"))); bool flag3 = Input.GetKeyDown((KeyCode)276) || Input.GetKeyDown((KeyCode)273) || (gamepad && (ZInput.GetButtonDown("JoyDPadLeft") || ZInput.GetButtonDown("JoyDPadUp"))); if (y != 0f || flag2 || flag3) { int num = ((y == 0f) ? (flag2 ? 1 : (-1)) : ((y < 0f) ? 1 : (-1))); _selected = MercRadialRules.WrapIndex((_selected >= 0) ? (_selected + num) : ((num <= 0) ? (Choices.Count - 1) : 0), Choices.Count); if (_selected >= 0) { _targetRotation = _rotation + MercRadialRules.ShortestDelta(_rotation, 0f - MercRadialRules.SlotAngle(_selected, Choices.Count)); } _aimingMouse = false; _awaitNeutral = false; } else if (gamepad) { Vector2 value = ZInput.GetValue("RadialStick"); if (((Vector2)(ref value)).magnitude < 0.25f) { _awaitNeutral = false; } if (!_awaitNeutral && ((Vector2)(ref value)).magnitude >= 0.35f) { _selected = MercRadialRules.IndexAt(value.x * 200f, value.y * 200f, Choices.Count, _rotation, 50f); _targetRotation = _rotation; _aimingMouse = false; } } else if (_aimingMouse) { Vector2 val2 = MousePoint(); if (!_awaitNeutral || flag) { _awaitNeutral = false; _selected = MercRadialRules.IndexAt(val2.x, val2.y, Choices.Count, _rotation, 102f); _targetRotation = _rotation; } } if (Input.GetMouseButtonDown(0)) { Vector2 val3 = MousePoint(); if (((Vector2)(ref val3)).magnitude <= 99f) { Back(); return; } int num2 = MercRadialRules.IndexAt(val3.x, val3.y, Choices.Count, _rotation, 102f); if (num2 >= 0) { _selected = num2; Confirm(); } } else if (Input.GetKeyDown((KeyCode)13) || (gamepad && (ZInput.GetButtonDown("JoyButtonA") || ZInput.GetButtonDown("JoyRadialInteract")))) { Confirm(); } } private static Vector2 MousePoint() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) Canvas componentInParent = ((Component)_rect).GetComponentInParent(); Camera val = (((Object)(object)componentInParent != (Object)null && (int)componentInParent.renderMode != 0) ? componentInParent.worldCamera : null); Vector2 result = default(Vector2); RectTransformUtility.ScreenPointToLocalPointInRectangle(_rect, Vector2.op_Implicit(Input.mousePosition), val, ref result); return result; } private static void Confirm() { if (_selected >= 0 && _selected < Choices.Count) { Choices[_selected].Select?.Invoke(); } } private static void Back() { if (Parents.Count > 0) { Parents.Pop()(); } else { Close(); } } private static void Navigate(Action page) { Parents.Push(_currentPage); page(); } private static void BeginPage(string title, string scope, Action page, bool member) { foreach (Choice choice in Choices) { if ((Object)(object)choice.Text != (Object)null) { Object.Destroy((Object)(object)((Component)choice.Text).gameObject); } if ((Object)(object)choice.Image != (Object)null) { Object.Destroy((Object)(object)((Component)choice.Image).gameObject); } } Choices.Clear(); _currentPage = page; _memberPage = member; _pageScope = scope; _emptyMessage = T("dnpc_company_menu_no_members"); _title.text = title; _selected = -1; _rotation = (_targetRotation = 0f); _awaitNeutral = true; _aimingMouse = false; _openedFrame = Time.frameCount; _back.text = T((Parents.Count == 0) ? "dnpc_guide_close" : "dnpc_guide_back"); } private static void Add(string label, string detail, string icon, Action action, string scope = null) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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) if (Choices.Count >= 8) { throw new InvalidOperationException("Too many wheel segments"); } Choice choice = new Choice { Label = label, Detail = detail, Scope = scope, Icon = icon, Select = action }; choice.Text = Label("SegmentLabel", label, Vector2.zero, 130f, 54f, 18, Color.white); Sprite val = ItemIcon(icon); if ((Object)(object)val != (Object)null) { choice.Image = new GameObject("SegmentIcon", new Type[2] { typeof(RectTransform), typeof(Image) }).GetComponent(); ((Transform)((Graphic)choice.Image).rectTransform).SetParent((Transform)(object)_rect, false); ((Graphic)choice.Image).rectTransform.sizeDelta = new Vector2(44f, 44f); choice.Image.sprite = val; choice.Image.preserveAspect = true; ((Graphic)choice.Image).raycastTarget = false; } Choices.Add(choice); } private static void DrawSelection() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) _wheel.SetState(Choices.Count, _selected, _rotation); for (int i = 0; i < Choices.Count; i++) { float num = (MercRadialRules.SlotAngle(i, Choices.Count) + _rotation) * ((float)Math.PI / 180f); Vector2 val = new Vector2(Mathf.Sin(num), Mathf.Cos(num)); bool flag = i == _selected; Choice choice = Choices[i]; Vector2 val2 = val * (flag ? 208f : 202f); ((Graphic)choice.Text).rectTransform.anchoredPosition = val2 + (((Object)(object)choice.Image != (Object)null) ? (Vector2.down * 27f) : Vector2.zero); ((Graphic)choice.Text).color = (Color)(flag ? MercRadialWheel.Gold : new Color(0.9f, 0.87f, 0.78f)); if ((Object)(object)choice.Image != (Object)null) { ((Graphic)choice.Image).rectTransform.anchoredPosition = val2 + Vector2.up * 24f; ((Graphic)choice.Image).color = (Color)(flag ? Color.white : new Color(0.78f, 0.78f, 0.78f)); } } _scope.text = ((_selected >= 0 && _selected < Choices.Count) ? (Choices[_selected].Scope ?? _pageScope) : _pageScope); _center.text = ((_selected >= 0 && _selected < Choices.Count) ? Choices[_selected].Label : T("dnpc_radial_choose")); _description.text = ((_selected >= 0 && _selected < Choices.Count) ? Choices[_selected].Detail : ((Choices.Count == 0) ? _emptyMessage : T("dnpc_radial_neutral"))); } private static void ShowCompany() { //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) BeginPage(T("dnpc_company_menu_title"), T("dnpc_radial_company"), ShowCompany, member: false); if (MercConfig.ContextCompanyOrdersEnabled != null && MercConfig.ContextCompanyOrdersEnabled.Value) { Add(T("dnpc_action_follow_me"), T("dnpc_company_menu_follow"), "Wishbone", delegate { SendCompany(MercContextOrderType.Recall); }); Add(T("dnpc_radial_hold_here"), T("dnpc_company_menu_hold"), "ShieldWood", delegate { SendCompany(MercContextOrderType.Hold); }); Add(T("dnpc_radial_stop"), T("dnpc_company_menu_stop"), "Hammer", delegate { SendCompany(MercContextOrderType.CancelGuide); }); } List list = new List(); foreach (Mercenary instance in Mercenary.Instances) { if ((Object)(object)instance != (Object)null && !((Character)instance).IsDead() && instance.IsAssignedTo(Player.m_localPlayer, requireFollowing: false)) { list.Add(instance); } } list.Sort((Mercenary a, Mercenary b) => ((int)a.Class).CompareTo((int)b.Class)); foreach (Mercenary item in list) { if (Choices.Count >= 6) { break; } Mercenary selected = item; string detail = MercLocalization.Text("dnpc_company_menu_member", item.GetName(), State(item), Mathf.RoundToInt(Vector3.Distance(((Component)Player.m_localPlayer).transform.position, ((Component)item).transform.position))); Add(item.GetText(), detail, (item.Class == MercClass.Tank) ? "SwordIron" : ((item.Class == MercClass.Healer) ? "MeadHealthMedium" : "BowFineWood"), delegate { _merc = selected; Navigate(ShowMember); }, T("dnpc_radial_individual")); } } private static string State(Mercenary merc) { return T(((Object)(object)merc.Ai != (Object)null && merc.Ai.IsSneakingWithEmployer) ? "dnpc_hud_sneaking" : (MercResourceWork.IsWorking(merc) ? "dnpc_hud_working" : (((Object)(object)merc.Ai != (Object)null && merc.Ai.IsGuiding) ? "dnpc_hud_guiding" : (merc.IsAssignedTo(Player.m_localPlayer, requireFollowing: true) ? "dnpc_hud_following" : "dnpc_hud_guarding")))); } private static void ShowMember() { if ((Object)(object)_merc == (Object)null) { Parents.Clear(); ShowCompany(); return; } BeginPage(_merc.GetName(), T("dnpc_radial_individual"), ShowMember, member: true); Add(T("dnpc_action_follow_me"), T("dnpc_company_menu_member_follow"), "Wishbone", delegate { Send(7, 0, null); }); Add(T("dnpc_radial_hold_position"), T("dnpc_company_menu_member_hold"), "ShieldWood", delegate { Send(8, 0, null); }); Add(T("dnpc_action_dismiss"), T("dnpc_company_menu_member_home"), "Hammer", delegate { Send(9, 0, null); }); Add(T("dnpc_radial_guide"), T("dnpc_company_menu_guide_range"), "MapTable", delegate { if (CanGuide(16f)) { Navigate(ShowGuide); } }); Add(T("dnpc_action_rename"), T("dnpc_radial_rename_detail"), "Feathers", delegate { Mercenary merc = _merc; Close(); merc?.TryBeginRename(Player.m_localPlayer, showFailure: true); }); } private static void ShowGuide() { BeginPage(MercLocalization.Text("dnpc_guide_named_title", _merc.GetText()), T("dnpc_radial_individual"), ShowGuide, member: true); Add(T("dnpc_guide_tombstone"), T("dnpc_company_menu_guide_range"), "TrophySkeleton", delegate { Send(1, 0, null); }); Add(T("dnpc_guide_banner"), T("dnpc_company_menu_guide_range"), "Hammer", delegate { Send(2, 0, null); }); Add(T("dnpc_guide_boss_runestone"), T("dnpc_guide_choose_boss"), "Ruby", delegate { Navigate(delegate { ShowBoss(runestone: true); }); }); Add(T("dnpc_guide_boss_altar"), T("dnpc_guide_choose_boss"), "TrophyEikthyr", delegate { Navigate(delegate { ShowBoss(runestone: false); }); }); Add(T("dnpc_radial_find"), T("dnpc_guide_find_nearby"), "Wishbone", delegate { Navigate(ShowCategories); }); Add(T("dnpc_guide_players"), T("dnpc_company_menu_guide_range"), "HelmetBronze", delegate { Navigate(ShowPlayers); }); } private static void ShowBoss(bool runestone) { BeginPage(T(runestone ? "dnpc_guide_boss_runestone" : "dnpc_guide_boss_altar"), T("dnpc_radial_individual"), delegate { ShowBoss(runestone); }, member: true); string[] array = new string[7] { "eikthyr", "elder", "bonemass", "moder", "yagluth", "queen", "fader" }; string[] array2 = new string[7] { "TrophyEikthyr", "TrophyTheElder", "TrophyBonemass", "TrophyDragonQueen", "TrophyGoblinKing", "TrophySeekerQueen", "TrophyFader" }; for (int num = 0; num < array.Length; num++) { int boss = num + 1; Add(T("dnpc_boss_" + array[num]), T("dnpc_company_menu_guide_range"), array2[num], delegate { Send(runestone ? 5 : 6, boss, null); }); } } private static void ShowCategories() { BeginPage(T("dnpc_radial_find"), T("dnpc_radial_individual"), ShowCategories, member: true); string[] array = new string[5] { "creatures", "plants", "crops", "ore", "places" }; string[] array2 = new string[5] { "TrophyDeer", "Raspberry", "Carrot", "CopperOre", "Wishbone" }; for (int i = 0; i < array.Length; i++) { string category = array[i]; Add(T("dnpc_guide_" + category), T("dnpc_guide_find_nearby"), array2[i], delegate { Navigate(delegate { ShowFind(category); }); }); } } private static void ShowFind(string category) { BeginPage(T("dnpc_guide_" + category), T("dnpc_radial_individual"), delegate { ShowFind(category); }, member: true); switch (category) { case "creatures": Find("deer", "deer", "TrophyDeer"); Find("boar", "boar", "TrophyBoar"); Find("hare", "hare", "HareMeat"); Find("wolf", "wolf", "TrophyWolf"); break; case "plants": Find("raspberries", "raspberry", "Raspberry"); Find("blueberries", "blueberry", "Blueberries"); Find("mushrooms", "mushroom", "Mushroom"); Find("thistle", "thistle", "Thistle"); break; case "crops": Find("carrots", "carrot", "Carrot"); Find("turnips", "turnip", "Turnip"); Find("onions", "onion", "Onion"); Find("barley", "barley", "Barley"); Find("flax", "flax", "Flax"); break; case "ore": Find("copper", "copper", "CopperOre"); Find("tin", "tin", "TinOre"); break; case "places": Find("sunken_crypt", "sunkencrypt", "CryptKey"); Find("burial_chamber", "burial", "SurtlingCore"); Find("frost_cave", "frostcave", "Crystal"); Find("fuling_village", "fulingvillage", "TotemGoblin"); Find("tar_pit", "tarpit", "Tar"); break; } } private static void Find(string token, string key, string icon) { Add(T("dnpc_find_" + token), T("dnpc_guide_find_nearby"), icon, delegate { Send(4, 0, key); }); } private static void ShowPlayers() { BeginPage(T("dnpc_guide_players"), T("dnpc_radial_individual"), ShowPlayers, member: true); _emptyMessage = T("dnpc_radial_no_players"); foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer == (Object)null || (Object)(object)allPlayer == (Object)(object)Player.m_localPlayer || Choices.Count >= 8) { continue; } string name = allPlayer.GetPlayerName(); if (!string.IsNullOrEmpty(name)) { Add(name, T("dnpc_company_menu_guide_range"), "HelmetBronze", delegate { Send(4, 0, "player:" + name); }); } } } private static bool CanGuide(float range) { //IL_003e: 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) Player localPlayer = Player.m_localPlayer; if ((Object)(object)_merc == (Object)null || (Object)(object)localPlayer == (Object)null || ((Character)_merc).IsDead() || !_merc.IsAssignedTo(localPlayer, requireFollowing: false)) { return false; } if (Vector3.Distance(((Component)localPlayer).transform.position, ((Component)_merc).transform.position) <= range) { return true; } ((Character)localPlayer).Message((MessageType)2, T((range < 16f) ? "dnpc_company_menu_member_too_far" : "dnpc_company_menu_guide_too_far"), 0, (Sprite)null, false); return false; } private static void SendCompany(MercContextOrderType order) { MercContextOrders.RequestMenuOrder(order); Close(); } private static void Send(int task, int bossValue, string findKey) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (!CanGuide((task >= 7) ? 8f : 16f)) { return; } ZNetView component = ((Component)_merc).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { string text = ((task >= 7) ? "" : GuideTasks.CaptureClientSnapshot().ToJson()); ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC("DynamicNPCs_GuideMenuRequestV1", new object[5] { component.GetZDO().m_uid, task, bossValue, findKey ?? "", text }); } Close(); } } private static Sprite ItemIcon(string prefab) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrEmpty(prefab)) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if (!((Object)(object)val != (Object)null)) { return null; } return val.m_itemData.GetIcon(); } private static Text Label(string name, string value, Vector2 position, float width, float height, int size, Color color) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Text component = GUIManager.Instance.CreateText(value, (Transform)(object)_rect, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), position, GUIManager.Instance.AveriaSerifBold, size, color, true, Color.black, width, height, false).GetComponent(); ((Object)((Component)component).gameObject).name = name; component.alignment = (TextAnchor)4; component.horizontalOverflow = (HorizontalWrapMode)0; component.verticalOverflow = (VerticalWrapMode)0; component.resizeTextForBestFit = true; component.resizeTextMinSize = 12; component.resizeTextMaxSize = size; component.supportRichText = false; ((Graphic)component).raycastTarget = false; return component; } private static string T(string token) { return MercLocalization.Text(token); } } internal static class MercLocalization { private const string EnvelopePrefix = "[[DNPC-L10N:"; private const string EnvelopeSuffix = "]]"; private const string EnglishResourceSuffix = ".Translations.English.json"; private static readonly Dictionary English = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _registered; internal static void Register() { if (_registered) { return; } Assembly assembly = typeof(MercLocalization).Assembly; string text = assembly.GetManifestResourceNames().FirstOrDefault((string name) => name.EndsWith(".Translations.English.json", StringComparison.OrdinalIgnoreCase)); if (text == null) { throw new InvalidOperationException("Embedded DynamicNPCs English localization was not found."); } string json; using (Stream stream = assembly.GetManifestResourceStream(text)) { using StreamReader streamReader = new StreamReader(stream ?? throw new InvalidOperationException("Embedded DynamicNPCs English localization could not be opened."), Encoding.UTF8); json = streamReader.ReadToEnd(); } Dictionary dictionary = ParseFlatJson(json, text); foreach (KeyValuePair item in dictionary) { English[item.Key] = item.Value; } CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string text2 = "English"; localization.AddTranslation(ref text2, dictionary); LoadExternalLanguages(localization, assembly.Location); _registered = true; MercPlugin.Log($"Localization registered: {English.Count} English tokens with client-language fallback."); } private static void LoadExternalLanguages(CustomLocalization localization, string assemblyPath) { string directoryName = Path.GetDirectoryName(assemblyPath); if (string.IsNullOrEmpty(directoryName)) { return; } string path = Path.Combine(directoryName, "Translations"); if (!Directory.Exists(path)) { return; } string[] files = Directory.GetFiles(path, "*.json", SearchOption.TopDirectoryOnly); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (!string.Equals(fileNameWithoutExtension, "English", StringComparison.OrdinalIgnoreCase)) { try { Dictionary dictionary = ParseFlatJson(File.ReadAllText(text), text); localization.AddTranslation(ref fileNameWithoutExtension, dictionary); MercPlugin.Log($"Loaded {dictionary.Count} {fileNameWithoutExtension} localization tokens from {text}."); } catch (Exception ex) { MercPlugin.LogWarn("Ignored invalid localization file " + text + ": " + ex.Message); } } } } private static Dictionary ParseFlatJson(string json, string source) { Dictionary obj = (MiniJson.Parse(json ?? "") as Dictionary) ?? throw new InvalidDataException(source + " must contain one JSON object of token/string pairs."); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair item in obj) { string text = (item.Key ?? "").Trim().TrimStart(new char[1] { '$' }); if (!string.IsNullOrEmpty(text) && item.Value != null) { if (!(item.Value is string value)) { throw new InvalidDataException("Token '" + text + "' in " + source + " must have a string value."); } dictionary[text] = value; } } if (dictionary.Count == 0) { throw new InvalidDataException(source + " contains no localization tokens."); } return dictionary; } internal static string Text(string token, params object[] arguments) { string text = (token ?? "").Trim().TrimStart(new char[1] { '$' }); string value = null; if (!string.IsNullOrEmpty(text) && Localization.instance != null) { string text2 = "$" + text; value = Localization.instance.Localize(text2); if (string.Equals(value, text2, StringComparison.Ordinal)) { value = null; } } if (string.IsNullOrEmpty(value)) { English.TryGetValue(text, out value); } if (string.IsNullOrEmpty(value)) { value = (string.IsNullOrEmpty(text) ? "" : ("$" + text)); } if (arguments == null || arguments.Length == 0) { return value; } try { return string.Format(CultureInfo.CurrentCulture, value, arguments); } catch (FormatException ex) { MercPlugin.LogWarn("Localization token " + text + " has invalid placeholders: " + ex.Message); return value; } } internal static string EnglishText(string token) { string text = (token ?? "").Trim().TrimStart(new char[1] { '$' }); if (!English.TryGetValue(text, out var value)) { return "$" + text; } return value; } internal static string TokenArgument(string token) { return "$" + (token ?? "").Trim().TrimStart(new char[1] { '$' }); } internal static string DirectionArgument(string englishDirection) { return (englishDirection ?? "").Trim().ToLowerInvariant() switch { "nearby" => TokenArgument("dnpc_direction_nearby"), "north" => TokenArgument("dnpc_direction_north"), "northeast" => TokenArgument("dnpc_direction_northeast"), "east" => TokenArgument("dnpc_direction_east"), "southeast" => TokenArgument("dnpc_direction_southeast"), "south" => TokenArgument("dnpc_direction_south"), "southwest" => TokenArgument("dnpc_direction_southwest"), "west" => TokenArgument("dnpc_direction_west"), "northwest" => TokenArgument("dnpc_direction_northwest"), _ => englishDirection ?? "", }; } internal static string Phrase(string token, params object[] arguments) { List list = new List(); list.Add((token ?? "").Trim().TrimStart(new char[1] { '$' })); List list2 = list; if (arguments != null) { for (int i = 0; i < arguments.Length; i++) { string s = Convert.ToString(arguments[i], CultureInfo.InvariantCulture) ?? ""; list2.Add(Convert.ToBase64String(Encoding.UTF8.GetBytes(s))); } } return "[[DNPC-L10N:" + string.Join("|", list2.ToArray()) + "]]"; } internal static string Resolve(string text) { if (string.IsNullOrEmpty(text) || !text.StartsWith("[[DNPC-L10N:", StringComparison.Ordinal) || !text.EndsWith("]]", StringComparison.Ordinal)) { return text ?? ""; } string[] array = text.Substring("[[DNPC-L10N:".Length, text.Length - "[[DNPC-L10N:".Length - "]]".Length).Split(new char[1] { '|' }); if (array.Length == 0 || string.IsNullOrWhiteSpace(array[0])) { return text; } object[] array2 = new object[Math.Max(0, array.Length - 1)]; try { for (int i = 1; i < array.Length; i++) { string text2 = Encoding.UTF8.GetString(Convert.FromBase64String(array[i])); array2[i - 1] = (text2.StartsWith("$", StringComparison.Ordinal) ? Text(text2) : text2); } return Text(array[0], array2); } catch (Exception ex) { MercPlugin.LogWarn("Could not resolve a localized network phrase: " + ex.Message); return text; } } internal static string Binding(string inputName, string fallback) { try { string text = ((ZInput.instance != null) ? ZInput.instance.GetBoundKeyString(inputName, true) : ""); return string.IsNullOrWhiteSpace(text) ? fallback : text; } catch { return fallback; } } } internal enum MercMovementMode { Travel, Settled, BaseRoam } internal readonly struct MercMovementSlot { internal readonly float Side; internal readonly float Forward; internal readonly float SoftRadius; internal readonly float MaxReferenceDistance; internal MercMovementSlot(float side, float forward, float softRadius, float maxReferenceDistance) { Side = side; Forward = forward; SoftRadius = softRadius; MaxReferenceDistance = maxReferenceDistance; } } internal static class MercMovementRules { internal const float MovingSpeed = 0.35f; internal const float SettledDelaySeconds = 2.5f; internal const float BaseRoamEnterDistance = 18f; internal const float BaseRoamExitDistance = 20f; internal const float BaseRoamMinimumRadius = 5f; internal const float BaseRoamRadius = 15f; internal const float BaseRoamMinimumPauseSeconds = 8f; internal const float BaseRoamMaximumPauseSeconds = 16f; internal const float AmbientActivityReleaseDistance = 15f; internal const float IdleRoamRadius = 6f; internal const float IdleReturnStopDistance = 3f; internal const float IdleMinimumPauseSeconds = 10f; internal const float IdleMaximumPauseSeconds = 20f; internal static bool IsEmployerMoving(float horizontalSpeed) { return horizontalSpeed >= 0.35f; } internal static float AdvanceSettledClock(float current, float horizontalSpeed, float deltaTime) { if (IsEmployerMoving(horizontalSpeed)) { return 0f; } if (deltaTime <= 0f) { return current; } float num = current + deltaTime; if (!(num > 2.5f)) { return num; } return 2.5f; } internal static MercMovementMode SelectMode(bool baseRoaming, bool hasBanner, float employerBannerDistance, float settledSeconds, bool baseRoamSuppressed = false) { if (!baseRoamSuppressed && ShouldRoamAtBase(baseRoaming, hasBanner, employerBannerDistance, settledSeconds)) { return MercMovementMode.BaseRoam; } if (!(settledSeconds >= 2.5f)) { return MercMovementMode.Travel; } return MercMovementMode.Settled; } internal static bool ShouldRoamAtBase(bool currentlyRoaming, bool hasBanner, float employerBannerDistance, float settledSeconds) { if (!hasBanner || employerBannerDistance < 0f) { return false; } if (currentlyRoaming) { return employerBannerDistance <= 20f; } if (employerBannerDistance <= 18f) { return settledSeconds >= 2.5f; } return false; } internal static bool ShouldReleaseRecallSuppression(bool hasBanner, float employerBannerDistance) { if (hasBanner) { return employerBannerDistance > 20f; } return true; } internal static bool ShouldHoldAmbientActivity(bool activityEngaged, float employerDistance) { if (activityEngaged && employerDistance >= 0f) { return employerDistance <= 15f; } return false; } internal static MercMovementSlot Slot(MercClass mercClass, MercMovementMode mode) { return mode switch { MercMovementMode.BaseRoam => mercClass switch { MercClass.Tank => new MercMovementSlot(-3.6f, 0.5f, 1.15f, 6f), MercClass.Healer => new MercMovementSlot(3.6f, 0.5f, 1.15f, 6f), _ => new MercMovementSlot(0f, -4.3f, 1.25f, 6f), }, MercMovementMode.Settled => mercClass switch { MercClass.Tank => new MercMovementSlot(-3.2f, -2.2f, 1.1f, 7.5f), MercClass.Healer => new MercMovementSlot(3.2f, -2.2f, 1.1f, 7.5f), _ => new MercMovementSlot(0f, -4.6f, 1.2f, 8f), }, _ => mercClass switch { MercClass.Tank => new MercMovementSlot(-1.8f, 1.8f, 1.6f, 7f), MercClass.Healer => new MercMovementSlot(-2.1f, -2f, 1.8f, 8f), _ => new MercMovementSlot(2.4f, -3f, 1.9f, 9f), }, }; } } internal static class MercNavigationMemory { private sealed class DoorMemory { internal Door Door; internal Mercenary Opener; internal Vector3 Position; internal Vector3 Passage; internal Vector3 Through; internal long EmployerId; internal float RetryAfter; internal float PassageExpiresAt; internal float CloseAfter; internal float ExpiresAt; internal bool HasPassage; internal bool CloseWhenClear; } private sealed class DetourMemory { internal Vector3 Point; internal int SourceId; internal float ExpiresAt; } private readonly struct RouteKey : IEquatable { private readonly int _x; private readonly int _z; private readonly int _heading; internal RouteKey(Vector3 origin, Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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) _x = Mathf.FloorToInt(origin.x / 5f); _z = Mathf.FloorToInt(origin.z / 5f); Vector3 val = destination - origin; int num = Mathf.RoundToInt(Mathf.Atan2(val.x, val.z) / ((float)Math.PI / 4f)); _heading = (num + 8) % 8; } public bool Equals(RouteKey other) { if (_x == other._x && _z == other._z) { return _heading == other._heading; } return false; } public override bool Equals(object obj) { if (obj is RouteKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((_x * 397) ^ _z) * 397) ^ _heading; } } private const float RouteCellSize = 5f; private const float DoorRetrySeconds = 1f; private const float PlayerDoorPrioritySeconds = 2f; private const float DoorPassageSeconds = 4f; private const float DoorCloseDelaySeconds = 1.25f; private const float DoorCloseMemorySeconds = 15f; private const float DoorCompanyWaitRadius = 12f; private const float DoorCloseActorRadius = 4f; private const float DoorClearRadius = 1.6f; private const float DoorCrossedDistance = 0.65f; private const float DetourSeconds = 6f; private static readonly Dictionary Doors = new Dictionary(); private static readonly Dictionary Detours = new Dictionary(); private static float _nextPruneTime; private static float _lastClock; internal static bool TryReserveDoor(int doorId) { Prepare(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (Doors.TryGetValue(doorId, out var value) && value.RetryAfter > realtimeSinceStartup) { return false; } if (value == null) { value = new DoorMemory(); Doors[doorId] = value; } value.HasPassage = false; value.CloseWhenClear = false; value.Door = null; value.Opener = null; value.RetryAfter = realtimeSinceStartup + 1f; value.ExpiresAt = Mathf.Max(value.ExpiresAt, realtimeSinceStartup + 1f); return true; } internal static void RememberPlayerDoorInteraction(Door door) { if (!((Object)(object)door == (Object)null)) { Prepare(); float realtimeSinceStartup = Time.realtimeSinceStartup; int instanceID = ((Object)door).GetInstanceID(); if (!Doors.TryGetValue(instanceID, out var value)) { value = new DoorMemory(); Doors[instanceID] = value; } bool closeWhenClear = value.CloseWhenClear; value.Door = door; value.Opener = null; value.EmployerId = 0L; value.HasPassage = false; value.CloseWhenClear = false; value.RetryAfter = realtimeSinceStartup + 2f; value.ExpiresAt = Mathf.Max(value.ExpiresAt, value.RetryAfter); if (closeWhenClear) { MercPlugin.Log("Player door interaction canceled mercenary close ownership."); } } } internal static void RememberDoorPassage(int doorId, Door door, Vector3 position, Vector3 passage, Vector3 through, long employerId, Mercenary opener) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) Prepare(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (!Doors.TryGetValue(doorId, out var value)) { value = new DoorMemory(); Doors[doorId] = value; } value.Door = door; value.Opener = opener; value.Position = position; value.Passage = passage; value.Through = through; value.EmployerId = employerId; value.RetryAfter = realtimeSinceStartup + 1f; value.PassageExpiresAt = realtimeSinceStartup + 4f; value.CloseAfter = realtimeSinceStartup + 1.25f; value.ExpiresAt = realtimeSinceStartup + 15f; value.HasPassage = true; value.CloseWhenClear = (Object)(object)door != (Object)null && (Object)(object)opener != (Object)null && employerId != 0; } internal static bool TryGetDoorPassage(Vector3 origin, Vector3 destination, out Vector3 passage) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: 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_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) Prepare(); Vector3 val = destination - origin; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { passage = Vector3.zero; return false; } ((Vector3)(ref val)).Normalize(); float num = float.MaxValue; passage = Vector3.zero; foreach (DoorMemory value in Doors.Values) { if (!value.HasPassage || value.PassageExpiresAt <= Time.realtimeSinceStartup || !IsDoorOpen(value.Door)) { continue; } Vector3 val2 = value.Position - origin; val2.y = 0f; float num2 = Vector3.Dot(val2, val); if (!(num2 < -0.25f) && !(num2 > 4.5f)) { Vector3 val3 = val2 - val * num2; if (!(((Vector3)(ref val3)).magnitude > 2.4f) && !(((Vector3)(ref val2)).sqrMagnitude >= num)) { num = ((Vector3)(ref val2)).sqrMagnitude; passage = value.Passage; } } } return num < float.MaxValue; } internal static void UpdateDoors(Mercenary actor) { //IL_0131: 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_014c: Unknown result type (might be due to invalid IL or missing references) Prepare(); if ((Object)(object)actor == (Object)null || ((Character)actor).IsDead()) { return; } long num = actor.EffectiveEmployerId(); if (num == 0L) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = new List(); foreach (KeyValuePair door2 in Doors) { DoorMemory value = door2.Value; if (!value.CloseWhenClear || value.EmployerId != num) { continue; } if (value.ExpiresAt <= realtimeSinceStartup) { list.Add(door2.Key); } else { if ((Object)(object)actor != (Object)(object)value.Opener) { continue; } Door door = value.Door; if ((Object)(object)door == (Object)null || !((Behaviour)door).isActiveAndEnabled) { list.Add(door2.Key); } else { if (!TryReadDoorState(door, out var state)) { continue; } if (state == 0) { if (realtimeSinceStartup >= value.CloseAfter) { list.Add(door2.Key); } } else { if (realtimeSinceStartup < value.CloseAfter || realtimeSinceStartup < value.RetryAfter) { continue; } if (door.m_canNotBeClosed) { list.Add(door2.Key); } else if (HasCrossedAndCleared(((Component)actor).transform.position, value) && !(HorizontalDistance(((Component)actor).transform.position, value.Position) > 4f) && CompanyHasCleared(value)) { value.RetryAfter = realtimeSinceStartup + 1f; bool flag; try { flag = door.Interact((Humanoid)(object)actor, false, false); } catch { flag = false; } if (flag) { list.Add(door2.Key); MercPlugin.Log(actor.GetName() + " closed the company-opened door after the passage cleared."); } } } } } } foreach (int item in list) { Doors.Remove(item); } } private static bool CompanyHasCleared(DoorMemory memory) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Character)instance).IsDead() && instance.EffectiveEmployerId() == memory.EmployerId && ((Object)(object)instance == (Object)(object)memory.Opener || instance.IsFollowing()) && !(HorizontalDistance(((Component)instance).transform.position, memory.Position) > 12f)) { if ((Object)(object)instance.Ai != (Object)null && instance.Ai.EngagedInCombat) { return false; } if (!HasCrossedAndCleared(((Component)instance).transform.position, memory)) { return false; } } } foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && !((Character)allPlayer).IsDead() && allPlayer.GetPlayerID() == memory.EmployerId && !(HorizontalDistance(((Component)allPlayer).transform.position, memory.Position) > 12f) && !HasCrossedAndCleared(((Component)allPlayer).transform.position, memory)) { return false; } } return true; } private static bool HasCrossedAndCleared(Vector3 position, DoorMemory memory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) Vector3 val = position - memory.Position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 2.5600002f) { return Vector3.Dot(val, memory.Through) > 0.65f; } return false; } private static float HorizontalDistance(Vector3 left, Vector3 right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = left.x - right.x; float num2 = left.z - right.z; return Mathf.Sqrt(num * num + num2 * num2); } private static bool IsDoorOpen(Door door) { if (TryReadDoorState(door, out var state)) { return state != 0; } return false; } private static bool TryReadDoorState(Door door, out int state) { state = 0; if ((Object)(object)door == (Object)null) { return false; } try { ZNetView component = ((Component)door).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null) { return false; } state = val.GetInt(ZDOVars.s_state, 0); return true; } catch { return false; } } internal static void RememberDetour(Vector3 origin, Vector3 destination, Vector3 point, int sourceId) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) Prepare(); Detours[new RouteKey(origin, destination)] = new DetourMemory { Point = point, SourceId = sourceId, ExpiresAt = Time.realtimeSinceStartup + 6f }; } internal static bool TryGetDetour(Vector3 origin, Vector3 destination, int consumerId, out Vector3 point) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) Prepare(); if (Detours.TryGetValue(new RouteKey(origin, destination), out var value) && value.ExpiresAt > Time.realtimeSinceStartup && value.SourceId != consumerId) { point = value.Point; return true; } point = Vector3.zero; return false; } private static void Prepare() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup + 1f < _lastClock) { Doors.Clear(); Detours.Clear(); _nextPruneTime = 0f; } _lastClock = realtimeSinceStartup; if (realtimeSinceStartup < _nextPruneTime) { return; } _nextPruneTime = realtimeSinceStartup + 5f; List list = new List(); foreach (KeyValuePair door in Doors) { if (door.Value.ExpiresAt <= realtimeSinceStartup) { list.Add(door.Key); } } foreach (int item in list) { Doors.Remove(item); } List list2 = new List(); foreach (KeyValuePair detour in Detours) { if (detour.Value.ExpiresAt <= realtimeSinceStartup) { list2.Add(detour.Key); } } foreach (RouteKey item2 in list2) { Detours.Remove(item2); } } } internal static class MercOrderRules { internal const float HoldRadius = 3f; internal const float HoldThreatRange = 20f; internal const float GatherRadius = 8f; internal static bool IsPreciseHoldActive(bool following, bool precise, int stayDay, int today) { if (!following && precise && stayDay > 0 && today > 0) { return (long)today - (long)stayDay < 1; } return false; } internal static bool ShouldGather(float horizontalDistance) { if (!float.IsInfinity(horizontalDistance)) { return horizontalDistance > 8f; } return false; } } public static class MercPatches { [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class ZNetScene_Awake { private static void Postfix() { MercSetup.EnsureRegistered("ZNetScene.Awake patch"); } } [HarmonyPatch(typeof(Game), "Logout")] private static class Game_Logout { private static void Prefix() { try { MercSetup.ResetSession("logout"); } catch (Exception ex) { MercPlugin.LogWarn("session reset on logout failed: " + ex.Message); } } } [HarmonyPatch(typeof(Chair), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] private static class Chair_Interact { private static void Prefix(Chair __instance, Humanoid human) { if (human is Player) { MercCampLife.YieldChair(__instance); } } } [HarmonyPatch(typeof(Door), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] private static class Door_Interact { private static void Prefix(Door __instance, Humanoid character, bool hold) { if (!hold && character is Player) { MercNavigationMemory.RememberPlayerDoorInteraction(__instance); } } } [HarmonyPatch(typeof(Door), "UseItem", new Type[] { typeof(Humanoid), typeof(ItemData) })] private static class Door_UseItem { private static void Prefix(Door __instance, Humanoid user) { if (user is Player) { MercNavigationMemory.RememberPlayerDoorInteraction(__instance); } } } [HarmonyPatch(typeof(ZoneSystem), "SetGlobalKey", new Type[] { typeof(string) })] private static class ZoneSystem_SetGlobalKey { private static void Postfix() { try { StageDirector.Poll(); } catch (Exception ex) { MercPlugin.LogWarn("progression poll failed: " + ex.Message); } } } [HarmonyPatch(typeof(Humanoid), "SetupVisEquipment", new Type[] { typeof(VisEquipment), typeof(bool) })] private static class Humanoid_SetupVisEquipment { private static void Postfix(Humanoid __instance, VisEquipment visEq) { if (!(__instance is Mercenary mercenary)) { return; } try { mercenary.ApplyHealerCosmeticVisuals(visEq); } catch (Exception ex) { MercPlugin.LogWarn("healer cosmetics failed: " + ex.Message); } } } [HarmonyPatch(typeof(Chat), "SendText", new Type[] { typeof(Type), typeof(string) })] private static class Chat_SendText { private static bool Prefix(Type type, string text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return !ConversationRouter.TryHearAgentChat(type, text); } catch (Exception ex) { MercPlugin.LogWarn("private agent chat failed: " + ex.Message); return true; } } } [HarmonyPatch(typeof(Talker), "Say", new Type[] { typeof(Type), typeof(string) })] private static class Talker_Say { private static void Postfix(Talker __instance, Type type, string text) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)__instance == (Object)null) && !((Object)(object)((Component)__instance).GetComponentInParent() != (Object)(object)localPlayer)) { ConversationRouter.HearLocalPlayerChat(type, text); } } catch (Exception ex) { MercPlugin.LogWarn("chat hearing failed: " + ex.Message); } } } [HarmonyPatch(typeof(Piece), "Awake")] private static class Piece_Awake { private static void Postfix(Piece __instance) { try { MercBanner.EnsureRuntimeComponent(__instance); } catch (Exception ex) { MercPlugin.LogWarn("banner runtime repair failed: " + ex.Message); } } } [HarmonyPatch(typeof(Player), "GetAvailableRecipes")] private static class Player_GetAvailableRecipes { private static void Postfix(ref List available) { if (available != null) { available.RemoveAll((Recipe recipe) => (Object)(object)recipe != (Object)null && (Object)(object)recipe.m_item != (Object)null && (Object)(object)((Component)recipe.m_item).gameObject != (Object)null && ((Object)((Component)recipe.m_item).gameObject).name.StartsWith("MercHammer", StringComparison.Ordinal)); } } } [HarmonyPatch(typeof(Player), "IsPieceAvailable")] private static class Player_IsPieceAvailable { private static bool Prefix(Player __instance, Piece piece, ref bool __result) { if (!MercBanner.IsBanner((Component)(object)piece)) { return true; } if (MercBanner.PlayerHasBanner(__instance)) { __result = false; } else { __result = true; } return false; } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] [HarmonyPriority(800)] private static class Player_TryPlacePiece { private static bool Prefix(Player __instance, Piece piece, ref bool __result) { if (!MercBanner.IsBanner((Component)(object)piece)) { return true; } if (MercBanner.PlayerHasBanner(__instance)) { ((Character)__instance).Message((MessageType)2, MercLocalization.Text("dnpc_message_banner_limit"), 0, (Sprite)null, false); __result = false; return false; } return true; } private static void Postfix(Player __instance, Piece piece, bool __result) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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) if (!__result || !MercBanner.IsBanner((Component)(object)piece)) { return; } try { Vector3 position = ((Component)piece).transform.position; ZNetView val = null; float num = 2f; foreach (MercBannerSpawn instance in MercBannerSpawn.Instances) { if ((Object)(object)instance == (Object)null) { continue; } ZNetView component = ((Component)instance).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { float num2 = Vector3.Distance(((Component)instance).transform.position, position); if (num2 <= num) { num = num2; val = component; } } } if ((Object)(object)val == (Object)null) { MercPlugin.LogWarn("Placed a Mercenary Banner but could not find its network object to claim."); } else { BannerAuthority.RequestClaim(val.GetZDO().m_uid, PlayerProgression.LocalSeedFromGear()); } } catch (Exception ex) { MercPlugin.LogWarn("banner placement claim failed: " + ex.Message); } } } [HarmonyPatch(typeof(Player), "RemovePiece")] [HarmonyPriority(800)] private static class Player_RemovePiece { private static bool Prefix(Player __instance, ref bool __result) { Piece val = (((Object)(object)__instance != (Object)null) ? __instance.GetHoveringPiece() : null); if (!MercBanner.IsBanner((Component)(object)val)) { return true; } if (!MercBanner.HasBuildHammerEquipped(__instance)) { ((Character)__instance).Message((MessageType)2, MercLocalization.Text("dnpc_message_equip_hammer"), 0, (Sprite)null, false); __result = false; return false; } MercBannerSpawn component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { return true; } component.RequestServerRemoval(); __result = true; return false; } } [HarmonyPatch(typeof(WearNTear), "Damage")] [HarmonyPriority(800)] private static class WearNTear_Damage { private static bool Prefix(WearNTear __instance) { return !MercBanner.IsBanner((Component)(object)__instance); } } [HarmonyPatch(typeof(WearNTear), "RPC_Damage", new Type[] { typeof(long), typeof(HitData) })] [HarmonyPriority(800)] private static class WearNTear_RPC_Damage { private static bool Prefix(WearNTear __instance) { return !MercBanner.IsBanner((Component)(object)__instance); } } [HarmonyPatch(typeof(WearNTear), "ApplyDamage", new Type[] { typeof(float), typeof(HitData) })] [HarmonyPriority(800)] private static class WearNTear_ApplyDamage { private static bool Prefix(WearNTear __instance, ref bool __result) { if (!MercBanner.IsBanner((Component)(object)__instance)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(WearNTear), "Remove", new Type[] { typeof(bool) })] [HarmonyPriority(800)] private static class WearNTear_Remove { private static bool Prefix(WearNTear __instance) { if (MercBanner.IsBanner((Component)(object)__instance)) { return MercBannerSpawn.ServerRemovalInProgress; } return true; } } [HarmonyPatch(typeof(WearNTear), "Destroy", new Type[] { typeof(HitData), typeof(bool) })] [HarmonyPriority(800)] private static class WearNTear_Destroy { private static bool Prefix(WearNTear __instance) { if (MercBanner.IsBanner((Component)(object)__instance)) { return MercBannerSpawn.ServerRemovalInProgress; } return true; } } [HarmonyPatch(typeof(Character), "CheckDeath")] [HarmonyPriority(800)] private static class Character_CheckDeath { private static bool Prefix(Character __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || ((Character)val).GetHealth() > 0f) { return true; } try { return !ResurrectionManager.TryPreventDeath(val); } catch (Exception ex) { MercPlugin.LogWarn("resurrection intercept failed, continuing vanilla death: " + ex.Message); return true; } } } [HarmonyPatch(typeof(TreeLog), "Destroy", new Type[] { typeof(HitData), typeof(bool) })] private static class TreeLog_Destroy_Activity { private static void Prefix(TreeLog __instance, HitData hitData, ref object __state) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) __state = WorldDataService.BeginHarvestCapture(hitData, ((Object)(object)__instance != (Object)null) ? ((Object)((Component)__instance).gameObject).name : "TreeLog", ((Object)(object)__instance != (Object)null) ? ((Component)__instance).transform.position : Vector3.zero); } private static Exception Finalizer(Exception __exception, object __state) { WorldDataService.EndHarvestCapture(__state); return __exception; } } [HarmonyPatch(typeof(MineRock5), "DamageArea", new Type[] { typeof(int), typeof(HitData) })] private static class MineRock5_DamageArea_Activity { private static void Prefix(MineRock5 __instance, HitData hit, ref object __state) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) __state = WorldDataService.BeginHarvestCapture(hit, ((Object)(object)__instance != (Object)null) ? ((Object)((Component)__instance).gameObject).name : "MineRock", ((Object)(object)__instance != (Object)null) ? ((Component)__instance).transform.position : Vector3.zero); } private static Exception Finalizer(Exception __exception, object __state) { WorldDataService.EndHarvestCapture(__state); return __exception; } } [HarmonyPatch(typeof(ItemDrop), "OnCreateNew", new Type[] { typeof(GameObject), typeof(bool) })] private static class ItemDrop_OnCreateNew_Activity { private static void Postfix(GameObject go) { try { WorldDataService.CaptureCreatedItem(go); } catch (Exception ex) { MercPlugin.LogWarn("item capture failed: " + ex.Message); } } } [HarmonyPatch(typeof(Player), "SetControls")] [HarmonyPriority(800)] private static class Player_SetControls { private static void Prefix(Player __instance, ref Vector3 movedir, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold, ref bool jump, ref bool crouch, ref bool run, ref bool autoRun, ref bool dodge) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ResurrectionManager.IsDowned(__instance)) { movedir = Vector3.zero; attack = (attackHold = false); secondaryAttack = (secondaryAttackHold = false); block = (blockHold = false); jump = (crouch = (run = (autoRun = (dodge = false)))); } } } [HarmonyPatch(typeof(CharacterAnimEvent), "DodgeMortal")] private static class CharacterAnimEvent_DodgeMortal { private static void Postfix(Character ___m_character) { if (___m_character is Mercenary mercenary) { mercenary.OnDodgeMortal(); } } } [HarmonyPatch(typeof(Character), "Damage")] private static class Character_Damage { private static bool Prefix(Character __instance, HitData hit) { ApplyMercenaryDamageMultiplier(hit); CombatDiagnostics.LogDamageDispatch(__instance, hit); if (SnikkCombatPolicy.IsSnikk(__instance)) { return SnikkCombatPolicy.SanitizeDirectDamage(__instance, hit, authoritative: false); } if (!MercConfig.PreventFriendlyFire.Value) { return true; } if (!(__instance is Mercenary) || hit == null) { return true; } try { Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null && attacker.IsPlayer()) { return false; } } catch { } return true; } private static void ApplyMercenaryDamageMultiplier(HitData hit) { if (hit == null || MercConfig.DamageDealtMultiplier == null) { return; } try { if (hit.GetAttacker() is Mercenary) { float value = MercConfig.DamageDealtMultiplier.Value; if (!Mathf.Approximately(value, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(value); } } } catch (Exception ex) { MercPlugin.LogWarn("Could not apply mercenary damage multiplier: " + ex.Message); } } } [HarmonyPatch(typeof(Character), "RPC_Damage", new Type[] { typeof(long), typeof(HitData) })] [HarmonyPriority(800)] private static class Character_RPC_Damage { private struct DamageState { public bool Trace; public float Health; public float IncomingDamage; public bool ValidView; public bool IsOwner; public bool DebugFlying; public bool Dead; public bool Teleporting; public bool Cutscene; } private static bool Prefix(Character __instance, HitData hit, out DamageState __state) { __state = default(DamageState); if (!SnikkCombatPolicy.SanitizeDirectDamage(__instance, hit, authoritative: true)) { return false; } ResurrectionManager.NoteIncomingDamage(__instance, hit); if (__instance is Mercenary mercenary && hit != null && hit.GetTotalDamage() > 0f) { mercenary.Ai?.CancelAmbientCamp(); } if (!CombatDiagnostics.ShouldTrace(__instance, hit)) { return true; } ZNetView component = ((Component)__instance).GetComponent(); __state.Trace = true; __state.Health = __instance.GetHealth(); __state.IncomingDamage = hit.GetTotalDamage(); __state.ValidView = (Object)(object)component != (Object)null && component.IsValid(); __state.IsOwner = __state.ValidView && component.IsOwner(); __state.DebugFlying = __instance.IsDebugFlying(); __state.Dead = __instance.IsDead(); __state.Teleporting = __instance.IsTeleporting(); __state.Cutscene = __instance.InCutscene(); return true; } private static void Postfix(Character __instance, HitData hit, DamageState __state) { SnikkCombatPolicy.EnforceMinimumHealth(__instance); if (__state.Trace) { float health = __instance.GetHealth(); float num = ((hit != null) ? hit.GetTotalDamage() : 0f); string arg = ((health < __state.Health) ? $"applied {__state.Health - health:0.##} health" : ((!__state.ValidView) ? "rejected: invalid ZNetView" : ((!__state.IsOwner) ? "rejected: this peer was not the owner" : (__state.DebugFlying ? "rejected: victim was debug-flying" : (__state.Dead ? "rejected: victim was already dead" : (__state.Teleporting ? "rejected: victim was teleporting" : (__state.Cutscene ? "rejected: victim was in a cutscene" : ((!(num <= 0.1f)) ? "unchanged: another RPC guard or Harmony prefix canceled it" : "rejected: final damage was zero")))))))); MercPlugin.Log("[Combat trace] Neck RPC from " + CombatDiagnostics.AttackerName(hit) + ": " + $"incoming={__state.IncomingDamage:0.##}, final={num:0.##}, " + $"health={__state.Health:0.##}->{health:0.##}, {arg}"); } } } [HarmonyPatch(typeof(Character), "ApplyDamage")] [HarmonyPriority(800)] private static class Character_ApplyDamage { private static bool Prefix(Character __instance, HitData hit) { return SnikkCombatPolicy.SanitizeAppliedDamage(__instance, hit); } private static void Postfix(Character __instance) { SnikkCombatPolicy.EnforceMinimumHealth(__instance); } } [HarmonyPatch(typeof(BaseAI), "IsEnemy", new Type[] { typeof(Character) })] private static class BaseAI_IsEnemy_Instance { private static bool Prefix(BaseAI __instance, Character other, ref bool __result) { if (!SnikkCombatPolicy.IsIgnoredByHostileAI(((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponent() : null) && !SnikkCombatPolicy.IsIgnoredByHostileAI(other)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(BaseAI), "IsEnemy", new Type[] { typeof(Character), typeof(Character) })] private static class BaseAI_IsEnemy_Static { private static bool Prefix(Character a, Character b, ref bool __result) { if (!SnikkCombatPolicy.IsIgnoredByHostileAI(a) && !SnikkCombatPolicy.IsIgnoredByHostileAI(b)) { return true; } __result = false; return false; } } private static class CombatDiagnostics { internal static bool ShouldTrace(Character victim, HitData hit) { if ((Object)(object)victim == (Object)null || hit == null) { return false; } try { Character attacker = hit.GetAttacker(); if (!(attacker is Player) && !(attacker is Mercenary)) { return false; } } catch { return false; } return Utils.GetPrefabName(((Object)((Component)victim).gameObject).name) == "Neck"; } internal static void LogDamageDispatch(Character victim, HitData hit) { if (ShouldTrace(victim, hit)) { ZNetView component = ((Component)victim).GetComponent(); bool flag = (Object)(object)component != (Object)null && component.IsValid(); MercPlugin.Log("[Combat trace] Neck Damage dispatch from " + AttackerName(hit) + ": " + $"damage={hit.GetTotalDamage():0.##}, viewValid={flag}"); } } internal static string AttackerName(HitData hit) { try { Character val = ((hit != null) ? hit.GetAttacker() : null); return ((Object)(object)val != (Object)null) ? (val.GetHoverName() + " (" + Utils.GetPrefabName(((Object)((Component)val).gameObject).name) + ")") : "unknown"; } catch { return "unknown"; } } } [HarmonyPatch(typeof(Teleport), "Interact")] private static class Teleport_Interact { private static void Prefix() { Mercenary.IsDungeonTeleport = true; } private static Exception Finalizer(Exception __exception) { Mercenary.IsDungeonTeleport = false; return __exception; } } [HarmonyPatch(typeof(Player), "TeleportTo")] private static class Player_TeleportTo { private static void Postfix(Player __instance, Vector3 pos, Quaternion rot, bool __result) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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) if (__result) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { MoveFollowers(__instance, pos, rot); } else if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { MoveFollowers(__instance, pos, rot); } } } private static void MoveFollowers(Player player, Vector3 pos, Quaternion rot) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) float num = ((MercConfig.FollowTeleportDistance != null) ? Mathf.Max(5f, MercConfig.FollowTeleportDistance.Value) : 30f); foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Character)instance).IsDead() && instance.IsFollowing() && !((Object)(object)instance.Ai == (Object)null) && !((Object)(object)((MonsterAI)instance.Ai).GetFollowTarget() != (Object)(object)((Component)player).gameObject) && !(Vector3.Distance(((Component)instance).transform.position, ((Component)player).transform.position) > num)) { Vector3 val = Random.insideUnitSphere * 1.5f; val.y = 0f; ((Character)instance).TeleportTo(pos + val, rot, true); } } } } private static class RoutedRpcSenderSanitizer { [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(long), typeof(string), typeof(object[]) })] private static class ZRoutedRpc_Invoke_Targeted { private static void Prefix() { _insideLocalInvoke++; } private static Exception Finalizer(Exception __exception) { _insideLocalInvoke--; return __exception; } } [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(string), typeof(object[]) })] private static class ZRoutedRpc_Invoke_Everybody { private static void Prefix() { _insideLocalInvoke++; } private static Exception Finalizer(Exception __exception) { _insideLocalInvoke--; return __exception; } } [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(long), typeof(ZDOID), typeof(string), typeof(object[]) })] private static class ZRoutedRpc_Invoke_ZdoTargeted { private static void Prefix() { _insideLocalInvoke++; } private static Exception Finalizer(Exception __exception) { _insideLocalInvoke--; return __exception; } } [HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")] private static class ZRoutedRpc_RPC_RoutedRPC { private static void Prefix(ZRpc rpc) { _currentWireRpc = rpc; } private static Exception Finalizer(Exception __exception) { _currentWireRpc = null; return __exception; } } [HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")] private static class ZRoutedRpc_HandleRoutedRPC { private static void Prefix(object data) { try { if (_currentWireRpc != null && _insideLocalInvoke <= 0 && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { long num = ResolveWirePeerUid(_currentWireRpc); if (num != 0L) { SanitizeSenderField(data, num); } } } catch (Exception ex) { MercPlugin.LogWarn("routed-RPC sender sanitize failed: " + ex.Message); } } } [HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")] private static class ZRoutedRpc_RouteRPC { private static void Prefix(object rpcData) { try { if (_currentWireRpc != null && _insideLocalInvoke <= 0 && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { long num = ResolveWirePeerUid(_currentWireRpc); if (num != 0L) { SanitizeSenderField(rpcData, num); } } } catch (Exception ex) { MercPlugin.LogWarn("routed-RPC route sanitize failed: " + ex.Message); } } } private static ZRpc _currentWireRpc; private static int _insideLocalInvoke; private static FieldInfo _senderField; private static long ResolveWirePeerUid(ZRpc rpc) { if (rpc == null || (Object)(object)ZNet.instance == (Object)null) { return 0L; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_rpc == rpc) { return peer.m_uid; } } return 0L; } private static void SanitizeSenderField(object routedData, long trueSender) { if (routedData != null) { FieldInfo fieldInfo = SenderFieldCache(routedData.GetType()); if (!(fieldInfo == null) && (!(fieldInfo.GetValue(routedData) is long num) || num != trueSender)) { fieldInfo.SetValue(routedData, trueSender); } } } private static FieldInfo SenderFieldCache(Type dataType) { if (_senderField != null && _senderField.DeclaringType == dataType) { return _senderField; } _senderField = AccessTools.Field(dataType, "m_senderPeerID"); return _senderField; } } [HarmonyPatch(typeof(Character), "OnDeath")] private static class Character_OnDeath_Progression { private static void Postfix(Character __instance) { try { if (__instance.m_boss) { PlayerProgression.RecordBossDefeat(__instance); } } catch (Exception ex) { MercPlugin.LogWarn("boss progression attribution failed: " + ex.Message); } } } [HarmonyPatch(typeof(ZDOMan), "DestroyZDO")] private static class ZDOMan_DestroyZDO_MercTrace { private static void Prefix(ZDO zdo) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) try { if (zdo != null && zdo.IsValid()) { int prefab = zdo.GetPrefab(); if (prefab == StringExtensionMethods.GetStableHashCode("Merc_Tank") || prefab == StringExtensionMethods.GetStableHashCode("Merc_Healer") || prefab == StringExtensionMethods.GetStableHashCode("Merc_Archer")) { MercPlugin.LogWarn($"Mercenary ZDO {zdo.m_uid} is being DESTROYED at {zdo.GetPosition()}. " + "Destroyer stack: " + Environment.StackTrace); } } } catch { } } } [HarmonyPatch(typeof(Character), "OnDeath")] private static class Character_OnDeath_PlayerChronicle { private static void Postfix(Character __instance) { try { if (!(__instance is Mercenary) && __instance.IsPlayer()) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { BannerAuthority.HoldFollowingCompanyOnOwnerDeath(val); PlayerChronicle.Record(val.GetPlayerID(), val.GetPlayerName(), "died"); } } } catch { } } } [HarmonyPatch(typeof(Character), "OnDeath")] private static class Character_OnDeath_MercFallen { private static void Postfix(Character __instance) { try { if (__instance is Mercenary fallen) { ServerAuthority.AnnounceMercenaryFallen(fallen); } } catch (Exception ex) { MercPlugin.LogWarn("mercenary fall announcement failed: " + ex.Message); } } } } public static class MercPrefabs { public const string TankPrefab = "Merc_Tank"; public const string HealerPrefab = "Merc_Healer"; public const string ArcherPrefab = "Merc_Archer"; public static readonly string[] AllPrefabs = new string[3] { "Merc_Tank", "Merc_Healer", "Merc_Archer" }; private static GameObject _playerPrefabClone; private static readonly Dictionary BuiltPrefabs = new Dictionary(); private static ZNetScene _registeredScene; private static string _registeredVia; public static bool IsRegistered { get { if ((Object)(object)_registeredScene != (Object)null) { return (Object)(object)_registeredScene == (Object)(object)ZNetScene.instance; } return false; } } public static string RegisteredVia => _registeredVia; public static void Register(ZNetScene scene, string via) { if ((Object)(object)scene == (Object)null || (Object)(object)_registeredScene == (Object)(object)scene) { return; } MercPlugin.Log("Scene registration started (via " + via + ")"); try { GameObject val = ResolvePlayerPrefab(); if ((Object)(object)val == (Object)null) { MercPlugin.LogWarn("Could not find the player prefab - mercenaries disabled."); return; } string[] allPrefabs = AllPrefabs; foreach (string text in allPrefabs) { if (!BuiltPrefabs.TryGetValue(text, out var value) || (Object)(object)value == (Object)null) { value = Object.Instantiate(val, Container(), false); ((Object)value).name = text; BuildMerc(value); BuiltPrefabs[text] = value; } RegisterPrefab(scene, value); MercPlugin.Log("Registered prefab " + text); } _registeredScene = scene; _registeredVia = via; } catch (Exception arg) { MercPlugin.LogWarn($"Prefab registration failed: {arg}"); } } private static GameObject ResolvePlayerPrefab() { GameObject val = null; if ((Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab("Player"); } if ((Object)(object)val == (Object)null && (Object)(object)FejdStartup.instance != (Object)null) { val = FejdStartup.instance.m_playerPrefab; } return val; } internal static GameObject PlayerPrefabSource() { return ResolvePlayerPrefab(); } private static Transform Container() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)_playerPrefabClone == (Object)null) { _playerPrefabClone = new GameObject("MercCompanions_Prefabs"); _playerPrefabClone.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_playerPrefabClone); } return _playerPrefabClone.transform; } private static void BuildMerc(GameObject go) { Player component = go.GetComponent(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("Player clone has no Player component"); } Component[] array = (Component[])(object)new Component[3] { (Component)go.GetComponent(), (Component)go.GetComponent(), (Component)go.GetComponent() }; foreach (Component val in array) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } } Mercenary mercenary = go.AddComponent(); CopyCharacterData(component, (Humanoid)(object)mercenary); MercAI ai = go.AddComponent(); ConfigureAI(ai); RebindPlayerReferences(go, component, (Character)(object)mercenary); RebindAnimationComponents(go, (Character)(object)mercenary, (MonsterAI)(object)ai); Object.DestroyImmediate((Object)(object)component); RebindAnimationComponents(go, (Character)(object)mercenary, (MonsterAI)(object)ai); AddInteractionForwarders(go, mercenary); Rigidbody component2 = go.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.isKinematic = false; } ZNetView component3 = go.GetComponent(); if ((Object)(object)component3 != (Object)null) { component3.m_persistent = true; } } private static void AddInteractionForwarders(GameObject go, Mercenary merc) { int num = 0; Collider[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)(object)go)) { MercenaryInteractionProxy mercenaryInteractionProxy = ((Component)val).GetComponent(); if ((Object)(object)mercenaryInteractionProxy == (Object)null) { mercenaryInteractionProxy = ((Component)val).gameObject.AddComponent(); } mercenaryInteractionProxy.Bind(merc); num++; } } MercPlugin.Log($"Prepared mercenary interaction forwarding on {num} child collider(s) for {((Object)go).name}."); } internal static void BindInteractionForwarders(Mercenary merc) { if ((Object)(object)merc == (Object)null) { return; } Collider[] componentsInChildren = ((Component)merc).GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)(object)((Component)merc).gameObject)) { MercenaryInteractionProxy component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { component.Bind(merc); } } } } internal static void RebindPlayerReferences(GameObject go, Player source, Character target) { Component[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)source || (Object)(object)val == (Object)(object)target) { continue; } Type type = ((object)val).GetType(); while (type != null && type != typeof(object)) { FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.IsStatic || fieldInfo.IsInitOnly) { continue; } try { if (fieldInfo.GetValue(val) == source) { if (fieldInfo.FieldType.IsAssignableFrom(((object)target).GetType())) { fieldInfo.SetValue(val, target); } else { fieldInfo.SetValue(val, null); } } } catch { } } type = type.BaseType; } } } internal static void RebindAnimationComponents(GameObject go, Character body, MonsterAI ai) { Animator componentInChildren = go.GetComponentInChildren(true); CharacterAnimEvent componentInChildren2 = go.GetComponentInChildren(true); if ((Object)(object)componentInChildren2 != (Object)null) { MercUtil.SetPrivate(componentInChildren2, body, "m_character", typeof(CharacterAnimEvent)); MercUtil.SetPrivate(componentInChildren2, componentInChildren, "m_animator", typeof(CharacterAnimEvent)); MercUtil.SetPrivate(componentInChildren2, ai, "m_monsterAI", typeof(CharacterAnimEvent)); } FootStep[] componentsInChildren = go.GetComponentsInChildren(true); foreach (FootStep instance in componentsInChildren) { MercUtil.SetPrivate(instance, body, "m_character", typeof(FootStep)); MercUtil.SetPrivate(instance, componentInChildren, "m_animator", typeof(FootStep)); } } internal static void CopyCharacterData(Player source, Humanoid target) { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) Type[] array = new Type[2] { typeof(Character), typeof(Humanoid) }; for (int i = 0; i < array.Length; i++) { foreach (FieldInfo declaredField in AccessTools.GetDeclaredFields(array[i])) { try { if (!declaredField.IsStatic && !declaredField.IsInitOnly) { object value = declaredField.GetValue(source); if (value != null) { declaredField.SetValue(target, value); } } } catch { } } } Transform val = Utils.FindChild(((Component)target).transform, "EyePos", (IterativeSearchType)0); if ((Object)(object)val != (Object)null) { ((Character)target).m_eye = val; } ((Character)target).m_name = "Mercenary"; ((Character)target).m_faction = (Faction)0; ((Character)target).m_group = "mercs"; } private static void ConfigureAI(MercAI ai) { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown ((MonsterAI)ai).m_attackPlayerObjects = false; ((BaseAI)ai).m_aggravatable = false; ((BaseAI)ai).m_passiveAggresive = false; ((BaseAI)ai).m_avoidFire = true; ((BaseAI)ai).m_avoidLava = true; ((BaseAI)ai).m_avoidLavaFlee = true; ((BaseAI)ai).m_avoidWater = true; ((BaseAI)ai).m_skipLavaTargets = true; ((MonsterAI)ai).m_alertRange = 30f; ((BaseAI)ai).m_hearRange = 30f; ((BaseAI)ai).m_viewRange = 40f; ((BaseAI)ai).m_moveMinAngle = 90f; ((BaseAI)ai).m_smoothMovement = true; ((BaseAI)ai).m_randomMoveInterval = 20f; ((BaseAI)ai).m_randomMoveRange = 8f; ((MonsterAI)ai).m_fleeIfLowHealth = 0f; ((MonsterAI)ai).m_minAttackInterval = 1.5f; ((MonsterAI)ai).m_interceptTimeMax = 2f; ((MonsterAI)ai).m_interceptTimeMin = 0.5f; ((MonsterAI)ai).m_maxChaseDistance = 120f; ((MonsterAI)ai).m_enableHuntPlayer = false; ((MonsterAI)ai).m_circulateWhileCharging = true; MercUtil.SetPrivate(ai, false, "m_patrol", typeof(BaseAI), typeof(MonsterAI)); try { ((BaseAI)ai).m_pathAgentType = (AgentType)6; } catch (Exception ex) { MercPlugin.LogWarn("pathAgentType: " + ex.Message); } try { GameObject prefabSafe = MercUtil.GetPrefabSafe("sfx_dverger_vo_alerted"); List list = new List(); if ((Object)(object)prefabSafe != (Object)null) { list.Add(new EffectData { m_prefab = prefabSafe }); } if (list.Count > 0) { ((BaseAI)ai).m_alertedEffects.m_effectPrefabs = list.ToArray(); } if (((BaseAI)ai).m_idleSound != null) { ((BaseAI)ai).m_idleSound.m_effectPrefabs = Array.Empty(); } ((BaseAI)ai).m_idleSoundInterval = 0f; ((BaseAI)ai).m_idleSoundChance = 0f; } catch { } } public static void RegisterPrefab(ZNetScene scene, GameObject prefab) { MercUtil.RegisterNamedPrefab(scene, prefab); if (!scene.m_prefabs.Contains(prefab)) { scene.m_prefabs.Add(prefab); } } public static GameObject GetMercPrefab(MercClass @class) { string text = @class switch { MercClass.Healer => "Merc_Healer", MercClass.Tank => "Merc_Tank", _ => "Merc_Archer", }; if (!((Object)(object)ZNetScene.instance != (Object)null)) { return null; } return ZNetScene.instance.GetPrefab(text); } } internal static class MercProgressionRules { internal const int MinimumStage = 0; internal const int MaximumStage = 7; internal const int MissingStage = -1; internal static int ResolveStage(int firstCandidate, int secondCandidate) { int num = ((firstCandidate > secondCandidate) ? firstCandidate : secondCandidate); if (num < 0) { return 0; } if (num > 7) { return 7; } return num; } internal static int ResolveObservedEquipmentStage(bool playerStateAvailable, params int[] observedItemStages) { if (!playerStateAvailable) { return -1; } int num = 0; if (observedItemStages != null) { for (int i = 0; i < observedItemStages.Length; i++) { if (observedItemStages[i] > num) { num = observedItemStages[i]; } } } return ResolveStage(num, -1); } internal static bool CanMutatePersonalProgression(bool personalModeEnabled, bool worldStateReady) { return personalModeEnabled && worldStateReady; } internal static bool TryReadPersistedStage(object value, out int stage) { stage = -1; if (value is long num) { if (num < 0 || num > 7) { return false; } stage = (int)num; return true; } if (value is double num2) { if (double.IsNaN(num2) || double.IsInfinity(num2) || num2 < 0.0 || num2 > 7.0 || num2 != Math.Truncate(num2)) { return false; } stage = (int)num2; return true; } return false; } internal static int SelectDurableStage(int personalStage, int bannerStage, bool recoveredFromBackup) { if (personalStage >= 0) { if (recoveredFromBackup && bannerStage > personalStage) { return ResolveStage(bannerStage, -1); } return ResolveStage(personalStage, -1); } if (bannerStage < 0) { return -1; } return ResolveStage(bannerStage, -1); } } [HarmonyPatch(typeof(Player), "HandleRadialInput")] internal static class MercRadialInputPatch { private static bool Prefix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { return !MercGuideMenu.SuppressVanillaRadial(); } return true; } } internal static class MercRadialRules { internal const int MaxEntries = 8; internal static float SlotAngle(int index, int count) { int num = WrapIndex(index, count); if (num >= 0) { return (float)num * (360f / (float)count); } return 0f; } internal static int IndexAt(float x, float y, int count, float rotationDegrees, float deadZone = 80f, float outerRadius = 310f) { if (!ValidCount(count) || !Finite(x) || !Finite(y) || !Finite(rotationDegrees) || !Finite(deadZone) || !Finite(outerRadius) || deadZone < 0f || outerRadius <= deadZone) { return -1; } double num = (double)x * (double)x + (double)y * (double)y; if (num <= (double)deadZone * (double)deadZone || num > (double)outerRadius * (double)outerRadius) { return -1; } return WrapIndex((int)Math.Floor(((Math.Atan2(x, y) * (180.0 / Math.PI) - (double)rotationDegrees) % 360.0 + 360.0) % 360.0 / (360.0 / (double)count) + 0.5), count); } internal static int WrapIndex(int index, int count) { if (!ValidCount(count)) { return -1; } int num = index % count; if (num >= 0) { return num; } return num + count; } internal static float ShortestDelta(float from, float to) { if (!Finite(from) || !Finite(to)) { return 0f; } double num = ((double)to - (double)from) % 360.0; if (num > 180.0) { num -= 360.0; } if (num < -180.0) { num += 360.0; } return (float)num; } internal static float StepRotation(float current, float target, float deltaTime) { if (!Finite(current)) { if (!Finite(target)) { return 0f; } return target; } if (!Finite(target) || !Finite(deltaTime) || deltaTime <= 0f) { return current; } float num = ShortestDelta(current, target); double num2 = 1.0 - Math.Exp((0.0 - (double)deltaTime) / 0.1); return (float)((double)current + (double)num * num2); } private static bool ValidCount(int count) { if (count > 0) { return count <= 8; } return false; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal sealed class MercRadialWheel : MaskableGraphic { private int _count; private int _selected = -1; private float _rotation; internal static Color Gold { get; private set; } = new Color(0.94f, 0.73f, 0.33f, 1f); internal static void ReadNativePalette() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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) RadialDataSO sO = RadialData.SO; Image val = (((Object)(object)sO != (Object)null && (Object)(object)sO.GroupElement != (Object)null) ? ((RadialMenuElement)sO.GroupElement).Background : null); Material val2 = (((Object)(object)val != (Object)null) ? ((Graphic)val).material : null); if (!((Object)(object)val2 == (Object)null) && val2.HasProperty("_SelectedColor")) { Color color = val2.GetColor("_SelectedColor"); if (!(((Color)(ref color)).maxColorComponent < 0.1f)) { color.a = 1f; Gold = color; } } } internal void SetState(int count, int selected, float rotation) { if (_count != count || _selected != selected || !(Mathf.Abs(_rotation - rotation) < 0.01f)) { _count = count; _selected = selected; _rotation = rotation; ((Graphic)this).SetVerticesDirty(); } } protected override void OnPopulateMesh(VertexHelper mesh) { //IL_002f: 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_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) mesh.Clear(); Arc(mesh, 0f, 99f, 0f, 360f, new Color(0.07f, 0.06f, 0.045f, 0.96f)); Arc(mesh, 98f, 100f, 0f, 360f, new Color(0.57f, 0.43f, 0.22f, 0.85f)); if (_count <= 0) { return; } float num = 360f / (float)_count; for (int i = 0; i < _count; i++) { float num2 = MercRadialRules.SlotAngle(i, _count) + _rotation; float start = num2 - num / 2f + 1.2f; float end = num2 + num / 2f - 1.2f; bool flag = i == _selected; Arc(mesh, 108f, 286f, start, end, flag ? new Color(0.3f, 0.24f, 0.13f, 0.97f) : new Color(0.09f, 0.085f, 0.07f, 0.95f)); Arc(mesh, 282f, flag ? 289f : 284f, start, end, (Color)(flag ? Gold : new Color(0.49f, 0.4f, 0.25f, 0.9f))); Arc(mesh, 108f, flag ? 112f : 109f, start, end, (Color)(flag ? Gold : new Color(0.37f, 0.31f, 0.2f, 0.8f))); if (flag) { Arc(mesh, 294f, 300f, num2 - 3f, num2 + 3f, Gold); } } } private static void Arc(VertexHelper mesh, float inner, float outer, float start, float end, Color tint) { //IL_007a: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Max(2, Mathf.CeilToInt((end - start) / 4f)); Vector2 val = default(Vector2); Vector2 val2 = default(Vector2); for (int i = 0; i < num; i++) { float num2 = Mathf.Lerp(start, end, (float)i / (float)num) * ((float)Math.PI / 180f); float num3 = Mathf.Lerp(start, end, ((float)i + 1f) / (float)num) * ((float)Math.PI / 180f); ((Vector2)(ref val))..ctor(Mathf.Sin(num2), Mathf.Cos(num2)); ((Vector2)(ref val2))..ctor(Mathf.Sin(num3), Mathf.Cos(num3)); int currentVertCount = mesh.currentVertCount; mesh.AddVert(Vector2.op_Implicit(val * inner), Color32.op_Implicit(tint), Vector4.op_Implicit(Vector2.zero)); mesh.AddVert(Vector2.op_Implicit(val * outer), Color32.op_Implicit(tint), Vector4.op_Implicit(Vector2.zero)); mesh.AddVert(Vector2.op_Implicit(val2 * outer), Color32.op_Implicit(tint), Vector4.op_Implicit(Vector2.zero)); mesh.AddVert(Vector2.op_Implicit(val2 * inner), Color32.op_Implicit(tint), Vector4.op_Implicit(Vector2.zero)); mesh.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2); mesh.AddTriangle(currentVertCount, currentVertCount + 2, currentVertCount + 3); } } } internal static class MercRecallRecovery { private static readonly int SolidMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "terrain", "blocker", "vehicle" }); private static readonly int ClearanceMask = SolidMask | LayerMask.GetMask(new string[2] { "character", "character_net" }); internal static bool TryRecover(ServerAuthority.MercenaryState merc, ServerAuthority.SenderPlayerState requester) { //IL_01f7: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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_011f: 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_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: 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_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || merc == null || requester == null || requester.IsDead || requester.PlayerId == 0L || !ServerAuthority.IsAssignedTo(merc, requester, requireFollowing: false)) { return false; } ZDO mercZdo = merc.MercZdo; if (mercZdo == null || !mercZdo.IsValid() || mercZdo.m_uid != merc.MercId || mercZdo.GetBool(ZDOVars.s_dead, false) || !IsPositiveFinite(mercZdo.GetFloat(ZDOVars.s_health, 0f)) || Mercenary.FollowNameOf(mercZdo) != requester.PlayerName || !IsFinite(requester.Position) || !IsFinite(mercZdo.GetPosition())) { return false; } try { if (Vector3.Distance(mercZdo.GetPosition(), requester.Position) <= 60f || MayHaveSimulator(mercZdo) || !TryFindLanding(merc, requester, out var landing)) { return false; } if (MayHaveSimulator(mercZdo)) { return false; } Quaternion val = ((requester.PlayerZdo != null) ? requester.PlayerZdo.GetRotation() : Quaternion.identity); if ((Object)(object)requester.LivePlayer != (Object)null) { val = ((Component)requester.LivePlayer).transform.rotation; } if (!IsFinite(new Vector3(val.x, val.y, val.z)) || float.IsNaN(val.w) || float.IsInfinity(val.w)) { return false; } ServerAuthority.EnsureZdoServerOwnership(mercZdo); if (mercZdo.GetOwner() != ZNet.GetUID()) { return false; } mercZdo.SetPosition(landing); mercZdo.SetRotation(val); ZDOMan.instance.ForceSendZDO(mercZdo.m_uid); MercPlugin.Log($"Recall recovered unloaded {merc.Name} {mercZdo.m_uid} beside {requester.PlayerName}."); return true; } catch (Exception ex) { MercPlugin.LogWarn($"Unloaded recall deferred for {merc.MercId}: {ex.Message}"); return false; } } private static bool MayHaveSimulator(ZDO zdo) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNetScene.instance.FindInstance(zdo.m_uid) != (Object)null) { return true; } SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance(); int num = Math.Max(1, ((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance) + 1; Vector3 position = zdo.GetPosition(); if (InPeerArea(position, ZNet.instance.GetReferencePosition(), num)) { return true; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer == null || !peer.IsReady()) { continue; } int radius = Math.Max(num, Math.Max(1, ((SimulationDistance)(ref peer.m_simulationDistance)).NearSimulationDistance) + 1); if (!IsFinite(peer.m_refPos) || InPeerArea(position, peer.m_refPos, radius)) { return true; } if (!((ZDOID)(ref peer.m_characterID)).IsNone()) { ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO == null || !zDO.IsValid() || !IsFinite(zDO.GetPosition()) || InPeerArea(position, zDO.GetPosition(), radius)) { return true; } } } return false; } private static bool InPeerArea(Vector3 position, Vector3 reference, int radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) Vector2s zone = ZoneSystem.GetZone(position); Vector2s zone2 = ZoneSystem.GetZone(reference); if (Math.Abs((long)zone.x - (long)zone2.x) <= radius) { return Math.Abs((long)zone.y - (long)zone2.y) <= radius; } return false; } private static bool TryFindLanding(ServerAuthority.MercenaryState merc, ServerAuthority.SenderPlayerState requester, out Vector3 landing) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_0083: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015d: 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_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: 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_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) landing = Vector3.zero; Player livePlayer = requester.LivePlayer; if ((Object)(object)livePlayer != (Object)null && (((Character)livePlayer).IsDead() || ((Character)livePlayer).IsTeleporting() || Mercenary.IsPlayerWaterborne(livePlayer))) { return false; } Vector3 position = requester.Position; if (!TryFloor(position, position.y, out var floor)) { return false; } Vector3 val = (((Object)(object)livePlayer != (Object)null) ? ((Component)livePlayer).transform.rotation : ((requester.PlayerZdo != null) ? requester.PlayerZdo.GetRotation() : Quaternion.identity)) * Vector3.forward; val.y = 0f; if (!IsFinite(val)) { return false; } if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.z, 0f, 0f - val.x); CompanyFormation formation = (CompanyFormation)Mathf.Clamp(merc.MercZdo.GetInt("dnpc.company.formation.v1", 0), 0, 2); Vector3[] obj = new Vector3[7] { MercFormation.Offset(merc.Class, val, MercMovementMode.Travel, formation), -val * 2.5f, val2 * 2.5f, -val2 * 2.5f, val * 2.5f, default(Vector3), default(Vector3) }; floor = -val + val2; obj[5] = ((Vector3)(ref floor)).normalized * 3.5f; floor = -val - val2; obj[6] = ((Vector3)(ref floor)).normalized * 3.5f; Vector3[] array = (Vector3[])(object)obj; foreach (Vector3 val3 in array) { if (TryFloor(position + val3, position.y, out var floor2) && !Physics.CheckCapsule(floor2 + Vector3.up * 0.4f, floor2 + Vector3.up * 1.6f, 0.3f, ClearanceMask, (QueryTriggerInteraction)1) && !Physics.Linecast(position + Vector3.up, floor2 + Vector3.up, SolidMask, (QueryTriggerInteraction)1)) { landing = floor2; return true; } } return false; } private static bool TryFloor(Vector3 point, float referenceHeight, out Vector3 floor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) floor = Vector3.zero; RaycastHit val = default(RaycastHit); if (!ZoneSystem.instance.IsZoneLoaded(point) || !Physics.Raycast(point + Vector3.up * 0.6f, Vector3.down, ref val, 2.6f, SolidMask, (QueryTriggerInteraction)1) || (Object)(object)((RaycastHit)(ref val)).collider == (Object)null || ((RaycastHit)(ref val)).normal.y < 0.7f || (Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent() != (Object)null || !IsFinite(((RaycastHit)(ref val)).point) || Mathf.Abs(((RaycastHit)(ref val)).point.y - referenceHeight) > 2f) { return false; } WaterVolume val2 = null; float waterLevel = Floating.GetWaterLevel(((RaycastHit)(ref val)).point, ref val2); if (float.IsNaN(waterLevel) || float.IsInfinity(waterLevel) || ((Object)(object)val2 != (Object)null && waterLevel > ((RaycastHit)(ref val)).point.y + 0.1f)) { return false; } floor = ((RaycastHit)(ref val)).point + Vector3.up * 0.1f; return true; } private static bool IsPositiveFinite(float value) { if (value > 0f) { return !float.IsInfinity(value); } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } [HarmonyPatch(typeof(Humanoid), "OnStopMoving")] internal static class MercResourceAnimationStopPatch { private static bool Prefix(Humanoid __instance, Attack ___m_currentAttack) { if (___m_currentAttack == null) { if (__instance is Mercenary mercenary && (Object)(object)mercenary.Ai != (Object)null) { return !mercenary.Ai.ResourceWorker.OwnsSwingAnimation; } return true; } return true; } } [HarmonyPatch(typeof(Mercenary), "OnDestroy")] internal static class MercResourceUnloadPatch { private static void Prefix(Mercenary __instance) { try { __instance.Ai?.ResourceWorker.Cancel(__instance.Ai); } catch { } } } internal static class MercResourceRules { internal const float EmployerLeash = 60f; internal static string GetToolPrefab(int stage, bool chopping) { if (stage < 0 || stage > 7) { return string.Empty; } if (chopping) { if (stage >= 4) { return "AxeBlackMetal"; } if (stage >= 2) { return "AxeIron"; } if (stage != 1) { return "AxeStone"; } return "AxeBronze"; } if (stage >= 5) { return "PickaxeBlackMetal"; } if (stage >= 2) { return "PickaxeIron"; } if (stage != 1) { return string.Empty; } return "PickaxeBronze"; } internal static bool IsToolAllowed(int stage, bool chopping, string prefab, int toolTier, int requiredToolTier, float chopDamage, float pickDamage, bool fineWood = false) { if (fineWood && stage < 1) { return false; } string toolPrefab = GetToolPrefab(stage, chopping); if (toolPrefab.Length == 0 || !string.Equals(toolPrefab, prefab, StringComparison.Ordinal) || toolTier < 0 || toolTier > 32767 || requiredToolTier < 0 || toolTier < requiredToolTier) { return false; } float num = (chopping ? chopDamage : pickDamage); if (num > 0f) { return !float.IsInfinity(num); } return false; } internal static bool CanWork(bool alive, bool following, bool swimming, bool shipAttached, bool sneaking, float employerDistance) { if (alive && following && !swimming && !shipAttached && !sneaking && employerDistance >= 0f) { return employerDistance <= 60f; } return false; } } internal static class MercResourceWork { internal sealed class Target { internal Component Resource; internal ZNetView View; internal Collider Collider; internal int ColliderIndex; internal bool Chopping; internal bool FineWood; internal int MinimumTier; internal Vector3 LocalPoint; internal Vector3 Point => Resource.transform.TransformPoint(LocalPoint); internal ZDOID Id => View.GetZDO().m_uid; internal bool Valid { get { if ((Object)(object)Resource != (Object)null && (Object)(object)View != (Object)null && View.IsValid() && (Object)(object)Collider != (Object)null && Collider.enabled) { return ((Component)Collider).gameObject.activeInHierarchy; } return false; } } internal void Damage(HitData hit) { Component resource = Resource; TreeBase val = (TreeBase)(object)((resource is TreeBase) ? resource : null); if (val != null) { val.Damage(hit); return; } Component resource2 = Resource; TreeLog val2 = (TreeLog)(object)((resource2 is TreeLog) ? resource2 : null); if (val2 != null) { val2.Damage(hit); return; } Component resource3 = Resource; MineRock val3 = (MineRock)(object)((resource3 is MineRock) ? resource3 : null); if (val3 != null) { val3.Damage(hit); return; } Component resource4 = Resource; MineRock5 val4 = (MineRock5)(object)((resource4 is MineRock5) ? resource4 : null); if (val4 != null) { val4.Damage(hit); return; } Component resource5 = Resource; Destructible val5 = (Destructible)(object)((resource5 is Destructible) ? resource5 : null); if (val5 != null) { val5.Damage(hit); } } } internal const float MaximumRange = 35f; internal const float Reach = 3f; internal const float Lifetime = 120f; internal const float SwingSeconds = 2f; internal static void Register() { ServerAuthority.RegisterResourceWork(); } internal static void Reset() { ServerAuthority.ResetResourceWork(); } internal static void CancelForMerc(ZDOID mercId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) ServerAuthority.CancelResourceWork(mercId); } internal static bool IsWorking(Mercenary merc) { if ((Object)(object)merc != (Object)null && (Object)(object)merc.Ai != (Object)null) { return merc.Ai.ResourceWorker.Active; } return false; } internal static bool TryRequest(Player player, RaycastHit hit) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)((RaycastHit)(ref hit)).collider == (Object)null || !TryTarget(((RaycastHit)(ref hit)).collider, ((RaycastHit)(ref hit)).point, out var target)) { return false; } ServerAuthority.RequestResourceWork(target.Id, target.LocalPoint, target.ColliderIndex, MercContextOrders.NextSequence()); return true; } internal static bool TryTarget(Collider collider, Vector3 worldPoint, out Target target) { //IL_0046: 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_00cc: Invalid comparison between Unknown and I4 //IL_0134: 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_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) target = null; if ((Object)(object)collider == (Object)null || (Object)(object)((Component)collider).GetComponentInParent() != (Object)null || (Object)(object)((Component)collider).GetComponentInParent() != (Object)null) { return false; } Component val = (Component)(object)((Component)collider).GetComponentInParent(); bool chopping = (Object)(object)val != (Object)null; int minimumTier = (((Object)(object)val != (Object)null) ? ((TreeBase)val).m_minToolTier : 0); if ((Object)(object)val == (Object)null) { TreeLog componentInParent = ((Component)collider).GetComponentInParent(); if (componentInParent != null) { val = (Component)(object)componentInParent; chopping = true; minimumTier = componentInParent.m_minToolTier; } } if ((Object)(object)val == (Object)null) { MineRock componentInParent2 = ((Component)collider).GetComponentInParent(); if (componentInParent2 != null) { val = (Component)(object)componentInParent2; minimumTier = componentInParent2.m_minToolTier; } } if ((Object)(object)val == (Object)null) { MineRock5 componentInParent3 = ((Component)collider).GetComponentInParent(); if (componentInParent3 != null) { val = (Component)(object)componentInParent3; minimumTier = componentInParent3.m_minToolTier; } } if ((Object)(object)val == (Object)null) { Destructible componentInParent4 = ((Component)collider).GetComponentInParent(); if (componentInParent4 != null && (int)componentInParent4.GetDestructibleType() == 2 && (Utils.GetPrefabName(((Component)componentInParent4).gameObject) ?? "").IndexOf("stub", StringComparison.OrdinalIgnoreCase) >= 0) { val = (Component)(object)componentInParent4; chopping = true; minimumTier = componentInParent4.m_minToolTier; } } if ((Object)(object)val == (Object)null) { return false; } ZNetView component = val.GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !collider.enabled || collider.isTrigger || !MercContextOrders.IsFinite(worldPoint)) { return false; } int num = Array.IndexOf(val.GetComponentsInChildren(), collider); if (num < 0 || num > 4095) { return false; } target = new Target { Resource = val, View = component, Collider = collider, ColliderIndex = num, Chopping = chopping, MinimumTier = minimumTier, FineWood = YieldsFineWood(val), LocalPoint = val.transform.InverseTransformPoint(worldPoint) }; return true; } internal static bool TryResolve(ZDOID id, Vector3 localPoint, int colliderIndex, out Target target) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_008b: Unknown result type (might be due to invalid IL or missing references) target = null; GameObject val = ((!((ZDOID)(ref id)).IsNone() && (Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(id) : null); if ((Object)(object)val == (Object)null || !MercContextOrders.IsFinite(localPoint) || colliderIndex < 0 || colliderIndex > 4095) { return false; } Collider[] componentsInChildren = val.GetComponentsInChildren(); if (colliderIndex >= componentsInChildren.Length) { return false; } Vector3 val2 = val.transform.TransformPoint(localPoint); if (!TryTarget(componentsInChildren[colliderIndex], val2, out target) || target.Id != id || Vector3.Distance(target.Collider.ClosestPoint(val2), val2) > 0.6f) { return false; } return true; } internal static bool TryTool(int stage, Target target, out ItemData tool, out string prefab) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_0083: Unknown result type (might be due to invalid IL or missing references) prefab = MercResourceRules.GetToolPrefab(stage, target.Chopping); GameObject val = ((!string.IsNullOrEmpty(prefab) && (Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefab) : null); tool = ((!((Object)(object)val != (Object)null)) ? null : val.GetComponent()?.m_itemData); if (tool == null) { return false; } DamageTypes damage = tool.GetDamage(); return MercResourceRules.IsToolAllowed(stage, target.Chopping, prefab, tool.m_shared.m_toolTier, target.MinimumTier, damage.m_chop, damage.m_pickaxe, target.FineWood); } private static bool YieldsFineWood(Component resource) { if (HasFineWood(resource.GetComponent()?.m_dropWhenDestroyed)) { return true; } TreeLog val = (TreeLog)(object)((resource is TreeLog) ? resource : null); if (val != null) { return LogYieldsFineWood(val, 4); } TreeBase val2 = (TreeBase)(object)((resource is TreeBase) ? resource : null); if (val2 == null) { return false; } TreeLog log = (((Object)(object)val2.m_logPrefab != (Object)null) ? val2.m_logPrefab.GetComponent() : null); if (!HasFineWood(val2.m_dropWhenDestroyed)) { return LogYieldsFineWood(log, 4); } return true; } private static bool LogYieldsFineWood(TreeLog log, int remaining) { if ((Object)(object)log == (Object)null) { return false; } if (remaining <= 0 || HasFineWood(log.m_dropWhenDestroyed)) { return true; } if ((Object)(object)log.m_subLogPrefab != (Object)null) { return LogYieldsFineWood(log.m_subLogPrefab.GetComponent(), remaining - 1); } return false; } private static bool HasFineWood(DropTable drops) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) if (drops?.m_drops == null) { return false; } foreach (DropData drop in drops.m_drops) { if ((Object)(object)drop.m_item != (Object)null && string.Equals(Utils.GetPrefabName(drop.m_item), "FineWood", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static bool ClearLine(Vector3 from, Target target, Transform ignored) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = target.Point - from; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { return true; } RaycastHit[] array = Physics.RaycastAll(from, ((Vector3)(ref val)).normalized, ((Vector3)(ref val)).magnitude + 0.1f, -5, (QueryTriggerInteraction)1); Array.Sort(array, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); RaycastHit[] array2 = array; for (int num = 0; num < array2.Length; num++) { RaycastHit val2 = array2[num]; if (!((Object)(object)((RaycastHit)(ref val2)).collider == (Object)null) && (!((Object)(object)ignored != (Object)null) || !((Component)((RaycastHit)(ref val2)).collider).transform.IsChildOf(ignored))) { if (!((Object)(object)((RaycastHit)(ref val2)).collider == (Object)(object)target.Collider)) { return ((Component)((RaycastHit)(ref val2)).collider).transform.IsChildOf(target.Resource.transform); } return true; } } return true; } } internal sealed class MercResourceWorker { private static readonly MethodInfo SetupEquipment = typeof(Humanoid).GetMethod("SetupVisEquipment", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(VisEquipment), typeof(bool) }, null); private MercResourceWork.Target _target; private int _token; private float _expires; private float _swing; private float _impact; private Vector3 _approach; private float _approachRefresh; private bool _presenting; private long _owner; private float _health; private int _stage; private float _animationUntil; internal bool Active { get { if (_token != 0) { return Time.time < _expires; } return false; } } internal bool OwnsSwingAnimation => Time.time < _animationUntil; internal void CancelToken(MercAI ai, int token) { if (_token == token) { Cancel(ai, notify: false); } } internal void OwnershipLost(MercAI ai) { if (_presenting) { Cancel(ai, notify: false); } } internal void Begin(MercAI ai, MercResourceWork.Target target, int token, int stage) { Cancel(ai, notify: false); _target = target; _token = token; _owner = ((Component)ai).GetComponent().GetZDO().GetOwner(); _health = ((Character)ai.Merc).GetHealth(); _stage = stage; _expires = Time.time + 120f; _approachRefresh = 0f; _swing = 0.3f; } internal void Cancel(MercAI ai, bool notify = true) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) int token = _token; _token = 0; _target = null; _impact = 0f; Mercenary merc = ai.Merc; ZNetView val = (((Object)(object)merc != (Object)null) ? ((Component)merc).GetComponent() : null); if (token != 0 && (Object)(object)merc != (Object)null && (Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) { VisEquipment component = ((Component)merc).GetComponent(); if ((Object)(object)component != (Object)null && SetupEquipment != null) { SetupEquipment.Invoke(merc, new object[2] { component, false }); if (merc.Class == MercClass.Healer) { merc.ApplyHealerCosmeticVisuals(component); } } } _presenting = false; if (notify && token != 0 && (Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) { ServerAuthority.RequestResourceSwing(val.GetZDO().m_uid, token, strike: false); } } internal bool Tick(MercAI ai, float dt) { //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_00c4: 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_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: 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_021b: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) if (_token == 0) { return false; } Mercenary merc = ai.Merc; if (_owner != ZNet.GetUID() || !Active || _target == null || !_target.Valid || ((Character)merc).IsDead() || ((Character)merc).GetHealth() + 0.1f < _health || ((Character)merc).IsSwimming() || ((Character)merc).InWater() || ((Character)merc).IsAttachedToShip() || ai.IsGuiding || ai.IsResurrectionActive || MercStealth.ShouldFollowSneak(merc) || !TryEmployer(merc, out var position) || Vector3.Distance(((Component)merc).transform.position, position) > 35f || Vector3.Distance(_target.Point, position) > 35f || ai.HasResourceWorkThreat() || !MercResourceWork.TryTool(_stage, _target, out var tool, out var prefab)) { Cancel(ai); return false; } _health = ((Character)merc).GetHealth(); ai.CancelAmbientCamp(); _approachRefresh -= dt; if (_approachRefresh <= 0f) { _approachRefresh = 2f; Vector3 val = ((Component)merc).transform.position - _target.Point; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.1f) { val = -((Component)merc).transform.forward; } Vector3 candidate = _target.Point + ((Vector3)(ref val)).normalized * 1.7f; candidate.y = ((Component)merc).transform.position.y; if (!ai.TryGetAmbientApproach(candidate, out _approach)) { Cancel(ai); return false; } } if (Vector3.Distance(((Character)merc).GetCenterPoint(), _target.Point) > 3f || !MercResourceWork.ClearLine(((Character)merc).GetCenterPoint(), _target, ((Component)merc).transform)) { _impact = 0f; ai.MoveForResourceWork(dt, _approach); return true; } ai.StopForAmbient(); Vector3 val2 = _target.Point - ((Component)merc).transform.position; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude > 0.01f) { ((Component)merc).transform.rotation = Quaternion.RotateTowards(((Component)merc).transform.rotation, Quaternion.LookRotation(val2), 360f * dt); } Present(merc, prefab); if (_impact > 0f) { _impact -= dt; if (_impact <= 0f) { ServerAuthority.RequestResourceSwing(((Character)merc).GetZDOID(), _token, strike: true); } } _swing -= dt; if (_swing > 0f || ((Character)merc).InAttack() || ((Character)merc).InDodge()) { return true; } float num = Mathf.Max(6f, tool.m_shared.m_attack.m_attackStamina); if (!((Character)merc).HaveStamina(num)) { return true; } ((Character)merc).UseStamina(num); string text = tool.m_shared.m_attack.m_attackAnimation; if (tool.m_shared.m_attack.m_attackChainLevels > 1 || tool.m_shared.m_attack.m_attackRandomAnimations >= 2) { text += "0"; } ZSyncAnimation component = ((Component)merc).GetComponent(); if (component != null) { component.SetTrigger(text); } _animationUntil = Time.time + 2f; _impact = 0.65f; _swing = 2f; return true; } private void Present(Mercenary merc, string prefab) { VisEquipment component = ((Component)merc).GetComponent(); if (!((Object)(object)component == (Object)null) && !(SetupEquipment == null)) { _presenting = true; component.SetLeftItem(0, 0, 0); component.SetRightItem(MercVisuals.ItemHash(prefab), 1); } } private static bool TryEmployer(Mercenary merc, out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; long num = merc.EffectiveEmployerId(); Player player = Player.GetPlayer(num); if ((Object)(object)player != (Object)null) { position = ((Component)player).transform.position; if (!((Character)player).IsDead() && !((Character)player).IsAttachedToShip() && !((Character)player).InWater() && !((Character)player).IsSwimming()) { return merc.IsAssignedTo(player, requireFollowing: true); } return false; } if (!ServerAuthority.TryFindConnectedPeerByPlayerId(num, out var peerUid, out var playerName) || !merc.IsAssignedTo(num, playerName, requireFollowing: true) || (Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return false; } ZNetPeer peer = ZNet.instance.GetPeer(peerUid); ZDO val = ((peer != null) ? ZDOMan.instance.GetZDO(peer.m_characterID) : null); if (val == null || !val.IsValid() || val.GetBool(ZDOVars.s_dead, false)) { return false; } position = val.GetPosition(); return true; } } internal static class MercStealth { internal static readonly int Crouching = ZSyncAnimation.GetHash("crouching"); private static readonly int CrouchingZdo = 438569 + Crouching; internal static bool ShouldFollowSneak(Mercenary merc) { //IL_012c: 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_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)merc == (Object)null || !((Component)merc).gameObject.activeInHierarchy) { return false; } long num = merc.EffectiveEmployerId(); if (num == 0L) { return false; } Player player = Player.GetPlayer(num); if ((Object)(object)player != (Object)null) { ZNetView component = ((Component)player).GetComponent(); bool employerCrouching = (((Object)(object)component != (Object)null && component.IsValid() && !component.IsOwner()) ? component.GetZDO().GetBool(CrouchingZdo, false) : ((Character)player).IsCrouching()); return MercStealthRules.ShouldFollowSneak(merc.IsAssignedTo(player, requireFollowing: true), !((Character)player).IsDead(), employerCrouching, !((Character)merc).IsDead(), ((Character)merc).IsSwimming() || ((Character)merc).InWater(), ((Character)merc).IsAttachedToShip() || ((Character)player).IsAttachedToShip(), Vector3.Distance(((Component)merc).transform.position, ((Component)player).transform.position)); } if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null || !ServerAuthority.TryFindConnectedPeerByPlayerId(num, out var peerUid, out var playerName) || !merc.IsAssignedTo(num, playerName, requireFollowing: true)) { return false; } ZNetPeer peer = ZNet.instance.GetPeer(peerUid); ZDO val = ((peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone()) ? ZDOMan.instance.GetZDO(peer.m_characterID) : null); if (val != null && val.IsValid()) { return MercStealthRules.ShouldFollowSneak(following: true, !val.GetBool(ZDOVars.s_dead, false), val.GetBool(CrouchingZdo, false), !((Character)merc).IsDead(), ((Character)merc).IsSwimming() || ((Character)merc).InWater(), ((Character)merc).IsAttachedToShip(), Vector3.Distance(((Component)merc).transform.position, val.GetPosition())); } return false; } internal static bool IsConcealed(Character character) { if (character is Mercenary mercenary && ShouldFollowSneak(mercenary) && !((Character)mercenary).InAttack()) { return !((Character)mercenary).InDodge(); } return false; } } [HarmonyPatch(typeof(Character), "IsCrouching")] internal static class MercStealthCrouchingPatch { private static void Postfix(Character __instance, ref bool __result) { if (__instance is Mercenary) { __result = MercStealth.IsConcealed(__instance); } } } [HarmonyPatch(typeof(BaseAI), "CanHearTarget", new Type[] { typeof(Transform), typeof(float), typeof(Character) })] internal static class MercStealthHearingPatch { private static bool Prefix(Character target, ref bool __result) { if (!MercStealth.IsConcealed(target)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(BaseAI), "CanSeeTarget", new Type[] { typeof(Transform), typeof(Vector3), typeof(float), typeof(float), typeof(bool), typeof(bool), typeof(Character) })] internal static class MercStealthSightPatch { private static bool Prefix(Character target, ref bool __result) { if (!MercStealth.IsConcealed(target)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] internal static class MercStealthForgetTargetPatch { private static void Prefix(ref Character ___m_targetCreature, ref float ___m_updateTargetTimer) { if (MercStealth.IsConcealed(___m_targetCreature)) { ___m_targetCreature = null; ___m_updateTargetTimer = 0f; } } } internal static class MercStealthRules { internal const float EmployerRange = 60f; internal static bool ShouldFollowSneak(bool following, bool employerAlive, bool employerCrouching, bool mercAlive, bool swimming, bool onShip, float employerDistance) { if (following && employerAlive && employerCrouching && mercAlive && !swimming && !onShip && employerDistance >= 0f) { return employerDistance <= 60f; } return false; } } internal static class MercVisuals { internal static int ItemHash(string prefabName) { if (!string.IsNullOrEmpty(prefabName)) { return StringExtensionMethods.GetStableHashCode(prefabName); } return 0; } } public static class MiniJson { internal const int MaximumDepth = 32; public static string Escape(string s) { if (s == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 8); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } public static string GetString(string json, string key) { if (!(Parse(json) is Dictionary dictionary) || !dictionary.TryGetValue(key, out var value) || !(value is string)) { return null; } return (string)value; } public static object Parse(string json) { if (!TryParseComplete(json, out var value)) { return null; } return value; } internal static bool TryParseComplete(string json, out object value) { value = null; if (json == null) { return false; } int pos = 0; try { object obj = ParseValue(json, ref pos, 0); SkipWs(json, ref pos); if (pos != json.Length) { return false; } value = obj; return true; } catch { value = null; return false; } } private static void SkipWs(string s, ref int pos) { while (pos < s.Length && (s[pos] == ' ' || s[pos] == '\t' || s[pos] == '\r' || s[pos] == '\n')) { pos++; } } private static object ParseValue(string s, ref int pos, int depth) { SkipWs(s, ref pos); if (pos >= s.Length) { throw new FormatException("eof"); } char c = s[pos]; switch (c) { case '[': case '{': if (depth >= 32) { throw new FormatException("JSON nesting exceeds its bound."); } if (c != '{') { return ParseArray(s, ref pos, depth + 1); } return ParseObject(s, ref pos, depth + 1); case '"': return ParseString(s, ref pos); case 't': ReadLiteral(s, ref pos, "true"); return true; case 'f': ReadLiteral(s, ref pos, "false"); return false; case 'n': ReadLiteral(s, ref pos, "null"); return null; default: return ParseNumber(s, ref pos); } } private static void ReadLiteral(string s, ref int pos, string literal) { if (pos + literal.Length > s.Length || string.CompareOrdinal(s, pos, literal, 0, literal.Length) != 0) { throw new FormatException("Invalid JSON literal."); } pos += literal.Length; } private static Dictionary ParseObject(string s, ref int pos, int depth) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); pos++; SkipWs(s, ref pos); if (pos < s.Length && s[pos] == '}') { pos++; return dictionary; } while (pos < s.Length) { SkipWs(s, ref pos); string key = ParseString(s, ref pos); SkipWs(s, ref pos); if (pos >= s.Length || s[pos] != ':') { throw new FormatException("object colon"); } pos++; object value = ParseValue(s, ref pos, depth); if (dictionary.ContainsKey(key)) { throw new FormatException("Duplicate JSON object key."); } dictionary.Add(key, value); SkipWs(s, ref pos); if (pos < s.Length && s[pos] == ',') { pos++; continue; } if (pos < s.Length && s[pos] == '}') { pos++; return dictionary; } throw new FormatException("Missing JSON object separator."); } throw new FormatException("object"); } private static List ParseArray(string s, ref int pos, int depth) { List list = new List(); pos++; SkipWs(s, ref pos); if (pos < s.Length && s[pos] == ']') { pos++; return list; } while (pos < s.Length) { list.Add(ParseValue(s, ref pos, depth)); SkipWs(s, ref pos); if (pos < s.Length && s[pos] == ',') { pos++; continue; } if (pos < s.Length && s[pos] == ']') { pos++; return list; } throw new FormatException("Missing JSON array separator."); } throw new FormatException("array"); } private static string ParseString(string s, ref int pos) { if (pos >= s.Length || s[pos] != '"') { throw new FormatException("JSON object keys and strings must be quoted."); } pos++; StringBuilder stringBuilder = new StringBuilder(); while (pos < s.Length) { char c = s[pos++]; if (c == '"') { return stringBuilder.ToString(); } if (c < ' ') { throw new FormatException("Unescaped JSON control character."); } if (c == '\\') { if (pos >= s.Length) { throw new FormatException("Truncated JSON escape."); } switch (s[pos++]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': { if (pos + 4 > s.Length) { throw new FormatException("Truncated JSON Unicode escape."); } int num = 0; for (int i = 0; i < 4; i++) { char c2 = s[pos++]; int num2 = ((c2 >= '0' && c2 <= '9') ? (c2 - 48) : ((c2 >= 'a' && c2 <= 'f') ? (c2 - 97 + 10) : ((c2 >= 'A' && c2 <= 'F') ? (c2 - 65 + 10) : (-1)))); if (num2 < 0) { throw new FormatException("Invalid JSON Unicode escape."); } num = num * 16 + num2; } stringBuilder.Append((char)num); break; } default: throw new FormatException("Invalid JSON escape."); } } else { stringBuilder.Append(c); } } throw new FormatException("string"); } private static double ParseNumber(string s, ref int pos) { int num = pos; if (pos < s.Length && s[pos] == '-') { pos++; } if (pos >= s.Length) { throw new FormatException("Missing JSON number."); } if (s[pos] == '0') { pos++; } else { if (s[pos] < '1' || s[pos] > '9') { throw new FormatException("Invalid JSON number."); } while (pos < s.Length && IsDigit(s[pos])) { pos++; } } if (pos < s.Length && s[pos] == '.') { pos++; int num2 = pos; while (pos < s.Length && IsDigit(s[pos])) { pos++; } if (num2 == pos) { throw new FormatException("Missing JSON fractional digits."); } } if (pos < s.Length && (s[pos] == 'e' || s[pos] == 'E')) { pos++; if (pos < s.Length && (s[pos] == '+' || s[pos] == '-')) { pos++; } int num3 = pos; while (pos < s.Length && IsDigit(s[pos])) { pos++; } if (num3 == pos) { throw new FormatException("Missing JSON exponent digits."); } } if (!double.TryParse(s.Substring(num, pos - num), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || double.IsInfinity(result) || double.IsNaN(result)) { throw new FormatException("JSON number is not finite and representable."); } return result; } private static bool IsDigit(char value) { if (value >= '0') { return value <= '9'; } return false; } } internal static class NpcDefinitionRegistry { internal sealed class Definition { internal string Id = "snikk"; internal string DisplayName = "Snikk the Gossip"; internal string Prefab = "Fuling"; internal float MaxHealth = 1000f; internal float PlayerDamagePerHit = 1f; internal float MinimumHealth = 1f; internal bool CanAttack; internal bool IgnoredByHostileAI = true; internal bool IgnoreNonPlayerDamage = true; internal bool AllowStagger; internal bool AllowStatusDamage; internal int MaxFactsPerResponse = 2; internal string Profile = ""; } private static Definition _snikk; private static DateTime _definitionWrite; private static DateTime _profileWrite; private static string _loadedRoot = ""; private static float _nextFileCheck; internal static Definition Snikk { get { EnsureLoaded(); return _snikk ?? (_snikk = new Definition()); } } internal static void Reset() { _snikk = null; _definitionWrite = default(DateTime); _profileWrite = default(DateTime); _loadedRoot = ""; _nextFileCheck = 0f; } internal static void EnsureLoaded() { string rootPath = WorldDataService.RootPath; if (string.IsNullOrWhiteSpace(rootPath) || (_snikk != null && string.Equals(_loadedRoot, rootPath, StringComparison.OrdinalIgnoreCase) && Time.unscaledTime < _nextFileCheck)) { return; } _nextFileCheck = Time.unscaledTime + 5f; string text = Path.Combine(rootPath, "NPCs", "snikk"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "Definition.json"); string text3 = Path.Combine(text, "Profile.md"); EnsureTemplates(text2, text3); DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text2); DateTime lastWriteTimeUtc2 = File.GetLastWriteTimeUtc(text3); if (_snikk == null || !string.Equals(_loadedRoot, rootPath, StringComparison.OrdinalIgnoreCase) || !(lastWriteTimeUtc == _definitionWrite) || !(lastWriteTimeUtc2 == _profileWrite)) { Definition definition = LoadDefinition(text2); try { definition.Profile = File.ReadAllText(text3); } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Could not read Profile.md: " + ex.Message); } _snikk = definition; _definitionWrite = lastWriteTimeUtc; _profileWrite = lastWriteTimeUtc2; _loadedRoot = rootPath; MercPlugin.Log($"[Snikk] NPC definition loaded: prefab={definition.Prefab}, health={definition.MaxHealth:0}, profileChars={definition.Profile.Length}."); } } private static Definition LoadDefinition(string path) { Definition definition = new Definition(); try { if (!(MiniJson.Parse(File.ReadAllText(path)) is Dictionary obj)) { return definition; } definition.Id = String(obj, "id", definition.Id); definition.DisplayName = String(obj, "displayName", definition.DisplayName); definition.Prefab = String(obj, "prefab", definition.Prefab); if (Object(obj, "combat") is Dictionary obj2) { definition.MaxHealth = ClampFloat(Number(obj2, "maxHealth", definition.MaxHealth), 1f, 100000f); definition.PlayerDamagePerHit = ClampFloat(Number(obj2, "playerDamagePerHit", definition.PlayerDamagePerHit), 0f, 100f); definition.MinimumHealth = ClampFloat(Number(obj2, "minimumHealth", definition.MinimumHealth), 1f, definition.MaxHealth); definition.CanAttack = Boolean(obj2, "canAttack", definition.CanAttack); definition.IgnoredByHostileAI = Boolean(obj2, "ignoredByHostileAI", definition.IgnoredByHostileAI); definition.IgnoreNonPlayerDamage = Boolean(obj2, "ignoreNonPlayerDamage", definition.IgnoreNonPlayerDamage); definition.AllowStagger = Boolean(obj2, "allowStagger", definition.AllowStagger); definition.AllowStatusDamage = Boolean(obj2, "allowStatusDamage", definition.AllowStatusDamage); } if (Object(obj, "dialogue") is Dictionary obj3) { definition.MaxFactsPerResponse = Math.Max(1, Math.Min(2, (int)Number(obj3, "maxFactsPerResponse", 2f))); } } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Could not parse Definition.json; using defaults: " + ex.Message); } return definition; } private static void EnsureTemplates(string definitionPath, string profilePath) { if (!File.Exists(definitionPath)) { File.WriteAllText(definitionPath, "{\r\n \"id\": \"snikk\",\r\n \"displayName\": \"Snikk the Gossip\",\r\n \"prefab\": \"Fuling\",\r\n \"lifecycle\": { \"type\": \"ephemeral_visitor\", \"persistent\": false, \"uniqueServerWide\": true },\r\n \"combat\": { \"canAttack\": false, \"ignoredByHostileAI\": true, \"maxHealth\": 1000, \"playerDamagePerHit\": 1, \"minimumHealth\": 1, \"ignoreNonPlayerDamage\": true, \"allowStagger\": false, \"allowStatusDamage\": false },\r\n \"visits\": { \"baseVisit\": { \"minimumDays\": 10, \"maximumDays\": 20, \"requiresPlayerAtBase\": true }, \"worldVisit\": { \"afterNoSightingDays\": 30, \"requiresBase\": false } },\r\n \"knowledge\": { \"activityLedger\": true, \"dailyStats\": true, \"activityFacts\": true, \"worldInventory\": false },\r\n \"dialogue\": { \"profile\": \"Profile.md\", \"maxFactsPerResponse\": 2 }\r\n}\r\n", Encoding.UTF8); } if (!File.Exists(profilePath)) { File.WriteAllText(profilePath, "# Snikk the Gossip\r\n\r\n## Identity\r\nSnikk is a wandering Fuling gossip who periodically finds players and tells them embarrassing things that actually happened on the server. He never lives in the world permanently.\r\n\r\n## Personality\r\n- Extremely cheeky, sarcastic, rude, smug, blunt, and nosy.\r\n- Funny rather than hateful; never threatening or sincere.\r\n- Rare praise is reluctant or backhanded.\r\n\r\n## Speech\r\nSnikk uses short authored remarks about confirmed player activity. This file is a character reference; editing it does not generate or change runtime dialogue.\r\n\r\n## Factual Boundary\r\nReplies use recorded facts. Missing player activity, statistics, locations and quantities remain unknown. Add shared server answers to WorldKnowledge.md.\r\n\r\n## Attack Personality\r\nThe first hit response is always: \"Stop, stupid.\" Later hits are increasingly dismissive, but Snikk never becomes hostile.\r\n", Encoding.UTF8); } } private static object Object(Dictionary obj, string key) { if (obj == null || !obj.TryGetValue(key, out var value)) { return null; } return value; } private static string String(Dictionary obj, string key, string fallback) { if (!(Object(obj, key) is string text) || string.IsNullOrWhiteSpace(text)) { return fallback; } return text.Trim(); } private static float Number(Dictionary obj, string key, float fallback) { object obj2 = Object(obj, key); if (obj2 is double num) { return (float)num; } if (obj2 == null || !float.TryParse(obj2.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return fallback; } return result; } private static bool Boolean(Dictionary obj, string key, bool fallback) { object obj2 = Object(obj, key); if (obj2 is bool) { return (bool)obj2; } if (obj2 == null || !bool.TryParse(obj2.ToString(), out var result)) { return fallback; } return result; } private static float ClampFloat(float value, float minimum, float maximum) { return Math.Max(minimum, Math.Min(maximum, value)); } } internal static class NpcKnowledge { private const string PresenceUnavailable = "I haven't confirmed the current player roster yet. Ask again in a few seconds."; internal static bool TryAnswer(string question, ServerAuthority.SenderPlayerState requester, out string answer) { answer = ""; if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || requester == null || requester.PlayerId == 0L || string.IsNullOrWhiteSpace(question) || question.Length > 512) { return false; } KnowledgeQuery knowledgeQuery = KnowledgeRules.Parse(question); DateTime utcNow = DateTime.UtcNow; IReadOnlyList players = PlayerChronicle.GetSnapshot(); bool flag = KnowledgeRules.IsFresh(PlayerChronicle.PresenceObservedUtc, utcNow, 20.0); switch (knowledgeQuery.Intent) { case KnowledgeIntent.ServerDateTime: answer = "The server's real-world clock reads " + KnowledgeRules.Timestamp(utcNow) + "."; return true; case KnowledgeIntent.OnlineRoster: answer = (flag ? OnlineRoster(players) : "I haven't confirmed the current player roster yet. Ask again in a few seconds."); return true; case KnowledgeIntent.KnownRoster: answer = KnownRoster(players, flag); return true; case KnowledgeIntent.RecentActivity: answer = Activity(players, requester.PlayerId, utcNow); return true; default: if (knowledgeQuery.Intent != KnowledgeIntent.None) { List list; if (KnowledgeRules.IsSelf(knowledgeQuery.Target)) { PlayerKnowledgeRecord record = PlayerChronicle.GetRecord(requester.PlayerId); list = ((record == null) ? new List() : new List { record }); } else { list = (from index in KnowledgeRules.MatchPlayers(knowledgeQuery.Target, players.Select((PlayerKnowledgeRecord player) => player.Name).ToArray(), players.Select((PlayerKnowledgeRecord player) => player.PlayerId).ToArray()).ToArray() select players[index]).ToList(); } if (list.Count > 1) { answer = "More than one recorded player has that name. Use a full selector: " + string.Join("; ", from player in list.Take(5) select "\"" + Name(player) + " #" + player.PlayerId.ToString(CultureInfo.InvariantCulture) + "\"") + "."; return true; } if (list.Count == 1) { answer = PlayerAnswer(knowledgeQuery.Intent, list[0], requester, utcNow, flag); return true; } if (knowledgeQuery.ExplicitPlayer || KnowledgeRules.IsSelf(knowledgeQuery.Target)) { if (WorldDataService.TryAnswerWorldQuestion(question, requester, out answer)) { return true; } answer = "I don't have a confirmed player record for \"" + KnowledgeRules.Display(knowledgeQuery.Target) + "\". Use their full character name or ask who's online."; return true; } } return WorldDataService.TryAnswerWorldQuestion(question, requester, out answer); } } private static string OnlineRoster(IReadOnlyList players) { PlayerKnowledgeRecord[] array = players.Where((PlayerKnowledgeRecord player) => player.IsOnline).OrderBy((PlayerKnowledgeRecord player) => player.Name, StringComparer.OrdinalIgnoreCase).ToArray(); if (array.Length == 0) { return "The latest complete server check found no active player characters."; } return "Online: " + string.Join(", ", array.Take(20).Select(Name)) + ((array.Length > 20) ? (" (and " + (array.Length - 20).ToString(CultureInfo.InvariantCulture) + " more)") : "") + ". Checked " + KnowledgeRules.DescribeAge(PlayerChronicle.PresenceObservedUtc, DateTime.UtcNow) + "."; } private static string KnownRoster(IReadOnlyList players, bool presenceFresh) { if (!WorldDataService.OtherPlayerActivityShared()) { if (!presenceFresh) { return "I haven't confirmed the current player roster yet. Ask again in a few seconds."; } return OnlineRoster(players) + " Saved activity sharing is disabled."; } if (players.Count == 0) { return "My player notebook has no confirmed entries yet."; } return "Recorded players: " + string.Join(", ", players.OrderBy((PlayerKnowledgeRecord player) => player.Name, StringComparer.OrdinalIgnoreCase).Take(20).Select(Name)) + ((players.Count > 20) ? (" (and " + (players.Count - 20).ToString(CultureInfo.InvariantCulture) + " more)") : "") + "."; } private static string PlayerAnswer(KnowledgeIntent intent, PlayerKnowledgeRecord player, ServerAuthority.SenderPlayerState requester, DateTime now, bool presenceFresh) { //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) string text = Name(player); switch (intent) { case KnowledgeIntent.PlayerOnline: if (!presenceFresh) { return "I haven't confirmed the current player roster yet. Ask again in a few seconds."; } return text + (player.IsOnline ? " is online." : " is not online in the latest server check."); case KnowledgeIntent.PlayerLocation: { if (player.PlayerId != requester.PlayerId && MercConfig.SharePlayerLocations != null && !MercConfig.SharePlayerLocations.Value) { return "The server has disabled sharing other players' locations."; } if (!presenceFresh) { return "I can't confirm " + text + "'s current location until the player roster is checked again."; } if (!player.IsOnline) { return text + " is offline. I don't keep offline travel coordinates."; } if (!player.HasKnownPosition || !KnowledgeRules.IsFresh(player.PositionObservedUtc, now, 15.0) || !KnowledgeRules.FiniteCoordinate(player.PositionY) || !KnowledgeRules.TryDirection(requester.Position.x, requester.Position.z, player.PositionX, player.PositionZ, out var direction, out var distance)) { return text + " is online, but I don't have a fresh, confirmed position. Ask again in a few seconds."; } string text4 = ((direction == "right nearby") ? ("right nearby (about " + distance + " away)") : ("about " + distance + " to your " + direction)); string text5 = (string.IsNullOrWhiteSpace(player.Biome) ? "" : (" in " + KnowledgeRules.Display(player.Biome, 60))); if (Character.InInterior(new Vector3(player.PositionX, player.PositionY, player.PositionZ))) { return text + " was inside an interior when checked " + KnowledgeRules.DescribeAge(player.PositionObservedUtc, now) + ". I don't have a confirmed entrance or route to them."; } if (Character.InInterior(requester.Position)) { return text + " was outdoors" + text5 + " when checked " + KnowledgeRules.DescribeAge(player.PositionObservedUtc, now) + ". Ask for a bearing once you are outside the interior."; } return text + " was " + text4 + text5 + " when checked " + KnowledgeRules.DescribeAge(player.PositionObservedUtc, now) + ". Straight-line bearing; the route may cross water or obstacles."; } default: if (player.PlayerId != requester.PlayerId && !WorldDataService.OtherPlayerActivityShared()) { return "The server has disabled sharing other players' recorded activity. You can still ask who's online."; } switch (intent) { case KnowledgeIntent.PlayerLastSeen: if (presenceFresh && player.IsOnline) { return text + " is online now; last confirmed " + KnowledgeRules.DescribeAge(player.LastObservedUtc, now) + "."; } return Seen(player, now, presenceFresh); case KnowledgeIntent.PlayerLastLeft: if (!(player.LastDisconnectUtc == DateTime.MinValue)) { return text + " was last recorded leaving at " + KnowledgeRules.Timestamp(player.LastDisconnectUtc) + " (" + KnowledgeRules.DescribeAge(player.LastDisconnectUtc, now) + ")."; } return "I don't have a confirmed departure time for " + text + "."; case KnowledgeIntent.PlayerActivity: { string text3 = Events(player, now); if (text3.Length != 0) { return text3; } return "I have no recorded recent activity for " + text + "."; } default: { string obj = (presenceFresh ? (text + (player.IsOnline ? " is online." : " is offline.")) : ("I know " + text + ", but their current connection status is unconfirmed.")); string text2 = Events(player, now, 2); return obj + ((text2.Length > 0) ? (" " + text2) : (" " + Seen(player, now, presenceFresh))); } } } } private static string Seen(PlayerKnowledgeRecord player, DateTime now, bool presenceFresh) { if (player.LastObservedUtc == DateTime.MinValue) { return "I haven't recorded when " + Name(player) + " was last online."; } return Name(player) + " was last confirmed online at " + KnowledgeRules.Timestamp(player.LastObservedUtc) + " (" + KnowledgeRules.DescribeAge(player.LastObservedUtc, now) + ")." + (presenceFresh ? "" : " Their current connection status is unconfirmed."); } private static string Events(PlayerKnowledgeRecord player, DateTime now, int limit = 3) { if (player.RecentEvents == null) { return ""; } string[] array = (from entry in player.RecentEvents.Where((PlayerKnowledgeEvent entry) => ValidEvent(entry, now)).Take(limit) select "[" + KnowledgeRules.DescribeAge(entry.AtUtc, now) + "] " + KnowledgeRules.Display(entry.Text, 220)).ToArray(); if (array.Length != 0) { return Name(player) + ": " + string.Join("; ", array) + "."; } return ""; } private static string Activity(IReadOnlyList players, long requesterId, DateTime now) { bool shared = WorldDataService.OtherPlayerActivityShared(); string[] array = (from item in (from item in players.Where((PlayerKnowledgeRecord player) => shared || player.PlayerId == requesterId).SelectMany((PlayerKnowledgeRecord player) => from entry in player.RecentEvents ?? Array.Empty() where ValidEvent(entry, now) select new { Player = player, Event = entry }) orderby item.Event.AtUtc descending select item).Take(4) select "[" + KnowledgeRules.DescribeAge(item.Event.AtUtc, now) + "] " + Name(item.Player) + ": " + KnowledgeRules.Display(item.Event.Text, 180)).ToArray(); if (array.Length == 0) { if (!shared) { return "I have no recent activity recorded for you. Sharing other players' activity is disabled."; } return "There is no recent activity recorded in my notebook."; } return string.Join("; ", array) + (shared ? "." : ". Only your own activity is shared here."); } private static bool ValidEvent(PlayerKnowledgeEvent entry, DateTime now) { if (entry != null && !string.IsNullOrWhiteSpace(entry.Text) && entry.AtUtc != DateTime.MinValue && (now - entry.AtUtc).TotalSeconds >= -2.0) { return (now - entry.AtUtc).TotalDays <= 7.0; } return false; } private static string Name(PlayerKnowledgeRecord player) { return KnowledgeRules.Display(player.Name); } } internal static class NpcSupport { private static readonly SupportConversationMemory Subjects = new SupportConversationMemory(); private static readonly Dictionary LastLines = new Dictionary(StringComparer.Ordinal); internal static void ResetSceneMemory() { Subjects.Clear(); LastLines.Clear(); } internal static void Forget(ServerAuthority.SenderPlayerState requester, DialogueRole role, string speakerKey) { if (requester != null) { Subjects.Remember(Key(requester, role, speakerKey), "", "", 0.0); } } private unsafe static string Key(ServerAuthority.SenderPlayerState requester, DialogueRole role, string speakerKey) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) string[] obj = new string[7] { requester.PeerUid.ToString(), ":", null, null, null, null, null }; ZDOID characterZdoId = requester.CharacterZdoId; obj[2] = ((object)(*(ZDOID*)(&characterZdoId))/*cast due to .constrained prefix*/).ToString(); obj[3] = ":"; obj[4] = role.ToString(); obj[5] = ":"; obj[6] = speakerKey; return string.Concat(obj); } internal static string Answer(string question, ServerAuthority.SenderPlayerState requester, DialogueRole role, string speakerKey, DialogueContext context = null) { if (requester == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || string.IsNullOrWhiteSpace(question) || question.Length > 512) { return MercLocalization.Phrase("dnpc_support_question_needed"); } context = context ?? MercDialogue.CaptureContext(null, null, requester); string zone = ((context.BiomeKnown && context.InteriorKnown) ? (context.Biome + (context.IsInterior ? ":interior" : ":surface")) : ""); string key = Key(requester, role, speakerKey); string text = Subjects.Resolve(key, question, zone, Time.unscaledTime); if (text == null) { return MercLocalization.Phrase("dnpc_support_followup_unknown"); } if (NpcKnowledge.TryAnswer(text, requester, out var answer)) { Subjects.Remember(key, "", zone, Time.unscaledTime); return answer; } if (SupportQueryRules.IsCompanyQuestion(text, MercConfig.TankName.Value, MercConfig.HealerName.Value, MercConfig.ArcherName.Value) && MercenaryKnowledge.TryBuildFallback(text, requester, out answer)) { Subjects.Remember(key, "", zone, Time.unscaledTime); return answer; } if (LlmBrain.TryRecipeReply(text, out answer)) { Subjects.Remember(key, "", zone, Time.unscaledTime); return answer; } string text2 = DialogueRules.ReplyToken(text, role, context, 0); if (LastLines.TryGetValue(key, out var value) && value == text2) { text2 = DialogueRules.ReplyToken(text, role, context, 1); } if (!LastLines.ContainsKey(key) && LastLines.Count >= 128) { LastLines.Clear(); } LastLines[key] = text2; Subjects.Remember(key, DialogueRules.FollowupSubject(text), zone, Time.unscaledTime); return MercLocalization.Phrase(text2); } } internal static class PlayerChronicle { private const string FileName = "PlayerKnowledge.json"; private static PlayerKnowledgeData _data = new PlayerKnowledgeData(); private static readonly Dictionary PeerPlayers = new Dictionary(); private static string _path; private static bool _loaded; private static bool _readOnly; private static float _nextObservationAt; private static float _nextSaveAt; private static long _savedGeneration; private static long _writingGeneration; private static Task _writer; internal static DateTime PresenceObservedUtc { get; private set; } = DateTime.MinValue; internal static bool IsStateReady { get { if (_loaded) { return _path != null; } return false; } } internal static bool IsReadOnly => _readOnly; internal static IReadOnlyList GetSnapshot() { return _data.Snapshot(); } internal static PlayerKnowledgeRecord GetRecord(long playerId) { return _data.Find(playerId); } internal static void SetStatePath(string statePath) { string text = (string.IsNullOrEmpty(statePath) ? null : Path.Combine(statePath, "PlayerKnowledge.json")); if (_loaded && string.Equals(_path, text, StringComparison.OrdinalIgnoreCase)) { return; } EndSession(); _path = text; _data = new PlayerKnowledgeData(); _loaded = false; _readOnly = false; _savedGeneration = 0L; _nextObservationAt = 0f; _nextSaveAt = 0f; PeerPlayers.Clear(); PresenceObservedUtc = DateTime.MinValue; if (_path != null) { PlayerKnowledgeLoadResult playerKnowledgeLoadResult = PlayerKnowledgePersistence.Load(_path, DateTime.UtcNow); _data = playerKnowledgeLoadResult.Data; _readOnly = playerKnowledgeLoadResult.IsReadOnly; _savedGeneration = (playerKnowledgeLoadResult.NeedsSave ? (-1) : _data.Generation); _loaded = true; if (!string.IsNullOrEmpty(playerKnowledgeLoadResult.Diagnostic)) { MercPlugin.LogWarn(playerKnowledgeLoadResult.Diagnostic); } } } internal static void ResetSceneMemory() { EndSession(); _data = new PlayerKnowledgeData(); PeerPlayers.Clear(); PresenceObservedUtc = DateTime.MinValue; _loaded = false; _readOnly = false; _path = null; _nextObservationAt = 0f; _nextSaveAt = 0f; _savedGeneration = 0L; } private static void EndSession() { if (_loaded) { _data.ConfirmAbsent(new HashSet(), DateTime.UtcNow); } Flush(); } internal static void Update() { if (_loaded && _path != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { CompleteWriter(wait: false); float unscaledTime = Time.unscaledTime; if (unscaledTime >= _nextObservationAt) { _nextObservationAt = unscaledTime + 5f; ObserveConnections(); } if (!_readOnly && _data.Generation != _savedGeneration && _writer == null && unscaledTime >= _nextSaveAt) { _nextSaveAt = unscaledTime + 60f; StartSave(); } } } private static void ObserveConnections() { DateTime utcNow = DateTime.UtcNow; HashSet present = new HashSet(); HashSet connectedPeers = new HashSet(); bool flag = true; try { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null) { connectedPeers.Add(peer.m_uid); if (!ObservePeer(peer.m_uid, present, utcNow)) { flag = false; } } } long uID = ZNet.GetUID(); if (!ZNet.instance.IsDedicated() && ((Object)(object)Player.m_localPlayer != (Object)null || PeerPlayers.ContainsKey(uID))) { connectedPeers.Add(uID); if (!ObservePeer(uID, present, utcNow)) { flag = false; } } long[] array = PeerPlayers.Keys.Where((long id) => !connectedPeers.Contains(id)).ToArray(); foreach (long key in array) { PeerPlayers.Remove(key); } _data.ConfirmAbsent(present, utcNow); PresenceObservedUtc = (flag ? utcNow : DateTime.MinValue); } catch (Exception ex) { PresenceObservedUtc = DateTime.MinValue; MercPlugin.LogWarn("Player knowledge observation failed: " + ex.Message); } } private static bool ObservePeer(long peerUid, HashSet present, DateTime now) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if (ServerAuthority.TryResolveSender(peerUid, out var state) && state.PlayerId != 0L) { if (PeerPlayers.Count >= 2048 && !PeerPlayers.ContainsKey(peerUid)) { return false; } PeerPlayers[peerUid] = state.PlayerId; present.Add(state.PlayerId); Vector3 position = state.Position; bool flag = !state.IsDead && Finite(position) && state.PlayerZdo != null && state.PlayerZdo.IsValid(); string biome = ((flag && WorldGenerator.instance != null) ? ((object)WorldGenerator.instance.GetBiome(position)/*cast due to .constrained prefix*/).ToString() : ""); string text = PlayerKnowledgeData.Clean(state.PlayerName, 64); if (text.Length == 0) { text = _data.Find(state.PlayerId)?.Name ?? "Player"; } _data.Observe(state.PlayerId, text, now, flag, position.x, position.y, position.z, biome); return _data.Find(state.PlayerId) != null; } if (PeerPlayers.TryGetValue(peerUid, out var value)) { PlayerKnowledgeRecord playerKnowledgeRecord = _data.Find(value); if (playerKnowledgeRecord != null) { present.Add(value); _data.Observe(value, playerKnowledgeRecord.Name, now); return true; } } return false; } internal static void Record(long playerId, string playerName, string text) { if (_loaded && _path != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { _data.Record(playerId, playerName, text, DateTime.UtcNow); } } internal static void Observe(List online, float dt) { Update(); } private static void StartSave() { try { string path = _path; string immutableSnapshot = _data.Encode(); _writingGeneration = _data.Generation; _writer = Task.Run(delegate { AtomicFile.WriteAllText(path, immutableSnapshot); }); } catch (Exception ex) { MercPlugin.LogWarn("Could not prepare player knowledge snapshot: " + ex.Message); } } private static void CompleteWriter(bool wait) { if (_writer == null || (!wait && !_writer.IsCompleted)) { return; } try { _writer.GetAwaiter().GetResult(); _savedGeneration = _writingGeneration; } catch (Exception ex) { MercPlugin.LogWarn("Could not save player knowledge; previous snapshot retained: " + ex.Message); } finally { _writer = null; } } internal static void Flush() { CompleteWriter(wait: true); if (_loaded && !_readOnly && _path != null && _data.Generation != _savedGeneration) { StartSave(); CompleteWriter(wait: true); } } internal static List RecentLines(int maxEntries, double maxAgeHours) { return RecentLines(maxEntries, maxAgeHours, 0L, MercConfig.ShareOtherPlayersActivity?.Value ?? true); } internal static List RecentLines(int maxEntries, double maxAgeHours, long requesterId, bool includeOthers) { DateTime now = DateTime.UtcNow; double hours = (double.IsNaN(maxAgeHours) ? 0.0 : Math.Max(0.0, Math.Min(2160.0, maxAgeHours))); return (from item in (from item in (from player in _data.Snapshot() where includeOthers || player.PlayerId == requesterId select player).SelectMany((PlayerKnowledgeRecord player) => player.RecentEvents.Select((PlayerKnowledgeEvent item) => new { Player = player, Event = item })) where (now - item.Event.AtUtc).TotalHours <= hours && item.Event.AtUtc <= now.AddMinutes(5.0) orderby item.Event.AtUtc descending select item).Take(Math.Max(0, Math.Min(64, maxEntries))) select "[" + DescribeAge((now - item.Event.AtUtc).TotalSeconds) + "] " + item.Player.Name + ": " + item.Event.Text).ToList(); } internal static List OfflineLastSeen(HashSet onlineIds) { DateTime now = DateTime.UtcNow; if (PresenceObservedUtc == DateTime.MinValue || (now - PresenceObservedUtc).TotalSeconds > 20.0) { return new List(); } return (from player in (from player in _data.Snapshot() where !player.IsOnline && player.LastObservedUtc != DateTime.MinValue && (onlineIds == null || !onlineIds.Contains(player.PlayerId)) orderby player.LastObservedUtc descending select player).Take(64) select player.Name + " " + DescribeAge((now - player.LastObservedUtc).TotalSeconds)).ToList(); } private static string DescribeAge(double seconds) { if (seconds < 60.0) { return "just now"; } if (seconds < 3600.0) { return Math.Max(1, (int)(seconds / 60.0)) + "m ago"; } if (seconds < 86400.0) { return Math.Max(1, (int)(seconds / 3600.0)) + "h ago"; } return Math.Max(1, (int)(seconds / 86400.0)) + "d ago"; } private static bool Finite(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(position.x) && !float.IsInfinity(position.x) && !float.IsNaN(position.y) && !float.IsInfinity(position.y) && !float.IsNaN(position.z)) { return !float.IsInfinity(position.z); } return false; } } internal sealed class PlayerKnowledgeEvent { internal DateTime AtUtc { get; } internal string Text { get; } internal PlayerKnowledgeEvent(DateTime atUtc, string text) { AtUtc = atUtc; Text = text; } } internal sealed class PlayerKnowledgeRecord { internal long PlayerId { get; } internal string Name { get; } internal DateTime LastObservedUtc { get; } internal DateTime LastDisconnectUtc { get; } internal DateTime PositionObservedUtc { get; } internal bool IsOnline { get; } internal bool HasKnownPosition { get; } internal float PositionX { get; } internal float PositionY { get; } internal float PositionZ { get; } internal string Biome { get; } internal IReadOnlyList RecentEvents { get; } internal PlayerKnowledgeRecord(long id, string name, DateTime seen, DateTime disconnected, bool online, DateTime positionAt, float x, float y, float z, string biome, IEnumerable events) { PlayerId = id; Name = name; LastObservedUtc = seen; LastDisconnectUtc = disconnected; IsOnline = online; PositionObservedUtc = (online ? positionAt : DateTime.MinValue); HasKnownPosition = online && positionAt != DateTime.MinValue; PositionX = (HasKnownPosition ? x : 0f); PositionY = (HasKnownPosition ? y : 0f); PositionZ = (HasKnownPosition ? z : 0f); Biome = (HasKnownPosition ? (biome ?? "") : ""); RecentEvents = Array.AsReadOnly(events.OrderByDescending((PlayerKnowledgeEvent item) => item.AtUtc).ToArray()); } } internal sealed class PlayerKnowledgeData { internal const int MaximumPlayers = 2048; internal const int MaximumEventsPerPlayer = 32; internal const int MaximumEvents = 512; internal const int MaximumFileBytes = 2097152; internal const int RetentionDays = 90; private readonly Dictionary _players = new Dictionary(); private IReadOnlyList _snapshot = Array.AsReadOnly(Array.Empty()); private bool _snapshotDirty; private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); internal long Generation { get; private set; } internal bool PrunedOnLoad { get; private set; } internal int Count => _players.Count; internal IReadOnlyList Snapshot() { if (_snapshotDirty) { _snapshot = Array.AsReadOnly(_players.Values.OrderBy((PlayerKnowledgeRecord item) => item.Name, StringComparer.OrdinalIgnoreCase).ThenBy((PlayerKnowledgeRecord item) => item.PlayerId).ToArray()); _snapshotDirty = false; } return _snapshot; } internal PlayerKnowledgeRecord Find(long id) { if (!_players.TryGetValue(id, out var value)) { return null; } return value; } internal void Observe(long id, string name, DateTime now, bool positionKnown = false, float x = 0f, float y = 0f, float z = 0f, string biome = "") { if (id == 0L || !ValidTime(now)) { return; } name = Clean(name, 64); if (name.Length == 0) { return; } PlayerKnowledgeRecord playerKnowledgeRecord = Find(id); if (playerKnowledgeRecord == null && !MakeRoom(now)) { return; } bool flag = positionKnown && Finite(x) && Finite(y) && Finite(z) && Math.Abs(x) <= 20000f && Math.Abs(z) <= 20000f && Math.Abs(y) <= 10000f; List list = ((playerKnowledgeRecord != null) ? playerKnowledgeRecord.RecentEvents.ToList() : new List()); int num; if (playerKnowledgeRecord != null) { num = ((!playerKnowledgeRecord.IsOnline) ? 1 : 0); if (num == 0) { goto IL_00bd; } } else { num = 1; } list.Add(new PlayerKnowledgeEvent(now, "connected to the world")); goto IL_00bd; IL_00bd: Put(new PlayerKnowledgeRecord(id, name, Later(playerKnowledgeRecord?.LastObservedUtc ?? DateTime.MinValue, now), playerKnowledgeRecord?.LastDisconnectUtc ?? DateTime.MinValue, online: true, flag ? now : DateTime.MinValue, x, y, z, Clean(biome, 32), LimitEvents(list, now))); if (num != 0) { TrimGlobalEvents(); } } internal void ConfirmAbsent(ISet present, DateTime now) { if (!ValidTime(now)) { return; } PlayerKnowledgeRecord[] array = _players.Values.ToArray(); foreach (PlayerKnowledgeRecord playerKnowledgeRecord in array) { if (playerKnowledgeRecord.IsOnline && !present.Contains(playerKnowledgeRecord.PlayerId)) { List list = playerKnowledgeRecord.RecentEvents.ToList(); list.Add(new PlayerKnowledgeEvent(now, "left the world")); Put(new PlayerKnowledgeRecord(playerKnowledgeRecord.PlayerId, playerKnowledgeRecord.Name, playerKnowledgeRecord.LastObservedUtc, now, online: false, DateTime.MinValue, 0f, 0f, 0f, "", LimitEvents(list, now))); } } Prune(now); } internal void Record(long id, string name, string text, DateTime now) { if (id == 0L || !ValidTime(now)) { return; } text = Clean(text, 240); name = Clean(name, 64); if (text.Length == 0 || name.Length == 0) { return; } PlayerKnowledgeRecord playerKnowledgeRecord = Find(id); if (playerKnowledgeRecord != null || MakeRoom(now)) { List list = ((playerKnowledgeRecord != null) ? playerKnowledgeRecord.RecentEvents.ToList() : new List()); if (!list.Any((PlayerKnowledgeEvent item) => item.Text == text && Math.Abs((now - item.AtUtc).TotalSeconds) < 10.0)) { list.Add(new PlayerKnowledgeEvent(now, text)); Put(new PlayerKnowledgeRecord(id, name, playerKnowledgeRecord?.LastObservedUtc ?? DateTime.MinValue, playerKnowledgeRecord?.LastDisconnectUtc ?? DateTime.MinValue, playerKnowledgeRecord?.IsOnline ?? false, playerKnowledgeRecord?.PositionObservedUtc ?? DateTime.MinValue, playerKnowledgeRecord?.PositionX ?? 0f, playerKnowledgeRecord?.PositionY ?? 0f, playerKnowledgeRecord?.PositionZ ?? 0f, playerKnowledgeRecord?.Biome ?? "", LimitEvents(list, now))); TrimGlobalEvents(); } } } internal void Prune(DateTime now) { DateTime dateTime = now.AddDays(-90.0); PlayerKnowledgeRecord[] array = _players.Values.ToArray(); foreach (PlayerKnowledgeRecord playerKnowledgeRecord in array) { DateTime dateTime2 = Later(playerKnowledgeRecord.LastObservedUtc, playerKnowledgeRecord.LastDisconnectUtc); if (playerKnowledgeRecord.RecentEvents.Count != 0) { dateTime2 = Later(dateTime2, playerKnowledgeRecord.RecentEvents[0].AtUtc); } if (!playerKnowledgeRecord.IsOnline && dateTime2 < dateTime) { _players.Remove(playerKnowledgeRecord.PlayerId); Changed(); continue; } PlayerKnowledgeEvent[] array2 = LimitEvents(playerKnowledgeRecord.RecentEvents, now); if (array2.Length != playerKnowledgeRecord.RecentEvents.Count) { ReplaceEvents(playerKnowledgeRecord, array2); } } TrimGlobalEvents(); } private bool MakeRoom(DateTime now) { Prune(now); if (_players.Count < 2048) { return true; } PlayerKnowledgeRecord playerKnowledgeRecord = (from item in _players.Values where !item.IsOnline orderby Later(item.LastObservedUtc, item.LastDisconnectUtc), item.PlayerId select item).FirstOrDefault(); if (playerKnowledgeRecord == null) { return false; } _players.Remove(playerKnowledgeRecord.PlayerId); Changed(); return true; } private void TrimGlobalEvents() { var array = (from item in _players.Values.SelectMany((PlayerKnowledgeRecord player) => player.RecentEvents.Select((PlayerKnowledgeEvent item) => new { Player = player, Event = item })) orderby item.Event.AtUtc descending, item.Player.PlayerId select item).ToArray(); if (array.Length <= 512) { return; } HashSet keep = new HashSet(from item in array.Take(512) select item.Event); PlayerKnowledgeRecord[] array2 = _players.Values.ToArray(); foreach (PlayerKnowledgeRecord playerKnowledgeRecord in array2) { if (playerKnowledgeRecord.RecentEvents.Any((PlayerKnowledgeEvent item) => !keep.Contains(item))) { ReplaceEvents(playerKnowledgeRecord, playerKnowledgeRecord.RecentEvents.Where(keep.Contains)); } } } private void ReplaceEvents(PlayerKnowledgeRecord player, IEnumerable events) { Put(new PlayerKnowledgeRecord(player.PlayerId, player.Name, player.LastObservedUtc, player.LastDisconnectUtc, player.IsOnline, player.PositionObservedUtc, player.PositionX, player.PositionY, player.PositionZ, player.Biome, events)); } private static PlayerKnowledgeEvent[] LimitEvents(IEnumerable events, DateTime now) { return (from item in events where item.AtUtc >= now.AddDays(-90.0) && item.AtUtc <= now.AddMinutes(5.0) orderby item.AtUtc descending select item).Take(32).ToArray(); } private void Put(PlayerKnowledgeRecord player) { _players[player.PlayerId] = player; Changed(); } private void Changed() { Generation++; _snapshotDirty = true; } internal string Encode() { StringBuilder stringBuilder = new StringBuilder("{\"version\":1,\"players\":["); bool flag = true; foreach (PlayerKnowledgeRecord item in _players.Values.OrderBy((PlayerKnowledgeRecord item) => item.PlayerId)) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"id\":\"").Append(item.PlayerId.ToString(CultureInfo.InvariantCulture)).Append("\",\"name\":\"") .Append(MiniJson.Escape(item.Name)) .Append("\",\"seen\":") .Append(Seconds(item.LastObservedUtc)) .Append(",\"disconnected\":") .Append(Seconds(item.LastDisconnectUtc)) .Append(",\"events\":["); bool flag2 = true; foreach (PlayerKnowledgeEvent recentEvent in item.RecentEvents) { if (!flag2) { stringBuilder.Append(','); } flag2 = false; stringBuilder.Append("{\"at\":").Append(Seconds(recentEvent.AtUtc)).Append(",\"text\":\"") .Append(MiniJson.Escape(recentEvent.Text)) .Append("\"}"); } stringBuilder.Append("]}"); } stringBuilder.Append("]}"); string text = stringBuilder.ToString(); if (Encoding.UTF8.GetByteCount(text) > 2097152) { throw new InvalidDataException("Player knowledge snapshot exceeds its byte bound."); } return text; } internal static PlayerKnowledgeData Decode(string json, DateTime now) { if (json == null || Encoding.UTF8.GetByteCount(json) > 2097152) { throw new InvalidDataException("Player knowledge input exceeds its byte bound."); } ValidateDepth(json); if (!MiniJson.TryParseComplete(json, out var value) || !(value is Dictionary dictionary)) { throw new InvalidDataException("Player knowledge is not a complete JSON object."); } long num = Number(dictionary, "version"); if (num > 1) { throw new NotSupportedException("Player knowledge was written by a newer schema."); } if (num != 1 || !dictionary.TryGetValue("players", out var value2) || !(value2 is List { Count: <=2048 } list)) { throw new InvalidDataException("Invalid player knowledge schema or player count."); } PlayerKnowledgeData playerKnowledgeData = new PlayerKnowledgeData(); int num2 = 0; foreach (object item in list) { if (!(item is Dictionary dictionary2) || !dictionary2.TryGetValue("id", out var value3) || !(value3 is string s) || !long.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result == 0L || playerKnowledgeData._players.ContainsKey(result)) { throw new InvalidDataException("Invalid or duplicate stable player ID."); } string name = Text(dictionary2, "name", 64); DateTime seen = TimeValue(dictionary2, "seen", now, allowEmpty: true); DateTime disconnected = TimeValue(dictionary2, "disconnected", now, allowEmpty: true); if (!dictionary2.TryGetValue("events", out var value4) || !(value4 is List { Count: <=32 } list2) || (num2 += list2.Count) > 512) { throw new InvalidDataException("Player knowledge event count exceeds its bound."); } List list3 = new List(); foreach (object item2 in list2) { if (!(item2 is Dictionary data)) { throw new InvalidDataException("Invalid player event."); } list3.Add(new PlayerKnowledgeEvent(TimeValue(data, "at", now, allowEmpty: false), Text(data, "text", 240))); } playerKnowledgeData.Put(new PlayerKnowledgeRecord(result, name, seen, disconnected, online: false, DateTime.MinValue, 0f, 0f, 0f, "", list3)); } long generation = playerKnowledgeData.Generation; playerKnowledgeData.Prune(now); playerKnowledgeData.PrunedOnLoad = playerKnowledgeData.Generation != generation; return playerKnowledgeData; } internal static PlayerKnowledgeData ImportLegacy(string text, DateTime now) { if (text == null || Encoding.UTF8.GetByteCount(text) > 2097152) { throw new InvalidDataException("Legacy player chronicle exceeds its byte bound."); } PlayerKnowledgeData playerKnowledgeData = new PlayerKnowledgeData(); using StringReader stringReader = new StringReader(text); int num = 0; string text2; while ((text2 = stringReader.ReadLine()) != null) { if (++num > 4096) { throw new InvalidDataException("Legacy chronicle has too many lines."); } string[] array = text2.Split(new char[1] { '|' }, 4); if (array.Length != 4 || !long.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !long.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 == 0L) { continue; } DateTime dateTime; try { dateTime = FromSeconds(result); } catch { continue; } if (!ValidTime(dateTime) || dateTime > now.AddMinutes(5.0) || dateTime < now.AddDays(-90.0)) { continue; } string text3 = Clean(array[2], 64); string text4 = Clean(array[3], 240); if (text3.Length != 0 && text4.Length != 0) { PlayerKnowledgeRecord playerKnowledgeRecord = playerKnowledgeData.Find(result2); if (playerKnowledgeRecord != null || playerKnowledgeData.MakeRoom(now)) { List list = ((playerKnowledgeRecord != null) ? playerKnowledgeRecord.RecentEvents.ToList() : new List()); list.Add(new PlayerKnowledgeEvent(dateTime, text4)); bool flag = text4.IndexOf("left the world", StringComparison.OrdinalIgnoreCase) >= 0; playerKnowledgeData.Put(new PlayerKnowledgeRecord(result2, text3, Later(playerKnowledgeRecord?.LastObservedUtc ?? DateTime.MinValue, dateTime), flag ? Later(playerKnowledgeRecord?.LastDisconnectUtc ?? DateTime.MinValue, dateTime) : (playerKnowledgeRecord?.LastDisconnectUtc ?? DateTime.MinValue), online: false, DateTime.MinValue, 0f, 0f, 0f, "", LimitEvents(list, now))); playerKnowledgeData.TrimGlobalEvents(); } } } return playerKnowledgeData; } private static long Number(Dictionary data, string key) { if (!data.TryGetValue(key, out var value) || !(value is double num) || double.IsNaN(num) || double.IsInfinity(num) || num < 0.0 || num > 253402300799.0 || num != Math.Truncate(num)) { throw new InvalidDataException("Invalid numeric player knowledge field: " + key); } return (long)num; } private static string Text(Dictionary data, string key, int max) { if (!data.TryGetValue(key, out var value) || !(value is string { Length: not 0 } text) || text.Length > max || Clean(text, max) != text) { throw new InvalidDataException("Invalid player knowledge text field: " + key); } return text; } private static DateTime TimeValue(Dictionary data, string key, DateTime now, bool allowEmpty) { long num = Number(data, key); if (num == 0 && allowEmpty) { return DateTime.MinValue; } DateTime dateTime = FromSeconds(num); if (!ValidTime(dateTime) || dateTime > now.AddMinutes(5.0)) { throw new InvalidDataException("Invalid future player timestamp."); } return dateTime; } private static void ValidateDepth(string text) { int num = 0; bool flag = false; bool flag2 = false; foreach (char c in text) { if (flag) { if (flag2) { flag2 = false; continue; } switch (c) { case '\\': flag2 = true; break; case '"': flag = false; break; } continue; } switch (c) { case '"': flag = true; break; case '[': case '{': if (++num > 6) { throw new InvalidDataException("Player knowledge nesting is too deep."); } break; case ']': case '}': if (--num < 0) { throw new InvalidDataException("Unbalanced player knowledge JSON."); } break; } } if (flag || num != 0) { throw new InvalidDataException("Incomplete player knowledge JSON."); } } internal static string Clean(string value, int max) { StringBuilder stringBuilder = new StringBuilder(); bool flag = false; value = value ?? ""; for (int i = 0; i < value.Length; i++) { char c = value[i]; switch (c) { case '<': flag = true; continue; case '>': flag = false; continue; default: if (flag || char.IsControl(c)) { continue; } if (stringBuilder.Length >= max) { break; } if (char.IsHighSurrogate(c)) { if (i + 1 < value.Length && char.IsLowSurrogate(value[i + 1]) && stringBuilder.Length + 2 <= max) { stringBuilder.Append(c).Append(value[++i]); } } else if (!char.IsLowSurrogate(c)) { stringBuilder.Append(c); } continue; } break; } return stringBuilder.ToString().Trim(); } internal static long Seconds(DateTime at) { if (!(at == DateTime.MinValue)) { return (long)(at - Epoch).TotalSeconds; } return 0L; } private static DateTime FromSeconds(long seconds) { DateTime epoch = Epoch; return epoch.AddSeconds(seconds); } private static bool ValidTime(DateTime time) { if (time.Kind == DateTimeKind.Utc) { return time >= Epoch; } return false; } private static DateTime Later(DateTime a, DateTime b) { if (!(a > b)) { return b; } return a; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal sealed class PlayerKnowledgeLoadResult { internal PlayerKnowledgeData Data = new PlayerKnowledgeData(); internal bool IsReadOnly; internal bool NeedsSave; internal string Diagnostic = ""; } internal static class PlayerKnowledgePersistence { internal static PlayerKnowledgeLoadResult Load(string path, DateTime now) { PlayerKnowledgeLoadResult playerKnowledgeLoadResult = new PlayerKnowledgeLoadResult(); try { if (!File.Exists(path)) { if (File.Exists(path + ".bak")) { string text = ReadBounded(path + ".bak"); playerKnowledgeLoadResult.Data = PlayerKnowledgeData.Decode(text, now); AtomicFile.WriteAllText(path, text); playerKnowledgeLoadResult.NeedsSave = playerKnowledgeLoadResult.Data.PrunedOnLoad; playerKnowledgeLoadResult.Diagnostic = "Restored missing player knowledge from the validated backup."; return playerKnowledgeLoadResult; } string path2 = Path.GetDirectoryName(path) ?? "."; string path3 = Path.Combine(path2, "player_chronicle.md"); if (!File.Exists(path3)) { path3 = Path.Combine(path2, "player_chronicle.txt"); } if (File.Exists(path3)) { playerKnowledgeLoadResult.Data = PlayerKnowledgeData.ImportLegacy(ReadBounded(path3), now); playerKnowledgeLoadResult.Diagnostic = "Imported the previous player chronicle; the original file was retained."; } playerKnowledgeLoadResult.NeedsSave = true; return playerKnowledgeLoadResult; } string text2 = ReadBounded(path); try { playerKnowledgeLoadResult.Data = PlayerKnowledgeData.Decode(text2, now); } catch (InvalidDataException) { string path4 = path + ".bak"; if (!File.Exists(path4)) { throw; } string text3 = ReadBounded(path4); playerKnowledgeLoadResult.Data = PlayerKnowledgeData.Decode(text3, now); AtomicFile.WriteAllText(path + ".corrupt", text2); RestorePrimary(path, text3); playerKnowledgeLoadResult.Diagnostic = "Recovered player knowledge from the validated backup."; } playerKnowledgeLoadResult.NeedsSave = playerKnowledgeLoadResult.Data.PrunedOnLoad; } catch (Exception ex2) { playerKnowledgeLoadResult.IsReadOnly = true; playerKnowledgeLoadResult.Diagnostic = "Player knowledge storage is read-only: " + ex2.Message; } return playerKnowledgeLoadResult; } private static string ReadBounded(string path) { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); if (fileStream.Length > 2097152) { throw new InvalidDataException("Player knowledge file exceeds its byte bound."); } using StreamReader streamReader = new StreamReader(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true); string text = streamReader.ReadToEnd(); if (Encoding.UTF8.GetByteCount(text) > 2097152) { throw new InvalidDataException("Decoded player knowledge exceeds its byte bound."); } return text; } private static void RestorePrimary(string path, string snapshot) { string text = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { File.WriteAllText(text, snapshot, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); File.Replace(text, path, null, ignoreMetadataErrors: true); } finally { try { if (File.Exists(text)) { File.Delete(text); } } catch { } } } } internal static class PlayerProgression { private static readonly Dictionary Stages = new Dictionary(); private static string _path = ""; private static bool _loaded; private static bool _loadFailureLogged; private static bool _loadedFromBackup; private static readonly string[] BossPrefabs = new string[7] { "Eikthyr", "GDKing", "Bonemass", "Dragon", "GoblinKing", "SeekerQueen", "Fader" }; internal static bool IsStateReady { get { if (_loaded) { return !string.IsNullOrEmpty(_path); } return false; } } internal static bool WasRecoveredFromBackup => _loadedFromBackup; internal static void SetStatePath(string statePath) { string text = (string.IsNullOrEmpty(statePath) ? "" : Path.Combine(statePath, "PlayerProgression.json")); bool flag = !string.Equals(_path, text, StringComparison.OrdinalIgnoreCase); if (!_loaded || flag) { Stages.Clear(); _path = text; _loaded = false; if (flag) { _loadFailureLogged = false; _loadedFromBackup = false; } Load(); } } internal static void ResetSceneMemory() { Stages.Clear(); _path = ""; _loaded = false; _loadFailureLogged = false; _loadedFromBackup = false; } internal static int GetStage(long playerId) { if (playerId == 0L) { return -1; } Load(); if (!Stages.TryGetValue(playerId, out var value)) { return -1; } return value; } internal static int GetKnownStage(long playerId) { if (!IsStateReady || playerId == 0L || !Stages.TryGetValue(playerId, out var value)) { return -1; } return value; } internal static int EnsureSeeded(long playerId, int seedStage, string source) { if (playerId == 0L) { return -1; } Load(); bool personalModeEnabled = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (!IsStateReady || !MercProgressionRules.CanMutatePersonalProgression(personalModeEnabled, WorldDataService.IsWorldStateReady)) { return -1; } if (Stages.TryGetValue(playerId, out var value)) { if (seedStage > value) { return SetStage(playerId, seedStage, "seed upgrade via " + source); } BannerAuthority.PublishCompanyStage(playerId, value); return value; } int stage = Mathf.Clamp(seedStage, 0, 7); return SetStage(playerId, stage, "seeded from " + source); } internal static int SetStage(long playerId, int stage, string reason) { if (playerId == 0L) { return -1; } Load(); bool personalModeEnabled = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (!IsStateReady || !MercProgressionRules.CanMutatePersonalProgression(personalModeEnabled, WorldDataService.IsWorldStateReady)) { return -1; } int num = Mathf.Clamp(stage, 0, 7); int value; bool flag = Stages.TryGetValue(playerId, out value); if (flag && value == num) { BannerAuthority.PublishCompanyStage(playerId, num); return num; } Stages[playerId] = num; if (!Save()) { if (flag) { Stages[playerId] = value; } else { Stages.Remove(playerId); } if (!flag) { return -1; } return value; } BannerAuthority.PublishCompanyStage(playerId, num); MercPlugin.Log($"Player {playerId} company progression set to stage {num} " + "(" + StageDirector.GetStageName(num) + ") from " + reason + " " + (flag ? $"(was {value})." : "(was unseeded).")); if (flag ? (num > value) : (num > 0)) { UpgradeCompany(playerId, num); } return num; } internal static void RecordBossDefeat(Character boss) { //IL_0050: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } string prefabName = Utils.GetPrefabName(((Component)boss).gameObject); int num = Array.IndexOf(BossPrefabs, prefabName); if (num < 0) { return; } int num2 = Mathf.Clamp(num + 1, 1, 7); Vector3 position = ((Component)boss).transform.position; foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer == (Object)null || ((Character)allPlayer).IsDead() || Vector3.Distance(((Component)allPlayer).transform.position, position) > 100f) { continue; } long playerID = allPlayer.GetPlayerID(); if (playerID != 0L) { PlayerChronicle.Record(playerID, allPlayer.GetPlayerName(), "helped defeat " + prefabName); int stage = GetStage(playerID); if (stage < 0) { SetStage(playerID, num2, "first observed boss defeat (" + prefabName + ")"); } else if (stage < num2) { SetStage(playerID, num2, "defeated " + prefabName); } } } } catch (Exception ex) { MercPlugin.LogWarn("Boss-defeat progression tracking failed: " + ex.Message); } } internal static int LocalSeedFromGear() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return -1; } try { int num = 0; foreach (ItemData equippedItem in ((Humanoid)localPlayer).GetInventory().GetEquippedItems()) { if (equippedItem != null && !((Object)(object)equippedItem.m_dropPrefab == (Object)null)) { int num2 = GearTable.StageOfItem(Utils.GetPrefabName(equippedItem.m_dropPrefab)); if (num2 > num) { num = num2; } } } return num; } catch { return -1; } } private static void UpgradeCompany(long playerId, int stage) { if (!MercProgressionRules.CanMutatePersonalProgression(MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value, WorldDataService.IsWorldStateReady)) { return; } foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Character)instance).IsDead() && instance.EffectiveEmployerId() == playerId) { if (instance.IsLoadoutOwner()) { instance.ApplyLoadout(stage); instance.ApplyStageVisuals(stage); } instance.Say(MercLocalization.Phrase("dnpc_speech_gear_matches", StageDirector.GetStageArgument(stage))); } } } private static void Load() { if (_loaded || string.IsNullOrEmpty(_path)) { return; } try { Dictionary dictionary = new Dictionary(); string path = _path + ".bak"; bool loadedFromBackup = false; if (File.Exists(_path)) { try { dictionary = ReadStages(_path); } catch (Exception ex) { if (!File.Exists(path)) { throw; } try { dictionary = ReadStages(path); loadedFromBackup = true; MercPlugin.LogWarn("Recovered per-player company progression from the atomic backup after the primary file could not be read: " + ex.Message); } catch (Exception ex2) { throw new InvalidDataException("Both the primary progression file and its backup are unreadable. Primary: " + ex.Message + "; backup: " + ex2.Message, ex2); } } } else if (File.Exists(path)) { dictionary = ReadStages(path); loadedFromBackup = true; MercPlugin.LogWarn("Recovered per-player company progression from the atomic backup because the primary file was missing."); } Stages.Clear(); foreach (KeyValuePair item in dictionary) { Stages[item.Key] = item.Value; } _loaded = true; _loadFailureLogged = false; _loadedFromBackup = loadedFromBackup; MercPlugin.Log($"Loaded per-player company progression for {Stages.Count} player(s)."); } catch (Exception ex3) { _loaded = false; if (!_loadFailureLogged) { _loadFailureLogged = true; MercPlugin.LogWarn("Could not load player progression; personal company changes are deferred to protect the existing file: " + ex3.Message); } } } private static Dictionary ReadStages(string path) { if (!MiniJson.TryParseComplete(File.ReadAllText(path), out var value) || !(value is Dictionary dictionary)) { throw new InvalidDataException("The progression document is not a JSON object."); } Dictionary dictionary2 = new Dictionary(); foreach (KeyValuePair item in dictionary) { if (!long.TryParse(item.Key, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result == 0L) { throw new InvalidDataException("The progression document contains an invalid player ID."); } if (!MercProgressionRules.TryReadPersistedStage(item.Value, out var stage)) { throw new InvalidDataException($"The progression stage for player {result} is outside the supported integer range."); } dictionary2[result] = stage; } return dictionary2; } private static string SerializeStages(Dictionary stages) { StringBuilder stringBuilder = new StringBuilder("{"); bool flag = true; foreach (KeyValuePair stage in stages) { if (!flag) { stringBuilder.Append(","); } flag = false; stringBuilder.Append('"').Append(stage.Key.ToString(CultureInfo.InvariantCulture)).Append("\":") .Append(stage.Value.ToString(CultureInfo.InvariantCulture)); } return stringBuilder.Append("}").ToString(); } private static bool Save() { try { if (string.IsNullOrEmpty(_path)) { return false; } AtomicFile.WriteAllText(_path, SerializeStages(Stages)); return true; } catch (Exception ex) { MercPlugin.LogWarn("Could not save player progression: " + ex.Message); return false; } } } public static class MercSetup { private static ZNetScene _observedScene; private static ZNetScene _sceneReadyFor; public static void EnsureRegistered(string via) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } try { if ((Object)(object)_observedScene != (Object)(object)instance) { ResetSession("scene change"); _observedScene = instance; _sceneReadyFor = null; StageDirector.Reset(); ResurrectionManager.ResetForScene(); } if (!MercPrefabs.IsRegistered) { MercPlugin.Log("Scene hook fired via: " + via); } MercPrefabs.Register(instance, via); SnikkPrefabs.Register(instance); FarmerPlacement.Register(instance); MercBanner.RegisterBannerPiece(); ServerAuthority.Register(); BannerAuthority.Register(); WorldDataService.Register(); SnikkVisitService.Register(); FarmerService.Register(); if ((Object)(object)_sceneReadyFor != (Object)(object)instance && MercPrefabs.IsRegistered && MercBanner.IsRegistered) { _sceneReadyFor = instance; MercPlugin.OnSceneReady(); } } catch (Exception ex) { MercPlugin.LogWarn("EnsureRegistered(" + via + ") failed: " + ex.Message); } } public static void ResetSession(string reason) { MercPlugin.CancelCompanyPress(); Mercenary.ResetAllTransientSessionState(reason); SnikkVisitService.ResetSceneMemory(); FarmerService.ResetSceneMemory(); FarmerInspection.ResetSceneMemory(); FarmerPlacement.ResetScene(); LlmBrain.ResetSceneMemory(); GuideTasks.ResetSceneMemory(); PlayerChronicle.ResetSceneMemory(); PlayerProgression.ResetSceneMemory(); ServerAuthority.Reset(); BannerAuthority.ResetSceneMemory(); WorldDataService.ResetSceneMemory(); MercCompanyHud.Reset(); } } [BepInPlugin("jg224.DynamicNPCs", "DynamicNPCs", "0.5.2")] [BepInDependency("com.jg224.modcore", "0.5.0")] [BepInDependency("com.jotunn.jotunn", "2.30.0")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class MercPlugin : BaseUnityPlugin { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Func <>9__16_0; public static Action <>9__16_1; public static Func, string> <>9__17_0; public static Func, int> <>9__17_1; public static Func <>9__30_0; public static ConsoleEvent <>9__34_0; public static Func, int> <>9__34_5; public static Func, int> <>9__34_6; public static ConsoleEvent <>9__34_1; public static ConsoleEvent <>9__34_2; public static ConsoleEvent <>9__34_3; public static ConsoleEvent <>9__34_4; internal string b__16_0(string name) { return name; } internal void b__16_1() { MercSetup.EnsureRegistered("Jotunn.OnPrefabsRegistered"); } internal string b__17_0(IGrouping group) { return group.Key; } internal int b__17_1(IGrouping group) { return group.Count(); } internal bool b__30_0(KeyCode modifier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Input.GetKey(modifier); } internal void b__34_0(ConsoleEventArgs args) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { args.Context.AddString("dnpc_spawn: only the server/host can use this."); return; } SpawnTrioAt(((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); args.Context.AddString("Mercenaries spawned."); } catch (Exception ex) { args.Context.AddString("dnpc_spawn failed: " + ex.Message); } } internal void b__34_1(ConsoleEventArgs args) { try { if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null) { args.Context.AddString("dnpc_census: load into a world first."); return; } SortedDictionary> sortedDictionary = new SortedDictionary>(); foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } Character component = prefab.GetComponent(); if (!((Object)(object)component == (Object)null)) { string key = ((object)Unsafe.As(ref component.m_faction)/*cast due to .constrained prefix*/).ToString(); if (!sortedDictionary.TryGetValue(key, out var value)) { value = (sortedDictionary[key] = new List()); } value.Add(((Object)prefab).name); } } List list2 = new List(); foreach (KeyValuePair> item in sortedDictionary) { item.Value.Sort(StringComparer.OrdinalIgnoreCase); list2.Add("== " + item.Key + " (" + item.Value.Count + ")"); list2.AddRange(item.Value); list2.Add(""); } File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "creature-census.txt"), list2); SortedDictionary> sortedDictionary2 = new SortedDictionary>(); foreach (GameObject item2 in ObjectDB.instance.m_items) { if ((Object)(object)item2 == (Object)null) { continue; } ItemDrop component2 = item2.GetComponent(); if (component2?.m_itemData?.m_shared != null) { string key2 = ((object)Unsafe.As(ref component2.m_itemData.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(); if (!sortedDictionary2.TryGetValue(key2, out var value2)) { value2 = (sortedDictionary2[key2] = new List()); } value2.Add(((Object)item2).name); } } List list4 = new List(); foreach (KeyValuePair> item3 in sortedDictionary2) { item3.Value.Sort(StringComparer.OrdinalIgnoreCase); list4.Add("== " + item3.Key + " (" + item3.Value.Count + ")"); list4.AddRange(item3.Value); list4.Add(""); } File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "item-census.txt"), list4); args.Context.AddString($"Census written next to BepInEx: {sortedDictionary.Values.Sum((List b) => b.Count)} creatures, {ObjectDB.instance.m_items.Count} items."); Log($"Census dumped: {sortedDictionary.Values.Sum((List b) => b.Count)} creatures, {ObjectDB.instance.m_items.Count} items."); } catch (Exception ex) { args.Context.AddString("dnpc_census failed: " + ex.Message); } } internal int b__34_5(List b) { return b.Count; } internal int b__34_6(List b) { return b.Count; } internal void b__34_2(ConsoleEventArgs args) { try { if ((Object)(object)Player.m_localPlayer == (Object)null) { args.Context.AddString("dnpc_clear: join the world as an admin player first."); return; } float num = Mathf.Clamp(args.TryParameterFloat(1, 30f), 1f, 200f); ServerAuthority.RequestNpcCleanup(num); args.Context.AddString($"dnpc_clear: submitted {num:F0}m cleanup request to the server."); } catch (Exception ex) { args.Context.AddString("dnpc_clear failed: " + ex.Message); } } internal void b__34_3(ConsoleEventArgs args) { try { if ((Object)(object)Player.m_localPlayer == (Object)null) { args.Context.AddString("dnpc_purge: join the world as an admin player first."); return; } ServerAuthority.RequestMercPurge(); args.Context.AddString("dnpc_purge: submitted world-wide mercenary purge request to the server."); } catch (Exception ex) { args.Context.AddString("dnpc_purge failed: " + ex.Message); } } internal void b__34_4(ConsoleEventArgs args) { try { if ((Object)(object)Player.m_localPlayer == (Object)null) { args.Context.AddString("dnpc: join the world as an admin player first."); return; } string[] array = args.Args ?? Array.Empty(); if (array.Length < 2 || !string.Equals(array[1], "snikk", StringComparison.OrdinalIgnoreCase)) { args.Context.AddString("Usage: dnpc snikk status|force-base|force-find|despawn|bones|test|reset-schedule|set-last-sighting |gossip|dump-events |clear-recent-gossip"); return; } SnikkVisitService.RequestAdminCommand((array.Length > 2) ? string.Join(" ", array, 2, array.Length - 2) : "status"); args.Context.AddString("Snikk command submitted to the server."); } catch (Exception ex) { args.Context.AddString("dnpc failed: " + ex.Message); } } } public const string Guid = "jg224.DynamicNPCs"; public const string Name = "DynamicNPCs"; public const string Version = "0.5.2"; public const string ModCoreGuid = "com.jg224.modcore"; public const int ProtocolVersion = 1; public static readonly ModuleId ModuleId = new ModuleId("dynamicnpcs"); public const string LegacyConfigFileName = "com.family.mercenarycompanions.cfg"; internal static ManualLogSource LogSource; internal static ICoreServices Core; private Harmony _harmony; private readonly List _registrations = new List(); private bool _shutDown; private float _slowTimer; private static readonly MercCommandPress CompanyPress = new MercCommandPress(); private static bool _commandController; private static KeyCode _commandKey; private static bool _commandRequireRelease; internal static void Log(string message) { ManualLogSource logSource = LogSource; if (logSource != null) { logSource.LogInfo((object)message); } } internal static void LogWarn(string message) { ManualLogSource logSource = LogSource; if (logSource != null) { logSource.LogWarning((object)message); } } internal static void LogDebug(string message) { ManualLogSource logSource = LogSource; if (logSource != null) { logSource.LogDebug((object)message); } } private void Awake() { //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: 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_00bb: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Expected O, but got Unknown //IL_02cd: Unknown result type (might be due to invalid IL or missing references) LogSource = ((BaseUnityPlugin)this).Logger; try { string location = ((object)this).GetType().Assembly.Location; long num = (File.Exists(location) ? new FileInfo(location).Length : (-1)); Log(string.Format("DynamicNPCs v{0} loaded from: {1} ({2} bytes)", "0.5.2", location, num)); } catch { } try { if (!ModCoreApi.IsAvailable) { throw new InvalidOperationException("ModCore did not initialize before DynamicNPCs."); } Core = ModCoreApi.Services; SemanticVersion val = default(SemanticVersion); if (!SemanticVersion.TryParse("0.5.2", ref val)) { throw new InvalidOperationException("Invalid DynamicNPCs plugin version."); } _registrations.Add(Core.Modules.Register(new ModuleDescriptor(ModuleId, "jg224.DynamicNPCs", "DynamicNPCs", val, 1, (ModuleSide)3, (ModuleRequirement)4, 0uL, 1, 1))); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)1, "jg224.dnpc.", 1, Array.Empty())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)6, "jg224.dnpc.", 1, Array.Empty())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)4, "DynamicNPCs", 1, Array.Empty())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)8, "dynamicnpcs.", 1, Array.Empty())); MigrateLegacyConfigFile(); MercConfig.Bind(((BaseUnityPlugin)this).Config); MercLocalization.Register(); ((BaseUnityPlugin)this).Config.Save(); if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath)) { throw new IOException("DynamicNPCs config was not created at " + ((BaseUnityPlugin)this).Config.ConfigFilePath); } Log("DynamicNPCs configuration loaded from " + ((BaseUnityPlugin)this).Config.ConfigFilePath + ". Solo/local hosts use these values; multiplayer clients receive locked server values."); _harmony = new Harmony("jg224.DynamicNPCs"); _harmony.PatchAll(typeof(MercPatches).Assembly); List list = _harmony.GetPatchedMethods().ToList(); ValidateCriticalPatches(list); Log($"Harmony installed and verified {list.Count} game-method patches: " + string.Join(", ", (from name in list.Select(PatchTargetKey) orderby name select name).ToArray())); MercBanner.RegisterItems(); MenderHealingStatus.Register(); PrefabManager.OnPrefabsRegistered += delegate { MercSetup.EnsureRegistered("Jotunn.OnPrefabsRegistered"); }; RegisterCommands(); StageDirector.StageChanged += OnStageChanged; Core.Modules.SetState(ModuleId, (ModuleRuntimeState)2, "Required server-authoritative NPC runtime and protocol ready."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"DynamicNPCs 0.5.2 loaded. Local hosts control their cfg; connected clients use server-authoritative synchronized settings. NPC answers use server-owned records and authored knowledge; external providers are removed."); } catch (Exception ex) { LogSource.LogFatal((object)string.Format("{0} failed to initialize: {1}", "DynamicNPCs", ex)); if (Core != null) { Core.Modules.SetState(ModuleId, (ModuleRuntimeState)5, ex.Message); } Shutdown(); throw; } } private static void ValidateCriticalPatches(List patchedMethods) { Dictionary obj = new Dictionary(StringComparer.Ordinal) { { "ZNetScene.Awake", 1 }, { "Game.Logout", 1 }, { "Door.Interact", 1 }, { "Door.UseItem", 1 }, { "ZoneSystem.SetGlobalKey", 1 }, { "Humanoid.SetupVisEquipment", 1 }, { "Chat.SendText", 1 }, { "Talker.Say", 1 }, { "Piece.Awake", 1 }, { "Player.GetAvailableRecipes", 1 }, { "Player.IsPieceAvailable", 1 }, { "Player.TryPlacePiece", 1 }, { "Player.PlacePiece", 1 }, { "Player.RemovePiece", 1 }, { "WearNTear.Damage", 1 }, { "WearNTear.RPC_Damage", 1 }, { "WearNTear.ApplyDamage", 1 }, { "WearNTear.Remove", 1 }, { "WearNTear.Destroy", 1 }, { "Character.CheckDeath", 1 }, { "TreeLog.Destroy", 1 }, { "MineRock5.DamageArea", 1 }, { "ItemDrop.OnCreateNew", 1 }, { "Player.SetControls", 1 }, { "Player.HandleRadialInput", 1 }, { "CharacterAnimEvent.DodgeMortal", 1 }, { "Character.Damage", 1 }, { "Character.RPC_Damage", 1 }, { "Character.ApplyDamage", 1 }, { "BaseAI.IsEnemy", 2 }, { "Character.IsCrouching", 1 }, { "BaseAI.CanHearTarget", 1 }, { "BaseAI.CanSeeTarget", 1 }, { "MonsterAI.UpdateTarget", 1 }, { "Teleport.Interact", 1 }, { "Player.TeleportTo", 1 }, { "ZRoutedRpc.InvokeRoutedRPC", 3 }, { "ZRoutedRpc.RPC_RoutedRPC", 1 }, { "ZRoutedRpc.HandleRoutedRPC", 1 }, { "ZRoutedRpc.RouteRPC", 1 }, { "Character.OnDeath", 1 }, { "ZDOMan.DestroyZDO", 1 } }; Dictionary dictionary = patchedMethods.GroupBy(PatchTargetKey, StringComparer.Ordinal).ToDictionary, string, int>((IGrouping group) => group.Key, (IGrouping group) => group.Count(), StringComparer.Ordinal); List list = new List(); foreach (KeyValuePair item in obj) { dictionary.TryGetValue(item.Key, out var value); if (value < item.Value) { list.Add((item.Value == 1) ? item.Key : $"{item.Key} ({value}/{item.Value} overloads)"); } } if (list.Count > 0) { throw new InvalidOperationException("Critical Harmony patches missing: " + string.Join(", ", list.ToArray())); } } private static string PatchTargetKey(MethodBase method) { return (method?.DeclaringType?.FullName ?? "") + "." + (method?.Name ?? ""); } private void MigrateLegacyConfigFile() { string text = Path.Combine(Paths.ConfigPath, "com.family.mercenarycompanions.cfg"); try { if (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) || !File.Exists(text) || string.Equals(Path.GetFullPath(text), Path.GetFullPath(((BaseUnityPlugin)this).Config.ConfigFilePath), StringComparison.OrdinalIgnoreCase)) { return; } bool flag = false; string[] array = File.ReadAllLines(text); for (int i = 0; i < array.Length; i++) { if (array[i].Contains("=")) { flag = true; break; } } if (!flag) { Log("Legacy config com.family.mercenarycompanions.cfg is empty; ignoring it."); return; } File.Copy(text, ((BaseUnityPlugin)this).Config.ConfigFilePath, overwrite: false); Log("Migrated settings from com.family.mercenarycompanions.cfg to " + Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath) + "; the old file is no longer used."); } catch (Exception ex) { LogWarn("Could not migrate legacy config com.family.mercenarycompanions.cfg: " + ex.Message); } } private void OnDestroy() { Shutdown(); } private void OnApplicationFocus(bool focused) { if (!focused) { CancelCompanyPress(); } } private void Shutdown() { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (_shutDown) { return; } _shutDown = true; StageDirector.StageChanged -= OnStageChanged; CompanyPress.Reset(); MercGuideMenu.Close(); MercCompanyHud.Reset(); SnikkVisitService.Shutdown(); SnikkPrefabs.Shutdown(); FarmerService.ResetSceneMemory(); FarmerPlacement.Shutdown(); FarmerInspection.ResetSceneMemory(); WorldDataService.Shutdown(); PlayerChronicle.Flush(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; for (int num = _registrations.Count - 1; num >= 0; num--) { try { _registrations[num].Dispose(); } catch (Exception ex) { LogWarn("Registration cleanup failed: " + ex.Message); } } _registrations.Clear(); if (Core != null) { Core.Metrics.RemoveOwner(ModuleId); } Core = null; } private void Update() { MainThread.Drain(); ResurrectionManager.Update(); WorldDataService.Update(); BannerAuthority.Update(); SnikkVisitService.Update(); FarmerService.Update(); MercCompanyHud.Tick(); _slowTimer += Time.deltaTime; if (_slowTimer > 2f) { _slowTimer = 0f; MercSetup.EnsureRegistered("Update fallback"); StageDirector.Poll(); MercDialogue.Poll(); FarmerService.Poll(); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { LlmBrain.ObservePlayerState(); } } MercGuideMenu.Tick(); HandleGuideMenuHotkey(); } internal static void CancelCompanyPress() { CompanyPress.Reset(); _commandRequireRelease = true; } internal static bool CanReadCompanyInput() { Player localPlayer = Player.m_localPlayer; if (Application.isFocused && (Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && (Object)(object)ZNetScene.instance != (Object)null && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible() && !InventoryGui.IsVisible() && !Menu.IsVisible() && !Minimap.IsOpen()) { return !Hud.InRadial(); } return false; } private static void HandleGuideMenuHotkey() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut val = ((MercConfig.GuideMenuHotkey != null) ? MercConfig.GuideMenuHotkey.Value : KeyboardShortcut.Empty); bool flag = (int)((KeyboardShortcut)(ref val)).MainKey != 0 && Input.GetKeyDown(((KeyboardShortcut)(ref val)).MainKey) && ((KeyboardShortcut)(ref val)).Modifiers.All((KeyCode modifier) => Input.GetKey(modifier)); bool buttonDown = ZInput.GetButtonDown("JoyRadial"); if (MercGuideMenu.IsOpen) { CompanyPress.Reset(); if (flag || buttonDown) { MercGuideMenu.Close(); } return; } if (_commandRequireRelease) { if (((int)_commandKey != 0 && Input.GetKey(_commandKey)) || ((int)((KeyboardShortcut)(ref val)).MainKey != 0 && Input.GetKey(((KeyboardShortcut)(ref val)).MainKey)) || ZInput.GetButton("JoyRadial")) { return; } _commandRequireRelease = false; } if (!CanReadCompanyInput()) { CancelCompanyPress(); return; } bool flag2 = !CompanyPress.IsPending && (flag || buttonDown); if (flag2) { _commandController = buttonDown; _commandKey = ((KeyboardShortcut)(ref val)).MainKey; MercGuideMenu.ConsumeContextRadialPress(); } bool held = (_commandController ? ZInput.GetButton("JoyRadial") : ((int)_commandKey != 0 && Input.GetKey(_commandKey))); MercCommandPressAction mercCommandPressAction = CompanyPress.Step(flag2, held, allowed: true, Time.unscaledTime); Player localPlayer = Player.m_localPlayer; switch (mercCommandPressAction) { case MercCommandPressAction.Context: MercContextOrders.TryHandleContextInput(localPlayer); break; case MercCommandPressAction.Menu: { Mercenary mercenary = localPlayer.GetHoverCreature() as Mercenary; if ((Object)(object)mercenary == (Object)null) { GameObject hoverObject = ((Humanoid)localPlayer).GetHoverObject(); if ((Object)(object)hoverObject != (Object)null) { mercenary = hoverObject.GetComponentInParent(); } } if ((Object)(object)mercenary != (Object)null && !mercenary.IsAssignedTo(localPlayer, requireFollowing: false)) { mercenary = null; } MercGuideMenu.Open(mercenary); break; } } } internal static void OnSceneReady() { StageDirector.Poll(); foreach (Mercenary instance in Mercenary.Instances) { instance.ApplyStageVisuals(instance.GetEffectiveStage()); } } private void OnStageChanged(int stage) { string stageName = StageDirector.GetStageName(stage); bool flag = (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); if (flag) { LlmBrain.RecordEvent("progression", "A boss-progression milestone changed mercenary equipment to " + stageName + "."); } foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null)) { int effectiveStage = instance.GetEffectiveStage(); instance.ApplyStageVisuals(effectiveStage); instance.ApplyLoadout(effectiveStage); if (flag) { ((Character)instance).Heal(9999f, false); instance.Say(MercLocalization.Phrase("dnpc_speech_equipment_matches", StageDirector.GetStageArgument(stage))); } } } } private static Mercenary FindNearestMerc(Player player, float maxRange) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return null; } Mercenary result = null; float num = float.MaxValue; foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && !((Character)instance).IsDead() && instance.IsAssignedTo(player, requireFollowing: true)) { float num2 = Vector3.Distance(((Component)instance).transform.position, ((Component)player).transform.position); if (num2 < num && num2 < maxRange) { result = instance; num = num2; } } } return result; } private void RegisterCommands() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown ServerAuthority.RegisterCompanyCommand(); object obj = <>c.<>9__34_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { args.Context.AddString("dnpc_spawn: only the server/host can use this."); } else { SpawnTrioAt(((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); args.Context.AddString("Mercenaries spawned."); } } catch (Exception ex) { args.Context.AddString("dnpc_spawn failed: " + ex.Message); } }; <>c.<>9__34_0 = val; obj = (object)val; } new ConsoleCommand("dnpc_spawn", "spawns the three mercenaries at your position (admin/server-side)", (ConsoleEvent)obj, false, false, false, true, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__34_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { try { if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null) { args.Context.AddString("dnpc_census: load into a world first."); } else { SortedDictionary> sortedDictionary = new SortedDictionary>(); foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if (!((Object)(object)prefab == (Object)null)) { Character component = prefab.GetComponent(); if (!((Object)(object)component == (Object)null)) { string key = ((object)Unsafe.As(ref component.m_faction)/*cast due to .constrained prefix*/).ToString(); if (!sortedDictionary.TryGetValue(key, out var value)) { value = (sortedDictionary[key] = new List()); } value.Add(((Object)prefab).name); } } } List list2 = new List(); foreach (KeyValuePair> item in sortedDictionary) { item.Value.Sort(StringComparer.OrdinalIgnoreCase); list2.Add("== " + item.Key + " (" + item.Value.Count + ")"); list2.AddRange(item.Value); list2.Add(""); } File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "creature-census.txt"), list2); SortedDictionary> sortedDictionary2 = new SortedDictionary>(); foreach (GameObject item2 in ObjectDB.instance.m_items) { if (!((Object)(object)item2 == (Object)null)) { ItemDrop component2 = item2.GetComponent(); if (component2?.m_itemData?.m_shared != null) { string key2 = ((object)Unsafe.As(ref component2.m_itemData.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(); if (!sortedDictionary2.TryGetValue(key2, out var value2)) { value2 = (sortedDictionary2[key2] = new List()); } value2.Add(((Object)item2).name); } } } List list4 = new List(); foreach (KeyValuePair> item3 in sortedDictionary2) { item3.Value.Sort(StringComparer.OrdinalIgnoreCase); list4.Add("== " + item3.Key + " (" + item3.Value.Count + ")"); list4.AddRange(item3.Value); list4.Add(""); } File.WriteAllLines(Path.Combine(Paths.BepInExRootPath, "item-census.txt"), list4); args.Context.AddString($"Census written next to BepInEx: {sortedDictionary.Values.Sum((List b) => b.Count)} creatures, {ObjectDB.instance.m_items.Count} items."); Log($"Census dumped: {sortedDictionary.Values.Sum((List b) => b.Count)} creatures, {ObjectDB.instance.m_items.Count} items."); } } catch (Exception ex) { args.Context.AddString("dnpc_census failed: " + ex.Message); } }; <>c.<>9__34_1 = val2; obj2 = (object)val2; } new ConsoleCommand("dnpc_census", "admin: dumps every creature prefab (grouped by faction) and every item/material (grouped by type) in this game build to BepInEx/creature-census.txt and item-census.txt", (ConsoleEvent)obj2, false, false, false, true, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj3 = <>c.<>9__34_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { try { if ((Object)(object)Player.m_localPlayer == (Object)null) { args.Context.AddString("dnpc_clear: join the world as an admin player first."); } else { float num = Mathf.Clamp(args.TryParameterFloat(1, 30f), 1f, 200f); ServerAuthority.RequestNpcCleanup(num); args.Context.AddString($"dnpc_clear: submitted {num:F0}m cleanup request to the server."); } } catch (Exception ex) { args.Context.AddString("dnpc_clear failed: " + ex.Message); } }; <>c.<>9__34_2 = val3; obj3 = (object)val3; } new ConsoleCommand("dnpc_clear", "admin: removes mercenaries, other non-player characters, and orphaned objects (old-version leftovers) in a radius; usage: dnpc_clear [radius, default 30, max 200]", (ConsoleEvent)obj3, false, false, false, true, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj4 = <>c.<>9__34_3; if (obj4 == null) { ConsoleEvent val4 = delegate(ConsoleEventArgs args) { try { if ((Object)(object)Player.m_localPlayer == (Object)null) { args.Context.AddString("dnpc_purge: join the world as an admin player first."); } else { ServerAuthority.RequestMercPurge(); args.Context.AddString("dnpc_purge: submitted world-wide mercenary purge request to the server."); } } catch (Exception ex) { args.Context.AddString("dnpc_purge failed: " + ex.Message); } }; <>c.<>9__34_3 = val4; obj4 = (object)val4; } new ConsoleCommand("dnpc_purge", "admin: removes every mercenary in the world, no radius limit (recognized banners respawn their rosters); usage: dnpc_purge", (ConsoleEvent)obj4, false, false, false, true, false, false, (ConsoleOptionsFetcher)null, false, false, true); object obj5 = <>c.<>9__34_4; if (obj5 == null) { ConsoleEvent val5 = delegate(ConsoleEventArgs args) { try { if ((Object)(object)Player.m_localPlayer == (Object)null) { args.Context.AddString("dnpc: join the world as an admin player first."); } else { string[] array = args.Args ?? Array.Empty(); if (array.Length < 2 || !string.Equals(array[1], "snikk", StringComparison.OrdinalIgnoreCase)) { args.Context.AddString("Usage: dnpc snikk status|force-base|force-find|despawn|bones|test|reset-schedule|set-last-sighting |gossip|dump-events |clear-recent-gossip"); } else { SnikkVisitService.RequestAdminCommand((array.Length > 2) ? string.Join(" ", array, 2, array.Length - 2) : "status"); args.Context.AddString("Snikk command submitted to the server."); } } } catch (Exception ex) { args.Context.AddString("dnpc failed: " + ex.Message); } }; <>c.<>9__34_4 = val5; obj5 = (object)val5; } new ConsoleCommand("dnpc", "admin: Snikk controls; usage: dnpc snikk status|force-base|force-find|despawn|bones|test|reset-schedule|set-last-sighting |gossip|dump-events |clear-recent-gossip", (ConsoleEvent)obj5, false, false, false, true, false, false, (ConsoleOptionsFetcher)null, false, false, true); } internal static void SpawnTrioAt(Vector3 pos) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_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_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_0070: 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_0094: Unknown result type (might be due to invalid IL or missing references) MercClass[] array = new MercClass[3] { MercClass.Tank, MercClass.Healer, MercClass.Archer }; foreach (MercClass mercClass in array) { GameObject mercPrefab = MercPrefabs.GetMercPrefab(mercClass); if ((Object)(object)mercPrefab == (Object)null) { LogWarn($"prefab missing for {mercClass}"); continue; } Vector3 val = Random.insideUnitSphere * 2f; val.y = 0f; Vector3 val2 = pos + val; float solidHeight = ZoneSystem.instance.GetSolidHeight(val2); if (solidHeight > -10000f) { val2.y = solidHeight + 0.1f; } GameObject val3 = Object.Instantiate(mercPrefab, val2, Quaternion.identity); ZNetView val4 = (((Object)(object)val3 != (Object)null) ? val3.GetComponent() : null); if ((Object)(object)val4 != (Object)null && val4.IsValid()) { val4.GetZDO().Set(Mercenary.ZdoStandalone, true); } } } } internal interface IQuestionContextProvider { string Name { get; } string Build(string question, ServerAuthority.SenderPlayerState requester); } internal static class QuestionContextProviders { private sealed class WorldNotesProvider : IQuestionContextProvider { public string Name => "world-notes"; public string Build(string question, ServerAuthority.SenderPlayerState requester) { return WorldDataService.BuildWorldNotesContext(question); } } private sealed class StorageProvider : IQuestionContextProvider { public string Name => "storage"; public string Build(string question, ServerAuthority.SenderPlayerState requester) { return WorldDataService.BuildStorageContext(question, requester); } } private sealed class StatsProvider : IQuestionContextProvider { public string Name => "stats"; public string Build(string question, ServerAuthority.SenderPlayerState requester) { return WorldDataService.BuildStatsContext(question, requester); } } private const int MaxCombinedChars = 14000; private static readonly List Providers = new List { new WorldNotesProvider(), new StorageProvider(), new StatsProvider() }; internal static string Build(string question, ServerAuthority.SenderPlayerState requester) { if (string.IsNullOrWhiteSpace(question)) { return ""; } StringBuilder stringBuilder = new StringBuilder(); foreach (IQuestionContextProvider provider in Providers) { string text; try { text = provider.Build(question, requester); } catch (Exception ex) { MercPlugin.LogWarn("Context provider " + provider.Name + " failed: " + ex.Message); continue; } if (!string.IsNullOrWhiteSpace(text)) { int num = 14000 - stringBuilder.Length; if (num <= 0) { break; } if (text.Length > num) { text = text.Substring(0, num); } if (stringBuilder.Length > 0) { stringBuilder.AppendLine(); } stringBuilder.Append(text.Trim()); } } return stringBuilder.ToString(); } } public static class ResurrectionManager { private static Player _downedPlayer; private static Mercenary _healer; private static float _downedAt; private static float _graceUntil; private static bool _failureRequested; private static Player _recentlyDamagedPlayer; private static float _recentDamageAt; private const float DamageArmSeconds = 2f; public static double NetworkTime { get { if (!((Object)(object)ZNet.instance != (Object)null)) { return Time.realtimeSinceStartup; } return ZNet.instance.GetTimeSeconds(); } } public static bool IsDowned(Player player) { if ((Object)(object)player != (Object)null) { return (Object)(object)player == (Object)(object)_downedPlayer; } return false; } public static void NoteIncomingDamage(Character target, HitData hit) { Player val = (Player)(object)((target is Player) ? target : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && hit != null && !(hit.GetTotalDamage() <= 0f)) { _recentlyDamagedPlayer = val; _recentDamageAt = Time.unscaledTime; LlmBrain.RecordEvent("player-hit", "The player was recently hit in combat.", 30f); } } public static bool TryPreventDeath(Player player) { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return false; } if (((Character)player).GetHealth() > 0f) { return false; } if (!((Character)player).IsOwner()) { MercPlugin.LogWarn("Resurrect did not intercept the local death because the local Player was not the ZNetView owner."); return false; } if (Time.unscaledTime < _graceUntil) { ((Character)player).SetHealth(1f); return true; } if ((Object)(object)_downedPlayer == (Object)(object)player) { ((Character)player).SetHealth(1f); return true; } if ((Object)(object)_recentlyDamagedPlayer != (Object)(object)player || Time.unscaledTime - _recentDamageAt > 2f) { return false; } _recentlyDamagedPlayer = null; _recentDamageAt = 0f; if (!MercConfig.ResurrectionEnabled.Value) { MercPlugin.LogWarn("Resurrect did not intercept the local death because the feature is disabled."); return false; } Mercenary mercenary = FindEligibleHealer(player); if ((Object)(object)mercenary == (Object)null) { LogIneligibleHealers(player); LlmBrain.RecordEvent("ordinary-death", "The player suffered lethal damage and no eligible Mender could intercept the death.", 10f); return false; } ((Character)player).SetHealth(1f); ((Character)player).SetMoveDir(Vector3.zero); ((Character)player).SetRun(false); player.StartEmote("despair", false); _downedPlayer = player; _healer = mercenary; _downedAt = Time.unscaledTime; _failureRequested = false; ((Character)player).Message((MessageType)2, MercLocalization.Text("dnpc_message_downed_rescue", mercenary.GetName()), 0, (Sprite)null, false); mercenary.RequestResurrection(player); LlmBrain.RecordEvent("resurrect-start", "The player was downed and " + mercenary.GetName() + " began a Resurrect rescue."); MercPlugin.Log("Prevented vanilla death for " + player.GetPlayerName() + "; requesting Resurrect from " + mercenary.GetName()); return true; } public static void Update() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_downedPlayer == (Object)null) { return; } if ((Object)(object)_downedPlayer != (Object)(object)Player.m_localPlayer) { ClearPending(); return; } if (_failureRequested) { ResumeVanillaDeath("healer rejected or lost the Resurrect cast"); return; } float num = Mathf.Max(20f, MercConfig.ResurrectionCastSeconds.Value + 16f); if (Time.unscaledTime - _downedAt > num) { ResumeVanillaDeath("Resurrect timed out"); return; } if (((Character)_downedPlayer).GetHealth() < 1f) { ((Character)_downedPlayer).SetHealth(1f); } ((Character)_downedPlayer).SetMoveDir(Vector3.zero); ((Character)_downedPlayer).SetRun(false); Rigidbody component = ((Component)_downedPlayer).GetComponent(); if ((Object)(object)component != (Object)null && !component.isKinematic) { Vector3 linearVelocity = component.linearVelocity; linearVelocity.x = 0f; linearVelocity.z = 0f; if (linearVelocity.y > 0f) { linearVelocity.y = 0f; } component.linearVelocity = linearVelocity; component.angularVelocity = Vector3.zero; } } public static void OnCastStarted(Mercenary healer, Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)_downedPlayer) && (!((Object)(object)_healer != (Object)null) || !((Object)(object)healer != (Object)(object)_healer))) { _healer = healer; ((Character)player).Message((MessageType)2, MercLocalization.Text("dnpc_message_resurrect_cast", healer.GetName()), 0, (Sprite)null, false); } } public static void OnCompleted(Mercenary healer, Player player, float healthFraction) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)_downedPlayer) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && (!((Object)(object)_healer != (Object)null) || !((Object)(object)healer != (Object)(object)_healer))) { float num = Mathf.Max(1f, ((Character)player).GetMaxHealth() * Mathf.Clamp(healthFraction, 0.05f, 1f)); ((Character)player).SetHealth(num); MercUtil.StopPlayerEmote(player); _graceUntil = Time.unscaledTime + Mathf.Max(0f, MercConfig.ResurrectionGraceSeconds.Value); ClearPending(); ((Character)player).Message((MessageType)2, MercLocalization.Text("dnpc_message_resurrect_complete", Mathf.CeilToInt(num), Mathf.CeilToInt(Mathf.Max(0f, MercConfig.ResurrectionGraceSeconds.Value))), 0, (Sprite)null, false); LlmBrain.RecordEvent("resurrect-complete", healer.GetName() + " completed Resurrect and brought the player back."); MercPlugin.Log($"{healer.GetName()} resurrected {player.GetPlayerName()} with {num:0} health"); } } public static void OnDenied(Mercenary healer, Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)_downedPlayer) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && (!((Object)(object)_healer != (Object)null) || !((Object)(object)healer != (Object)(object)_healer))) { _failureRequested = true; } } public static void ResetForScene() { _downedPlayer = null; _healer = null; _failureRequested = false; _graceUntil = 0f; _recentlyDamagedPlayer = null; _recentDamageAt = 0f; } private static Mercenary FindEligibleHealer(Player player) { //IL_0035: 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) Mercenary result = null; float num = float.MaxValue; foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && instance.CanOfferResurrection(player)) { float num2 = Vector3.Distance(((Component)instance).transform.position, ((Component)player).transform.position); if (num2 < num) { result = instance; num = num2; } } } return result; } private static void LogIneligibleHealers(Player player) { //IL_007f: 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) bool flag = false; foreach (Mercenary instance in Mercenary.Instances) { if (!((Object)(object)instance == (Object)null) && instance.Class == MercClass.Healer) { flag = true; List list = new List(); if (((Character)instance).IsDead()) { list.Add("dead"); } if (!instance.IsAssignedTo(player, requireFollowing: false)) { list.Add("not recruited by this player"); } else if (!instance.IsAssignedTo(player, requireFollowing: true)) { list.Add("not following"); } float num = Vector3.Distance(((Component)instance).transform.position, ((Component)player).transform.position); float num2 = Mathf.Max(1f, MercConfig.ResurrectionRange.Value); if (num > num2) { list.Add($"{num:0}m away (limit {num2:0}m)"); } float resurrectionCooldownRemaining = instance.GetResurrectionCooldownRemaining(); if (resurrectionCooldownRemaining > 0f) { list.Add($"cooldown {Mathf.CeilToInt(resurrectionCooldownRemaining)}s"); } if (list.Count == 0) { list.Add("unknown eligibility mismatch"); } MercPlugin.LogWarn("Resurrect did not intercept death: " + instance.GetName() + " was " + string.Join(", ", list.ToArray()) + "."); } } if (!flag) { MercPlugin.LogWarn("Resurrect did not intercept death: no loaded Mira healer was found."); } } private static void ResumeVanillaDeath(string reason) { Player downedPlayer = _downedPlayer; ClearPending(); if (!((Object)(object)downedPlayer == (Object)null) && !((Character)downedPlayer).IsDead()) { MercUtil.StopPlayerEmote(downedPlayer); ((Character)downedPlayer).SetHealth(0f); LlmBrain.RecordEvent("resurrect-failed", "A Resurrect attempt failed and the player continued through normal Valheim death."); MercPlugin.LogWarn("Resurrect failed for " + downedPlayer.GetPlayerName() + ": " + reason + "; resuming vanilla death"); MercUtil.RunVanillaPlayerDeath(downedPlayer); } } private static void ClearPending() { _downedPlayer = null; _healer = null; _failureRequested = false; _downedAt = 0f; } } public sealed class LivingNpcNonCombatant : MonoBehaviour { } internal static class SnikkPrefabs { internal const string PrefabName = "DynamicNPC_Snikk"; private static GameObject _container; private static GameObject _built; private static ZNetScene _registeredScene; internal static bool IsRegistered { get { if ((Object)(object)_registeredScene != (Object)null) { return (Object)(object)_registeredScene == (Object)(object)ZNetScene.instance; } return false; } } internal static void Register(ZNetScene scene) { if ((Object)(object)scene == (Object)null || (Object)(object)_registeredScene == (Object)(object)scene) { return; } NpcDefinitionRegistry.EnsureLoaded(); GameObject val = MercPrefabs.PlayerPrefabSource(); if ((Object)(object)val == (Object)null) { MercPlugin.LogWarn("[Snikk] Player prefab was not found; visitor disabled for this scene."); return; } try { if ((Object)(object)_built == (Object)null) { _built = Object.Instantiate(val, Container(), false); ((Object)_built).name = "DynamicNPC_Snikk"; Build(_built); LogBuildSummary(_built); } MercPrefabs.RegisterPrefab(scene, _built); _registeredScene = scene; MercPlugin.Log("[Snikk] Registered merc-style visitor prefab with Fuling presentation (persistent ZDO, strays swept on boot)."); } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Prefab construction failed: " + ex); if ((Object)(object)_built != (Object)null) { Object.DestroyImmediate((Object)(object)_built); _built = null; } } } private static void LogBuildSummary(GameObject go) { Transform val = go.transform.Find("Visual"); SkinnedMeshRenderer val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInChildren(true) : null); int num = go.GetComponentsInChildren(true).Length; SnikkNpc component = go.GetComponent(); string text = (((Object)(object)component != (Object)null && ((Humanoid)component).m_defaultItems != null) ? string.Join(",", Array.ConvertAll(((Humanoid)component).m_defaultItems, (GameObject item) => (!((Object)(object)item != (Object)null)) ? "" : Utils.GetPrefabName(item))) : ""); MercPlugin.Log("[Snikk] Prefab build complete: visual=" + (((Object)(object)val != (Object)null) ? "present" : "") + ", bodyMesh=" + (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "") + ", " + $"rootLayer={LayerMask.LayerToName(go.layer)}, colliders={num}, " + "body=" + (((Object)(object)go.GetComponent() != (Object)null) ? "ok" : "MISSING") + ", ai=" + (((Object)(object)go.GetComponent() != (Object)null) ? "ok" : "MISSING") + ", clothing=[" + text + "]."); } internal static GameObject GetPrefab() { if (!((Object)(object)ZNetScene.instance != (Object)null)) { return null; } return ZNetScene.instance.GetPrefab("DynamicNPC_Snikk"); } internal static string DescribePrefab() { if ((Object)(object)_built == (Object)null) { return "Snikk prefab is not built yet."; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Snikk prefab: root layer=").Append(LayerMask.LayerToName(_built.layer)).Append(", child colliders=") .Append(_built.GetComponentsInChildren(true).Length) .Append('\n'); int num = 0; Transform[] componentsInChildren = _built.GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { if (num >= 40) { stringBuilder.Append("... (truncated)\n"); break; } int num2 = 0; Transform parent = val.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)_built.transform) { num2++; parent = parent.parent; } stringBuilder.Append(new string(' ', Mathf.Min(num2, 6) * 2)).Append(((Object)val).name).Append(" [") .Append(LayerMask.LayerToName(((Component)val).gameObject.layer)) .Append(']'); SkinnedMeshRenderer component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { stringBuilder.Append(" (skinned, bones=").Append(component.bones.Length).Append(')'); } Collider[] components = ((Component)val).GetComponents(); foreach (Collider val2 in components) { stringBuilder.Append(" (").Append(((object)val2).GetType().Name).Append(')'); } stringBuilder.Append('\n'); num++; } return stringBuilder.ToString(); } internal static void ResetScene() { _registeredScene = null; } internal static void Shutdown() { _registeredScene = null; _built = null; if ((Object)(object)_container != (Object)null) { Object.Destroy((Object)(object)_container); _container = null; } } private static Transform Container() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)_container == (Object)null) { _container = new GameObject("DynamicNPCs_LivingNpcPrefabs"); _container.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_container); } return _container.transform; } private static void Build(GameObject go) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) Player component = go.GetComponent(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("Player clone has no Player component"); } Component[] array = (Component[])(object)new Component[3] { (Component)go.GetComponent(), (Component)go.GetComponent(), (Component)go.GetComponent() }; foreach (Component val in array) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } } SnikkNpc snikkNpc = go.AddComponent(); MercPrefabs.CopyCharacterData(component, (Humanoid)(object)snikkNpc); ((Character)snikkNpc).m_name = NpcDefinitionRegistry.Snikk.DisplayName; ((Character)snikkNpc).m_faction = (Faction)0; ((Character)snikkNpc).m_group = "dynamicnpcs_noncombatants"; ((Character)snikkNpc).m_aiSkipTarget = true; ((Character)snikkNpc).m_health = NpcDefinitionRegistry.Snikk.MaxHealth; ((Humanoid)snikkNpc).m_defaultItems = Array.Empty(); ((Humanoid)snikkNpc).m_randomWeapon = Array.Empty(); ((Humanoid)snikkNpc).m_randomShield = Array.Empty(); SnikkAI ai = go.AddComponent(); ConfigureSnikkAI(ai); if (!ApplyFulingPresentation(go)) { throw new InvalidOperationException("Fuling presentation and clothing contract could not be built"); } LogPhysicsSelfCheck(go); MercPrefabs.RebindPlayerReferences(go, component, (Character)(object)snikkNpc); MercPrefabs.RebindAnimationComponents(go, (Character)(object)snikkNpc, (MonsterAI)(object)ai); Object.DestroyImmediate((Object)(object)component); MercPrefabs.RebindAnimationComponents(go, (Character)(object)snikkNpc, (MonsterAI)(object)ai); Rigidbody component2 = go.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.isKinematic = false; } ZNetView component3 = go.GetComponent(); if ((Object)(object)component3 == (Object)null) { throw new InvalidOperationException("Player clone has no ZNetView"); } component3.m_persistent = true; component3.m_distant = false; go.AddComponent(); go.AddComponent(); AddInteractionForwarders(go, snikkNpc); } private static bool ApplyFulingPresentation(GameObject go) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) GameObject val = ResolveFulingPrefab(); if ((Object)(object)val == (Object)null) { MercPlugin.LogWarn("[Snikk] Fuling/Goblin donor prefab unavailable - keeping player visuals. Snikk will look like a mercenary instead of a Fuling."); return false; } Transform val2 = val.transform.Find("Visual"); if ((Object)(object)val2 == (Object)null) { foreach (Transform item in val.transform) { Transform val3 = item; if (!((Object)(object)((Component)val3).GetComponentInChildren(true) == (Object)null)) { val2 = val3; break; } } } Transform val4 = go.transform.Find("Visual"); if ((Object)(object)val2 == (Object)null || (Object)(object)val4 == (Object)null) { MercPlugin.LogWarn($"[Snikk] Visual transplant skipped - donorVisual={(Object)(object)val2 != (Object)null}, " + $"playerVisual={(Object)(object)val4 != (Object)null}. Keeping player visuals."); return false; } Object.DestroyImmediate((Object)(object)((Component)val4).gameObject); ((Object)Object.Instantiate(((Component)val2).gameObject, go.transform)).name = "Visual"; Animator component = val.GetComponent(); Animator component2 = go.GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component2 != (Object)null) { component2.runtimeAnimatorController = component.runtimeAnimatorController; component2.avatar = component.avatar; } ConfigureFulingFootsteps(go, val); AdoptFulingPhysics(go, val); if ((Object)(object)Utils.FindChild(go.transform, "EyePos", (IterativeSearchType)0) == (Object)null) { GameObject val5 = new GameObject("EyePos"); val5.transform.SetParent(go.transform, false); val5.transform.localPosition = new Vector3(0f, 1.1f, 0.25f); } VisEquipment component3 = val.GetComponent(); VisEquipment component4 = go.GetComponent(); if ((Object)(object)component3 != (Object)null && (Object)(object)component4 != (Object)null) { CopyFulingVisEquipment(val.transform, go.transform, component3, component4); } else { MercPlugin.LogWarn($"[Snikk] Gear copy skipped: donorVis={(Object)(object)component3 != (Object)null}, targetVis={(Object)(object)component4 != (Object)null}."); } Humanoid component5 = val.GetComponent(); SnikkNpc component6 = go.GetComponent(); if ((Object)(object)component5 != (Object)null && (Object)(object)component6 != (Object)null && component5.m_defaultItems != null && component5.m_defaultItems.Length != 0) { ((Humanoid)component6).m_defaultItems = (GameObject[])component5.m_defaultItems.Clone(); GameObject[] defaultItems = ((Humanoid)component6).m_defaultItems; foreach (GameObject val6 in defaultItems) { if ((Object)(object)val6 == (Object)null || (Object)(object)val6.GetComponent() == (Object)null) { throw new InvalidOperationException("Fuling clothing donor contains a missing or non-item prefab"); } } MercPlugin.Log($"[Snikk] Inherited {component5.m_defaultItems.Length} default item(s) " + "(goblin loincloth and bands) from the donor."); return true; } MercPlugin.LogWarn($"[Snikk] Clothing contract failed: donor={(Object)(object)component5 != (Object)null}, " + $"target={(Object)(object)component6 != (Object)null}, items={(component5?.m_defaultItems?.Length).GetValueOrDefault()}."); return false; } private static void ConfigureFulingFootsteps(GameObject go, GameObject fuling) { FootStep componentInChildren = fuling.GetComponentInChildren(true); FootStep val = go.GetComponent(); FootStep[] componentsInChildren = go.GetComponentsInChildren(true); foreach (FootStep val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)val) { Object.DestroyImmediate((Object)(object)val2); } } if ((Object)(object)componentInChildren == (Object)null) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } MercPlugin.LogWarn("[Snikk] Fuling FootStep donor unavailable; footsteps disabled safely."); return; } if ((Object)(object)val == (Object)null) { val = go.AddComponent(); } val.m_footlessFootsteps = componentInChildren.m_footlessFootsteps; val.m_footlessTriggerDistance = componentInChildren.m_footlessTriggerDistance; val.m_footstepCullDistance = componentInChildren.m_footstepCullDistance; val.m_effects = ((componentInChildren.m_effects != null) ? new List(componentInChildren.m_effects) : new List()); val.m_feet = MapComponents(fuling.transform, go.transform, componentInChildren.m_feet); if (val.m_feet.Length == 0) { val.FindJoints(); } MercPlugin.Log($"[Snikk] Configured one root FootStep with {val.m_feet.Length} mapped joint(s) " + $"and {val.m_effects.Count} Fuling effect set(s)."); } private static void CopyFulingVisEquipment(Transform donorRoot, Transform targetRoot, VisEquipment donor, VisEquipment target) { target.m_bodyModel = MapComponent(donorRoot, targetRoot, donor.m_bodyModel); target.m_nViewOverride = null; target.m_leftHand = MapTransform(donorRoot, targetRoot, donor.m_leftHand); target.m_rightHand = MapTransform(donorRoot, targetRoot, donor.m_rightHand); target.m_helmet = MapTransform(donorRoot, targetRoot, donor.m_helmet); target.m_backShield = MapTransform(donorRoot, targetRoot, donor.m_backShield); target.m_backMelee = MapTransform(donorRoot, targetRoot, donor.m_backMelee); target.m_backTwohandedMelee = MapTransform(donorRoot, targetRoot, donor.m_backTwohandedMelee); target.m_backBow = MapTransform(donorRoot, targetRoot, donor.m_backBow); target.m_backTool = MapTransform(donorRoot, targetRoot, donor.m_backTool); target.m_backAtgeir = MapTransform(donorRoot, targetRoot, donor.m_backAtgeir); target.m_clothColliders = new List(); if (donor.m_clothColliders != null) { foreach (ColliderComponent clothCollider in donor.m_clothColliders) { ColliderComponent val = MapComponent(donorRoot, targetRoot, clothCollider); if ((Object)(object)val != (Object)null) { target.m_clothColliders.Add(val); } } } target.m_models = ((donor.m_models != null) ? ((PlayerModel[])donor.m_models.Clone()) : Array.Empty()); target.m_isPlayer = donor.m_isPlayer; target.m_useAllTrails = donor.m_useAllTrails; target.m_isArmorStand = false; MercPlugin.Log("[Snikk] Copied donor VisEquipment attachment contract with clean runtime item state."); } private static T MapComponent(Transform donorRoot, Transform targetRoot, T donor) where T : Component { if ((Object)(object)donor == (Object)null) { return default(T); } Transform val = MapTransform(donorRoot, targetRoot, ((Component)donor).transform); if (!((Object)(object)val != (Object)null)) { return default(T); } return ((Component)val).GetComponent(); } private static T[] MapComponents(Transform donorRoot, Transform targetRoot, T[] donors) where T : Component { if (donors == null || donors.Length == 0) { return Array.Empty(); } List list = new List(donors.Length); foreach (T donor in donors) { T val = MapComponent(donorRoot, targetRoot, donor); if ((Object)(object)val != (Object)null) { list.Add(val); } } return list.ToArray(); } private static Transform MapTransform(Transform donorRoot, Transform targetRoot, Transform donor) { string text = RelativePath(donorRoot, donor); if (text == null) { return null; } if (text.Length != 0) { return targetRoot.Find(text); } return targetRoot; } private static string RelativePath(Transform donorRoot, Transform donor) { if ((Object)(object)donorRoot == (Object)null || (Object)(object)donor == (Object)null || ((Object)(object)donor != (Object)(object)donorRoot && !donor.IsChildOf(donorRoot))) { return null; } if ((Object)(object)donor == (Object)(object)donorRoot) { return string.Empty; } List list = new List(); Transform val = donor; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)donorRoot) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list.ToArray()); } private static void AdoptFulingPhysics(GameObject go, GameObject fuling) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown MercPlugin.Log("[Snikk] Root layer " + LayerMask.LayerToName(go.layer) + " -> " + LayerMask.LayerToName(fuling.layer) + "."); go.layer = fuling.layer; Collider[] components = go.GetComponents(); int num = 0; Collider[] components2 = fuling.GetComponents(); foreach (Collider source in components2) { if (CopyCollider(go, source, fuling.layer)) { num++; } } if (num > 0) { components2 = components; foreach (Collider val in components2) { if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject == (Object)(object)go) { Object.DestroyImmediate((Object)(object)val); } } } else { MercPlugin.LogWarn("[Snikk] Donor root has no colliders; keeping the player capsule."); } foreach (Transform item in fuling.transform) { Transform val2 = item; if (!(((Object)val2).name == "Visual") && !((Object)(object)((Component)val2).GetComponentInChildren(true) == (Object)null) && !((Object)(object)Utils.FindChild(go.transform, ((Object)val2).name, (IterativeSearchType)0) != (Object)null)) { ((Object)Object.Instantiate(((Component)val2).gameObject, go.transform)).name = ((Object)val2).name; MercPlugin.Log("[Snikk] Transplanted donor child '" + ((Object)val2).name + "' (colliders)."); } } } private static bool CopyCollider(GameObject target, Collider source, int layer) { //IL_0012: 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) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) try { CapsuleCollider val = (CapsuleCollider)(object)((source is CapsuleCollider) ? source : null); if (val != null) { CapsuleCollider obj = target.AddComponent(); obj.center = val.center; obj.radius = val.radius; obj.height = val.height; obj.direction = val.direction; ((Collider)obj).isTrigger = ((Collider)val).isTrigger; ((Collider)obj).sharedMaterial = ((Collider)val).sharedMaterial; } else { BoxCollider val2 = (BoxCollider)(object)((source is BoxCollider) ? source : null); if (val2 != null) { BoxCollider obj2 = target.AddComponent(); obj2.center = val2.center; obj2.size = val2.size; ((Collider)obj2).isTrigger = ((Collider)val2).isTrigger; ((Collider)obj2).sharedMaterial = ((Collider)val2).sharedMaterial; } else { SphereCollider val3 = (SphereCollider)(object)((source is SphereCollider) ? source : null); if (val3 != null) { SphereCollider obj3 = target.AddComponent(); obj3.center = val3.center; obj3.radius = val3.radius; ((Collider)obj3).isTrigger = ((Collider)val3).isTrigger; ((Collider)obj3).sharedMaterial = ((Collider)val3).sharedMaterial; } else { MeshCollider val4 = (MeshCollider)(object)((source is MeshCollider) ? source : null); if (val4 == null) { return false; } MeshCollider obj4 = target.AddComponent(); obj4.sharedMesh = val4.sharedMesh; obj4.convex = val4.convex; ((Collider)obj4).isTrigger = ((Collider)val4).isTrigger; ((Collider)obj4).sharedMaterial = ((Collider)val4).sharedMaterial; } } } MercPlugin.Log("[Snikk] Copied " + ((object)source).GetType().Name + " from donor root (layer " + LayerMask.LayerToName(layer) + ")."); return true; } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Collider copy failed for " + ((object)source).GetType().Name + ": " + ex.Message); return false; } } private static void LogPhysicsSelfCheck(GameObject go) { string[] array = new string[6] { "character", "character_net", "character_ghost", "hitbox", "character_noenv", "vehicle" }; StringBuilder stringBuilder = new StringBuilder(); int num = 0; int num2 = 0; Collider[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { num++; string value = LayerMask.LayerToName(((Component)val).gameObject.layer); if (Array.IndexOf(array, value) >= 0) { num2++; } stringBuilder.Append('[').Append(value).Append(']') .Append(((object)val).GetType().Name) .Append('@') .Append(((Object)((Component)val).transform).name) .Append(' '); } } if (num2 > 0) { MercPlugin.Log($"[Snikk] Physics self-check PASS: {num2}/{num} collider(s) on attack " + $"layers: {stringBuilder}"); } else { MercPlugin.LogWarn("[Snikk] Physics self-check FAIL: no collider sits on an attack layer " + $"({num} total: {stringBuilder}). Snikk will not be hittable - run " + "'dnpc snikk bones' and report the output."); } } private static GameObject ResolveFulingPrefab() { GameObject val = null; if ((Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab(NpcDefinitionRegistry.Snikk.Prefab); if ((Object)(object)val == (Object)null && string.Equals(NpcDefinitionRegistry.Snikk.Prefab, "Fuling", StringComparison.OrdinalIgnoreCase)) { val = ZNetScene.instance.GetPrefab("Goblin"); } } return val; } private static void ConfigureSnikkAI(SnikkAI ai) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) ((MonsterAI)ai).m_attackPlayerObjects = false; ((BaseAI)ai).m_aggravatable = true; ((BaseAI)ai).m_passiveAggresive = false; ((MonsterAI)ai).m_enableHuntPlayer = false; ((BaseAI)ai).m_avoidFire = true; ((BaseAI)ai).m_avoidLava = true; ((BaseAI)ai).m_avoidWater = true; ((BaseAI)ai).m_randomMoveRange = 0f; ((BaseAI)ai).m_randomMoveInterval = 99999f; ((MonsterAI)ai).m_maxChaseDistance = 0f; ((MonsterAI)ai).m_alertRange = 10f; ((BaseAI)ai).m_hearRange = 10f; ((BaseAI)ai).m_viewRange = 20f; ((BaseAI)ai).m_pathAgentType = (AgentType)6; MercUtil.SetPrivate(ai, false, "m_patrol", typeof(BaseAI), typeof(MonsterAI)); } private static void AddInteractionForwarders(GameObject root, SnikkNpc npc) { Collider[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)(object)root)) { SnikkInteractionProxy snikkInteractionProxy = ((Component)val).GetComponent(); if ((Object)(object)snikkInteractionProxy == (Object)null) { snikkInteractionProxy = ((Component)val).gameObject.AddComponent(); } snikkInteractionProxy.Bind(npc); } } } } public sealed class SnikkNpc : Humanoid, Interactable { public static readonly List Instances = new List(); public override void Awake() { ((Humanoid)this).Awake(); if (!Instances.Contains(this)) { Instances.Add(this); } } public override void OnDestroy() { Instances.Remove(this); ((Humanoid)this).OnDestroy(); } public override string GetHoverName() { return NpcDefinitionRegistry.Snikk.DisplayName; } public override string GetHoverText() { string text = (ZInput.IsGamepadActive() ? MercLocalization.Binding("JoyUse", "A") : MercLocalization.Binding("Use", "E")); return NpcDefinitionRegistry.Snikk.DisplayName + "\n[" + text + "] " + MercLocalization.Text("dnpc_action_talk"); } public bool Interact(Humanoid user, bool hold, bool alt) { //IL_0038: 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_0034: Unknown result type (might be due to invalid IL or missing references) if (!hold) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { SnikkController component = ((Component)this).GetComponent(); SnikkVisitService.RequestInteraction((ZDOID)(((Object)(object)component != (Object)null) ? component.NetworkId : default(ZDOID))); return true; } } return false; } public bool UseItem(Humanoid user, ItemData item) { return false; } } public sealed class SnikkController : MonoBehaviour { internal const string DestinationKey = "dnpc_snikk_destination"; internal const string PhaseKey = "dnpc_snikk_phase"; internal const string TargetNameKey = "dnpc_snikk_target_name"; internal const string TargetPlayerIdKey = "dnpc_snikk_target_id"; internal const string PresentationReadyKey = "dnpc_snikk_presentation_ready"; private const string SpeechRpc = "RPC_DynamicNpcSnikkSay"; private ZNetView _view; private Humanoid _humanoid; private Animator _animator; private VisEquipment _equipment; private GameObject _visual; private float _normalAnimatorSpeed = 1f; private float _neutralIdleTimer; private bool _neutralIdleLocked; internal ZDOID NetworkId { get { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_view != (Object)null) || !_view.IsValid()) { return default(ZDOID); } return _view.GetZDO().m_uid; } } internal Humanoid Character => _humanoid; private void Awake() { //IL_00d8: Unknown result type (might be due to invalid IL or missing references) _view = ((Component)this).GetComponent(); _humanoid = ((Component)this).GetComponent(); _animator = ((Component)this).GetComponent(); _equipment = ((Component)this).GetComponent(); Transform obj = ((Component)this).transform.Find("Visual"); _visual = ((obj != null) ? ((Component)obj).gameObject : null); if ((Object)(object)_animator != (Object)null) { _normalAnimatorSpeed = Mathf.Max(0.01f, _animator.speed); } if ((Object)(object)_visual != (Object)null) { _visual.SetActive(false); } ZNetView view = _view; if (view != null) { view.Register("RPC_DynamicNpcSnikkSay", (Action)RPC_Say); } if ((Object)(object)_humanoid != (Object)null) { ((Character)_humanoid).m_aiSkipTarget = true; ((Character)_humanoid).m_faction = (Faction)0; } } private void Start() { if (!((Object)(object)_humanoid == (Object)null) && !((Object)(object)_view == (Object)null) && _view.IsValid()) { if (_view.IsOwner()) { float maxHealth = NpcDefinitionRegistry.Snikk.MaxHealth; ((Character)_humanoid).SetMaxHealth(maxHealth); ((Character)_humanoid).SetHealth(maxHealth); } ((MonoBehaviour)this).StartCoroutine(VerifyClothingThenReveal()); } } private void Update() { if (!((Object)(object)_humanoid == (Object)null)) { ((Character)_humanoid).m_aiSkipTarget = true; UpdateNeutralIdle(); } } internal void ApplyAuthoritativeTeleport(Vector3 position, Quaternion rotation) { //IL_0006: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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) ((Component)this).transform.position = position; ((Component)this).transform.rotation = rotation; if ((Object)(object)_humanoid != (Object)null) { ((Character)_humanoid).SetMoveDir(Vector3.zero); ((Character)_humanoid).SetRun(false); MercUtil.ResetCharacterGroundState((Character)(object)_humanoid); } Rigidbody component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && !component.isKinematic) { component.linearVelocity = Vector3.zero; component.angularVelocity = Vector3.zero; } } private IEnumerator VerifyClothingThenReveal() { yield return null; if ((Object)(object)_view == (Object)null || !_view.IsValid()) { yield break; } if (_view.IsOwner() && !HasReplicatedClothing()) { _humanoid.GiveDefaultItems(); } for (int frame = 0; frame < 60; frame++) { if ((Object)(object)_view == (Object)null || !_view.IsValid()) { yield break; } if (HasReplicatedClothing()) { if (TryApplyReplicatedClothingVisual()) { if (_view.IsOwner()) { _view.GetZDO().Set("dnpc_snikk_presentation_ready", 1); } if ((Object)(object)_visual != (Object)null) { _visual.SetActive(true); } MercPlugin.LogDebug("[Snikk] Clothing visual verified and revealed."); yield break; } yield return null; } else { yield return null; } } if ((Object)(object)_view != (Object)null && _view.IsValid() && _view.IsOwner()) { _view.GetZDO().Set("dnpc_snikk_presentation_ready", -1); } MercPlugin.LogWarn("[Snikk] Clothing visual did not replicate; keeping the bare base mesh hidden and ending the encounter."); } private bool HasReplicatedClothing() { ZDO val = (((Object)(object)_view != (Object)null && _view.IsValid()) ? _view.GetZDO() : null); if (val != null) { if (val.GetInt(ZDOVars.s_chestItem, 0) == 0 && val.GetInt(ZDOVars.s_legItem, 0) == 0 && val.GetInt(ZDOVars.s_helmetItem, 0) == 0 && val.GetInt(ZDOVars.s_shoulderItem, 0) == 0 && val.GetInt(ZDOVars.s_utilityItem, 0) == 0) { return val.GetInt(ZDOVars.s_trinketItem, 0) != 0; } return true; } return false; } private bool TryApplyReplicatedClothingVisual() { if ((Object)(object)_equipment == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || (Object)(object)_view == (Object)null || !_view.IsValid()) { return false; } _equipment.CustomUpdate(0f, Time.time); ZDO zDO = _view.GetZDO(); if (zDO == null) { return false; } int[] array = new int[6] { zDO.GetInt(ZDOVars.s_chestItem, 0), zDO.GetInt(ZDOVars.s_legItem, 0), zDO.GetInt(ZDOVars.s_helmetItem, 0), zDO.GetInt(ZDOVars.s_shoulderItem, 0), zDO.GetInt(ZDOVars.s_utilityItem, 0), zDO.GetInt(ZDOVars.s_trinketItem, 0) }; foreach (int num in array) { if (num != 0 && (Object)(object)ObjectDB.instance.GetItemPrefab(num) != (Object)null) { return true; } } return false; } private void UpdateNeutralIdle() { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_animator == (Object)null || (Object)(object)_view == (Object)null || !_view.IsValid()) { return; } ZDO zDO = _view.GetZDO(); if (zDO == null || zDO.GetInt("dnpc_snikk_phase", 0) != 2) { _neutralIdleTimer = 0f; if (_neutralIdleLocked) { _animator.speed = _normalAnimatorSpeed; _neutralIdleLocked = false; } } else { if (_neutralIdleLocked) { return; } _neutralIdleTimer += Time.deltaTime; if (!(_neutralIdleTimer < 0.35f) && !_animator.IsInTransition(0)) { Vector3 velocity = ((Character)_humanoid).GetVelocity(); if (!(((Vector3)(ref velocity)).sqrMagnitude > 0.01f)) { AnimatorStateInfo currentAnimatorStateInfo = _animator.GetCurrentAnimatorStateInfo(0); _animator.Play(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash, 0, 0f); _animator.Update(0f); _animator.speed = 0f; _neutralIdleLocked = true; } } } } private void OnDestroy() { if ((Object)(object)_animator != (Object)null && _neutralIdleLocked) { _animator.speed = _normalAnimatorSpeed; } } private void RPC_Say(long sender, string text) { if (ServerAuthority.IsAuthoritativeServerSender(sender)) { ShowSpeech(text); } } private void ShowSpeech(string text) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrWhiteSpace(text) && !((Object)(object)Chat.instance == (Object)null)) { Chat.instance.SetNpcText(((Component)this).gameObject, Vector3.up * 1.8f, 25f, Mathf.Min(4f + (float)text.Length / 30f, 14f), "", text, false); ((Terminal)Chat.instance).AddString(NpcDefinitionRegistry.Snikk.DisplayName, text, (Type)1, false); } } internal void ShowServerSpeech(string text) { ShowSpeech(text); } } public sealed class SnikkInteractionProxy : MonoBehaviour, Hoverable, Interactable { private SnikkNpc _npc; private SnikkNpc Target { get { if (!((Object)(object)_npc != (Object)null)) { return _npc = ((Component)this).GetComponentInParent(); } return _npc; } } internal void Bind(SnikkNpc npc) { _npc = npc; } public string GetHoverName() { if (!((Object)(object)Target != (Object)null)) { return ""; } return ((Character)Target).GetHoverName(); } public string GetHoverText() { if (!((Object)(object)Target != (Object)null)) { return ""; } return ((Character)Target).GetHoverText(); } public float GetHoverOffset() { if (!((Object)(object)Target != (Object)null)) { return 0f; } return ((Character)Target).GetHoverOffset(); } public bool Interact(Humanoid user, bool hold, bool alt) { if ((Object)(object)Target != (Object)null) { return Target.Interact(user, hold, alt); } return false; } public bool UseItem(Humanoid user, ItemData item) { return false; } } public sealed class SnikkAI : MonsterAI { private static readonly Func BaseUpdateAI = BuildBaseUpdateAi(); private static bool _baseAiMissingLogged; private Humanoid _snikk; private static Func BuildBaseUpdateAi() { MethodInfo method = typeof(BaseAI).GetMethod("UpdateAI", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return null; } DynamicMethod dynamicMethod = new DynamicMethod("SnikkBaseAI_UpdateAI", typeof(bool), new Type[2] { typeof(SnikkAI), typeof(float) }, restrictedSkipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Call, method); iLGenerator.Emit(OpCodes.Ret); return (Func)dynamicMethod.CreateDelegate(typeof(Func)); } public override void Awake() { ((MonsterAI)this).Awake(); _snikk = ((Component)this).GetComponent(); } public override bool UpdateAI(float dt) { //IL_00b6: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_snikk == (Object)null) { _snikk = ((Component)this).GetComponent(); } if ((Object)(object)_snikk == (Object)null) { return false; } if (BaseUpdateAI == null) { if (!_baseAiMissingLogged) { _baseAiMissingLogged = true; MercPlugin.LogWarn("[Snikk] BaseAI.UpdateAI delegate unavailable; Snikk stays idle until the mod is updated."); } return false; } if (!BaseUpdateAI(this, dt)) { return false; } try { ((BaseAI)this).SetAggravated(false, (AggravatedReason)0); ((BaseAI)this).SetAlerted(false); ZNetView component = ((Component)this).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); int num = ((val != null) ? val.GetInt("dnpc_snikk_phase", 0) : 0); if (num == 1 || num == 3) { Vector3 vec = val.GetVec3("dnpc_snikk_destination", ((Component)this).transform.position); float num2 = ((num == 1) ? 4.5f : 2f); bool flag = num == 3; ((BaseAI)this).MoveTo(dt, vec, num2, flag); Vector3 val2 = vec - ((Component)this).transform.position; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude > 0.1f) { ((Character)_snikk).SetLookDir(((Vector3)(ref val2)).normalized, 0f); } } else { ((BaseAI)this).StopMoving(); } return true; } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Navigation tick failed: " + ex.Message); return false; } } } internal static class SnikkCombatPolicy { internal static bool IsSnikk(Character character) { if ((Object)(object)character != (Object)null) { return (Object)(object)((Component)character).GetComponent() != (Object)null; } return false; } internal static bool SanitizeDirectDamage(Character victim, HitData hit, bool authoritative) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (!authoritative || !IsSnikk(victim)) { return true; } Character val = null; try { val = ((hit != null) ? hit.GetAttacker() : null); } catch { } ZNetView component = ((Component)victim).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { return true; } NpcDefinitionRegistry.Definition snikk = NpcDefinitionRegistry.Snikk; if ((Object)(object)val == (Object)null || !val.IsPlayer()) { return !snikk.IgnoreNonPlayerDamage; } SnikkController component2 = ((Component)victim).GetComponent(); SnikkVisitService.NotifyPlayerHit((ZDOID)(((Object)(object)component2 != (Object)null) ? component2.NetworkId : default(ZDOID)), val.GetZDOID()); if (hit == null) { return false; } if (!snikk.AllowStagger) { hit.m_staggerMultiplier = 0f; } if (!snikk.AllowStatusDamage) { hit.m_statusEffectHash = 0; } hit.m_pushForce = 0f; hit.m_backstabBonus = 1f; float allowed = SnikkRules.AllowedPlayerDamage(victim.GetHealth(), snikk.PlayerDamagePerHit, snikk.MinimumHealth); return SetBoundedDamage(hit, allowed, snikk.AllowStatusDamage); } internal static bool SanitizeAppliedDamage(Character victim, HitData hit) { if (!IsSnikk(victim)) { return true; } Character val = null; try { val = ((hit != null) ? hit.GetAttacker() : null); } catch { } NpcDefinitionRegistry.Definition snikk = NpcDefinitionRegistry.Snikk; if ((Object)(object)val == (Object)null || !val.IsPlayer()) { return !snikk.IgnoreNonPlayerDamage; } float num = SnikkRules.AllowedPlayerDamage(victim.GetHealth(), snikk.PlayerDamagePerHit, snikk.MinimumHealth); float num2 = Mathf.Max(0.0001f, Game.m_playerDamageRate); return SetBoundedDamage(hit, num / num2, snikk.AllowStatusDamage); } internal static void EnforceMinimumHealth(Character victim) { if (!IsSnikk(victim)) { return; } ZNetView component = ((Component)victim).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner()) { float minimumHealth = NpcDefinitionRegistry.Snikk.MinimumHealth; if (victim.GetHealth() < minimumHealth) { victim.SetHealth(minimumHealth); } } } internal static bool IsIgnoredByHostileAI(Character character) { if (IsSnikk(character)) { return NpcDefinitionRegistry.Snikk.IgnoredByHostileAI; } return false; } private static bool SetBoundedDamage(HitData hit, float allowed, bool allowStatusDamage) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (hit == null || allowed <= 0f) { return false; } float totalDamage = hit.GetTotalDamage(); if (totalDamage <= 0f) { return false; } if (allowStatusDamage) { ((DamageTypes)(ref hit.m_damage)).Modify(Mathf.Min(1f, allowed / totalDamage)); } else { hit.m_damage = default(DamageTypes); hit.m_damage.m_damage = allowed; hit.m_statusEffectHash = 0; } return hit.GetTotalDamage() > 0f; } } internal static class SnikkDialogueService { private static readonly string[] GenericLines = new string[5] { "Quiet lately. Suspicious.", "Nothing embarrassing to report. You're letting me down.", "I came all this way and you've been boring.", "What? That wasn't enough?", "Go do something embarrassing. I'll hear about it." }; private static readonly string[] AttackLines = new string[5] { "Did you think the second one would work?", "One damage. Terrifying.", "Ow. Anyway.", "Careful. At this rate you'll kill me sometime next year.", "Still trying? That's almost adorable." }; internal static void Generate(string targetName, List facts, Action completed, bool explainEmptyTest = false) { completed?.Invoke(BuildFallback(facts, explainEmptyTest)); } internal static string AttackFallback(int encounterHitCount, string attackerName) { int num = ((encounterHitCount >= 2) ? ((encounterHitCount - 2) % AttackLines.Length) : 0); return AttackLines[num]; } private static string BuildFallback(List facts, bool explainEmptyTest) { if (facts != null) { for (int i = 0; i < facts.Count && i < 2; i++) { WorldDataService.GossipFact gossipFact = facts[i]; if (gossipFact == null) { continue; } string text = CleanField(gossipFact.PlayerName, 60); if (text.Length != 0) { string text2 = CleanField(gossipFact.Biome, 40); string text3 = ((text2.Length > 0 && !string.Equals(text2, "unknown", StringComparison.OrdinalIgnoreCase)) ? (" in the " + text2) : ""); if (gossipFact.Type == "RepeatedDeaths" && gossipFact.Amount > 0) { return $"{text} died {gossipFact.Amount} times{text3}. {gossipFact.Amount}. Impressive commitment to a bad idea."; } if (gossipFact.Type == "Death") { return text + " died" + text3 + ". Strong work, as usual."; } string text4 = CleanField(gossipFact.Resource, 60); if (gossipFact.Type == "ResourceTotal" && gossipFact.Amount > 0 && text4.Length > 0) { return $"{text} gathered {gossipFact.Amount} {text4}. Apparently there was nothing better to do."; } } } } if (explainEmptyTest) { return "Fresh world, empty notebook. Give me a little history to gossip about, then call me back."; } return GenericLines[(Environment.TickCount & 0x7FFFFFFF) % GenericLines.Length]; } private static string CleanField(string text, int maximum) { string input = Regex.Replace(text ?? "", "<[^>]*>", ""); input = Regex.Replace(input, "[\\u0000-\\u001f\\u007f]+", " "); input = Regex.Replace(input, "\\s+", " ").Trim(); if (input.Length > maximum) { return input.Substring(0, maximum).TrimEnd(Array.Empty()); } return input; } } internal static class SnikkRules { internal const float LocalSpeechRadius = 30f; internal const float HitNoticeCooldownSeconds = 1f; internal const float TestSpawnDistance = 8f; internal const int TestSpawnCandidateCount = 8; internal const float TestIdleMinimumSeconds = 10f; internal const float TestIdleMaximumSeconds = 15f; internal const string EmptyTestGossipLine = "Fresh world, empty notebook. Give me a little history to gossip about, then call me back."; internal static float TestSpawnYawDegrees(int candidateIndex) { return candidateIndex switch { 0 => 0f, 1 => -30f, 2 => 30f, 3 => -60f, 4 => 60f, 5 => -90f, 6 => 90f, _ => 180f, }; } internal static float IdleDurationSeconds(bool testEncounter, float configuredMinimum, float configuredMaximum, float normalizedSample) { float num = (testEncounter ? 10f : Math.Max(10f, configuredMinimum)); float num2 = (testEncounter ? 15f : Math.Max(num, configuredMaximum)); if (float.IsNaN(normalizedSample) || float.IsInfinity(normalizedSample)) { normalizedSample = 0f; } normalizedSample = Math.Max(0f, Math.Min(1f, normalizedSample)); return num + normalizedSample * (num2 - num); } internal static float AllowedPlayerDamage(float currentHealth, float configuredDamage, float minimumHealth) { if (float.IsNaN(currentHealth) || float.IsInfinity(currentHealth) || float.IsNaN(configuredDamage) || float.IsInfinity(configuredDamage) || float.IsNaN(minimumHealth) || float.IsInfinity(minimumHealth)) { return 0f; } return Math.Max(0f, Math.Min(Math.Max(0f, configuredDamage), currentHealth - Math.Max(0f, minimumHealth))); } internal static bool IsWithinLocalSpeech(float squaredDistance) { if (float.IsNaN(squaredDistance) || float.IsInfinity(squaredDistance) || squaredDistance < 0f) { return false; } return squaredDistance <= 900f; } internal static bool CanAcceptHitNotice(float now, float readyAt) { if (!float.IsNaN(now) && !float.IsInfinity(now)) { return now >= readyAt; } return false; } } internal static class SnikkVisitService { internal enum VisitPhase { None, ApproachTarget, IdleNearTarget, LeaveTarget } private sealed class Relationship { internal long PlayerId; internal string Name; internal int TimesVisited; internal int TimesAttacked; internal int LastVisitDay; internal int LastAttackDay; } private sealed class RecentFact { internal string Id; internal int UsedDay; internal int VisitSequence; } private sealed class PersistentState { internal int LastSuccessfulSightingDay; internal int NextNormalVisitDay; internal long LastTargetPlayerId; internal int VisitSequence; internal readonly Dictionary Relationships = new Dictionary(); internal readonly List RecentFacts = new List(); } private sealed class OnlinePlayer { internal long PeerUid; internal ZDOID CharacterId; internal ZDO Zdo; internal long PlayerId; internal string Name; internal Vector3 Position; internal Quaternion Rotation; internal bool Dead; internal WorldDataService.RecognizedBase Base; } private const string InteractionRequestRpc = "DynamicNPCs_SnikkInteractRequestV1"; private const string KnowledgeRequestRpc = "jg224.dnpc.SnikkKnowledgeV1"; private const string HitNoticeRpc = "DynamicNPCs_SnikkHitNoticeV1"; private const string SpeechRpc = "DynamicNPCs_SnikkSpeechV1"; private const string AdminRequestRpc = "DynamicNPCs_SnikkAdminRequestV1"; private const string AdminReplyRpc = "DynamicNPCs_SnikkAdminReplyV1"; private static readonly Random Rng = new Random(); private static readonly Dictionary InteractionCooldown = new Dictionary(); private static readonly Dictionary HitNoticeCooldown = new Dictionary(); private static ZRoutedRpc _registeredRpc; private static long _loadedWorldUid; private static string _stateFile = ""; private static PersistentState _state; private static ZDOID _activeId; private static ZDOID _targetCharacterId; private static long _targetPlayerId; private static string _targetName = ""; private static VisitPhase _phase; private static float _schedulerTimer; private static float _runtimeTimer; private static float _sightingTimer; private static float _phaseTimer; private static float _nextAttemptAt; private static float _idleUntil; private static float _lastMovementCheck; private static Vector3 _lastMovementPosition; private static int _stuckCount; private static bool _forcedEncounter; private static bool _testEncounter; private static string _lastEndReason = ""; private static bool _forceBaseRequested; private static bool _forceFindRequested; private static int _encounterHitCount; private static OnlinePlayer _cachedTarget; private static float _targetCacheTimer; private static Vector3 _lastDestinationWrite = Vector3.zero; private static bool _hasDestinationWrite; private static int _pendingSinceDay = -1; private static float _lastSpawnFailLogAt = -60f; private static float _nextStraySweepAttemptAt; private static bool _straySweepDone; internal static bool HasActiveEncounter => !((ZDOID)(ref _activeId)).IsNone(); internal static ZDOID ActiveId => _activeId; internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { instance.Register("DynamicNPCs_SnikkInteractRequestV1", (Action)OnInteractionRequest); instance.Register("jg224.dnpc.SnikkKnowledgeV1", (Action)OnKnowledgeRequest); instance.Register("DynamicNPCs_SnikkHitNoticeV1", (Action)OnHitNotice); instance.Register("DynamicNPCs_SnikkSpeechV1", (Action)OnSpeech); instance.Register("DynamicNPCs_SnikkAdminRequestV1", (Action)OnAdminRequest); instance.Register("DynamicNPCs_SnikkAdminReplyV1", (Action)OnAdminReply); _registeredRpc = instance; MercPlugin.Log("[Snikk] Registered server-authoritative visit RPC protocol."); } } internal static void ResetSceneMemory() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { Despawn("scene reset"); } SaveState(); _loadedWorldUid = 0L; _stateFile = ""; _state = null; _activeId = default(ZDOID); _targetCharacterId = default(ZDOID); _targetPlayerId = 0L; _targetName = ""; _phase = VisitPhase.None; _schedulerTimer = 0f; _runtimeTimer = 0f; _sightingTimer = 0f; _phaseTimer = 0f; _nextAttemptAt = 0f; _forceBaseRequested = false; _forceFindRequested = false; _cachedTarget = null; _targetCacheTimer = 0f; _hasDestinationWrite = false; _pendingSinceDay = -1; _straySweepDone = false; _nextStraySweepAttemptAt = 0f; InteractionCooldown.Clear(); HitNoticeCooldown.Clear(); NpcDefinitionRegistry.Reset(); SnikkPrefabs.ResetScene(); } internal static void Shutdown() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { Despawn("plugin unload"); } SaveState(); _registeredRpc = null; } internal static void Update() { Register(); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null) { return; } EnsureWorldState(); if (_state == null) { return; } SweepStraySnikkZdos(); if (!MercConfig.SnikkEnabled.Value) { if (HasActiveEncounter && _testEncounter) { _runtimeTimer += Time.deltaTime; _phaseTimer += Time.deltaTime; if (_runtimeTimer >= Mathf.Max(60f, MercConfig.SnikkHardTimeoutSeconds.Value)) { Despawn("hard test encounter timeout"); } else { UpdateEncounter(Time.deltaTime); } } else if (HasActiveEncounter) { Despawn("feature disabled"); } return; } NpcDefinitionRegistry.EnsureLoaded(); if (HasActiveEncounter) { _runtimeTimer += Time.deltaTime; _phaseTimer += Time.deltaTime; if (_runtimeTimer >= Mathf.Max(60f, MercConfig.SnikkHardTimeoutSeconds.Value)) { Despawn("hard encounter timeout"); } else { UpdateEncounter(Time.deltaTime); } return; } _schedulerTimer += Time.deltaTime; if (!(_schedulerTimer < Mathf.Max(2f, MercConfig.SnikkSchedulerPollSeconds.Value))) { _schedulerTimer = 0f; if (!(Time.unscaledTime < _nextAttemptAt)) { EvaluateSchedule(); } } } private static void EnsureWorldState() { long currentWorldUid = WorldDataService.CurrentWorldUid; string statePath = WorldDataService.StatePath; if (currentWorldUid != 0L && !string.IsNullOrWhiteSpace(statePath) && (_loadedWorldUid != currentWorldUid || _state == null)) { _loadedWorldUid = currentWorldUid; string text = Path.Combine(statePath, "NPCs"); Directory.CreateDirectory(text); _stateFile = Path.Combine(text, "snikk.json"); _state = LoadState(_stateFile) ?? new PersistentState(); SweepStraySnikkZdos(); int currentDay = WorldDataService.CurrentDay; if (_state.NextNormalVisitDay <= 0) { _state.NextNormalVisitDay = currentDay + NextVisitInterval(); } if (_state.LastSuccessfulSightingDay <= 0) { _state.LastSuccessfulSightingDay = currentDay; } SaveState(); MercPlugin.Log($"[Snikk] Schedule ready: worldDay={currentDay}, nextNormal={_state.NextNormalVisitDay}, lastSighting={_state.LastSuccessfulSightingDay}."); } } private static void SweepStraySnikkZdos() { //IL_0081: 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) if (_straySweepDone || Time.unscaledTime < _nextStraySweepAttemptAt) { return; } if (!IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { _nextStraySweepAttemptAt = Time.unscaledTime + 5f; return; } try { List list = new List(); int num = 0; while (ZDOMan.instance.GetAllZDOsWithPrefabIterative("DynamicNPC_Snikk", list, ref num)) { } int num2 = 0; foreach (ZDO item in list) { if (item != null && item.IsValid() && !(item.m_uid == _activeId)) { BannerAuthority.DestroyWorldZdo(item, "stray Snikk from an interrupted visit"); num2++; } } if (num2 > 0) { MercPlugin.Log($"[Snikk] Removed {num2} stray Snikk ZDO(s) left by an interrupted visit."); } _straySweepDone = true; } catch (Exception ex) { _nextStraySweepAttemptAt = Time.unscaledTime + 5f; MercPlugin.LogWarn("[Snikk] Stray sweep failed: " + ex.Message); } } private static void EvaluateSchedule() { int currentDay = WorldDataService.CurrentDay; bool flag = _forceFindRequested || currentDay - _state.LastSuccessfulSightingDay >= Mathf.Max(1, MercConfig.SnikkForcedFindDays.Value); bool flag2 = _forceBaseRequested || currentDay >= _state.NextNormalVisitDay; if (!flag && !flag2) { return; } List onlinePlayers = GetOnlinePlayers(); if (onlinePlayers.Count == 0) { return; } OnlinePlayer onlinePlayer; if (flag) { onlinePlayer = SelectTarget(onlinePlayers); if (onlinePlayer == null) { return; } MercPlugin.Log("[Snikk] Forced world encounter due; selected " + onlinePlayer.Name + "."); } else { List list = onlinePlayers.Where((OnlinePlayer p) => WorldDataService.TryFindRecognizedBase(p.Position, p.Name, out p.Base)).ToList(); if (list.Count == 0) { if (_pendingSinceDay != currentDay) { _pendingSinceDay = currentDay; MercPlugin.Log($"[Snikk] Normal visit due on day {_state.NextNormalVisitDay}; waiting for an eligible player at a recognized base."); } return; } onlinePlayer = SelectTarget(list); if (onlinePlayer == null) { return; } MercPlugin.Log("[Snikk] Selected " + onlinePlayer.Name + " at " + (onlinePlayer.Base?.Name ?? "a recognized base") + "."); } if (!TryStartEncounter(onlinePlayer, flag)) { _nextAttemptAt = Time.unscaledTime + Mathf.Max(10f, MercConfig.SnikkRetrySeconds.Value); if (Time.unscaledTime - _lastSpawnFailLogAt > 300f) { _lastSpawnFailLogAt = Time.unscaledTime; MercPlugin.LogWarn("[Snikk] No safe/pathable spawn candidate was found; visit remains pending."); } } else { _pendingSinceDay = -1; } } private static List GetOnlinePlayers() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) List list = new List(); HashSet hashSet = new HashSet(); try { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone() && hashSet.Add(peer.m_characterID)) { ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); OnlinePlayer onlinePlayer = BuildOnlinePlayer(peer.m_uid, peer.m_characterID, zDO, peer.m_playerName); if (onlinePlayer != null) { list.Add(onlinePlayer); } } } if ((Object)(object)Player.m_localPlayer != (Object)null) { ZNetView component = ((Component)Player.m_localPlayer).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && hashSet.Add(component.GetZDO().m_uid)) { OnlinePlayer onlinePlayer2 = BuildOnlinePlayer(ZNet.GetUID(), component.GetZDO().m_uid, component.GetZDO(), Player.m_localPlayer.GetPlayerName()); if (onlinePlayer2 != null) { list.Add(onlinePlayer2); } } } } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Could not enumerate online players: " + ex.Message); } return list; } private static OnlinePlayer BuildOnlinePlayer(long peerUid, ZDOID id, ZDO zdo, string peerName) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) if (zdo == null || !zdo.IsValid()) { return null; } bool flag = zdo.GetBool(ZDOVars.s_dead, false) || zdo.GetFloat(ZDOVars.s_health, 1f) <= 0f; if (flag) { return null; } return new OnlinePlayer { PeerUid = peerUid, CharacterId = id, Zdo = zdo, PlayerId = zdo.GetLong(ZDOVars.s_playerID, 0L), Name = zdo.GetString(ZDOVars.s_playerName, peerName ?? "Player"), Position = zdo.GetPosition(), Rotation = zdo.GetRotation(), Dead = flag }; } private static OnlinePlayer SelectTarget(List candidates) { if (candidates == null || candidates.Count == 0) { return null; } IEnumerable enumerable = candidates; if (candidates.Count > 1) { List list = candidates.Where((OnlinePlayer p) => p.PlayerId != _state.LastTargetPlayerId).ToList(); if (list.Count > 0) { enumerable = list; } } List> list2 = new List>(); foreach (OnlinePlayer item in enumerable) { Relationship value; int num = (_state.Relationships.TryGetValue(item.PlayerId, out value) ? value.LastVisitDay : int.MinValue); list2.Add(new KeyValuePair(item, num * 100000 + Rng.Next(100000))); } list2.Sort((KeyValuePair left, KeyValuePair right) => left.Value.CompareTo(right.Value)); if (list2.Count <= 0) { return null; } return list2[0].Key; } private static bool TryStartEncounter(OnlinePlayer target, bool forced) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //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_0033: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) GameObject prefab = SnikkPrefabs.GetPrefab(); if ((Object)(object)prefab == (Object)null || target == null) { return false; } if (!TryFindSpawnPoint(target, forced, out var spawn, out var leave)) { return false; } GameObject val = null; try { Vector3 val2 = spawn; Vector3 val3 = target.Position - spawn; val = Object.Instantiate(prefab, val2, Quaternion.LookRotation(((Vector3)(ref val3)).normalized, Vector3.up)); ZNetView val4 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val4 == (Object)null || !val4.IsValid()) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } return false; } ZDO zDO = val4.GetZDO(); zDO.Set("dnpc_snikk_phase", 1); zDO.Set("dnpc_snikk_destination", target.Position); zDO.Set("dnpc_snikk_target_name", target.Name); zDO.Set("dnpc_snikk_target_id", target.PlayerId); zDO.Set("dnpc_snikk_leave", leave); _activeId = zDO.m_uid; _targetCharacterId = target.CharacterId; _targetPlayerId = target.PlayerId; _targetName = target.Name; _phase = VisitPhase.ApproachTarget; _runtimeTimer = 0f; _phaseTimer = 0f; _sightingTimer = 0f; _idleUntil = 0f; _lastMovementCheck = 0f; _lastMovementPosition = spawn; _stuckCount = 0; _forcedEncounter = forced; _encounterHitCount = 0; HitNoticeCooldown.Clear(); _forceBaseRequested = false; _forceFindRequested = false; _cachedTarget = null; _targetCacheTimer = 0f; _lastDestinationWrite = target.Position; _hasDestinationWrite = true; _pendingSinceDay = -1; MercPlugin.Log($"[Snikk] Spawn candidate accepted at {Vector3.Distance(spawn, target.Position):F1}m; id={_activeId}, forced={forced}."); return true; } catch (Exception ex) { DestroyIncompleteSpawn(val, "spawn initialization exception"); if (!((ZDOID)(ref _activeId)).IsNone()) { ClearRuntime("spawn initialization failed"); } MercPlugin.LogWarn("[Snikk] Encounter spawn failed: " + ex); return false; } } private static void DestroyIncompleteSpawn(GameObject instance, string reason) { if ((Object)(object)instance == (Object)null) { return; } try { ZNetView component = instance.GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val != null && val.IsValid()) { BannerAuthority.DestroyWorldZdo(val, "incomplete Snikk: " + reason); } else { Object.Destroy((Object)(object)instance); } } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Could not remove incomplete spawn: " + ex.Message); Object.Destroy((Object)(object)instance); } } private static bool TryFindSpawnPoint(OnlinePlayer target, bool forced, out Vector3 spawn, out Vector3 leave) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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) //IL_0113: 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_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: 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_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) spawn = default(Vector3); leave = default(Vector3); if ((Object)(object)ZoneSystem.instance == (Object)null || (Object)(object)Pathfinding.instance == (Object)null) { return false; } float num = Mathf.Max(20f, MercConfig.SnikkSpawnMinimumDistance.Value); float num2 = Mathf.Max(num + 5f, MercConfig.SnikkSpawnMaximumDistance.Value + (forced ? 10f : 0f)); Vector3 val = target.Rotation * Vector3.forward; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.1f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); for (int i = 0; i < 24; i++) { float num3 = 140f + (float)Rng.NextDouble() * 80f; Vector3 val2 = Quaternion.Euler(0f, num3, 0f) * val; float num4 = num + (float)Rng.NextDouble() * (num2 - num); Vector3 grounded = target.Position + val2 * num4; if (TryGroundPoint(grounded, out grounded) && ZoneSystem.instance.IsZoneLoaded(grounded) && (forced || Pathfinding.instance.HavePath(grounded, target.Position, (AgentType)6))) { spawn = grounded; Vector3 val3 = grounded - target.Position; Vector3 normalized = ((Vector3)(ref val3)).normalized; Vector3 point = target.Position + normalized * Mathf.Max(70f, num2); leave = (TryGroundPoint(point, out var grounded2) ? grounded2 : (grounded + normalized * 40f)); return true; } } return false; } private static bool TryGroundPoint(Vector3 point, out Vector3 grounded) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) grounded = point; try { float num = default(float); if (!ZoneSystem.instance.GetSolidHeight(point, ref num, 1000)) { return false; } point.y = num + 0.1f; WaterVolume val = null; if (Floating.GetWaterLevel(point, ref val) > num + 0.25f) { return false; } grounded = point; return true; } catch { return false; } } private static void UpdateEncounter(float dt) { //IL_0005: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = ZDOMan.instance.GetZDO(_activeId); _targetCacheTimer -= dt; if (_cachedTarget == null || _targetCacheTimer <= 0f) { _targetCacheTimer = 0.5f; _cachedTarget = GetOnlinePlayers().FirstOrDefault((OnlinePlayer p) => p.CharacterId == _targetCharacterId || p.PlayerId == _targetPlayerId); } OnlinePlayer cachedTarget = _cachedTarget; if (zDO == null || !zDO.IsValid()) { ClearRuntime("active ZDO disappeared"); return; } if (zDO.GetInt("dnpc_snikk_presentation_ready", 0) < 0) { Despawn("clothing presentation failed"); return; } if (cachedTarget == null) { Despawn("target disconnected or died"); return; } Vector3 position = zDO.GetPosition(); float num = Vector3.Distance(position, cachedTarget.Position); if (_phase == VisitPhase.ApproachTarget) { WriteDestinationIfMoved(zDO, cachedTarget.Position); if (num <= Mathf.Max(5f, MercConfig.SnikkSightingDistance.Value)) { _sightingTimer += dt; } else { _sightingTimer = 0f; } if (_sightingTimer >= Mathf.Max(0.5f, MercConfig.SnikkSightingSeconds.Value)) { CompleteSighting(cachedTarget, zDO); return; } CheckStuck(zDO, cachedTarget, position, num); if (_phaseTimer > 180f) { Despawn("approach timeout/path failure"); } } else if (_phase == VisitPhase.IdleNearTarget) { WriteDestinationIfMoved(zDO, cachedTarget.Position); if (Time.unscaledTime >= _idleUntil) { Vector3 val = position - cachedTarget.Position; Vector3 vec = zDO.GetVec3("dnpc_snikk_leave", position + ((Vector3)(ref val)).normalized * 60f); SetPhase(zDO, VisitPhase.LeaveTarget, vec); MercPlugin.Log("[Snikk] Gossip delivered; leaving encounter."); } } else if (_phase == VisitPhase.LeaveTarget && (num >= 65f || _phaseTimer >= 90f)) { Despawn("departure complete"); } } private static void WriteDestinationIfMoved(ZDO snikk, Vector3 destination) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_hasDestinationWrite || !(Vector3.Distance(_lastDestinationWrite, destination) < 2.5f)) { _lastDestinationWrite = destination; _hasDestinationWrite = true; snikk.Set("dnpc_snikk_destination", destination); } } private static void CheckStuck(ZDO snikk, OnlinePlayer target, Vector3 position, float distance) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_0091: Unknown result type (might be due to invalid IL or missing references) if (Time.unscaledTime - _lastMovementCheck < 10f) { return; } float num = Vector3.Distance(position, _lastMovementPosition); _lastMovementPosition = position; _lastMovementCheck = Time.unscaledTime; if (num >= 1.5f || distance <= 6f) { _stuckCount = 0; return; } _stuckCount++; if (_stuckCount == 1) { Vector3 val = Random.insideUnitSphere * 5f; val.y = 0f; Vector3 grounded = target.Position + val; if (TryGroundPoint(grounded, out grounded)) { snikk.Set("dnpc_snikk_destination", grounded); } MercPlugin.LogWarn("[Snikk] Approach appears stuck; trying a nearby reachable destination."); } else if (TeleportNearTarget(snikk, target)) { MercPlugin.LogWarn("[Snikk] Approach stalled; teleported Snikk closer to " + target.Name + "."); } else if (_stuckCount >= 5) { Despawn("irrecoverable navigation stall"); } } private static bool TeleportNearTarget(ZDO snikk, OnlinePlayer target) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 8; i++) { Vector3 val = Quaternion.Euler(0f, (float)Rng.Next(360), 0f) * Vector3.forward; Vector3 grounded = target.Position + val * 7f; if (!TryGroundPoint(grounded, out grounded)) { continue; } try { Vector3 val2 = target.Position - grounded; Quaternion rotation = Quaternion.LookRotation(((Vector3)(ref val2)).normalized, Vector3.up); if (snikk.GetOwner() != ZNet.GetUID()) { snikk.SetOwner(ZNet.GetUID()); } snikk.SetPosition(grounded); snikk.SetRotation(rotation); GameObject val3 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(snikk.m_uid) : null); (((Object)(object)val3 != (Object)null) ? val3.GetComponent() : null)?.ApplyAuthoritativeTeleport(grounded, rotation); snikk.Set("dnpc_snikk_destination", target.Position); ZDOMan instance = ZDOMan.instance; if (instance != null) { instance.ForceSendZDO(snikk.m_uid); } _lastMovementPosition = grounded; return true; } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Teleport near target failed: " + ex.Message); return false; } } return false; } private static void CompleteSighting(OnlinePlayer target, ZDO snikk) { //IL_0138: Unknown result type (might be due to invalid IL or missing references) int currentDay = WorldDataService.CurrentDay; List list; if (_testEncounter) { list = WorldDataService.SelectGossipFacts(target.PlayerId, currentDay, RecentlyBlockedFactIds(), NpcDefinitionRegistry.Snikk.MaxFactsPerResponse); RestrictFactsToConsented(list, target.PlayerId); } else { _state.LastSuccessfulSightingDay = currentDay; _state.NextNormalVisitDay = currentDay + NextVisitInterval(); _state.LastTargetPlayerId = target.PlayerId; _state.VisitSequence++; Relationship relationship = GetRelationship(target.PlayerId, target.Name); relationship.TimesVisited++; relationship.LastVisitDay = currentDay; List blockedIds = RecentlyBlockedFactIds(); list = WorldDataService.SelectGossipFacts(target.PlayerId, currentDay, blockedIds, NpcDefinitionRegistry.Snikk.MaxFactsPerResponse); RestrictFactsToConsented(list, target.PlayerId); foreach (WorldDataService.GossipFact item in list) { _state.RecentFacts.Add(new RecentFact { Id = item.Id, UsedDay = currentDay, VisitSequence = _state.VisitSequence }); } TrimRecentFacts(); SaveState(); } SetPhase(snikk, VisitPhase.IdleNearTarget, target.Position); float num = SnikkRules.IdleDurationSeconds(_testEncounter, MercConfig.SnikkIdleMinimumSeconds.Value, MercConfig.SnikkIdleMaximumSeconds.Value, (float)Rng.NextDouble()); _idleUntil = Time.unscaledTime + num; MercPlugin.Log(string.Format("[Snikk] Encounter sighted: worldDay={0}, player={1}, facts={2}, test={3}.", currentDay, target.Name, string.Join(",", list.Select((WorldDataService.GossipFact f) => f.Id).ToArray()), _testEncounter)); SnikkDialogueService.Generate(target.Name, list, delegate(string reply) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Say(reply, _activeId); }, _testEncounter); } private static void RestrictFactsToConsented(List facts, long targetPlayerId) { if (facts != null && !WorldDataService.OtherPlayerActivityShared()) { facts.RemoveAll((WorldDataService.GossipFact f) => f.PlayerId != targetPlayerId && f.PlayerId != 0); } } private static void SetPhase(ZDO snikk, VisitPhase phase, Vector3 destination) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) _phase = phase; _phaseTimer = 0f; snikk.Set("dnpc_snikk_phase", (int)phase); snikk.Set("dnpc_snikk_destination", destination); } internal static void RequestInteraction(ZDOID snikkId) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) Register(); if (ZRoutedRpc.instance != null && !((ZDOID)(ref snikkId)).IsNone()) { ZRoutedRpc.instance.InvokeRoutedRPC("DynamicNPCs_SnikkInteractRequestV1", new object[1] { snikkId }); } } private unsafe static void OnInteractionRequest(long sender, ZDOID snikkId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || snikkId != _activeId || _phase == VisitPhase.None) { MercPlugin.LogDebug($"[Snikk] Interaction request ignored: sender={sender}, snikk={snikkId}."); return; } MercPlugin.Log($"[Snikk] Interaction request received: sender={sender}, snikk={snikkId}."); if (!ServerAuthority.TryResolveSender(sender, out var state)) { return; } ZDO zDO = ZDOMan.instance.GetZDO(snikkId); if (zDO == null || Vector3.Distance(zDO.GetPosition(), state.Position) > 8f) { return; } if (InteractionCooldown.TryGetValue(state.PlayerId, out var value) && Time.unscaledTime < value) { ServerAuthority.SendPlayerMessage(sender, "Snikk ignores you for a moment."); return; } InteractionCooldown[state.PlayerId] = Time.unscaledTime + 5f; if (ServerAuthority.TryUseKnowledgeBudget(sender)) { List blockedIds = RecentlyBlockedFactIds(); NpcSupport.Forget(state, DialogueRole.Visitor, ((object)(*(ZDOID*)(&snikkId))/*cast due to .constrained prefix*/).ToString()); List list = WorldDataService.SelectGossipFacts(state.PlayerId, WorldDataService.CurrentDay, blockedIds, 1); RestrictFactsToConsented(list, state.PlayerId); if (list.Count > 0 && !_testEncounter) { _state.RecentFacts.Add(new RecentFact { Id = list[0].Id, UsedDay = WorldDataService.CurrentDay, VisitSequence = _state.VisitSequence }); SaveState(); } SnikkDialogueService.Generate(state.PlayerName, list, delegate(string reply) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) Say(reply, snikkId); }, _testEncounter); } } private unsafe static void OnKnowledgeRequest(long sender, ZDOID snikkId, string question) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0084: 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) //IL_0126: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || snikkId != _activeId || string.IsNullOrWhiteSpace(question) || question.Length > 512 || !MercConfig.HearPlayerChat.Value || !ServerAuthority.TryResolveSender(sender, out var state) || state.IsDead || !FarmerInspection.IsFinite(state.Position)) { return; } ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(snikkId) : null); if (val == null || !val.IsValid() || !FarmerInspection.IsFinite(val.GetPosition()) || Vector3.Distance(state.Position, val.GetPosition()) > 15f || (InteractionCooldown.TryGetValue(state.PlayerId, out var value) && Time.unscaledTime < value)) { return; } InteractionCooldown[state.PlayerId] = Time.unscaledTime + 5f; if (ServerAuthority.TryUseKnowledgeBudget(sender)) { string text = NpcSupport.Answer(question, state, DialogueRole.Visitor, ((object)(*(ZDOID*)(&snikkId))/*cast due to .constrained prefix*/).ToString()); if (text.Length > 1200) { text = text.Substring(0, 1200); } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "DynamicNPCs_SnikkSpeechV1", new object[2] { snikkId, text }); } } } internal static void NotifyPlayerHit(ZDOID snikkId, ZDOID attackerId) { //IL_0036: 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) Register(); if (ZRoutedRpc.instance != null && !((ZDOID)(ref snikkId)).IsNone() && !((ZDOID)(ref attackerId)).IsNone()) { ZRoutedRpc.instance.InvokeRoutedRPC(ServerAuthority.GetServerPeerUid(), "DynamicNPCs_SnikkHitNoticeV1", new object[2] { snikkId, attackerId }); } } private static void OnHitNotice(long sender, ZDOID snikkId, ZDOID attackerId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || snikkId != _activeId) { return; } ZDO zDO = ZDOMan.instance.GetZDO(snikkId); if (sender != ZNet.GetUID()) { ICoreServices core = MercPlugin.Core; IPlayerIdentityService val = ((core != null) ? core.Identity : null); PlayerIdentity val2 = default(PlayerIdentity); if (val == null || !val.TryGetPeer(sender, ref val2) || !val2.IsResolved) { MercPlugin.LogWarn($"[Snikk] Rejected hit notice from unresolved ModCore peer={sender}."); return; } } if (zDO == null || !zDO.IsValid() || zDO.GetOwner() != sender) { MercPlugin.LogWarn($"[Snikk] Rejected hit notice from non-simulator sender={sender}, snikk={snikkId}."); return; } OnlinePlayer onlinePlayer = GetOnlinePlayers().FirstOrDefault((OnlinePlayer player) => player.CharacterId == attackerId); if (onlinePlayer != null && onlinePlayer.PlayerId != 0L && !(Vector3.Distance(zDO.GetPosition(), onlinePlayer.Position) > 35f) && (!HitNoticeCooldown.TryGetValue(onlinePlayer.PlayerId, out var value) || SnikkRules.CanAcceptHitNotice(Time.unscaledTime, value))) { HitNoticeCooldown[onlinePlayer.PlayerId] = Time.unscaledTime + 1f; long playerId = onlinePlayer.PlayerId; string name = onlinePlayer.Name; Relationship relationship = GetRelationship(playerId, name); relationship.TimesAttacked++; relationship.LastAttackDay = WorldDataService.CurrentDay; _encounterHitCount++; SaveState(); Say((_encounterHitCount == 1) ? "Stop, stupid." : SnikkDialogueService.AttackFallback(_encounterHitCount, name), snikkId); MercPlugin.Log($"[Snikk] Recorded direct player hit: player={name}, encounterHits={_encounterHitCount}, lifetimeHits={relationship.TimesAttacked}."); } } private static void Say(string text, ZDOID expectedId) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0073: 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_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_009e: 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) //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (!IsServer() || string.IsNullOrWhiteSpace(text) || expectedId != _activeId) { return; } ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(expectedId) : null); if (val == null || !val.IsValid() || ZRoutedRpc.instance == null) { return; } string text2 = ((text.Trim().Length <= 600) ? text.Trim() : text.Trim().Substring(0, 600)); Vector3 position = val.GetPosition(); HashSet hashSet = new HashSet(); foreach (OnlinePlayer onlinePlayer in GetOnlinePlayers()) { Vector3 val2 = onlinePlayer.Position - position; if (SnikkRules.IsWithinLocalSpeech(((Vector3)(ref val2)).sqrMagnitude) && hashSet.Add(onlinePlayer.PeerUid)) { ZRoutedRpc.instance.InvokeRoutedRPC(onlinePlayer.PeerUid, "DynamicNPCs_SnikkSpeechV1", new object[2] { expectedId, text2 }); } } MercPlugin.LogDebug($"[Snikk] Speech routed to {hashSet.Count} nearby player(s)."); } private static void OnSpeech(long sender, ZDOID snikkId, string text) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (ServerAuthority.IsAuthoritativeServerSender(sender)) { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(snikkId) : null); SnikkController snikkController = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)snikkController != (Object)null) { snikkController.ShowServerSpeech(text); } } } internal static void RequestAdminCommand(string command) { Register(); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ServerAuthority.GetServerPeerUid(), "DynamicNPCs_SnikkAdminRequestV1", new object[1] { command ?? "status" }); } } private static void OnAdminRequest(long sender, string command) { if (IsServer()) { MercPlugin.Log($"[Snikk] Admin command received: sender={sender}, command='{command}'."); if (!ServerAuthority.TryResolveSender(sender, out var state) || !ServerAuthority.IsSenderAdmin(sender)) { MercPlugin.LogWarn($"[Snikk] Admin command rejected: sender={sender} did not resolve to a server admin player."); ReplyAdmin(sender, "Snikk commands require a server admin player."); return; } string text = ExecuteAdmin(command, state); MercPlugin.Log("[Snikk] Admin command result: player=" + state.PlayerName + ", command='" + command + "', result=" + text); ReplyAdmin(sender, text); } } private static string ExecuteAdmin(string command, ServerAuthority.SenderPlayerState player) { EnsureWorldState(); string[] array = (command ?? "status").Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string text = ((array.Length != 0) ? array[0].ToLowerInvariant() : "status"); int currentDay = WorldDataService.CurrentDay; switch (text) { case "status": return StatusText(); case "force-base": _forceBaseRequested = true; _nextAttemptAt = 0f; _schedulerTimer = 999f; return "Snikk base visit forced; it remains pending until an eligible player is at a recognized base."; case "force-find": _forceFindRequested = true; _nextAttemptAt = 0f; _schedulerTimer = 999f; return "Snikk wilderness find forced; the server will attempt it on the next scheduler tick."; case "despawn": Despawn("admin command"); return "Active Snikk encounter despawned; schedule preserved."; case "bones": return SnikkPrefabs.DescribePrefab(); case "test": return StartTestEncounter(player); case "reset-schedule": _state.LastSuccessfulSightingDay = currentDay; _state.NextNormalVisitDay = currentDay + NextVisitInterval(); _forceBaseRequested = (_forceFindRequested = false); SaveState(); return "Snikk schedule reset. " + StatusText(); case "set-last-sighting": { if (array.Length >= 2 && int.TryParse(array[1], out var result)) { _state.LastSuccessfulSightingDay = currentDay - Mathf.Max(0, result); SaveState(); return "Last successful sighting set to world day " + _state.LastSuccessfulSightingDay + "."; } break; } } switch (text) { case "gossip": { if (!HasActiveEncounter) { return "No active Snikk encounter."; } List facts = WorldDataService.SelectGossipFacts(player.PlayerId, currentDay, RecentlyBlockedFactIds(), 1); RestrictFactsToConsented(facts, player.PlayerId); SnikkDialogueService.Generate(player.PlayerName, facts, delegate(string reply) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Say(reply, _activeId); }, _testEncounter); return "Snikk gossip requested."; } case "dump-events": return WorldDataService.DumpActivityForPlayer((array.Length > 1) ? string.Join(" ", array.Skip(1).ToArray()) : player.PlayerName); case "clear-recent-gossip": _state.RecentFacts.Clear(); SaveState(); return "Snikk's recent gossip blocklist was cleared."; default: return "Usage: dnpc snikk status|force-base|force-find|despawn|bones|test|reset-schedule|set-last-sighting |gossip|dump-events |clear-recent-gossip"; } } private static string StartTestEncounter(ServerAuthority.SenderPlayerState player) { //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_0081: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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) if (HasActiveEncounter) { return "A Snikk encounter is already active. Use 'dnpc snikk despawn' first, or watch the current one."; } if (player == null || player.PlayerZdo == null || !player.PlayerZdo.IsValid()) { return "Your character was not found on the server; join as a player first."; } if ((Object)(object)SnikkPrefabs.GetPrefab() == (Object)null) { return "The Snikk prefab is not registered in this scene yet."; } OnlinePlayer onlinePlayer = new OnlinePlayer { PeerUid = player.PeerUid, CharacterId = player.CharacterZdoId, Zdo = player.PlayerZdo, PlayerId = player.PlayerId, Name = player.PlayerName, Position = player.Position, Rotation = player.PlayerZdo.GetRotation() }; if (!TryStartVisibleTestEncounter(onlinePlayer)) { return "No safe visible spawn point was found near you; move to open ground and try again."; } _testEncounter = true; MercPlugin.Log($"[Snikk] Test encounter started by admin {onlinePlayer.Name} (id={_activeId})."); return "Snikk test encounter started near you: he will approach, deliver gossip when he sees you, idle for 10-15 seconds, then leave. The visit schedule is not affected. Say 'gossip' (or E him) to hear more lines; 'despawn' ends it early."; } internal static bool TryHearGossipChat(Player player, string message) { //IL_00e1: 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) //IL_0149: 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) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || string.IsNullOrWhiteSpace(message)) { return false; } string text = message.Trim(); bool flag = text.StartsWith("Snikk", StringComparison.OrdinalIgnoreCase) && (text.Length == 5 || char.IsWhiteSpace(text[5]) || ",:!?".IndexOf(text[5]) >= 0); string[] array = message.ToLowerInvariant().Split(new char[12] { ' ', ',', '.', '!', '?', ';', ':', '"', '\'', '-', '(', ')' }, StringSplitOptions.RemoveEmptyEntries); bool flag2 = false; string[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (!(array2[i] != "gossip")) { flag2 = true; break; } } if (!flag2 && !flag) { return false; } SnikkController snikkController = null; float num = 15f; SnikkNpc[] array3 = SnikkNpc.Instances.ToArray(); foreach (SnikkNpc snikkNpc in array3) { if ((Object)(object)snikkNpc == (Object)null) { continue; } float num2 = Vector3.Distance(((Component)snikkNpc).transform.position, ((Component)player).transform.position); if (num2 >= num) { continue; } SnikkController component = ((Component)snikkNpc).GetComponent(); if (!((Object)(object)component == (Object)null)) { ZDOID networkId = component.NetworkId; if (!((ZDOID)(ref networkId)).IsNone()) { num = num2; snikkController = component; } } } if ((Object)(object)snikkController == (Object)null) { return false; } MercPlugin.LogDebug($"[Snikk] 'gossip' heard in chat near {snikkController.NetworkId}; routing to Snikk."); if (flag && !flag2) { Register(); string text2 = text.Substring(5).TrimStart(' ', ',', ':', '!', '?'); if (text2.Length == 0) { text2 = "hello"; } if (text2.Length > 512) { text2 = text2.Substring(0, 512); } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(ServerAuthority.GetServerPeerUid(), "jg224.dnpc.SnikkKnowledgeV1", new object[2] { snikkController.NetworkId, text2 }); } } else { RequestInteraction(snikkController.NetworkId); } return true; } private static bool SpawnTestSnikkAt(OnlinePlayer target, Vector3 spawn) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //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_0033: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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) //IL_0076: 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_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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: 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_018b: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: 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_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) Vector3 val = spawn - target.Position; Vector3 val2; if (!(((Vector3)(ref val)).sqrMagnitude > 0.01f)) { val2 = Vector3.forward; } else { val = spawn - target.Position; val2 = ((Vector3)(ref val)).normalized; } Vector3 val3 = val2; Vector3 point = target.Position + val3 * 70f; point = (TryGroundPoint(point, out var grounded) ? grounded : (spawn + val3 * 40f)); GameObject val4 = null; try { GameObject prefab = SnikkPrefabs.GetPrefab(); val = target.Position - spawn; val4 = Object.Instantiate(prefab, spawn, Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up)); ZNetView val5 = (((Object)(object)val4 != (Object)null) ? val4.GetComponent() : null); if ((Object)(object)val5 == (Object)null || !val5.IsValid()) { DestroyIncompleteSpawn(val4, "invalid test-spawn network view"); return false; } ZDO zDO = val5.GetZDO(); zDO.Set("dnpc_snikk_phase", 1); zDO.Set("dnpc_snikk_destination", target.Position); zDO.Set("dnpc_snikk_target_name", target.Name); zDO.Set("dnpc_snikk_target_id", target.PlayerId); zDO.Set("dnpc_snikk_leave", point); _activeId = zDO.m_uid; _targetCharacterId = target.CharacterId; _targetPlayerId = target.PlayerId; _targetName = target.Name; _phase = VisitPhase.ApproachTarget; _runtimeTimer = 0f; _phaseTimer = 0f; _sightingTimer = 0f; _idleUntil = 0f; _lastMovementCheck = 0f; _lastMovementPosition = spawn; _stuckCount = 0; _forcedEncounter = true; _encounterHitCount = 0; HitNoticeCooldown.Clear(); _cachedTarget = null; _targetCacheTimer = 0f; _lastDestinationWrite = target.Position; _hasDestinationWrite = true; MercPlugin.Log($"[Snikk] Test spawn accepted at {Vector3.Distance(spawn, target.Position):F1}m (relaxed); id={_activeId}."); return true; } catch (Exception ex) { DestroyIncompleteSpawn(val4, "test-spawn initialization exception"); if (!((ZDOID)(ref _activeId)).IsNone()) { ClearRuntime("test-spawn initialization failed"); } MercPlugin.LogWarn("[Snikk] Test spawn failed: " + ex); return false; } } private static bool TryStartVisibleTestEncounter(OnlinePlayer target) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_009e: 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) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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) Vector3 val = target.Rotation * Vector3.forward; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.1f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); for (int i = 0; i < 8; i++) { Vector3 val2 = Quaternion.Euler(0f, SnikkRules.TestSpawnYawDegrees(i), 0f) * val; Vector3 grounded = target.Position + val2 * 8f; if (TryGroundPoint(grounded, out grounded) && ZoneSystem.instance.IsZoneLoaded(grounded) && SpawnTestSnikkAt(target, grounded)) { return true; } } Vector3 spawn = target.Position + val * 8f; spawn.y = target.Position.y + 0.1f; return SpawnTestSnikkAt(target, spawn); } private static string StatusText() { int currentDay = WorldDataService.CurrentDay; return $"Snikk enabled={MercConfig.SnikkEnabled.Value}; active={HasActiveEncounter}; phase={_phase}; " + "lastEnd=" + _lastEndReason + "; " + $"worldDay={currentDay}; nextNormal={_state?.NextNormalVisitDay ?? 0}; " + $"normalPending={_state != null && currentDay >= _state.NextNormalVisitDay}; " + $"lastSighting={_state?.LastSuccessfulSightingDay ?? 0}; " + $"daysSinceSighting={((_state != null) ? (currentDay - _state.LastSuccessfulSightingDay) : 0)}; " + $"forcedPending={_state != null && currentDay - _state.LastSuccessfulSightingDay >= MercConfig.SnikkForcedFindDays.Value}."; } private static void ReplyAdmin(long target, string text) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(target, "DynamicNPCs_SnikkAdminReplyV1", new object[1] { text ?? "" }); } } private static void OnAdminReply(long sender, string text) { if (ServerAuthority.IsAuthoritativeServerSender(sender)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false); } if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString("DynamicNPCs", text, (Type)1, false); } Terminal.Log((object)("[DynamicNPCs] " + text)); } } private static Relationship GetRelationship(long playerId, string name) { if (!_state.Relationships.TryGetValue(playerId, out var value)) { value = new Relationship { PlayerId = playerId, Name = (name ?? "Player") }; _state.Relationships[playerId] = value; } value.Name = (string.IsNullOrWhiteSpace(name) ? value.Name : name); return value; } private static List RecentlyBlockedFactIds() { int minimumSequence = _state.VisitSequence - Mathf.Max(0, MercConfig.SnikkRecentVisitBlock.Value) + 1; return (from f in _state.RecentFacts where f.VisitSequence >= minimumSequence select f.Id into id where !string.IsNullOrWhiteSpace(id) select id).Distinct().ToList(); } private static void TrimRecentFacts() { int minimum = _state.VisitSequence - Mathf.Max(6, MercConfig.SnikkRecentVisitBlock.Value * 2); _state.RecentFacts.RemoveAll((RecentFact f) => f.VisitSequence < minimum); if (_state.RecentFacts.Count > 100) { _state.RecentFacts.RemoveRange(0, _state.RecentFacts.Count - 100); } } private static int NextVisitInterval() { int num = Mathf.Max(1, MercConfig.SnikkMinimumVisitDays.Value); int num2 = Mathf.Max(num, MercConfig.SnikkMaximumVisitDays.Value); return Rng.Next(num, num2 + 1); } private static void Despawn(string reason) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!((ZDOID)(ref _activeId)).IsNone() && ZDOMan.instance != null) { ZDO zDO = ZDOMan.instance.GetZDO(_activeId); if (zDO != null) { BannerAuthority.DestroyWorldZdo(zDO, "temporary Snikk: " + reason); } } ClearRuntime(reason); } private static void ClearRuntime(string reason) { //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) if (!((ZDOID)(ref _activeId)).IsNone()) { MercPlugin.Log("[Snikk] Encounter cleared: " + reason + "."); _lastEndReason = reason; } _activeId = default(ZDOID); _targetCharacterId = default(ZDOID); _targetPlayerId = 0L; _targetName = ""; _phase = VisitPhase.None; _runtimeTimer = (_phaseTimer = (_sightingTimer = 0f)); _idleUntil = 0f; _stuckCount = 0; _forcedEncounter = false; _testEncounter = false; _encounterHitCount = 0; HitNoticeCooldown.Clear(); _cachedTarget = null; _targetCacheTimer = 0f; _hasDestinationWrite = false; } private static void SaveState() { if (_state == null || string.IsNullOrWhiteSpace(_stateFile)) { return; } try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\"lastSuccessfulSightingDay\":").Append(_state.LastSuccessfulSightingDay).Append(",\"nextNormalVisitDay\":") .Append(_state.NextNormalVisitDay) .Append(",\"lastTargetPlayerId\":\"") .Append(_state.LastTargetPlayerId.ToString(CultureInfo.InvariantCulture)) .Append("\",\"visitSequence\":") .Append(_state.VisitSequence) .Append(",\"relationships\":["); bool flag = true; foreach (Relationship value in _state.Relationships.Values) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"playerId\":\"").Append(value.PlayerId.ToString(CultureInfo.InvariantCulture)).Append("\",\"name\":\"") .Append(MiniJson.Escape(value.Name ?? "Player")) .Append("\",\"timesVisited\":") .Append(value.TimesVisited) .Append(",\"timesAttacked\":") .Append(value.TimesAttacked) .Append(",\"lastVisitDay\":") .Append(value.LastVisitDay) .Append(",\"lastAttackDay\":") .Append(value.LastAttackDay) .Append('}'); } stringBuilder.Append("],\"recentFacts\":["); flag = true; foreach (RecentFact recentFact in _state.RecentFacts) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"id\":\"").Append(MiniJson.Escape(recentFact.Id ?? "")).Append("\",\"usedDay\":") .Append(recentFact.UsedDay) .Append(",\"visitSequence\":") .Append(recentFact.VisitSequence) .Append('}'); } stringBuilder.Append("]}"); AtomicFile.WriteAllText(_stateFile, stringBuilder.ToString(), Encoding.UTF8); } catch (Exception ex) { MercPlugin.LogWarn("[Snikk] Could not persist schedule state: " + ex.Message); } } private static PersistentState LoadState(string file) { if (!File.Exists(file)) { return null; } try { return ParseStateFile(file); } catch (Exception ex) { string text = file + ".bak"; MercPlugin.LogWarn("[Snikk] Primary schedule state is invalid; attempting its atomic backup: " + ex.Message); if (!File.Exists(text)) { return null; } try { PersistentState result = ParseStateFile(text); string text2 = file + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N").Substring(0, 8); if (File.Exists(file)) { File.Move(file, text2); } MercPlugin.LogWarn("[Snikk] Recovered schedule state from " + Path.GetFileName(text) + "; the invalid primary was preserved as " + Path.GetFileName(text2) + "."); return result; } catch (Exception ex2) { MercPlugin.LogWarn("[Snikk] Schedule backup is also invalid; a new schedule will be created: " + ex2.Message); return null; } } } private static PersistentState ParseStateFile(string file) { if (!(MiniJson.Parse(File.ReadAllText(file)) is Dictionary dictionary)) { throw new InvalidDataException("root value is not an object"); } PersistentState persistentState = new PersistentState { LastSuccessfulSightingDay = Int(dictionary, "lastSuccessfulSightingDay"), NextNormalVisitDay = Int(dictionary, "nextNormalVisitDay"), LastTargetPlayerId = Long(dictionary, "lastTargetPlayerId"), VisitSequence = Int(dictionary, "visitSequence") }; if (dictionary.TryGetValue("relationships", out var value) && value is List source) { foreach (Dictionary item in source.OfType>()) { Relationship relationship = new Relationship { PlayerId = Long(item, "playerId"), Name = String(item, "name", "Player"), TimesVisited = Int(item, "timesVisited"), TimesAttacked = Int(item, "timesAttacked"), LastVisitDay = Int(item, "lastVisitDay"), LastAttackDay = Int(item, "lastAttackDay") }; if (relationship.PlayerId != 0L) { persistentState.Relationships[relationship.PlayerId] = relationship; } } } if (dictionary.TryGetValue("recentFacts", out var value2) && value2 is List source2) { foreach (Dictionary item2 in source2.OfType>()) { persistentState.RecentFacts.Add(new RecentFact { Id = String(item2, "id", ""), UsedDay = Int(item2, "usedDay"), VisitSequence = Int(item2, "visitSequence") }); } } return persistentState; } private static int Int(Dictionary obj, string key) { if (!obj.TryGetValue(key, out var value)) { return 0; } return Convert.ToInt32(value, CultureInfo.InvariantCulture); } private static long Long(Dictionary obj, string key) { if (!obj.TryGetValue(key, out var value) || value == null) { return 0L; } if (!long.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0L; } return result; } private static string String(Dictionary obj, string key, string fallback) { if (!obj.TryGetValue(key, out var value) || !(value is string result)) { return fallback; } return result; } private static bool IsServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static class StageDirector { public static readonly string[] BossKeys = new string[7] { "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" }; public static readonly string[] StageNames = new string[8] { "Meadows (leather)", "Black Forest (bronze)", "Swamp (iron)", "Mountains (silver)", "Plains (black metal)", "Mistlands (carapace)", "Ashlands (flametal)", "Ashlands (flametal, final)" }; private static readonly string[] StageTokens = new string[8] { "dnpc_stage_0", "dnpc_stage_1", "dnpc_stage_2", "dnpc_stage_3", "dnpc_stage_4", "dnpc_stage_5", "dnpc_stage_6", "dnpc_stage_7" }; private static int _lastStage = -1; private static ZoneSystem _observedZoneSystem; public static event Action StageChanged; public static void Reset() { _observedZoneSystem = ZoneSystem.instance; _lastStage = -1; } public static int GetStage() { try { if ((Object)(object)ZoneSystem.instance == (Object)null) { return 0; } int num = 0; for (int i = 0; i < BossKeys.Length; i++) { if (ZoneSystem.instance.GetGlobalKey(BossKeys[i])) { num = Math.Max(num, i + 1); } } return num; } catch { return 0; } } public static string GetStageName(int stage) { return StageNames[Math.Min(Math.Max(stage, 0), StageNames.Length - 1)]; } public static string GetStageArgument(int stage) { int num = Math.Min(Math.Max(stage, 0), StageNames.Length - 1); return MercLocalization.TokenArgument(StageTokens[num]); } public static void Poll() { if ((Object)(object)_observedZoneSystem != (Object)(object)ZoneSystem.instance) { Reset(); } int stage = GetStage(); if (stage != _lastStage) { bool num = _lastStage == -1; _lastStage = stage; if (!num) { MercPlugin.Log($"Progression stage is now {stage}: {GetStageName(stage)}"); StageDirector.StageChanged?.Invoke(stage); } } } } public enum MercClass { Tank, Healer, Archer } public class MercLoadout { public string Chest; public string Legs; public string Helmet; public string Cape; public string Shield; public string Weapon; public string Ammo; public MercLoadout(string chest, string legs, string helmet, string cape, string shield, string weapon, string ammo) { Chest = chest; Legs = legs; Helmet = helmet; Cape = cape; Shield = shield; Weapon = weapon; Ammo = ammo; } } public static class GearTable { private static readonly string[] Chests = new string[8] { "ArmorLeatherChest", "ArmorBronzeChest", "ArmorIronChest", "ArmorWolfChest", "ArmorPaddedCuirass", "ArmorCarapaceChest", "ArmorFlametalChest", "ArmorFlametalChest" }; private static readonly string[] LegsItems = new string[8] { "ArmorLeatherLegs", "ArmorBronzeLegs", "ArmorIronLegs", "ArmorWolfLegs", "ArmorPaddedGreaves", "ArmorCarapaceLegs", "ArmorFlametalLegs", "ArmorFlametalLegs" }; private static readonly string[] Helmets = new string[8] { "HelmetLeather", "HelmetBronze", "HelmetIron", "HelmetDrake", "HelmetPadded", "HelmetCarapace", "HelmetFlametal", "HelmetFlametal" }; private static readonly string[] Capes = new string[8] { "CapeDeerHide", "CapeTrollHide", "CapeWolf", "CapeWolf", "CapeWolf", "CapeFeather", "CapeFeather", "CapeFeather" }; private static readonly string[] TankShields = new string[8] { "ShieldWood", "ShieldBronzeBuckler", "ShieldIronSquare", "ShieldSilver", "ShieldBlackmetal", "ShieldCarapace", "ShieldFlametal", "ShieldFlametal" }; private static readonly string[] TankWeapons = new string[8] { "AxeStone", "SwordBronze", "SwordIron", "SwordSilver", "SwordBlackmetal", "SwordMistwalker", "SwordNiedhogg", "SwordNiedhogg" }; private static readonly string[] HealerClubs = new string[8] { "Club", "MaceBronze", "MaceIron", "MaceSilver", "MaceNeedle", "MaceNeedle", "MaceEldner", "MaceEldner" }; private static readonly string[] ArcherBows = new string[8] { "Bow", "BowFineWood", "BowHuntsman", "BowDraugrFang", "BowDraugrFang", "BowSpineSnap", "BowAshlands", "BowAshlands" }; private static readonly string[] ArcherArrows = new string[8] { "ArrowWood", "ArrowFlint", "ArrowBronze", "ArrowIron", "ArrowObsidian", "ArrowNeedle", "ArrowCarapace", "ArrowCarapace" }; private static readonly string[] LightChests = new string[8] { "ArmorLeatherChest", "ArmorTrollLeatherChest", "ArmorRootChest", "ArmorFenringChest", "ArmorFenringChest", "ArmorMageChest", "ArmorAshlandsMediumChest", "ArmorAshlandsMediumChest" }; private static readonly string[] LightLegs = new string[8] { "ArmorLeatherLegs", "ArmorTrollLeatherLegs", "ArmorRootLegs", "ArmorFenringLegs", "ArmorFenringLegs", "ArmorMageLegs", "ArmorAshlandsMediumlegs", "ArmorAshlandsMediumlegs" }; private static readonly string[] LightHelmets = new string[8] { "HelmetLeather", "HelmetTrollLeather", "HelmetRoot", "HelmetFenring", "HelmetFenring", "HelmetMage", "HelmetAshlandsMediumHood", "HelmetAshlandsMediumHood" }; private static readonly string[] ArcherChests = new string[8] { "ArmorLeatherChest", "ArmorTrollLeatherChest", "ArmorRootChest", "ArmorFenringChest", "ArmorBerserkerUndeadChest", "ArmorBerserkerUndeadChest", "ArmorAshlandsMediumChest", "ArmorAshlandsMediumChest" }; private static readonly string[] ArcherLegs = new string[8] { "ArmorLeatherLegs", "ArmorTrollLeatherLegs", "ArmorRootLegs", "ArmorFenringLegs", "ArmorBerserkerUndeadLegs", "ArmorBerserkerUndeadLegs", "ArmorAshlandsMediumlegs", "ArmorAshlandsMediumlegs" }; private static readonly string[] ArcherHelmets = new string[8] { "HelmetLeather", "HelmetTrollLeather", "HelmetRoot", "HelmetFenring", "HelmetBerserkerUndead", "HelmetBerserkerUndead", "HelmetAshlandsMediumHood", "HelmetAshlandsMediumHood" }; private static readonly string[] HealerChests = new string[8] { "ArmorLeatherChest", "ArmorTrollLeatherChest", "ArmorRootChest", "ArmorFenringChest", "ArmorFenringChest", "ArmorMageChest", "ArmorMageChest", "ArmorMageChest" }; private static readonly string[] HealerLegs = new string[8] { "ArmorLeatherLegs", "ArmorTrollLeatherLegs", "ArmorRootLegs", "ArmorFenringLegs", "ArmorFenringLegs", "ArmorMageLegs", "ArmorMageLegs", "ArmorMageLegs" }; private static readonly string[] HealerHelmets = new string[8] { "HelmetLeather", "HelmetTrollLeather", "HelmetRoot", "HelmetFenring", "HelmetFenring", "HelmetMage", "HelmetMage", "HelmetMage" }; private static readonly Dictionary ItemProgressionTier = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "ArmorLeatherChest", 0 }, { "ArmorLeatherLegs", 0 }, { "HelmetLeather", 0 }, { "CapeDeerHide", 0 }, { "ShieldWood", 0 }, { "AxeStone", 0 }, { "Club", 0 }, { "Bow", 0 }, { "ArrowWood", 0 }, { "ArrowFlint", 0 }, { "ArmorBronzeChest", 1 }, { "ArmorBronzeLegs", 1 }, { "HelmetBronze", 1 }, { "ArmorTrollLeatherChest", 1 }, { "ArmorTrollLeatherLegs", 1 }, { "HelmetTrollLeather", 1 }, { "CapeTrollHide", 1 }, { "ShieldBronzeBuckler", 1 }, { "SwordBronze", 1 }, { "MaceBronze", 1 }, { "BowFineWood", 1 }, { "ArrowBronze", 1 }, { "ArmorIronChest", 2 }, { "ArmorIronLegs", 2 }, { "HelmetIron", 2 }, { "ArmorRootChest", 2 }, { "ArmorRootLegs", 2 }, { "HelmetRoot", 2 }, { "ShieldIronSquare", 2 }, { "SwordIron", 2 }, { "MaceIron", 2 }, { "BowHuntsman", 2 }, { "ArrowIron", 2 }, { "ArmorWolfChest", 3 }, { "ArmorWolfLegs", 3 }, { "HelmetDrake", 3 }, { "ArmorFenringChest", 3 }, { "ArmorFenringLegs", 3 }, { "HelmetFenring", 3 }, { "CapeWolf", 3 }, { "ShieldSilver", 3 }, { "SwordSilver", 3 }, { "MaceSilver", 3 }, { "BowDraugrFang", 3 }, { "ArrowObsidian", 3 }, { "ArmorPaddedCuirass", 4 }, { "ArmorPaddedGreaves", 4 }, { "HelmetPadded", 4 }, { "ArmorBerserkerUndeadChest", 4 }, { "ArmorBerserkerUndeadLegs", 4 }, { "HelmetBerserkerUndead", 4 }, { "ShieldBlackmetal", 4 }, { "SwordBlackmetal", 4 }, { "MaceNeedle", 4 }, { "ArrowNeedle", 4 }, { "ArmorCarapaceChest", 5 }, { "ArmorCarapaceLegs", 5 }, { "HelmetCarapace", 5 }, { "ArmorMageChest", 5 }, { "ArmorMageLegs", 5 }, { "HelmetMage", 5 }, { "CapeFeather", 5 }, { "ShieldCarapace", 5 }, { "SwordMistwalker", 5 }, { "BowSpineSnap", 5 }, { "ArrowCarapace", 5 }, { "ArmorFlametalChest", 6 }, { "ArmorFlametalLegs", 6 }, { "HelmetFlametal", 6 }, { "ArmorAshlandsMediumChest", 6 }, { "ArmorAshlandsMediumlegs", 6 }, { "HelmetAshlandsMediumHood", 6 }, { "ShieldFlametal", 6 }, { "SwordNiedhogg", 6 }, { "MaceEldner", 6 }, { "BowAshlands", 6 } }; private static readonly Dictionary ItemProgressionTierByHash = BuildItemProgressionTierByHash(); private static Dictionary BuildItemProgressionTierByHash() { Dictionary dictionary = new Dictionary(); foreach (KeyValuePair item in ItemProgressionTier) { int stableHashCode = StringExtensionMethods.GetStableHashCode(item.Key); if (dictionary.TryGetValue(stableHashCode, out var value) && value != item.Value) { dictionary[stableHashCode] = 0; } else { dictionary[stableHashCode] = item.Value; } } return dictionary; } public static MercLoadout Get(MercClass @class, int stage) { stage = Math.Min(Math.Max(stage, 0), 7); return @class switch { MercClass.Tank => new MercLoadout(Chests[stage], LegsItems[stage], Helmets[stage], Capes[stage], TankShields[stage], TankWeapons[stage], null), MercClass.Archer => new MercLoadout(ArcherChests[stage], ArcherLegs[stage], ArcherHelmets[stage], Capes[stage], null, ArcherBows[stage], ArcherArrows[stage]), _ => new MercLoadout(HealerChests[stage], HealerLegs[stage], HealerHelmets[stage], Capes[stage], null, HealerClubs[stage], null), }; } public static int StageOfItem(string itemPrefab) { if (string.IsNullOrEmpty(itemPrefab)) { return 0; } if (!ItemProgressionTier.TryGetValue(itemPrefab, out var value)) { return 0; } return value; } public static int StageOfItemHash(int itemPrefabHash) { if (itemPrefabHash == 0) { return 0; } if (!ItemProgressionTierByHash.TryGetValue(itemPrefabHash, out var value)) { return 0; } return value; } } internal sealed class RecipeQuestion { internal string Target = ""; internal bool Upgrade; } internal static class SupportQueryRules { internal static RecipeQuestion Recipe(string question) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return null; } string input = KnowledgeRules.Normalize(question); input = Regex.Replace(input, "^(?:(?:hi|hello|hey|please)\\s+)+", ""); input = Regex.Replace(input, "^(?:can|could|would) you (?:please )?(?:tell me |explain )?", ""); if (Regex.IsMatch(input, "\\b(?:do not|don t|never|instead of)\\b")) { return null; } string[] array = new string[4] { "^(?:(?:how (?:do|can) i|how to|what do i need to|what is needed to) )?(?:craft|make|build|upgrade) (.+)$", "^(?:what (?:materials|ingredients|resources|supplies)(?: do i need| are needed| are required)? (?:for|to (?:craft|make|build))|(?:what is |what s |show me |tell me )?(?:the )?(?:recipe|ingredients|materials|cost|requirements) (?:for|of)) (.+)$", "^(?:how much does|what does) (.+?) (?:cost|require|need)(?: to (?:craft|make|build))?$", "^(.+?) (?:recipe|crafting recipe|building recipe|ingredients|crafting cost)$" }; foreach (string pattern in array) { Match match = Regex.Match(input, pattern, RegexOptions.CultureInvariant); if (match.Success) { string input2 = Regex.Replace(match.Groups[1].Value.Trim(), "\\s+(?:please|again)$", ""); input2 = Regex.Replace(input2, "^(?:a|an|the|some)\\s+", ""); if (input2.Length == 0 || input2.Length > 160) { return new RecipeQuestion(); } return new RecipeQuestion { Target = input2, Upgrade = Regex.IsMatch(input, "\\bupgrade\\b") }; } } return null; } internal static bool IsFollowup(string question) { return FollowupTemplate(question) != null; } internal static bool IsCompanyQuestion(string question, params string[] names) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return false; } string text = KnowledgeRules.Normalize(question); KnowledgeQuery knowledgeQuery = KnowledgeRules.Parse(question); if (knowledgeQuery.ExplicitPlayer) { return false; } string[] array; if (knowledgeQuery.Intent == KnowledgeIntent.PlayerInfo || knowledgeQuery.Intent == KnowledgeIntent.PlayerLocation) { array = names ?? Array.Empty(); foreach (string text2 in array) { if (!string.IsNullOrWhiteSpace(text2) && knowledgeQuery.Target == KnowledgeRules.Normalize(text2)) { return true; } } if (Regex.IsMatch(knowledgeQuery.Target, "^(?:my |our |the )?(?:mercenary company|company abilities|mercenary banner|mercenary hammer|mercenaries|mercenary|company|mender|healer|archer|fletcher|tank|bulwark)$")) { return true; } } for (int j = 0; j < 3; j++) { string text3 = Regex.Replace(text, "^(?:please|hey|hello|hi|can you tell me|could you tell me|would you tell me|do you know)\\s+", ""); if (text3 == text) { break; } text = text3; } text = Regex.Replace(text, "^(what|who) s(?=\\s|$)", "$1 is"); text = Regex.Replace(text, "\\s+please$", ""); string text4 = "(?:(?:my |our |the )?(?:mercenary company|company abilities|mercenary banner|mercenary hammer|mercenaries|mercenary|company|mender|healer|archer|fletcher|tank|bulwark)|(?:the )?(?:renewal|greater heal|resurrect))"; if (Regex.IsMatch(text, "^(?:(?:tell me about|what do you know about|what is|what are|explain|describe) )?" + text4 + "$")) { return true; } if (Regex.IsMatch(text, "^what " + text4 + " (?:does|can do)$")) { return true; } if (Regex.IsMatch(text, "^(?:how (?:do|can) i use|how to use|when (?:does|can) (?:the )?healer use) (?:the )?(?:renewal|greater heal|resurrect)$")) { return true; } if (Regex.IsMatch(text, "^(?:how much does) (?:the )?(?:renewal|greater heal|resurrect) (?:heal|cost)$")) { return true; } array = names ?? Array.Empty(); foreach (string text5 in array) { if (!string.IsNullOrWhiteSpace(text5) && Regex.IsMatch(text, "^(?:what can|what does|how does) " + Regex.Escape(KnowledgeRules.Normalize(text5)) + " (?:do|help|work|fight|heal)(?: for me)?$")) { return true; } } return Regex.IsMatch(text, "^(?:what can|what does|how does) " + text4 + " (?:do|help|work|fight|heal)(?: for me)?$"); } internal static string WorldIntent(string question) { string input = KnowledgeRules.Normalize(question); input = Regex.Replace(input, "^(?:please |can you tell me |could you tell me |do you know )", ""); if (Regex.IsMatch(input, "^(?:what is (?:the )?(?:world|server) name|what s (?:the )?(?:world|server) name|(?:which|what) world (?:is this|are we in)|world name|server name)$")) { return "name"; } if (Regex.IsMatch(input, "^(?:(?:what|which) (?:world |valheim )?day (?:is it|is this|are we on)|world day|valheim day|current world day)$")) { return "day"; } if (Regex.IsMatch(input, "^(?:(?:what is |what s )?(?:the )?(?:world|server) (?:progress|progression|stage)|(?:which|what) bosses (?:have we (?:defeated|killed)|are defeated|have been defeated)|bosses defeated)$")) { return "world_stage"; } if (Regex.IsMatch(input, "^(?:(?:what is |what s )?my (?:progress|progression|stage|company tier)|(?:what|which) (?:stage|tier) (?:is my company|am i at))$")) { return "personal_stage"; } return ""; } internal static bool MatchesAuthoredTopic(string question, string topic) { string input = KnowledgeRules.Normalize(question); string text = KnowledgeRules.Normalize(topic); if (text.Length == 0) { return false; } input = Regex.Replace(input, "^(?:please |can you tell me |could you tell me |do you know )", ""); if (input == text) { return true; } input = Regex.Replace(input, "^(?:tell me about|what do you know about|what is|what s|what are|explain|describe)\\s+(?:the\\s+)?", ""); return input == text; } internal static bool IsStorageQuestion(string question, bool namedPlace) { string input = KnowledgeRules.Normalize(question); if (Regex.IsMatch(input, "\\b(?:do not|don t|how to|how do i|how can i|should i|should we|need to|bring|prepare|craft|build|make|grow|plant|tame|fight)\\b")) { return false; } if (Regex.IsMatch(input, "^(?:do we have|have we got)\\s+")) { return true; } bool flag = Regex.IsMatch(input, "\\b(?:storage|stored|stock|chests?|containers?)\\b"); if ((flag || namedPlace) && Regex.IsMatch(input, "^(?:is there any|are there any)\\s+")) { return true; } if (flag && Regex.IsMatch(input, "^(?:where|how much|how many|check|show|list|what is in|what s in|what is stored)\\b")) { return true; } if (namedPlace) { return Regex.IsMatch(input, "^how (?:much|many) .+? (?:is|are|have we got) (?:at|in)\\b"); } return false; } internal static bool IsPlaceDirection(string question, IEnumerable names) { KnowledgeQuery knowledgeQuery = KnowledgeRules.Parse(question); if (knowledgeQuery.Intent != KnowledgeIntent.PlayerLocation || knowledgeQuery.ExplicitPlayer) { return false; } foreach (string name in names) { if (knowledgeQuery.Target == KnowledgeRules.Normalize(name)) { return true; } } return false; } internal static string FollowupTemplate(string question) { if (string.IsNullOrWhiteSpace(question) || question.Length > 512) { return null; } string input = KnowledgeRules.Normalize(question); input = Regex.Replace(input, "^(?:and|also|please)\\s+", ""); if (Regex.IsMatch(input, "^where (?:can|do) i find (?:it|them)$|^where (?:is it|are they)$")) { return "Where can I find {0}?"; } if (Regex.IsMatch(input, "^how (?:do|can|should) i (?:fight|beat|kill) (?:it|them)$")) { return "How do I fight {0}?"; } if (Regex.IsMatch(input, "^can i tame (?:it|them)$|^how do i tame (?:it|them)$")) { return "Can I tame {0}?"; } if (Regex.IsMatch(input, "^what (?:does it|do they) drop$")) { return "What does {0} drop?"; } if (Regex.IsMatch(input, "^how (?:do|can) i (?:make|craft|build) (?:it|them)$")) { return "How do I craft {0}?"; } return null; } } internal sealed class SupportConversationMemory { private sealed class Entry { internal string Subject; internal string Zone; internal double At; } internal const int Capacity = 128; internal const double LifetimeSeconds = 45.0; private readonly Dictionary _entries = new Dictionary(StringComparer.Ordinal); internal int Count => _entries.Count; internal string Resolve(string key, string question, string zone, double now) { string text = SupportQueryRules.FollowupTemplate(question); if (text == null) { return question; } if (!_entries.TryGetValue(key, out var value) || now < value.At || now - value.At > 45.0 || string.IsNullOrEmpty(zone) || value.Zone != zone) { _entries.Remove(key); return null; } return string.Format(CultureInfo.InvariantCulture, text, value.Subject); } internal void Remember(string key, string subject, string zone, double now) { if (string.IsNullOrEmpty(key)) { return; } if (string.IsNullOrEmpty(subject) || subject.Length > 100 || string.IsNullOrEmpty(zone) || double.IsNaN(now) || double.IsInfinity(now)) { _entries.Remove(key); return; } if (!_entries.ContainsKey(key) && _entries.Count >= 128) { string text = null; double num = double.MaxValue; foreach (KeyValuePair entry in _entries) { if (entry.Value.At < num) { num = entry.Value.At; text = entry.Key; } } if (text != null) { _entries.Remove(text); } } _entries[key] = new Entry { Subject = subject, Zone = zone, At = now }; } internal void Clear() { _entries.Clear(); } } public static class MainThread { private static readonly ConcurrentQueue Queue = new ConcurrentQueue(); public static void Enqueue(Action action) { if (action != null) { Queue.Enqueue(action); } } public static void Drain() { int num = 0; Action result; while (Queue.TryDequeue(out result) && num++ < 64) { try { result(); } catch (Exception arg) { MercPlugin.Log($"MainThread action failed: {arg}"); } } } } public static class MercUtil { private static readonly FieldInfo ZNetSceneNamedPrefabs = AccessTools.Field(typeof(ZNetScene), "m_namedPrefabs"); private static readonly MethodInfo PlayerStopEmote = AccessTools.Method(typeof(Player), "StopEmote", (Type[])null, (Type[])null); private static readonly MethodInfo PlayerOnDeath = AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null); private static readonly MethodInfo CharacterResetGroundContact = AccessTools.Method(typeof(Character), "ResetGroundContact", (Type[])null, (Type[])null); private static readonly MethodInfo CharacterStandUpOnNextGround = AccessTools.Method(typeof(Character), "StandUpOnNextGround", (Type[])null, (Type[])null); public static void SetPrivate(object instance, object value, string fieldName, params Type[] declaringTypes) { for (int i = 0; i < declaringTypes.Length; i++) { FieldInfo fieldInfo = AccessTools.Field(declaringTypes[i], fieldName); if (fieldInfo != null) { fieldInfo.SetValue(instance, value); return; } } MercPlugin.LogWarn("SetPrivate: field '" + fieldName + "' not found"); } public static bool RegisterNamedPrefab(ZNetScene scene, GameObject prefab) { try { if (!(ZNetSceneNamedPrefabs?.GetValue(scene) is IDictionary dictionary)) { MercPlugin.LogWarn("m_namedPrefabs not accessible via reflection"); return false; } dictionary[StringExtensionMethods.GetStableHashCode(((Object)prefab).name)] = prefab; return true; } catch (Exception ex) { MercPlugin.LogWarn("RegisterNamedPrefab failed: " + ex.Message); return false; } } public static GameObject GetPrefabSafe(string name) { try { if ((Object)(object)ZNetScene.instance == (Object)null || string.IsNullOrEmpty(name)) { return null; } return ZNetScene.instance.GetPrefab(name); } catch { return null; } } public static GameObject GetItemPrefabSafe(string name) { try { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrEmpty(name)) { return null; } return ObjectDB.instance.GetItemPrefab(name); } catch { return null; } } public static void StopPlayerEmote(Player player) { if ((Object)(object)player == (Object)null) { return; } try { PlayerStopEmote?.Invoke(player, null); } catch (Exception ex) { MercPlugin.LogWarn("stop player emote: " + ex.Message); } } public static void RunVanillaPlayerDeath(Player player) { if ((Object)(object)player == (Object)null) { return; } try { PlayerOnDeath?.Invoke(player, null); } catch (Exception ex) { MercPlugin.LogWarn("vanilla player death: " + ex.Message); } } public static void ResetCharacterGroundState(Character character) { if ((Object)(object)character == (Object)null) { return; } try { CharacterResetGroundContact?.Invoke(character, null); } catch (Exception ex) { MercPlugin.LogWarn("reset ground contact: " + ex.Message); } try { CharacterStandUpOnNextGround?.Invoke(character, null); } catch (Exception ex2) { MercPlugin.LogWarn("stand on next ground: " + ex2.Message); } } public static void SpawnVfx(string name, Vector3 pos) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) GameObject prefabSafe = GetPrefabSafe(name); if (!((Object)(object)prefabSafe == (Object)null)) { Object.Instantiate(prefabSafe, pos, Quaternion.identity); } } } internal static class ValheimKnowledge { internal const string CoreFieldManual = "[BUILT-IN VALHEIM FIELD MANUAL - base-game facts]\nANSWERING: Prefer the player's live state and the verified local crafting lookup over memory. There is no universal best weapon or food: recommend for the current biome, enemy damage resistance, available recipes, and the player's preferred range. Explain only what was asked; do not dump this manual.\n\nCORE SURVIVAL: A player can have three different foods active. Foods temporarily raise maximum health and stamina; Mistlands-tier eitr foods also raise maximum eitr. The three square icons beside the health bar are active foods, not apples. Rested improves recovery and skill gain; higher comfort makes Rested last longer. Wet, Cold, and Freezing make travel or recovery harder. Shelter, a fire, food, repaired gear, and a rested bonus are sensible preparation. Inventory weight and slot count both matter. Normal death leaves carried items in a tombstone and marks the death location on the map; skills normally lose progress unless No skill drain is active. This mod's resurrection is separate from normal Valheim death.\n\nCOMBAT: Attacking, ordinary blocking, dodging, sprinting, jumping, and swimming consume stamina; a Perfect Block costs no stamina. Low stamina is dangerous, so stop attacking long enough to recover. Timed blocks can parry eligible attacks and stagger an enemy; tower shields block strongly but cannot parry. Damage types include blunt, slash, pierce, fire, frost, lightning, poison, spirit, and chop/pickaxe damage. Yellow damage numbers mean the target is weak; grey means resistant; white means normal. Clubs and maces deal blunt; axes and swords mainly slash; spears, atgeirs, bows, and many arrows deal pierce; knives are quick and reward backstabs. Skeletons and Bonemass are notably vulnerable to blunt. The Elder and Moder are vulnerable to fire; Bonemass is also vulnerable to frost and strongly resists pierce. Yagluth, the Queen, and Fader resist pierce. Use cover and range when safer; eat before combat; repair gear; bring appropriate resistance and healing meads when unlocked.\n\nCRAFTING AND BUILDING: Inventory recipes need no station. A workbench is built from its Hammer recipe and supplies a nearby build/repair area; the bench needs shelter for item crafting and equipment repair. Repairing equipment at the correct station costs no materials. Station extensions must be close enough and unobstructed; each raises station level once. The Forge handles metal equipment. The Cauldron and its upgrades unlock food tiers. The Black Forge and Galdr table handle much Mistlands/Ashlands gear. The Artisan table unlocks later processing structures. New recipes normally appear after the relevant material and station are discovered. Structural colors while holding the Hammer show support: blue is grounded, then green through red as support weakens; red pieces can break if they lack support. Roof tiles protect ordinary wood from rain. Enclosed fires need smoke ventilation.\n\nMEADOWS / FIRST BOSS: Meadows is the safest starting biome. Gather wood, stone, flint near water, berries, mushrooms, leather scraps from boars, and deer hide/trophies. Build shelter, a bed near a fire, storage, cooking stations, and a workbench. Eikthyr's vegvisir at the Sacrificial Stones or Meadows structures marks his altar. Offer (2) Deer trophies at Eikthyr's Meadows altar. Eikthyr drops Hard antlers used for the Antler pickaxe. His power greatly reduces stamina use for running, jumping, and swimming.\n\nBLACK FOREST / SECOND BOSS: Black Forest contains copper deposits, tin along shores, core wood from pine, burial chambers, trolls, greydwarfs, and surtling cores. Copper plus tin makes bronze at a Forge. Surtling cores enable a Smelter and Charcoal kiln. The Elder's vegvisir is found around burial chambers or ruined stone structures. Offer (3) Ancient seeds at his Black Forest altar. Fire is effective. The Elder drops the Swamp key, used to open Sunken Crypts.\n\nSWAMP / THIRD BOSS: Bring the Swamp key, poison resistance, a hoe, good food, and rested status. Sunken Crypts contain muddy scrap piles for scrap iron. Ancient bark comes from ancient trees; turnip seeds improve later food. Bonemass vegvisirs occur in Sunken Crypts and some ruins. Offer (10) Withered bones at his Swamp altar. Use blunt or frost damage; he is immune to poison and strongly resists pierce. Bonemass drops the Wishbone, which pulses near buried silver and other hidden treasure.\n\nMOUNTAIN / FOURTH BOSS: Freezing Mountains require Frost resistance mead or appropriate warm gear. Use the Wishbone to locate buried silver; mine obsidian and watch for wolves, drakes, stone golems, and steep falls. Dragon eggs are very heavy and normally cannot pass through portals. Offer (3) Dragon eggs at Moder's Mountain altar. Fire is effective and frost is not. Moder drops Dragon tears, enabling the Artisan table and later Plains processing.\n\nPLAINS / FIFTH BOSS: Plains contain fulings, deathsquitos, lox, tar pits, barley, flax, and black metal scrap. Barley and flax grow normally in the Plains. Defeated fulings drop black metal scrap, processed in a Blast furnace. Obtain flax/barley from fuling villages and process them with Artisan-table structures. Offer (5) Fuling totems at Yagluth's Plains altar. Yagluth resists fire and strongly resists pierce. He drops Torn spirits, used for a Wisp fountain.\n\nMISTLANDS / SIXTH BOSS: A Wisp fountain produces wisps at night; a Wisplight or Wisp torch clears nearby mist. Explore carefully for infested mines, black cores, soft tissue, sap, black marble, seekers, gjalls, and dvergr settlements. Damaging dvergr property can make nearby dvergr hostile. Black cores enable Mistlands crafting stations. Sap plus soft tissue is refined into eitr. Nine Sealbreaker fragments form the Sealbreaker used to enter the Queen's citadel. The Queen resists pierce and is immune to spirit. After the first defeat she can be summoned again with (3) Seeker soldier trophies.\n\nASHLANDS / SEVENTH BOSS: Reach Ashlands with a ship suitable for the region and prepare for heat, fire, lava, siege, and sustained combat. Establish protected footholds and portals, gather Ashwood and Flametal, and use Ashlands station upgrades. Lava is lethal without the intended protection. Nine bell fragments make the three Bells used at Fader's altar. Fader is immune to fire and spirit and resists pierce. He drops the Fader relic.\n\nEXPLORATION AND MAP: A vegvisir is a glowing runestone that reveals a specific boss altar on the map when read; it is not itself the altar. Boss altars exist at generated world locations and must be discovered or revealed. Mark bases, ports, resources, caves, crypts, and dangerous areas. Coastlines are useful for navigation. Different worlds have different procedural layouts, so do not invent an exact direction or distance when the live world data lacks it.\n\nPORTALS AND TRAVEL: Two portals connect when their case-sensitive tags match exactly and both are active. Ordinary portal rules block most ores and metal bars, though world modifiers can change this. Boats steer by rudder at low speed and use sails when the wind allows; the wind indicator shows direction. Headwind travel may require tacking. Repair a boat from a nearby workbench area and keep portal materials for remote landings when possible.\n\nFARMING, TAMING, AND RESOURCES: Cultivate soil before planting ordinary crops. Carrots and turnips mature into food or seed plants; barley and flax normally grow only in the Plains. Cultivated mushrooms have biome requirements. Boars eat suitable plant food, wolves eat meat, and lox eat cloudberries/barley/flax; taming requires food, calm, and time. Tamed animals stop progressing while frightened. Bees need open space and make honey over time. Trees can crush players and structures. Ore veins often extend below the visible surface.\n\nMULTIPLAYER AND WORLD RULES: Enemy health scales when more players are nearby. Beds set spawn points only when valid and claimed. World modifiers can change combat, raids, death penalties, resource rates, portals, and map behavior, so never contradict an explicit live setting. Recruited mercenaries should advise and guide, but must not claim they can reveal, craft, place, collect, or defeat something unless the mod exposes that action.]"; } internal static class WorldDataService { private sealed class KnowledgeSection { internal string Source; internal string Title; internal string Raw; internal string Body; internal string[] Aliases = Array.Empty(); internal HashSet SearchWords; internal PlaceDefinition Place; } private sealed class PlaceDefinition { internal string Id; internal string Name; internal string Owner; internal readonly List Aliases = new List(); internal Vector3 Position; internal bool HasPosition; internal float Radius = 80f; internal string Raw; internal IEnumerable AllNames() { yield return Name ?? ""; foreach (string alias in Aliases) { yield return alias; } } } private sealed class StoredItem { internal string Name; internal string Prefab; internal int Count; } private sealed class ContainerRecord { internal ZDOID Id; internal string Prefab; internal string Name; internal Vector3 Position; internal uint Revision; internal long IndexedUtcTicks; internal bool SeenThisDiscovery; internal bool InventoryKnown; internal readonly Dictionary Items = new Dictionary(StringComparer.OrdinalIgnoreCase); } private sealed class DailyStat { internal int Day; internal long ActorId; internal string ActorName; internal string Type; internal string Resource; internal string Biome; internal long Amount; internal long LastUtcTicks; } internal sealed class RecognizedBase { internal string Id; internal string Name; internal string Owner; internal Vector3 Position; internal float Radius; internal bool IsAuthored; } internal sealed class GossipFact { internal string Id; internal string Type; internal long PlayerId; internal string PlayerName; internal int WorldDay; internal int Interestingness; internal long Amount; internal string Resource; internal string Biome; internal string Summary; } private sealed class HarvestCapture { internal long ActorId; internal string ActorName; internal string Source; internal Vector3 Position; internal readonly Dictionary Items = new Dictionary(StringComparer.OrdinalIgnoreCase); } private sealed class HarvestItem { internal string Name; internal string Prefab; internal int Count; } private sealed class BufferedLedgerLine { internal string Path; internal string Line; } private const string HarvestRpc = "DynamicNPCs_ActivityHarvestV1"; private const float KnowledgeReloadSeconds = 5f; private const float KnownContainerRefreshSeconds = 10f; private const float ContainerDiscoverySeconds = 300f; private const float PersistSeconds = 30f; private const int MaxKnowledgeSections = 4; private const int MaxStorageItems = 40; private const int MaxAuthoredBytes = 524288; private const int MaxAuthoredSections = 256; private const int MaxIndexedContainers = 4096; private const int ContainerWorkPerFrame = 16; private const int MaxSerializedContainerBytes = 131072; private const int MaxContainerEntries = 256; private const int MaxIndexedItemRecords = 32768; private static int _indexedItemRecords; private static int _containerWorkRemaining; private static readonly Stopwatch ContainerBudget = new Stopwatch(); private static readonly Regex SectionPattern = new Regex("(?ms)^##\\s+([^\\r\\n]+)\\s*\\r?\\n(.*?)(?=^##\\s+|\\z)", RegexOptions.Compiled); private static readonly Regex WordPattern = new Regex("[a-z0-9]+", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly HashSet StopWords = new HashSet(new string[39] { "a", "an", "and", "are", "at", "be", "can", "could", "did", "do", "does", "for", "from", "have", "how", "i", "in", "is", "it", "me", "my", "of", "on", "or", "our", "please", "the", "there", "this", "to", "we", "what", "where", "which", "who", "with", "would", "you", "your" }, StringComparer.OrdinalIgnoreCase); [ThreadStatic] private static HarvestCapture _activeHarvest; private static readonly List Knowledge = new List(); private static readonly Queue ContainerRefreshQueue = new Queue(); private static bool _discoveryPrefabComplete; private static readonly List Profiles = new List(); private static readonly List Places = new List(); private static readonly Dictionary Containers = new Dictionary(); private static readonly Dictionary Stats = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary PlayerDeadState = new Dictionary(); private static readonly List ContainerPrefabs = new List(); private static readonly Dictionary KnownItems = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly List DiscoveryResults = new List(); private static ZRoutedRpc _registeredRpc; private static ZNetScene _prefabScene; private static long _worldUid; private static string _worldName = ""; private static string _rootPath = ""; private static string _statePath = ""; private static string _inventoryPath = ""; private static string _ledgerPath = ""; private static string _statsPath = ""; private static string _factsPath = ""; private static DateTime _knowledgeWriteUtc; private static DateTime _profilesWriteUtc; private static int _profileFileCount; private static float _knowledgeTimer; private static float _containerTimer; private static float _deathTimer; private static float _persistTimer; private static float _nextDiscovery; private static int _discoveryPrefabIndex = -1; private static int _discoverySectorIndex; private static int _discoveryProcessed; private static int _discoveryContainerCount; private static bool _inventoryDirty; private static bool _statsDirty; private static bool _worldInitFailureLogged; private static bool _worldStateReady; private static float _nextWorldInitRetryAt; private static readonly Queue LedgerBuffer = new Queue(); private const int MaxLedgerBuffer = 512; private const int MaxHarvestEventsPerWindow = 15; private const int MaxClientHarvestStack = 100; private const float HarvestWindowSeconds = 5f; private static readonly Dictionary HarvestWindowStart = new Dictionary(); private static readonly Dictionary HarvestWindowCount = new Dictionary(); private static readonly Dictionary ChronicleHarvestTimes = new Dictionary(StringComparer.Ordinal); internal static int CurrentDay => CurrentValheimDay(); internal static long CurrentWorldUid => _worldUid; internal static string CurrentWorldName => _worldName; internal static bool IsWorldStateReady { get { bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; if (_worldStateReady) { if (flag) { return PlayerProgression.IsStateReady; } return true; } return false; } } internal static string RootPath => _rootPath; internal static string StatePath => _statePath; internal static void Register() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { instance.Register("DynamicNPCs_ActivityHarvestV1", (Action)OnHarvestRpc); _registeredRpc = instance; } } internal static void ResetSceneMemory() { Flush(); _prefabScene = null; _worldUid = 0L; _worldName = ""; _statePath = ""; _inventoryPath = ""; _ledgerPath = ""; _statsPath = ""; _factsPath = ""; _worldStateReady = false; _nextWorldInitRetryAt = 0f; Knowledge.Clear(); Profiles.Clear(); ContainerRefreshQueue.Clear(); Places.Clear(); Containers.Clear(); _indexedItemRecords = 0; Stats.Clear(); PlayerDeadState.Clear(); ContainerPrefabs.Clear(); KnownItems.Clear(); DiscoveryResults.Clear(); _discoveryPrefabIndex = -1; _inventoryDirty = false; _statsDirty = false; _activeHarvest = null; HarvestWindowStart.Clear(); HarvestWindowCount.Clear(); ChronicleHarvestTimes.Clear(); } internal static void Shutdown() { Flush(); _registeredRpc = null; } internal static void Update() { Register(); if (!IsServerWorld()) { return; } EnsureWorldInitialized(); if (_worldStateReady) { _knowledgeTimer += Time.deltaTime; _containerTimer += Time.deltaTime; _deathTimer += Time.deltaTime; _persistTimer += Time.deltaTime; if (_knowledgeTimer >= 5f) { _knowledgeTimer = 0f; ReloadKnowledgeIfChanged(); } if (_containerTimer >= 10f) { _containerTimer = 0f; RefreshKnownContainers(); } if (_deathTimer >= 2f) { _deathTimer = 0f; PollPlayerDeaths(); } _containerWorkRemaining = 16; ContainerBudget.Restart(); UpdateContainerDiscovery(); RefreshContainerBatch(); ContainerBudget.Stop(); if (_persistTimer >= 30f) { _persistTimer = 0f; Flush(); } } } private static bool IsServerWorld() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return ZDOMan.instance != null; } return false; } private static void EnsureWorldInitialized() { long worldUID; string text; try { worldUID = ZNet.instance.GetWorldUID(); text = ZNet.instance.GetWorldName() ?? "world"; } catch (Exception ex) { if (!_worldInitFailureLogged) { _worldInitFailureLogged = true; MercPlugin.LogWarn("World data service could not read world identity: " + ex.Message); } return; } _worldInitFailureLogged = false; bool flag = MercConfig.ProgressionPerPlayer != null && MercConfig.ProgressionPerPlayer.Value; bool flag2 = flag && !PlayerProgression.IsStateReady; if (worldUID != 0L && (_worldUid != worldUID || !_worldStateReady || flag2) && (_worldUid != worldUID || !(Time.unscaledTime < _nextWorldInitRetryAt))) { Flush(); _worldStateReady = false; _worldUid = worldUID; _worldName = text; _rootPath = Path.Combine(Paths.ConfigPath, "DynamicNPCs"); _statePath = Path.Combine(_rootPath, "state", SafeFileName(text) + "-" + worldUID.ToString(CultureInfo.InvariantCulture)); _inventoryPath = Path.Combine(_statePath, "WorldInventory.json"); _ledgerPath = Path.Combine(_statePath, "ActivityLedger.jsonl"); _statsPath = Path.Combine(_statePath, "DailyStats.json"); _factsPath = Path.Combine(_statePath, "ActivityFacts.json"); Directory.CreateDirectory(_rootPath); Directory.CreateDirectory(Path.Combine(_rootPath, "NpcProfiles")); Directory.CreateDirectory(Path.Combine(_rootPath, "NPCs")); Directory.CreateDirectory(_statePath); EnsureKnowledgeTemplates(); LoadInventory(); LoadStats(); PlayerProgression.SetStatePath(_statePath); if (flag && !PlayerProgression.IsStateReady) { _nextWorldInitRetryAt = Time.unscaledTime + 5f; return; } PlayerChronicle.SetStatePath(_statePath); ReloadKnowledge(force: true); DiscoverContainerPrefabs(force: true); StartContainerDiscovery(); _worldStateReady = true; _nextWorldInitRetryAt = 0f; MercPlugin.Log($"World data service ready for '{_worldName}' ({_worldUid}); " + "knowledge=" + Path.Combine(_rootPath, "WorldKnowledge.md") + ", state=" + _statePath + "."); } } private static void EnsureKnowledgeTemplates() { string path = Path.Combine(_rootPath, "WorldKnowledge.md"); if (!File.Exists(path)) { File.WriteAllText(path, "# DynamicNPCs World Knowledge\r\n\r\nAdd stable server facts and named places here. Live inventory counts belong to the generated index.\r\nUse ## headings for ordinary facts and ## place: Name for searchable storage areas.\r\n\r\n\r\n\r\n\r\n", Encoding.UTF8); } string path2 = Path.Combine(_rootPath, "NpcProfiles", "README.md"); if (!File.Exists(path2)) { File.WriteAllText(path2, "# NPC Profiles\r\n\r\nCreate one Markdown file per future NPC. Store personality, role, permissions, and private knowledge here.\r\n", Encoding.UTF8); } } private static void ReloadKnowledgeIfChanged() { try { string path = Path.Combine(_rootPath, "WorldKnowledge.md"); DateTime obj = (File.Exists(path) ? File.GetLastWriteTimeUtc(path) : DateTime.MinValue); string text = Path.Combine(_rootPath, "NpcProfiles"); DateTime dateTime = LatestMarkdownWrite(text); int num = (Directory.Exists(text) ? Directory.EnumerateFiles(text, "*.md", SearchOption.TopDirectoryOnly).Take(257).Count() : 0); if (obj != _knowledgeWriteUtc || dateTime != _profilesWriteUtc || num != _profileFileCount) { ReloadKnowledge(force: true); } } catch (Exception ex) { MercPlugin.LogWarn("Knowledge change check failed: " + ex.Message); } } private static DateTime LatestMarkdownWrite(string directory) { if (!Directory.Exists(directory)) { return DateTime.MinValue; } DateTime dateTime = DateTime.MinValue; foreach (string item in Directory.EnumerateFiles(directory, "*.md", SearchOption.TopDirectoryOnly).Take(256)) { try { DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(item); if (lastWriteTimeUtc > dateTime) { dateTime = lastWriteTimeUtc; } } catch { } } return dateTime; } private static void ReloadKnowledge(bool force) { if (!force || string.IsNullOrEmpty(_rootPath)) { return; } List list = new List(); List list2 = new List(); List list3 = new List(); DateTime knowledgeWriteUtc = DateTime.MinValue; DateTime profilesWriteUtc = DateTime.MinValue; int profileFileCount = 0; try { string text = Path.Combine(_rootPath, "WorldKnowledge.md"); if (File.Exists(text)) { if (!TryParseMarkdown(text, list, list3, includePlaces: true)) { return; } knowledgeWriteUtc = File.GetLastWriteTimeUtc(text); } string text2 = Path.Combine(_rootPath, "NpcProfiles"); if (Directory.Exists(text2)) { string[] array = Directory.EnumerateFiles(text2, "*.md", SearchOption.TopDirectoryOnly).Take(257).ToArray(); if (array.Length > 256) { throw new InvalidDataException("NPC profiles exceed the 256-file limit."); } profileFileCount = array.Length; string[] array2 = array; foreach (string text3 in array2) { if (!string.Equals(Path.GetFileName(text3), "README.md", StringComparison.OrdinalIgnoreCase) && !TryParseMarkdown(text3, list2, list3, includePlaces: false)) { return; } } profilesWriteUtc = LatestMarkdownWrite(text2); } } catch (Exception ex) { MercPlugin.LogWarn("Knowledge reload failed; retaining the previous snapshot: " + ex.Message); return; } Knowledge.Clear(); Knowledge.AddRange(list); Profiles.Clear(); Profiles.AddRange(list2); Places.Clear(); Places.AddRange(list3); _knowledgeWriteUtc = knowledgeWriteUtc; _profilesWriteUtc = profilesWriteUtc; _profileFileCount = profileFileCount; MercPlugin.Log($"World knowledge hot reload: sections={Knowledge.Count}, places={Places.Count}, profiles={Profiles.Count}."); } private static bool TryParseMarkdown(string file, List destination, List places, bool includePlaces) { string input; try { if (new FileInfo(file).Length > 524288) { throw new InvalidDataException("Authored knowledge file exceeds 512 KiB."); } input = File.ReadAllText(file); input = Regex.Replace(input, "(?s)", ""); } catch (Exception ex) { MercPlugin.LogWarn("Could not read " + file + ": " + ex.Message); return false; } MatchCollection matchCollection = SectionPattern.Matches(input); foreach (Match item in matchCollection) { if (destination.Count >= 256) { return false; } string text = item.Groups[1].Value.Trim(); string text2 = item.Groups[2].Value.Trim(); if (text2.Length > 4096 || text.Length > 160) { return false; } string raw = "## " + text + "\n" + text2; KnowledgeSection knowledgeSection = new KnowledgeSection(); knowledgeSection.Source = Path.GetFileNameWithoutExtension(file); knowledgeSection.Title = text; knowledgeSection.Raw = raw; knowledgeSection.Body = text2; knowledgeSection.Aliases = (from alias in (from line in text2.Split(new char[1] { '\n' }) where line.TrimStart(Array.Empty()).StartsWith("aliases:", StringComparison.OrdinalIgnoreCase) select line).SelectMany((string line) => line.Substring(line.IndexOf(':') + 1).Split(new char[2] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries)) select Normalize(alias) into alias where alias.Length > 0 select alias).Take(24).ToArray(); knowledgeSection.SearchWords = Words(text + " " + text2); KnowledgeSection knowledgeSection2 = knowledgeSection; if (includePlaces && text.StartsWith("place:", StringComparison.OrdinalIgnoreCase)) { places.Add(knowledgeSection2.Place = ParsePlace(text.Substring(6).Trim(), text2, raw)); } destination.Add(knowledgeSection2); } if (matchCollection.Count == 0 && !includePlaces && !string.IsNullOrWhiteSpace(input)) { string[] array = input.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); string text3 = ((array.Length != 0) ? array[0].TrimStart('#', ' ') : Path.GetFileNameWithoutExtension(file)); if (destination.Count >= 256 || input.Length > 4096 || text3.Length > 160) { return false; } destination.Add(new KnowledgeSection { Source = Path.GetFileNameWithoutExtension(file), Title = text3, Raw = input.Trim(), Body = input.Trim(), SearchWords = Words(text3 + " " + input) }); } return true; } private static PlaceDefinition ParsePlace(string name, string body, string raw) { //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) PlaceDefinition placeDefinition = new PlaceDefinition { Name = name, Id = Slug(name), Raw = raw }; string[] array = body.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { int num = text.IndexOf(':'); if (num <= 0) { continue; } string text2 = text.Substring(0, num).Trim().ToLowerInvariant(); string text3 = text.Substring(num + 1).Trim(); switch (text2) { case "aliases": { string[] array2 = text3.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int j = 0; j < array2.Length; j++) { string text4 = array2[j].Trim(); if (!string.IsNullOrEmpty(text4) && placeDefinition.Aliases.Count < 24) { placeDefinition.Aliases.Add(text4); } } continue; } case "owner": placeDefinition.Owner = text3; continue; case "radius": { if (float.TryParse(text3, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && KnowledgeRules.FiniteCoordinate(result)) { placeDefinition.Radius = Mathf.Clamp(result, 1f, 1000f); continue; } break; } } if (text2 == "position") { placeDefinition.HasPosition = false; if (KnowledgeRules.TryPosition(text3, out var x, out var y, out var z)) { placeDefinition.Position = new Vector3(x, y, z); placeDefinition.HasPosition = true; } } } return placeDefinition; } internal static string BuildWorldNotesContext(string question) { if (!IsServerWorld() || string.IsNullOrWhiteSpace(question)) { return ""; } string text = Normalize(question); List selected = new List(); foreach (KnowledgeSection item in Knowledge) { if (item.Place != null && PlaceMentioned(item.Place, text)) { selected.Add(item); } } foreach (KnowledgeSection profile in Profiles) { string value = Normalize(profile.Source); string value2 = Normalize(profile.Title); if ((!string.IsNullOrEmpty(value) && text.Contains(value)) || (!string.IsNullOrEmpty(value2) && text.Contains(value2))) { selected.Add(profile); } } if (selected.Count < 4) { HashSet questionWords = Words(question); foreach (KnowledgeSection item2 in from s in Knowledge.Concat(Profiles) where !selected.Contains(s) select new { Section = s, Score = s.SearchWords.Count(questionWords.Contains) } into x where x.Score >= 2 orderby x.Score descending select x.Section) { selected.Add(item2); if (selected.Count >= 4) { break; } } } if (selected.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder("[RELEVANT SERVER WORLD KNOWLEDGE]\n"); foreach (KnowledgeSection item3 in selected.Take(4)) { stringBuilder.AppendLine(item3.Raw); } return stringBuilder.ToString().TrimEnd(Array.Empty()); } internal static bool TryAnswerWorldQuestion(string question, ServerAuthority.SenderPlayerState requester, out string answer) { answer = null; if (!IsServerWorld() || !_worldStateReady || requester == null || string.IsNullOrWhiteSpace(question)) { return false; } string text = Normalize(question); switch (SupportQueryRules.WorldIntent(question)) { case "name": answer = "This world is " + _worldName + "."; return true; case "day": answer = "It is day " + CurrentDay.ToString(CultureInfo.InvariantCulture) + " in " + _worldName + "."; return true; case "world_stage": answer = "The shared world's progression is " + StageDirector.GetStageName(StageDirector.GetStage()) + ". Personal company progression can differ."; return true; case "personal_stage": { int knownStage = PlayerProgression.GetKnownStage(requester.PlayerId); answer = ((knownStage < 0) ? "Your company progression has not been confirmed yet." : ("Your recorded company progression is " + StageDirector.GetStageName(knownStage) + ".")); return true; } default: { List list = ResolvePlaces(text); bool flag = SupportQueryRules.IsStorageQuestion(question, list.Count > 0); HashSet hashSet = (flag ? ResolveRequestedItems(RemovePlaceNames(text, list)) : new HashSet()); if (hashSet.Count > 0 && flag) { if (!ContainerIndexingEnabled()) { answer = "The server's storage index is disabled."; return true; } if (!ContainerContextAllowed(requester)) { answer = "I cannot share those storage records."; return true; } if (list.Count == 0) { list = Places.Where((PlaceDefinition placeDefinition2) => placeDefinition2.HasPosition).Take(4).ToList(); } if (list.Count == 0) { answer = "No named storage area is recorded for that question."; return true; } List list2 = new List(); foreach (PlaceDefinition place in list.Take(4)) { int num = 0; Dictionary totals = new Dictionary(StringComparer.OrdinalIgnoreCase); List list3 = Containers.Values.Where((ContainerRecord record) => Vector3.Distance(record.Position, place.Position) <= place.Radius).ToList(); foreach (ContainerRecord item in list3) { if (!item.InventoryKnown) { num++; continue; } foreach (StoredItem value3 in item.Items.Values) { if (hashSet.Contains(Normalize(value3.Name)) || hashSet.Contains(Normalize(value3.Prefab))) { string key = value3.Name ?? value3.Prefab ?? "item"; totals.TryGetValue(key, out var value); totals[key] = value + value3.Count; } } } long value2; string text2 = string.Join(", ", from name in RequestedItemLabels(hashSet).Take(6) select (!totals.TryGetValue(name, out value2)) ? (name + ": none confirmed") : (value2.ToString(CultureInfo.InvariantCulture) + " " + name + " recorded")); list2.Add(place.Name + ": " + text2 + " across " + list3.Count.ToString(CultureInfo.InvariantCulture) + " indexed containers" + ((num > 0) ? ("; " + num + " unconfirmed") : "") + ". " + IndexAge(list3) + "."); } answer = BoundText(string.Join("\n", list2) + " This is a partial storage index, not carried inventory or a live guarantee.", 1200); return true; } List list4 = list.Where((PlaceDefinition placeDefinition2) => SupportQueryRules.IsPlaceDirection(question, placeDefinition2.AllNames())).ToList(); if (list4.Count > 0) { if (list4.Count > 1) { answer = "I know several matching places: " + string.Join(", ", from placeDefinition2 in list4.Take(5) select placeDefinition2.Name) + ". Name one."; return true; } PlaceDefinition placeDefinition = list4[0]; if (!KnowledgeRules.TryDirection(requester.Position.x, requester.Position.z, placeDefinition.Position.x, placeDefinition.Position.z, out var direction, out var distance)) { return false; } answer = placeDefinition.Name + " is recorded about " + distance + " " + direction + " of you, according to the server's notes."; return true; } KnowledgeSection[] array = Knowledge.Where((KnowledgeSection section) => section.Place == null && (section.Aliases.Any((string alias) => SupportQueryRules.MatchesAuthoredTopic(question, alias)) || SupportQueryRules.MatchesAuthoredTopic(question, Normalize(section.Title.Contains(":") ? section.Title.Substring(section.Title.IndexOf(':') + 1) : section.Title)))).Take(3).ToArray(); if (array.Length != 0) { IEnumerable values = array.Select((KnowledgeSection section) => string.Join("\n", from line in (section.Body ?? "").Split(new char[1] { '\n' }) where !line.TrimStart(Array.Empty()).StartsWith("aliases:", StringComparison.OrdinalIgnoreCase) select (!line.TrimStart(Array.Empty()).StartsWith("answer:", StringComparison.OrdinalIgnoreCase)) ? line.Trim() : line.Substring(line.IndexOf(':') + 1).Trim())); answer = BoundText(string.Join("\n", values), 1200); return !string.IsNullOrWhiteSpace(answer); } if (LooksLikeStatsQuestion(question)) { answer = BoundText(BuildStatsContext(question, requester), 1200); if (string.IsNullOrWhiteSpace(answer)) { return false; } return true; } return false; } } } private static bool ContainsPhrase(string text, string phrase) { if (!string.IsNullOrWhiteSpace(phrase)) { return (" " + text + " ").IndexOf(" " + phrase + " ", StringComparison.Ordinal) >= 0; } return false; } private static bool ContainsAnyPhrase(string text, params string[] phrases) { return phrases.Any((string phrase) => ContainsPhrase(text, phrase)); } private static string RemovePlaceNames(string question, List places) { string text = " " + question + " "; foreach (string item in from name in places.SelectMany((PlaceDefinition place) => place.AllNames()).Select(Normalize) where name.Length > 0 orderby name.Length descending select name) { text = text.Replace(" " + item + " ", " "); } return text.Trim(); } internal static string BuildStorageContext(string question, ServerAuthority.SenderPlayerState requester) { if (!IsServerWorld() || string.IsNullOrWhiteSpace(question)) { return ""; } if (!ContainerContextAllowed(requester)) { return ""; } string normalized = Normalize(question); List list = ResolvePlaces(normalized); bool flag = Places.Any((PlaceDefinition p) => p.HasPosition && !string.IsNullOrWhiteSpace(p.Owner) && ContainsPhrase(normalized, Normalize(p.Owner))); if (!LooksLikeStorageQuestion(question) && list.Count == 0 && !flag) { return ""; } HashSet requestedItems = ResolveRequestedItems(RemovePlaceNames(normalized, list)); if (!LooksLikeStorageQuestion(question) && (requestedItems.Count <= 0 || !(list.Count > 0 || flag))) { return ""; } if (list.Count == 0) { list = Places.Where((PlaceDefinition p) => p.HasPosition && (ContainsAny(normalized, "which base", "what base", "where do we", "where does", "all bases", "our bases") || (!string.IsNullOrEmpty(p.Owner) && normalized.Contains(Normalize(p.Owner))))).ToList(); } if (list.Count == 0 && ContainsAny(normalized, "do we have", "enough materials", "which base", "what base")) { list = Places.Where((PlaceDefinition p) => p.HasPosition).ToList(); } if (list.Count == 0) { MercPlugin.Log("World storage lookup: no named place resolved for '" + BoundLog(question) + "'."); return "[VERIFIED WORLD STORAGE LOOKUP]\nNo named place in WorldKnowledge.md matched this request. Do not invent a storage location or count."; } StringBuilder stringBuilder = new StringBuilder("[VERIFIED WORLD STORAGE LOOKUP]\n"); foreach (PlaceDefinition place in list.Take(12)) { List list2 = Containers.Values.Where((ContainerRecord c) => Vector3.Distance(c.Position, place.Position) <= place.Radius).ToList(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (ContainerRecord item in list2.Where((ContainerRecord record) => record.InventoryKnown)) { foreach (StoredItem value2 in item.Items.Values) { string key = Normalize(value2.Name); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new StoredItem { Name = value2.Name, Prefab = value2.Prefab }); } value.Count += value2.Count; } } stringBuilder.AppendLine(string.Format("Location: {0} (owner: {1}, radius: {2:0}m)", place.Name, NonEmpty(place.Owner, "unspecified"), place.Radius)); stringBuilder.AppendLine($"Known containers: {list2.Count}; index state: {IndexAge(list2)}"); IEnumerable source = dictionary.Values; if (requestedItems.Count > 0) { source = source.Where((StoredItem i) => requestedItems.Contains(Normalize(i.Name)) || requestedItems.Contains(Normalize(i.Prefab))); } List list3 = (from i in source orderby i.Count descending, i.Name select i).Take(40).ToList(); if (list3.Count == 0) { if (requestedItems.Count > 0) { foreach (string item2 in RequestedItemLabels(requestedItems).Take(12)) { stringBuilder.AppendLine("Item: " + item2 + "; none confirmed in the partial index"); } } else { stringBuilder.AppendLine("No indexed items are currently known at this location."); } continue; } foreach (StoredItem item3 in list3) { stringBuilder.AppendLine($"Item: {item3.Name}; known quantity: {item3.Count}"); } } stringBuilder.AppendLine("Counts describe the latest server index, not items carried by players. Treat stale or incomplete results explicitly."); MercPlugin.Log("World storage lookup: places=" + string.Join(",", list.Select((PlaceDefinition p) => p.Id).ToArray()) + ", " + string.Format("items={0}, indexedContainers={1}.", string.Join(",", requestedItems.ToArray()), Containers.Count)); return stringBuilder.ToString().TrimEnd(Array.Empty()); } internal static string BuildStatsContext(string question, ServerAuthority.SenderPlayerState requester) { if (!IsServerWorld() || string.IsNullOrWhiteSpace(question) || !LooksLikeStatsQuestion(question)) { return ""; } string text = KnowledgeRules.Normalize(question); int currentDay = CurrentValheimDay(); bool flag = ContainsAnyPhrase(text, "week", "seven days", "7 days"); int firstDay = (flag ? (currentDay - 6) : currentDay); bool flag2 = OtherPlayerActivityAllowed(requester); IEnumerable source = Stats.Values.Where((DailyStat s) => s.Day >= firstDay && s.Day <= currentDay); string text2 = KnowledgeRules.ActivityTarget(question); bool flag3 = ContainsPhrase(text, "who"); long actorId = requester?.PlayerId ?? 0; if (text2.Length == 0 && !flag3 && !ContainsAnyPhrase(text, "i", "me", "my", "myself") && !Regex.IsMatch(text, "^(?:deaths?|activity|harvests?)(?: today| this week| this day)?$")) { return "Whose recorded activity should I check? Use the player's complete name, or say my activity."; } if (text2.Length > 0 && !KnowledgeRules.IsSelf(text2)) { Dictionary actors = new Dictionary(); Dictionary dictionary = new Dictionary(); foreach (DailyStat value2 in Stats.Values) { if (value2.ActorId != 0L && (!dictionary.TryGetValue(value2.ActorId, out var value) || value2.LastUtcTicks > value.LastUtcTicks)) { dictionary[value2.ActorId] = value2; } } foreach (DailyStat value3 in dictionary.Values) { actors[value3.ActorId] = value3.ActorName; } foreach (PlayerKnowledgeRecord item in PlayerChronicle.GetSnapshot()) { actors[item.PlayerId] = item.Name; } long[] ids = actors.Keys.ToArray(); string[] names = ids.Select((long id) => actors[id]).ToArray(); List list = KnowledgeRules.MatchPlayers(text2, names, ids); if (list.Count == 0) { return "I have no activity record for a player named " + KnowledgeRules.Display(text2) + "."; } if (list.Count > 1) { return "Several players use that name: " + string.Join(", ", from index in list.Take(5) select KnowledgeRules.Display(names[index]) + " #" + ids[index].ToString(CultureInfo.InvariantCulture)) + ". Include the ID to choose one."; } actorId = ids[list[0]]; if (actorId != (requester?.PlayerId ?? 0) && !flag2) { return "The server does not share other players' activity records."; } source = source.Where((DailyStat stat) => stat.ActorId == actorId); } else if (!flag3 || !flag2) { source = source.Where((DailyStat stat) => stat.ActorId == actorId); } bool flag4 = ContainsAnyPhrase(text, "death", "deaths", "died", "die"); bool flag5 = ContainsAnyPhrase(text, "cut", "chopped", "mine", "mined", "harvest", "harvested", "gather", "gathered", "collected", "resources"); if (flag4 && !flag5) { source = source.Where((DailyStat s) => s.Type == "player.death"); } else if (flag5 && !flag4) { source = source.Where((DailyStat s) => s.Type == "resource.harvested"); } string question2 = ((text2.Length == 0) ? text : (" " + text + " ").Replace(" " + text2 + " ", " ").Trim()); HashSet resources = KnowledgeRules.MatchLongestPhrases(question2, from resource in Stats.Values.Select((DailyStat stat) => stat.Resource).Concat(KnownItems.Values.Select((StoredItem item) => item.Name)) where !string.IsNullOrEmpty(resource) select resource); if (resources.Count > 0) { source = source.Where((DailyStat stat) => resources.Contains(KnowledgeRules.Normalize(stat.Resource))); } var list2 = (from stat in source group stat by new { ActorId = stat.ActorId, Type = stat.Type, Resource = KnowledgeRules.Normalize(stat.Resource) } into @group select new { Last = @group.OrderByDescending((DailyStat stat) => stat.LastUtcTicks).First(), Amount = @group.Sum((DailyStat stat) => stat.Amount) } into row orderby row.Amount descending, row.Last.ActorName select row).Take(12).ToList(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(flag ? $"Recorded activity for Valheim days {firstDay}–{currentDay}:" : $"Recorded activity for Valheim day {currentDay}:"); if (list2.Count == 0) { stringBuilder.AppendLine("No matching activity has been recorded for that period. Missing records do not establish a zero total."); } else { foreach (var item2 in list2) { string text3 = KnowledgeRules.Display(item2.Last.ActorName); if (item2.Last.Type == "player.death") { stringBuilder.AppendLine(text3 + ": " + item2.Amount + " recorded death" + ((item2.Amount == 1) ? "." : "s.")); } else if (item2.Last.Type == "resource.harvested") { stringBuilder.AppendLine(text3 + ": " + item2.Amount + " " + KnowledgeRules.Display(item2.Last.Resource) + " gathered."); } } } return stringBuilder.ToString().TrimEnd(Array.Empty()); } internal static bool OtherPlayerActivityShared() { if (MercConfig.ShareOtherPlayersActivity == null || MercConfig.ShareOtherPlayersActivity.Value) { return true; } return false; } internal static List KnownPlayerNames() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (DailyStat value in Stats.Values) { string text = (value.ActorName ?? "").Trim(); if (text.Length > 0 && value.ActorId != 0L) { hashSet.Add(text); } } return hashSet.ToList(); } private static bool OtherPlayerActivityAllowed(ServerAuthority.SenderPlayerState requester) { if (OtherPlayerActivityShared()) { return true; } if (requester != null) { return ServerAuthority.IsSenderAdmin(requester.SenderUid); } return false; } private static bool ContainerContextAllowed(ServerAuthority.SenderPlayerState requester) { if (MercConfig.IndexWorldContainers != null && MercConfig.IndexWorldContainers.Value) { return true; } if (requester != null) { return ServerAuthority.IsSenderAdmin(requester.SenderUid); } return false; } private static bool LooksLikeStorageQuestion(string question) { return ContainsAny(Normalize(question), "base", "outpost", "storage", "stored", "container", "containers", "chest", "chests", "supplies", "stock", "stockpile", "do we have", "does ", "enough materials", "missing at", "where is our"); } private static bool LooksLikeStatsQuestion(string question) { return KnowledgeRules.IsActivityQuestion(question); } private static List ResolvePlaces(string normalizedQuestion) { return Places.Where((PlaceDefinition p) => p.HasPosition && PlaceMentioned(p, normalizedQuestion)).ToList(); } private static bool PlaceMentioned(PlaceDefinition place, string normalizedQuestion) { foreach (string item in place.AllNames()) { string text = Normalize(item); if (text.Length >= 2 && ContainsPhrase(normalizedQuestion, text)) { return true; } } return false; } private static HashSet ResolveRequestedItems(string normalizedQuestion) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string item in KnowledgeRules.MatchLongestPhrases(normalizedQuestion, KnownItems.Keys)) { if (KnownItems.TryGetValue(item, out var value)) { string text = Normalize(value.Name); string text2 = Normalize(value.Prefab); if (!string.IsNullOrWhiteSpace(text)) { hashSet.Add(text); } if (!string.IsNullOrWhiteSpace(text2)) { hashSet.Add(text2); } } } return hashSet; } private static IEnumerable RequestedItemLabels(HashSet requestedItems) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string requestedItem in requestedItems) { if (KnownItems.TryGetValue(requestedItem, out var value) && !string.IsNullOrWhiteSpace(value.Name)) { hashSet.Add(value.Name); } else if (!string.IsNullOrWhiteSpace(requestedItem)) { hashSet.Add(requestedItem); } } return hashSet.OrderBy((string result) => result); } private static string IndexAge(List records) { if (records.Count == 0) { if (_discoveryPrefabIndex < 0) { return "no known containers"; } return "discovery still in progress"; } long num = records.Min((ContainerRecord r) => r.IndexedUtcTicks); if (num <= 0 || records.Any((ContainerRecord record) => record.IndexedUtcTicks > DateTime.UtcNow.AddSeconds(2.0).Ticks)) { return "oldest check time unknown"; } string text = KnowledgeRules.DescribeAge(new DateTime(num, DateTimeKind.Utc), DateTime.UtcNow); return "oldest container check " + text; } private static void DiscoverContainerPrefabs(bool force) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || (!force && instance == _prefabScene)) { return; } _prefabScene = instance; ContainerPrefabs.Clear(); KnownItems.Clear(); foreach (string prefabName in instance.GetPrefabNames()) { GameObject prefab = instance.GetPrefab(prefabName); if (!((Object)(object)prefab == (Object)null)) { if ((Object)(object)prefab.GetComponent() != (Object)null && !ContainerPrefabs.Contains(((Object)prefab).name)) { ContainerPrefabs.Add(((Object)prefab).name); } ItemDrop component = prefab.GetComponent(); if (!((Object)(object)component == (Object)null) && component.m_itemData != null) { string text = ((component.m_itemData.m_shared != null) ? Localize(component.m_itemData.m_shared.m_name, ((Object)prefab).name) : ((Object)prefab).name); StoredItem storedItem = new StoredItem { Name = BoundText(text, 64), Prefab = ((Object)prefab).name }; KnownItems[Normalize(text)] = storedItem; KnownItems[Normalize(storedItem.Name)] = storedItem; KnownItems[Normalize(((Object)prefab).name)] = storedItem; } } } ContainerPrefabs.Sort(StringComparer.Ordinal); } private static bool ContainerIndexingEnabled() { if (MercConfig.IndexWorldContainers != null) { return MercConfig.IndexWorldContainers.Value; } return true; } private static void StartContainerDiscovery() { if (!ContainerIndexingEnabled()) { _discoveryPrefabIndex = -1; return; } DiscoverContainerPrefabs(force: false); foreach (ContainerRecord value in Containers.Values) { value.SeenThisDiscovery = false; } _discoveryPrefabIndex = ((ContainerPrefabs.Count <= 0) ? (-1) : 0); _discoverySectorIndex = 0; _discoveryProcessed = 0; _discoveryContainerCount = 0; _discoveryPrefabComplete = false; DiscoveryResults.Clear(); } private static void UpdateContainerDiscovery() { //IL_0118: Unknown result type (might be due to invalid IL or missing references) if (!ContainerIndexingEnabled()) { _discoveryPrefabIndex = -1; _discoveryProcessed = 0; _discoveryPrefabComplete = false; DiscoveryResults.Clear(); ContainerRefreshQueue.Clear(); return; } if (_discoveryPrefabIndex < 0) { if (Time.realtimeSinceStartup >= _nextDiscovery) { StartContainerDiscovery(); } return; } if (_discoveryPrefabIndex >= ContainerPrefabs.Count) { FinishContainerDiscovery(); return; } bool discoveryPrefabComplete; try { if (_discoveryProcessed >= DiscoveryResults.Count && !_discoveryPrefabComplete) { _discoveryPrefabComplete = ZDOMan.instance.GetAllZDOsWithPrefabIterative(ContainerPrefabs[_discoveryPrefabIndex], DiscoveryResults, ref _discoverySectorIndex); } discoveryPrefabComplete = _discoveryPrefabComplete; } catch (Exception ex) { MercPlugin.LogWarn("Container discovery failed: " + ex.Message); _discoveryPrefabIndex = -1; _nextDiscovery = Time.realtimeSinceStartup + 300f; return; } while (_discoveryProcessed < DiscoveryResults.Count && _containerWorkRemaining > 0 && ContainerBudget.ElapsedMilliseconds < 2) { _containerWorkRemaining--; ZDO val = DiscoveryResults[_discoveryProcessed++]; if (val != null && val.IsValid()) { RefreshContainer(val, force: false); if (Containers.TryGetValue(val.m_uid, out var value)) { value.SeenThisDiscovery = true; } _discoveryContainerCount++; } } if (discoveryPrefabComplete && _discoveryProcessed >= DiscoveryResults.Count) { _discoveryPrefabIndex++; _discoverySectorIndex = 0; _discoveryProcessed = 0; _discoveryPrefabComplete = false; DiscoveryResults.Clear(); if (_discoveryPrefabIndex >= ContainerPrefabs.Count) { FinishContainerDiscovery(); } } } private static void FinishContainerDiscovery() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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) //IL_0089: Unknown result type (might be due to invalid IL or missing references) List list = (from pair in Containers where !pair.Value.SeenThisDiscovery && ZDOMan.instance.GetZDO(pair.Key) == null select pair.Key).ToList(); foreach (ZDOID item in list) { _indexedItemRecords -= Containers[item].Items.Count; Containers.Remove(item); } if (list.Count > 0) { _inventoryDirty = true; } _discoveryPrefabIndex = -1; _nextDiscovery = Time.realtimeSinceStartup + 300f; MercPlugin.Log($"World inventory discovery complete: prefabs={ContainerPrefabs.Count}, " + $"containers={Containers.Count}, visited={_discoveryContainerCount}, removed={list.Count}."); } private static void RefreshKnownContainers() { //IL_0029: 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_0034: Unknown result type (might be due to invalid IL or missing references) if (!ContainerIndexingEnabled() || ContainerRefreshQueue.Count > 0) { return; } foreach (ZDOID key in Containers.Keys) { ContainerRefreshQueue.Enqueue(key); } } private static void RefreshContainerBatch() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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) if (!ContainerIndexingEnabled()) { ContainerRefreshQueue.Clear(); return; } while (ContainerRefreshQueue.Count > 0 && _containerWorkRemaining > 0 && ContainerBudget.ElapsedMilliseconds < 2) { _containerWorkRemaining--; ZDOID val = ContainerRefreshQueue.Dequeue(); ZDO zDO = ZDOMan.instance.GetZDO(val); if (zDO != null && zDO.IsValid()) { RefreshContainer(zDO, force: false); } } } private static void RefreshContainer(ZDO zdo, bool force) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) if (zdo == null || !zdo.IsValid() || (Containers.Count >= 4096 && !Containers.ContainsKey(zdo.m_uid))) { return; } if (Containers.TryGetValue(zdo.m_uid, out var value) && !force && value.Revision == zdo.DataRevision && value.InventoryKnown) { value.Position = zdo.GetPosition(); value.IndexedUtcTicks = DateTime.UtcNow.Ticks; _inventoryDirty = true; return; } GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(zdo.GetPrefab()) : null); Container val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 == (Object)null) { return; } ContainerRecord containerRecord = value ?? new ContainerRecord { Id = zdo.m_uid }; containerRecord.Prefab = BoundText(((Object)val).name, 64); containerRecord.Name = BoundText(Localize(val2.m_name, ((Object)val).name), 64); containerRecord.Position = zdo.GetPosition(); containerRecord.Revision = zdo.DataRevision; containerRecord.IndexedUtcTicks = DateTime.UtcNow.Ticks; _indexedItemRecords -= containerRecord.Items.Count; containerRecord.Items.Clear(); containerRecord.InventoryKnown = false; string text = zdo.GetString(ZDOVars.s_items, ""); if (text.Length > 131072) { Containers[zdo.m_uid] = containerRecord; _inventoryDirty = true; return; } bool flag = false; if (!string.IsNullOrEmpty(text)) { try { flag = TryDecodeContainerItems(text, containerRecord.Items); } catch { flag = false; containerRecord.Items.Clear(); } } if (!flag || _indexedItemRecords + containerRecord.Items.Count > 32768) { flag = false; containerRecord.Items.Clear(); } _indexedItemRecords += containerRecord.Items.Count; Containers[zdo.m_uid] = containerRecord; containerRecord.InventoryKnown = flag; _inventoryDirty = true; } private static bool TryDecodeContainerItems(string encoded, Dictionary items) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ObjectDB.instance == (Object)null || encoded.Length == 0 || encoded.Length > 131072) { return false; } ZPackage val = new ZPackage(encoded); int num = val.ReadInt(); int num2 = val.ReadInt(); if (num < 100 || num > 106 || num2 < 0 || num2 > 256) { return false; } for (int i = 0; i < num2; i++) { string text = val.ReadString(); int num3 = val.ReadInt(); val.ReadSingle(); val.ReadVector2i(); val.ReadBool(); if (num >= 101) { val.ReadInt(); } if (num >= 102) { val.ReadInt(); } if (num >= 103) { val.ReadLong(); val.ReadString(); } if (num >= 104) { int num4 = val.ReadInt(); if (num4 < 0 || num4 > 64) { return false; } for (int j = 0; j < num4; j++) { val.ReadString(); val.ReadString(); } } if (num >= 105) { val.ReadInt(); } if (num >= 106) { val.ReadBool(); } if (text.Length == 0 || text.Length > 64 || num3 <= 0 || num3 > 1000000) { return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); ItemDrop val2 = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if (val2?.m_itemData?.m_shared == null) { return false; } string key = Normalize(text); if (!items.TryGetValue(key, out var value)) { value = (items[key] = new StoredItem { Name = BoundText(Localize(val2.m_itemData.m_shared.m_name, text), 64), Prefab = text }); } value.Count += num3; } return true; } private static void PollPlayerDeaths() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return; } HashSet connected = new HashSet(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer == null || ((ZDOID)(ref peer.m_characterID)).IsNone()) { continue; } ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID); if (zDO == null || !zDO.IsValid()) { continue; } connected.Add(peer.m_characterID); bool flag = zDO.GetBool(ZDOVars.s_dead, false); if (!PlayerDeadState.TryGetValue(peer.m_characterID, out var value)) { PlayerDeadState[peer.m_characterID] = flag; continue; } if (!value && flag) { long actorId = zDO.GetLong(ZDOVars.s_playerID, 0L); string actorName = zDO.GetString(ZDOVars.s_playerName, peer.m_playerName ?? "Player"); RecordActivity(actorId, actorName, "player.death", "", 1, zDO.GetPosition(), "player death"); } PlayerDeadState[peer.m_characterID] = flag; } foreach (ZDOID item in PlayerDeadState.Keys.Where((ZDOID id) => !connected.Contains(id)).ToList()) { PlayerDeadState.Remove(item); } } internal static object BeginHarvestCapture(HitData hit, string source, Vector3 position) { //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) HarvestCapture activeHarvest = _activeHarvest; try { Character obj = ((hit != null) ? hit.GetAttacker() : null); Player val = (Player)(object)((obj is Player) ? obj : null); if ((Object)(object)val == (Object)null || val.GetPlayerID() == 0L) { _activeHarvest = null; return activeHarvest; } _activeHarvest = new HarvestCapture { ActorId = val.GetPlayerID(), ActorName = val.GetPlayerName(), Source = (source ?? "world resource"), Position = position }; } catch { _activeHarvest = null; } return activeHarvest; } internal static void CaptureCreatedItem(GameObject go) { HarvestCapture activeHarvest = _activeHarvest; if (activeHarvest == null || (Object)(object)go == (Object)null) { return; } ItemDrop component = go.GetComponent(); ItemData val = (((Object)(object)component != (Object)null) ? component.m_itemData : null); if (val != null && val.m_stack > 0) { string text = (((Object)(object)val.m_dropPrefab != (Object)null) ? ((Object)val.m_dropPrefab).name : ((Object)go).name.Replace("(Clone)", "").Trim()); string name = ((val.m_shared != null) ? Localize(val.m_shared.m_name, text) : text); string key = Normalize(text); if (!activeHarvest.Items.TryGetValue(key, out var value)) { value = new HarvestItem { Name = name, Prefab = text }; activeHarvest.Items[key] = value; } value.Count += val.m_stack; } } internal static void EndHarvestCapture(object previous) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) HarvestCapture activeHarvest = _activeHarvest; _activeHarvest = previous as HarvestCapture; if (activeHarvest == null) { return; } foreach (HarvestItem value in activeHarvest.Items.Values) { SubmitHarvest(activeHarvest.ActorId, activeHarvest.ActorName, value.Name, value.Prefab, value.Count, activeHarvest.Position, activeHarvest.Source); } } private static void SubmitHarvest(long actorId, string actorName, string resource, string prefab, int amount, Vector3 position, string source) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (amount <= 0) { return; } if (IsServerWorld()) { RecordActivity(actorId, actorName, "resource.harvested", resource, amount, position, source); return; } Register(); if (ZRoutedRpc.instance != null) { string text = "{\"actorId\":\"" + actorId.ToString(CultureInfo.InvariantCulture) + "\",\"actorName\":\"" + MiniJson.Escape(actorName ?? "Player") + "\",\"resource\":\"" + MiniJson.Escape(BoundText(resource ?? prefab ?? "resource", 64)) + "\",\"prefab\":\"" + MiniJson.Escape(BoundText(prefab ?? "", 64)) + "\",\"amount\":" + Mathf.Clamp(amount, 1, 1000).ToString(CultureInfo.InvariantCulture) + ",\"x\":" + position.x.ToString(CultureInfo.InvariantCulture) + ",\"y\":" + position.y.ToString(CultureInfo.InvariantCulture) + ",\"z\":" + position.z.ToString(CultureInfo.InvariantCulture) + ",\"source\":\"" + MiniJson.Escape(BoundText(source ?? "world resource", 64)) + "\"}"; ZRoutedRpc.instance.InvokeRoutedRPC(ServerAuthority.GetServerPeerUid(), "DynamicNPCs_ActivityHarvestV1", new object[1] { text }); } } private static void OnHarvestRpc(long sender, string payload) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) if (!IsServerWorld() || !(MiniJson.Parse(payload ?? "") is Dictionary obj)) { return; } long num = ParseLongString(obj, "actorId"); if (!ServerAuthority.TryResolveSender(sender, out var state)) { MercPlugin.LogWarn($"Harvest activity rejected: sender={sender}, actorId={num}, reason=sender could not be resolved."); return; } if (num == 0L || state.PlayerId != num) { MercPlugin.LogWarn($"Harvest activity rejected: sender={sender}, actorId={num}, " + $"resolvedPlayerId={state.PlayerId}, reason=actor does not match sender."); return; } if (Vector3.Distance(new Vector3((float)GetDouble(obj, "x"), (float)GetDouble(obj, "y"), (float)GetDouble(obj, "z")), state.Position) > 35f) { MercPlugin.LogWarn($"Harvest activity rejected: sender={sender}, actorId={num}, reason=distance."); return; } if (!WithinHarvestRateLimit(sender)) { MercPlugin.LogWarn($"Harvest activity rejected: sender={sender}, actorId={num}, reason=rate limit."); return; } if (!TryResolveHarvestItem(BoundText(GetString(obj, "prefab", ""), 64), out var resource, out var canonicalPrefab, out var maximumAmount)) { MercPlugin.LogWarn($"Harvest activity rejected: sender={sender}, actorId={num}, reason=unknown item prefab."); return; } int num2 = (int)GetDouble(obj, "amount"); if (num2 < 1 || num2 > maximumAmount) { MercPlugin.LogWarn($"Harvest activity rejected: sender={sender}, actorId={num}, " + "prefab=" + canonicalPrefab + ", reason=invalid stack amount."); } else { RecordActivity(num, state.PlayerName, "resource.harvested", resource, num2, state.Position, "client-observed harvest: " + canonicalPrefab, includeInStats: false); } } private static bool TryResolveHarvestItem(string submittedPrefab, out string resource, out string canonicalPrefab, out int maximumAmount) { resource = ""; canonicalPrefab = ""; maximumAmount = 0; if (string.IsNullOrWhiteSpace(submittedPrefab) || (Object)(object)ObjectDB.instance == (Object)null) { return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(submittedPrefab); SharedData val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null)?.m_itemData?.m_shared; if (val == null) { return false; } canonicalPrefab = Utils.GetPrefabName(((Object)itemPrefab).name); if (string.IsNullOrWhiteSpace(canonicalPrefab)) { return false; } resource = Localize(val.m_name, canonicalPrefab); maximumAmount = Mathf.Clamp(val.m_maxStackSize, 1, 100); return true; } private static bool WithinHarvestRateLimit(long sender) { int tickCount = Environment.TickCount; HarvestWindowStart.TryGetValue(sender, out var value); HarvestWindowCount.TryGetValue(sender, out var value2); if (tickCount - value < 0 || (float)(tickCount - value) > 5000f) { HarvestWindowStart[sender] = tickCount; value2 = (HarvestWindowCount[sender] = 0); } else if (HarvestWindowStart.Count > 64) { HarvestWindowStart.Clear(); HarvestWindowCount.Clear(); HarvestWindowStart[sender] = tickCount; value2 = (HarvestWindowCount[sender] = 0); } if (value2 >= 15) { return false; } HarvestWindowCount[sender] = value2 + 1; return true; } private static string BoundText(string value, int max) { if (string.IsNullOrEmpty(value)) { return ""; } string text = value.Trim(); if (text.Length <= max) { return text; } if (char.IsHighSurrogate(text[max - 1])) { return text.Substring(0, max - 1); } return text.Substring(0, max); } private static void RecordActivity(long actorId, string actorName, string type, string resource, int amount, Vector3 position, string source, bool includeInStats = true) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (actorId == 0L || amount <= 0 || string.IsNullOrEmpty(_ledgerPath)) { return; } int day = CurrentValheimDay(); string text = ResolveBiomeName(position); PlaceDefinition placeDefinition = (from p in Places where p.HasPosition && Vector3.Distance(position, p.Position) <= p.Radius orderby Vector3.Distance(position, p.Position) select p).FirstOrDefault(); string s = ((placeDefinition != null) ? placeDefinition.Id : ""); string text2 = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); string line = "{\"eventId\":\"" + Guid.NewGuid().ToString("N") + "\",\"worldId\":\"" + MiniJson.Escape(_worldName + ":" + _worldUid) + "\",\"valheimDay\":" + day.ToString(CultureInfo.InvariantCulture) + ",\"utc\":\"" + text2 + "\",\"actorId\":\"" + actorId.ToString(CultureInfo.InvariantCulture) + "\",\"actorName\":\"" + MiniJson.Escape(actorName ?? "Player") + "\",\"type\":\"" + MiniJson.Escape(type) + "\",\"resource\":\"" + MiniJson.Escape(resource ?? "") + "\",\"biome\":\"" + MiniJson.Escape(text) + "\",\"amount\":" + amount.ToString(CultureInfo.InvariantCulture) + ",\"placeId\":\"" + MiniJson.Escape(s) + "\",\"source\":\"" + MiniJson.Escape(source ?? "") + "\"}"; try { lock (LedgerBuffer) { LedgerBuffer.Enqueue(new BufferedLedgerLine { Path = _ledgerPath, Line = line }); while (LedgerBuffer.Count > 512) { LedgerBuffer.Dequeue(); } } } catch (Exception ex) { MercPlugin.LogWarn("Could not buffer activity ledger: " + ex.Message); } if (!includeInStats) { return; } if (type == "resource.harvested") { long now = DateTime.UtcNow.Ticks; string key = actorId.ToString(CultureInfo.InvariantCulture) + "|" + Normalize(resource); if (!ChronicleHarvestTimes.TryGetValue(key, out var value) || now < value || now - value >= 300000000) { if (ChronicleHarvestTimes.Count >= 2048 && !ChronicleHarvestTimes.ContainsKey(key)) { string[] array = (from pair in ChronicleHarvestTimes where now < pair.Value || now - pair.Value >= 300000000 select pair.Key).ToArray(); foreach (string key2 in array) { ChronicleHarvestTimes.Remove(key2); } } if (ChronicleHarvestTimes.Count < 2048 || ChronicleHarvestTimes.ContainsKey(key)) { ChronicleHarvestTimes[key] = now; PlayerChronicle.Record(actorId, actorName, "gathered " + KnowledgeRules.Display(resource, 64)); } } } string text3 = ((type == "player.death") ? text : ""); string key3 = day.ToString(CultureInfo.InvariantCulture) + "|" + actorId.ToString(CultureInfo.InvariantCulture) + "|" + type + "|" + Normalize(resource) + "|" + Normalize(text3); if (!Stats.TryGetValue(key3, out var value2)) { value2 = new DailyStat { Day = day, ActorId = actorId, ActorName = (actorName ?? "Player"), Type = type, Resource = (resource ?? ""), Biome = text3 }; Stats[key3] = value2; } value2.ActorName = actorName ?? value2.ActorName; value2.Amount += amount; value2.LastUtcTicks = DateTime.UtcNow.Ticks; _statsDirty = true; } private static string ResolveBiomeName(Vector3 position) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) try { string text = ((object)((WorldGenerator.instance != null) ? WorldGenerator.instance.GetBiome(position) : Heightmap.FindBiome(position))/*cast due to .constrained prefix*/).ToString(); if (text == "Mountain") { return "Mountains"; } return text; } catch { return "Unknown"; } } private static int CurrentValheimDay() { try { if ((Object)(object)EnvMan.instance != (Object)null && (Object)(object)ZNet.instance != (Object)null) { return EnvMan.instance.GetDay(ZNet.instance.GetTimeSeconds()); } } catch { } return 0; } internal static bool TryFindRecognizedBase(Vector3 playerPosition, string playerName, out RecognizedBase recognized) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) recognized = null; PlaceDefinition placeDefinition = (from p in Places where p.HasPosition && Vector3.Distance(playerPosition, p.Position) <= p.Radius && (string.IsNullOrWhiteSpace(p.Owner) || string.Equals(p.Owner.Trim(), playerName?.Trim(), StringComparison.OrdinalIgnoreCase)) orderby Vector3.Distance(playerPosition, p.Position) select p).FirstOrDefault(); if (placeDefinition != null) { recognized = new RecognizedBase { Id = placeDefinition.Id, Name = placeDefinition.Name, Owner = placeDefinition.Owner, Position = placeDefinition.Position, Radius = placeDefinition.Radius, IsAuthored = true }; return true; } try { if (EffectArea.GetBaseValue(playerPosition, 20f) >= 3) { recognized = new RecognizedBase { Id = "detected-base", Name = (string.IsNullOrWhiteSpace(playerName) ? "Player" : playerName) + "'s base", Owner = (playerName ?? ""), Position = playerPosition, Radius = 40f, IsAuthored = false }; return true; } } catch { } return false; } internal static List SelectGossipFacts(long targetPlayerId, int currentDay, ICollection blockedIds, int maximum = 2) { List list = new List(); foreach (DailyStat value in Stats.Values) { if (value.Amount > 0 && currentDay - value.Day <= 14 && currentDay >= value.Day) { GossipFact gossipFact = BuildFact(value); if (gossipFact != null) { list.Add(gossipFact); } } } HashSet blocked = ((blockedIds != null) ? new HashSet(blockedIds, StringComparer.Ordinal) : new HashSet(StringComparer.Ordinal)); return (from f in list where !blocked.Contains(f.Id) orderby (f.PlayerId == targetPlayerId) ? 20 : 0 descending, f.Interestingness descending, f.WorldDay descending select f).Take(Mathf.Clamp(maximum, 1, 2)).ToList(); } private static GossipFact BuildFact(DailyStat stat) { if (stat.Type == "player.death") { string text = (string.IsNullOrWhiteSpace(stat.Biome) ? "Unknown" : stat.Biome); string type = ((stat.Amount >= 2) ? "RepeatedDeaths" : "Death"); return new GossipFact { Id = $"death-{stat.ActorId}-{stat.Day}-{Normalize(text)}", Type = type, PlayerId = stat.ActorId, PlayerName = stat.ActorName, WorldDay = stat.Day, Interestingness = Mathf.Clamp(35 + (int)stat.Amount * 10, 35, 95), Amount = stat.Amount, Biome = text, Summary = ((stat.Amount == 1) ? (stat.ActorName + " died in the " + text + ".") : $"{stat.ActorName} died {stat.Amount} times in the {text}.") }; } if (stat.Type == "resource.harvested" && stat.Amount >= 10) { string text2 = (string.IsNullOrWhiteSpace(stat.Resource) ? "resources" : stat.Resource); return new GossipFact { Id = $"resource-{stat.ActorId}-{stat.Day}-{Normalize(text2)}", Type = "ResourceTotal", PlayerId = stat.ActorId, PlayerName = stat.ActorName, WorldDay = stat.Day, Interestingness = Mathf.Clamp(20 + (int)Math.Min(70L, stat.Amount / 3), 20, 90), Amount = stat.Amount, Resource = text2, Summary = $"{stat.ActorName} gathered {stat.Amount} {text2}." }; } return null; } internal static string DumpActivityForPlayer(string playerQuery) { string query = Normalize(playerQuery); List list = (from s in (from s in Stats.Values where string.IsNullOrEmpty(query) || Normalize(s.ActorName).Contains(query) orderby s.Day descending, s.Type select s).Take(20) select $"day {s.Day}: {s.ActorName} {s.Type} amount={s.Amount}" + (string.IsNullOrEmpty(s.Resource) ? "" : (" resource=" + s.Resource)) + (string.IsNullOrEmpty(s.Biome) ? "" : (" biome=" + s.Biome))).ToList(); if (list.Count != 0) { return string.Join("\n", list.ToArray()); } return "No matching recorded activity."; } private static void Flush() { FlushLedger(); if (_inventoryDirty) { SaveInventory(); } if (_statsDirty) { SaveStats(); } } private static void FlushLedger() { lock (LedgerBuffer) { while (LedgerBuffer.Count > 0) { BufferedLedgerLine bufferedLedgerLine = LedgerBuffer.Peek(); if (string.IsNullOrEmpty(bufferedLedgerLine.Path)) { LedgerBuffer.Dequeue(); continue; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (BufferedLedgerLine item in LedgerBuffer) { if (!string.Equals(item.Path, bufferedLedgerLine.Path, StringComparison.Ordinal)) { break; } stringBuilder.Append(item.Line).Append(Environment.NewLine); num++; } try { Directory.CreateDirectory(Path.GetDirectoryName(bufferedLedgerLine.Path)); if (File.Exists(bufferedLedgerLine.Path) && new FileInfo(bufferedLedgerLine.Path).Length >= 4194304) { File.Copy(bufferedLedgerLine.Path, bufferedLedgerLine.Path + ".previous", overwrite: true); File.WriteAllText(bufferedLedgerLine.Path, "", Encoding.UTF8); } File.AppendAllText(bufferedLedgerLine.Path, stringBuilder.ToString(), Encoding.UTF8); for (int i = 0; i < num; i++) { LedgerBuffer.Dequeue(); } } catch (Exception ex) { MercPlugin.LogWarn("Could not append activity ledger; buffered events retained: " + ex.Message); break; } } } } private static void SaveInventory() { if (string.IsNullOrEmpty(_inventoryPath)) { return; } try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\"worldId\":\"").Append(MiniJson.Escape(_worldName + ":" + _worldUid)).Append("\",\"generatedUtc\":\"") .Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)) .Append("\",\"containers\":["); bool flag = true; foreach (ContainerRecord item in Containers.Values.OrderBy((ContainerRecord r) => ((object)Unsafe.As(ref r.Id)/*cast due to .constrained prefix*/).ToString())) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"user\":\"").Append(((ZDOID)(ref item.Id)).UserID.ToString(CultureInfo.InvariantCulture)).Append("\",\"id\":") .Append(((ZDOID)(ref item.Id)).ID.ToString(CultureInfo.InvariantCulture)) .Append(",\"prefab\":\"") .Append(MiniJson.Escape(item.Prefab ?? "")) .Append("\",\"name\":\"") .Append(MiniJson.Escape(item.Name ?? "")) .Append("\",\"x\":") .Append(item.Position.x.ToString(CultureInfo.InvariantCulture)) .Append(",\"y\":") .Append(item.Position.y.ToString(CultureInfo.InvariantCulture)) .Append(",\"z\":") .Append(item.Position.z.ToString(CultureInfo.InvariantCulture)) .Append(",\"revision\":") .Append(item.Revision.ToString(CultureInfo.InvariantCulture)) .Append(",\"indexedTicks\":\"") .Append(item.IndexedUtcTicks.ToString(CultureInfo.InvariantCulture)) .Append("\",\"items\":["); bool flag2 = true; foreach (StoredItem item2 in item.Items.Values.OrderBy((StoredItem i) => i.Name)) { if (!flag2) { stringBuilder.Append(','); } flag2 = false; stringBuilder.Append("{\"name\":\"").Append(MiniJson.Escape(item2.Name ?? "")).Append("\",\"prefab\":\"") .Append(MiniJson.Escape(item2.Prefab ?? "")) .Append("\",\"count\":") .Append(item2.Count.ToString(CultureInfo.InvariantCulture)) .Append('}'); } stringBuilder.Append("]}"); } stringBuilder.Append("]}"); AtomicFile.WriteAllText(_inventoryPath, stringBuilder.ToString(), Encoding.UTF8); _inventoryDirty = false; } catch (Exception ex) { MercPlugin.LogWarn("Could not save WorldInventory.json: " + ex.Message); } } private static void LoadInventory() { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) Containers.Clear(); _indexedItemRecords = 0; if (!File.Exists(_inventoryPath)) { return; } try { if (new FileInfo(_inventoryPath).Length > 16777216) { throw new InvalidDataException("Inventory cache is oversized; it will be rebuilt incrementally."); } if (!(MiniJson.Parse(File.ReadAllText(_inventoryPath)) is Dictionary dictionary) || !dictionary.TryGetValue("containers", out var value) || !(value is List list)) { return; } foreach (object item in list) { if (Containers.Count >= 4096) { break; } if (item is Dictionary obj) { long num = ParseLongString(obj, "user"); uint num2 = (uint)Math.Max(0.0, GetDouble(obj, "id")); if (num != 0L && num2 != 0) { ContainerRecord containerRecord = new ContainerRecord { Id = new ZDOID(num, num2), Prefab = BoundText(GetString(obj, "prefab", ""), 64), Name = BoundText(GetString(obj, "name", "container"), 64), Position = new Vector3((float)GetDouble(obj, "x"), (float)GetDouble(obj, "y"), (float)GetDouble(obj, "z")), Revision = uint.MaxValue, IndexedUtcTicks = ParseLongString(obj, "indexedTicks") }; Containers[containerRecord.Id] = containerRecord; } } } } catch (Exception ex) { MercPlugin.LogWarn("Could not load WorldInventory.json: " + ex.Message); } } private static void SaveStats() { if (string.IsNullOrEmpty(_statsPath)) { return; } try { int currentDay = CurrentValheimDay(); if (currentDay > 0) { foreach (string item in (from pair in Stats where currentDay - pair.Value.Day > 15 select pair.Key).ToList()) { Stats.Remove(item); } } StringBuilder stringBuilder = new StringBuilder("{\"stats\":["); bool flag = true; foreach (DailyStat item2 in from s in Stats.Values orderby s.Day, s.ActorName select s) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("{\"day\":").Append(item2.Day.ToString(CultureInfo.InvariantCulture)).Append(",\"actorId\":\"") .Append(item2.ActorId.ToString(CultureInfo.InvariantCulture)) .Append("\",\"actorName\":\"") .Append(MiniJson.Escape(item2.ActorName ?? "Player")) .Append("\",\"type\":\"") .Append(MiniJson.Escape(item2.Type ?? "")) .Append("\",\"resource\":\"") .Append(MiniJson.Escape(item2.Resource ?? "")) .Append("\",\"biome\":\"") .Append(MiniJson.Escape(item2.Biome ?? "")) .Append("\",\"amount\":") .Append(item2.Amount.ToString(CultureInfo.InvariantCulture)) .Append(",\"lastUtcTicks\":\"") .Append(item2.LastUtcTicks.ToString(CultureInfo.InvariantCulture)) .Append("\"}"); } stringBuilder.Append("]}"); AtomicFile.WriteAllText(_statsPath, stringBuilder.ToString(), Encoding.UTF8); if (SaveActivityFacts()) { _statsDirty = false; } } catch (Exception ex) { MercPlugin.LogWarn("Could not save DailyStats.json: " + ex.Message); } } private static void LoadStats() { Stats.Clear(); if (!File.Exists(_statsPath)) { return; } try { if (new FileInfo(_statsPath).Length > 16777216) { throw new InvalidDataException("Daily activity cache is oversized."); } if (!(MiniJson.Parse(File.ReadAllText(_statsPath)) is Dictionary dictionary) || !dictionary.TryGetValue("stats", out var value) || !(value is List list)) { return; } foreach (object item in list) { if (item is Dictionary obj) { DailyStat dailyStat = new DailyStat { Day = (int)GetDouble(obj, "day"), ActorId = ParseLongString(obj, "actorId"), ActorName = GetString(obj, "actorName", "Player"), Type = GetString(obj, "type", ""), Resource = GetString(obj, "resource", ""), Biome = GetString(obj, "biome", ""), Amount = (long)GetDouble(obj, "amount"), LastUtcTicks = ParseLongString(obj, "lastUtcTicks") }; string key = dailyStat.Day.ToString(CultureInfo.InvariantCulture) + "|" + dailyStat.ActorId.ToString(CultureInfo.InvariantCulture) + "|" + dailyStat.Type + "|" + Normalize(dailyStat.Resource) + "|" + Normalize((dailyStat.Type == "player.death") ? dailyStat.Biome : ""); Stats[key] = dailyStat; } } } catch (Exception ex) { MercPlugin.LogWarn("Could not load DailyStats.json: " + ex.Message); } } private static bool SaveActivityFacts() { if (string.IsNullOrEmpty(_factsPath)) { return false; } try { int day = CurrentValheimDay(); List list = (from f in Stats.Values.Select(BuildFact) where f != null where day <= 0 || day - f.WorldDay <= 14 orderby f.WorldDay descending, f.Interestingness descending select f).ToList(); StringBuilder stringBuilder = new StringBuilder("{\"facts\":["); for (int num = 0; num < list.Count; num++) { GossipFact gossipFact = list[num]; if (num > 0) { stringBuilder.Append(','); } stringBuilder.Append("{\"id\":\"").Append(MiniJson.Escape(gossipFact.Id)).Append("\",\"type\":\"") .Append(MiniJson.Escape(gossipFact.Type)) .Append("\",\"playerId\":\"") .Append(gossipFact.PlayerId.ToString(CultureInfo.InvariantCulture)) .Append("\",\"playerName\":\"") .Append(MiniJson.Escape(gossipFact.PlayerName)) .Append("\",\"worldDay\":") .Append(gossipFact.WorldDay.ToString(CultureInfo.InvariantCulture)) .Append(",\"interestingness\":") .Append(gossipFact.Interestingness.ToString(CultureInfo.InvariantCulture)) .Append(",\"amount\":") .Append(gossipFact.Amount.ToString(CultureInfo.InvariantCulture)) .Append(",\"resource\":\"") .Append(MiniJson.Escape(gossipFact.Resource ?? "")) .Append("\",\"biome\":\"") .Append(MiniJson.Escape(gossipFact.Biome ?? "")) .Append("\",\"summary\":\"") .Append(MiniJson.Escape(gossipFact.Summary ?? "")) .Append("\"}"); } stringBuilder.Append("]}"); AtomicFile.WriteAllText(_factsPath, stringBuilder.ToString(), Encoding.UTF8); return true; } catch (Exception ex) { MercPlugin.LogWarn("Could not save ActivityFacts.json: " + ex.Message); return false; } } private static HashSet Words(string text) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (Match item in WordPattern.Matches((text ?? "").ToLowerInvariant())) { string value = item.Value; if (value.Length >= 2 && !StopWords.Contains(value)) { hashSet.Add(value); } } return hashSet; } private static string Normalize(string text) { if (string.IsNullOrWhiteSpace(text)) { return ""; } return Regex.Replace(text.ToLowerInvariant().Replace('’', '\''), "[^a-z0-9]+", " ").Trim(); } private static string Slug(string text) { return Normalize(text).Replace(' ', '-'); } private static string SafeFileName(string text) { string text2 = text ?? "world"; char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text2 = text2.Replace(oldChar, '_'); } if (!string.IsNullOrWhiteSpace(text2)) { return text2; } return "world"; } private static string Localize(string token, string fallback) { try { string text = ((Localization.instance != null && !string.IsNullOrEmpty(token)) ? Localization.instance.Localize(token) : token); return string.IsNullOrWhiteSpace(text) ? NonEmpty(fallback, "item") : text; } catch { return NonEmpty(fallback, "item"); } } private static bool ContainsAny(string text, params string[] values) { return values.Any((string value) => text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0); } private static string NonEmpty(string value, string fallback) { if (!string.IsNullOrWhiteSpace(value)) { return value; } return fallback; } private static string BoundLog(string text) { if (!string.IsNullOrEmpty(text)) { if (text.Length > 120) { return text.Substring(0, 120); } return text; } return ""; } private static string GetString(Dictionary obj, string key, string fallback) { if (!obj.TryGetValue(key, out var value) || !(value is string result)) { return fallback; } return result; } private static double GetDouble(Dictionary obj, string key) { if (obj.TryGetValue(key, out var value) && value is double) { return (double)value; } return 0.0; } private static long ParseLongString(Dictionary obj, string key) { if (!long.TryParse(GetString(obj, key, "0"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0L; } return result; } }