Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of AFKManager v0.3.1
plugins/AFKManager/AFKManager.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Splatform; using UnityEngine; [assembly: AssemblyProduct("AFKManager")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyTitle("AFKManager")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.3.0.0")] namespace AFKManager; internal sealed class AFKConfig { private const float DefaultAfkMinutes = 10f; private const float DefaultInterval = 5f; private const float DefaultMove = 0.5f; private const float DefaultLook = 10f; private readonly ConfigEntry<bool> _enabled; private readonly ConfigEntry<bool> _announceAfk; private readonly ConfigEntry<bool> _announceReturn; private readonly ConfigEntry<bool> _announceInChat; private readonly ConfigEntry<bool> _announceNotification; private readonly ConfigEntry<bool> _excludeFromSleep; private readonly ConfigEntry<bool> _excludeFromRaids; private readonly ConfigEntry<bool> _debug; private readonly ConfigEntry<float> _afkAfterMinutes; private readonly ConfigEntry<float> _checkInterval; private readonly ConfigEntry<float> _moveThreshold; private readonly ConfigEntry<float> _lookThreshold; private readonly ConfigEntry<string> _afkMessage; private readonly ConfigEntry<string> _returnMessage; private readonly ConfigEntry<string> _manualAfkEmote; private readonly ManualLogSource _log; private bool _warnedInvalid; internal bool Enabled => _enabled.Value; internal bool AnnounceAfk => _announceAfk.Value; internal bool AnnounceReturn => _announceReturn.Value; internal bool AnnounceInChat => _announceInChat.Value; internal bool AnnounceNotification => _announceNotification.Value; internal bool ExcludeAfkFromSleep => _excludeFromSleep.Value; internal bool ExcludeAfkFromRandomRaids => _excludeFromRaids.Value; internal bool DebugLogging => _debug.Value; internal string AfkMessage => _afkMessage.Value; internal string ReturnMessage => _returnMessage.Value; internal string ManualAfkEmote { get { string value = _manualAfkEmote.Value; if (value != null) { return value.Trim(); } return ""; } } internal float AfkAfterSeconds => Safe(_afkAfterMinutes, 10f, 0.1f, 1440f) * 60f; internal float CheckIntervalSeconds => Safe(_checkInterval, 5f, 1f, 60f); internal float MovementThresholdMeters => Safe(_moveThreshold, 0.5f, 0.05f, 10f); internal float LookThresholdDegrees => Safe(_lookThreshold, 10f, 1f, 180f); internal AFKConfig(ConfigFile cfg, ManualLogSource log) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown _log = log; _enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. When false AFKManager does nothing (no detection, no announcements, no sleep changes)."); _afkAfterMinutes = cfg.Bind<float>("General", "AfkAfterMinutes", 10f, new ConfigDescription("Minutes without any detected activity before a player is marked AFK. Small values (e.g. 1) are useful for testing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1440f), new object[0])); _checkInterval = cfg.Bind<float>("General", "CheckIntervalSeconds", 5f, new ConfigDescription("How often (seconds) connected players are checked. The check is extremely cheap; 5 is recommended. The movement threshold below was validated for a 5 second interval.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), new object[0])); _moveThreshold = cfg.Bind<float>("Detection", "MovementThresholdMeters", 0.5f, new ConfigDescription("Horizontal distance (meters) a player must move between two checks to count as active. 0.5 safely ignores physics drift (e.g. slowly sliding on a slope). On a ship, movement relative to the ship is used, so passengers standing still on a sailing ship do not count as moving.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), new object[0])); _lookThreshold = cfg.Bind<float>("Detection", "LookThresholdDegrees", 10f, new ConfigDescription("How far (degrees) a player must turn their view to count as active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 180f), new object[0])); _announceAfk = cfg.Bind<bool>("Announcements", "AnnounceAfk", true, "Tell all players when someone becomes AFK."); _announceReturn = cfg.Bind<bool>("Announcements", "AnnounceReturn", true, "Tell all players when someone is no longer AFK."); _announceInChat = cfg.Bind<bool>("Announcements", "AnnounceInChat", true, "Show announcements in the normal chat window (sender name: AFKManager). Works with vanilla clients."); _announceNotification = cfg.Bind<bool>("Announcements", "AnnounceNotification", true, "Show announcements as a small notification in the top-left message feed. Works with vanilla clients."); _afkMessage = cfg.Bind<string>("Announcements", "AfkMessage", "{player} is now AFK.", "Text shown when a player becomes AFK. {player} is replaced with the player's name."); _returnMessage = cfg.Bind<string>("Announcements", "ReturnMessage", "{player} is no longer AFK.", "Text shown when a player returns. {player} is replaced with the player's name."); _manualAfkEmote = cfg.Bind<string>("Manual AFK", "ManualAfkEmote", "rest", "Emote a player can use to mark THEMSELVES AFK right away, typed in the chat window as /<emote> (default: /rest). Starting it does not count as activity; any normal activity (moving, looking around, another emote, ...) makes the player active again. Valheim does not send made-up commands such as /afk to the server, but it does send emotes, so this works from a completely vanilla client on any platform. A player in a bed cannot emote, so sleeping never triggers it. Emote names: wave, sit, challenge, cheer, nonono, thumbsup, point, blowkiss, bow, cower, cry, despair, flex, comehere, headbang, kneel, laugh, roar, shrug, dance, relax, toast, rest, vibe, loveyou. Empty = disabled."); _excludeFromSleep = cfg.Bind<bool>("Gameplay", "ExcludeAfkFromSleep", true, "When true, AFK players who are not in bed no longer prevent the night from being skipped. At least one player must still be in bed. Requires a server restart to fully enable/disable the hook."); _excludeFromRaids = cfg.Bind<bool>("Raids", "ExcludeAfkFromRandomRaids", true, "When enabled, a new vanilla random raid will not begin in an AFK-only player area. If an active player is present, the raid is allowed. Existing raids are never cancelled when players become AFK. Boss, scripted and admin-started events are not affected. Requires a server restart to fully enable/disable the hook."); _debug = cfg.Bind<bool>("Logging", "DebugLogging", false, "Log activity reasons, AFK transitions and a short summary of every check. Leave off for normal play."); } private float Safe(ConfigEntry<float> entry, float fallback, float min, float max) { float value = entry.Value; if (float.IsNaN(value) || float.IsInfinity(value)) { if (!_warnedInvalid) { _warnedInvalid = true; _log.LogWarning((object)("Config value '" + ((ConfigEntryBase)entry).Definition.Key + "' is not a valid number; using default " + fallback + ".")); } return fallback; } if (!(value < min)) { if (!(value > max)) { return value; } return max; } return min; } } [BepInPlugin("torokal.afkmanager", "AFKManager", "0.3.0")] public sealed class AFKManagerPlugin : BaseUnityPlugin { public const string PluginGuid = "torokal.afkmanager"; public const string PluginName = "AFKManager"; public const string PluginVersion = "0.3.0"; private static AfkService _service; private AFKConfig _config; private float _nextCheck; private bool _wasServer; public static bool IsAfk(long peerUid) { if (_service != null) { return _service.IsAfk(peerUid); } return false; } private void Awake() { _config = new AFKConfig(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); AnnouncementService announcer = new AnnouncementService(_config, ((BaseUnityPlugin)this).Logger); _service = new AfkService(_config, ((BaseUnityPlugin)this).Logger, announcer); SleepIntegration.TryEnable(_service, _config, ((BaseUnityPlugin)this).Logger); RaidIntegration.TryEnable(_service, _config, ((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)"AFKManager 0.3.0 loaded (server-side only; does nothing on clients)."); } private void Update() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextCheck) { return; } _nextCheck = realtimeSinceStartup + _config.CheckIntervalSeconds; if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer() || !_config.Enabled) { if (_wasServer) { _service.Clear(); _wasServer = false; } } else { _wasServer = true; _service.Check(realtimeSinceStartup); } } } internal sealed class AfkService { private const int AnimatorZdoSalt = 438569; private readonly AFKConfig _config; private readonly ManualLogSource _log; private readonly AnnouncementService _announcer; private readonly Dictionary<long, PlayerAfkState> _states = new Dictionary<long, PlayerAfkState>(); private readonly List<long> _stale = new List<long>(); private readonly int _craftingKey; private int[] _equipmentKeys; private int _generation; internal AfkService(AFKConfig config, ManualLogSource log, AnnouncementService announcer) { _config = config; _log = log; _announcer = announcer; _craftingKey = 438569 + Animator.StringToHash("crafting"); } internal bool IsAfk(long peerUid) { if (_states.TryGetValue(peerUid, out var value) && value.Initialized) { return value.IsAfk; } return false; } internal void Clear() { _states.Clear(); } internal void Check(float now) { //IL_01d4: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; ZDOMan instance2 = ZDOMan.instance; if ((Object)(object)instance == (Object)null || instance2 == null) { return; } bool debugLogging = _config.DebugLogging; long num = (debugLogging ? Stopwatch.GetTimestamp() : 0); if (_equipmentKeys == null) { _equipmentKeys = new int[9] { ZDOVars.s_rightItem, ZDOVars.s_leftItem, ZDOVars.s_rightBackItem, ZDOVars.s_leftBackItem, ZDOVars.s_chestItem, ZDOVars.s_legItem, ZDOVars.s_helmetItem, ZDOVars.s_shoulderItem, ZDOVars.s_utilityItem }; } float afkAfterSeconds = _config.AfkAfterSeconds; float movementThresholdMeters = _config.MovementThresholdMeters; float moveThresholdSqr = movementThresholdMeters * movementThresholdMeters; float lookThresholdDegrees = _config.LookThresholdDegrees; _generation++; int num2 = 0; int num3 = 0; List<ZNetPeer> peers = instance.GetPeers(); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val == null || !val.IsReady()) { continue; } if (!_states.TryGetValue(val.m_uid, out var value)) { value = new PlayerAfkState(); value.PeerUid = val.m_uid; _states[val.m_uid] = value; } value.SeenGeneration = _generation; value.PlayerName = val.m_playerName; UpdatePlayer(value, val, instance2, now, moveThresholdSqr, lookThresholdDegrees); if (value.Initialized && !value.NeedsBaseline && !value.IsAfk && now - value.LastActivityTime >= afkAfterSeconds) { value.IsAfk = true; if (debugLogging) { _log.LogInfo((object)(value.PlayerName + " ACTIVE -> AFK")); } _announcer.AnnounceAfk(value.PlayerName, value.LastWorldPosition); } if (value.Initialized) { num2++; if (value.IsAfk) { num3++; } } } RemoveDisconnected(); if (debugLogging) { double num4 = (double)(Stopwatch.GetTimestamp() - num) * 1000.0 / (double)Stopwatch.Frequency; _log.LogInfo((object)("check: players=" + num2 + " active=" + (num2 - num3) + " afk=" + num3 + " elapsed=" + num4.ToString("F3") + "ms")); } } private void UpdatePlayer(PlayerAfkState state, ZNetPeer peer, ZDOMan zdoMan, float now, float moveThresholdSqr, float lookThreshold) { //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_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_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_005e: 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_005f: 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_0073: 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_008c: 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_008f: 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_0098: 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_009d: 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_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029a: 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_02a1: 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_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: 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_01d6: 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_0246: Unknown result type (might be due to invalid IL or missing references) ZDOID characterID = peer.m_characterID; ZDO val = (((ZDOID)(ref characterID)).IsNone() ? null : zdoMan.GetZDO(characterID)); if (val == null || val.GetBool(ZDOVars.s_dead, false)) { state.NeedsBaseline = true; return; } ZDOID connectionZDOID = val.GetConnectionZDOID((ConnectionType)2); Vector3 position = val.GetPosition(); Vector3 lastPosition = (((ZDOID)(ref connectionZDOID)).IsNone() ? position : val.GetVec3(ZDOVars.s_relPosHash, Vector3.zero)); Vector3 vec = val.GetVec3(ZDOVars.s_lookTarget, Vector3.zero); bool flag = vec != Vector3.zero; Vector3 val2; if (!flag) { val2 = Vector3.zero; } else { Vector3 val3 = vec - position; val2 = ((Vector3)(ref val3)).normalized; } Vector3 val4 = val2; int num = val.GetInt(ZDOVars.s_emoteID, 0); string text = val.GetString(ZDOVars.s_emote, ""); int num2 = val.GetInt(_craftingKey, 0); bool flag2 = val.GetBool(ZDOVars.s_inBed, false); int num3 = 17; for (int i = 0; i < _equipmentKeys.Length; i++) { num3 = num3 * 31 + val.GetInt(_equipmentKeys[i], 0); } ActivityReason activityReason = ActivityReason.None; bool flag3 = state.Initialized && !state.NeedsBaseline && characterID == state.CharacterId; bool flag4 = IsManualAfkEmote(text); bool flag5 = flag3 && flag4 && (text != state.LastEmote || num != state.LastEmoteId); if (!state.Initialized) { state.Initialized = true; state.LastActivityTime = now; } else if (flag3) { if (flag2 && !state.WasInBed) { activityReason |= ActivityReason.EnterBed; } if (num != state.LastEmoteId && !flag4) { activityReason |= ActivityReason.Emote; } if (num3 != state.LastEquipmentHash) { activityReason |= ActivityReason.Equipment; } if (num2 != state.LastCrafting) { activityReason |= ActivityReason.CraftingStation; } if (!(connectionZDOID != state.Parent) && flag2 == state.WasInBed) { float num4 = lastPosition.x - state.LastPosition.x; float num5 = lastPosition.z - state.LastPosition.z; if (num4 * num4 + num5 * num5 > moveThresholdSqr) { activityReason |= ActivityReason.Movement; } if (flag && state.HasLookReference && Vector3.Angle(val4, state.LookReference) > lookThreshold) { activityReason |= ActivityReason.Look; } } else { state.HasLookReference = false; } } else { state.HasLookReference = false; state.LastActivityTime += now - state.LastObservedTime; } state.NeedsBaseline = false; state.LastObservedTime = now; state.CharacterId = characterID; state.Parent = connectionZDOID; state.LastWorldPosition = position; state.LastPosition = lastPosition; state.LastEmoteId = num; state.LastEmote = text; state.LastEquipmentHash = num3; state.LastCrafting = num2; state.WasInBed = flag2; if (flag && (!state.HasLookReference || (activityReason & ActivityReason.Look) != ActivityReason.None)) { state.LookReference = val4; state.HasLookReference = true; } if (flag5) { MarkManualAfk(state, now); } else if (activityReason != ActivityReason.None) { MarkActivity(state, activityReason, now); } } private bool IsManualAfkEmote(string emote) { string manualAfkEmote = _config.ManualAfkEmote; if (emote.Length != 0 && manualAfkEmote.Length != 0) { return string.Equals(emote, manualAfkEmote, StringComparison.OrdinalIgnoreCase); } return false; } private void MarkManualAfk(PlayerAfkState state, float now) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) state.LastActivityTime = now; if (state.IsAfk) { if (_config.DebugLogging) { _log.LogInfo((object)(state.PlayerName + " manual AFK emote while already AFK: ignored")); } return; } state.IsAfk = true; if (_config.DebugLogging) { _log.LogInfo((object)(state.PlayerName + " ACTIVE -> AFK (manual emote)")); } _announcer.AnnounceAfk(state.PlayerName, state.LastWorldPosition); } private void MarkActivity(PlayerAfkState state, ActivityReason reasons, float now) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) state.LastActivityTime = now; if (state.IsAfk) { state.IsAfk = false; if (_config.DebugLogging) { _log.LogInfo((object)string.Concat(state.PlayerName, " AFK -> ACTIVE (", reasons, ")")); } _announcer.AnnounceReturn(state.PlayerName, state.LastWorldPosition); } else if (_config.DebugLogging) { _log.LogInfo((object)(state.PlayerName + " activity: " + reasons)); } } private void RemoveDisconnected() { if (_states.Count == 0) { return; } _stale.Clear(); foreach (KeyValuePair<long, PlayerAfkState> state in _states) { if (state.Value.SeenGeneration != _generation) { _stale.Add(state.Key); } } for (int i = 0; i < _stale.Count; i++) { if (_config.DebugLogging) { _log.LogInfo((object)("session ended: " + _states[_stale[i]].PlayerName)); } _states.Remove(_stale[i]); } } } internal sealed class AnnouncementService { private const int MaxNameLength = 32; private const string ChatSenderName = "AFKManager"; private const string ChatSenderId = "AFKManager_0"; private static readonly Vector3 ChatBubbleOffset = new Vector3(0f, 1.9f, 0f); private readonly AFKConfig _config; private readonly ManualLogSource _log; private readonly List<ZNetPeer> _recipients = new List<ZNetPeer>(); private readonly MethodInfo _sendPlayerList; private readonly MethodInfo _writePlayerInfo; private bool _chatWarned; private bool _notificationWarned; internal AnnouncementService(AFKConfig config, ManualLogSource log) { _config = config; _log = log; try { MethodInfo method = typeof(ZNet).GetMethod("SendPlayerList", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null && method.ReturnType == typeof(void)) { _sendPlayerList = method; } MethodInfo method2 = typeof(ZNet).GetMethod("WritePlayerInfo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(List<PlayerInfo>) }, null); if (method2 != null && method2.ReturnType == typeof(ZPackage)) { _writePlayerInfo = method2; } } catch (Exception ex) { _log.LogWarning((object)("Could not inspect ZNet (" + ex.GetType().Name + "): chat announcements use the built-in fallback.")); } if (_sendPlayerList == null || _writePlayerInfo == null) { _log.LogWarning((object)"ZNet.SendPlayerList/WritePlayerInfo not found in this game version: chat announcements use a fallback that may hide other mods' player-list entries for up to 2 seconds per announcement."); } } internal void AnnounceAfk(string playerName, Vector3 playerPosition) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (_config.AnnounceAfk) { Send(_config.AfkMessage, playerName, playerPosition); } } internal void AnnounceReturn(string playerName, Vector3 playerPosition) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (_config.AnnounceReturn) { Send(_config.ReturnMessage, playerName, playerPosition); } } private void Send(string template, string playerName, Vector3 playerPosition) { //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_0099: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(template) || ZRoutedRpc.instance == null) { return; } string text = template.Replace("{player}", SanitizeName(playerName)); if (_config.AnnounceNotification) { try { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 1, text }); } catch (Exception e) { if (!_notificationWarned) { _notificationWarned = true; _log.LogWarning((object)("Notification announcement failed: " + Describe(e))); } } } if (_config.AnnounceInChat) { try { SendChat(text, playerPosition + ChatBubbleOffset); } catch (Exception e2) { WarnChat("Chat announcement failed (notifications are unaffected): " + Describe(e2)); } } if (_config.DebugLogging) { _log.LogInfo((object)("announced: " + text)); } } private void SendChat(string text, Vector3 bubblePosition) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_0099: 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_011f: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return; } _recipients.Clear(); List<ZNetPeer> peers = instance.GetPeers(); for (int i = 0; i < peers.Count; i++) { if (peers[i] != null && peers[i].IsReady()) { _recipients.Add(peers[i]); } } if (_recipients.Count == 0) { return; } bool environmentList; int entries; ZPackage val = BuildTemporaryList(instance, out environmentList, out entries); UserInfo val2 = new UserInfo(); val2.Name = "AFKManager"; val2.UserId = new PlatformUserID("AFKManager_0"); bool flag = false; bool flag2 = false; int num = 0; try { for (int j = 0; j < _recipients.Count; j++) { ZNetPeer val3 = _recipients[j]; try { if (val3.m_rpc != null && val3.m_rpc.IsConnected()) { flag = true; val3.m_rpc.Invoke("PlayerList", new object[1] { val }); ZRoutedRpc.instance.InvokeRoutedRPC(val3.m_uid, "ChatMessage", new object[4] { bubblePosition, 1, val2, text }); num++; } } catch (Exception e) { WarnChat("Chat announcement to one player failed (others and notifications are unaffected): " + Describe(e)); } } } finally { if (flag) { flag2 = RestorePlayerList(instance); } _recipients.Clear(); } if (_config.DebugLogging) { _log.LogInfo((object)("chat: delivered=" + num + " temporaryList=" + entries + " entries (" + (environmentList ? "environment+1" : "rebuilt+1") + ") restore=" + (flag2 ? "vanilla SendPlayerList" : "fallback"))); } } private ZPackage BuildTemporaryList(ZNet znet, out bool environmentList, out int entries) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_00e1: 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown environmentList = false; entries = 0; ZPackage val = null; if (_writePlayerInfo != null) { try { val = (ZPackage)_writePlayerInfo.Invoke(znet, new object[1] { znet.GetPlayerList() }); int num = val.Size(); val.SetPos(0); int num2 = val.ReadInt(); if (num2 < 0 || num < 4) { throw new InvalidOperationException("unexpected player list package"); } val.SetPos(0); val.Write(num2 + 1); entries = num2 + 1; val.SetPos(num); environmentList = true; } catch (Exception e) { val = null; WarnChat("ZNet.WritePlayerInfo could not be used (" + Describe(e) + "); chat announcements use a rebuilt player list."); } } if (val == null) { List<PlayerInfo> playerList = znet.GetPlayerList(); val = new ZPackage(); val.Write(playerList.Count + 1); entries = playerList.Count + 1; WritePlayers(val, playerList); } WriteEntry(val, "AFKManager", ZDOID.None, "AFKManager_0", "AFKManager", "", "", publicPosition: false, Vector3.zero); return val; } private bool RestorePlayerList(ZNet znet) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown if (_sendPlayerList != null) { try { _sendPlayerList.Invoke(znet, null); return true; } catch (Exception e) { WarnChat("ZNet.SendPlayerList failed (" + Describe(e) + "); the player list was restored by the fallback."); } } try { List<PlayerInfo> playerList = znet.GetPlayerList(); ZPackage val = new ZPackage(); val.Write(playerList.Count); WritePlayers(val, playerList); for (int i = 0; i < _recipients.Count; i++) { try { if (_recipients[i].m_rpc != null) { _recipients[i].m_rpc.Invoke("PlayerList", new object[1] { val }); } } catch (Exception) { } } } catch (Exception e2) { WarnChat("Player list fallback restore failed (" + Describe(e2) + "); vanilla re-sends the list within 2 seconds."); } return false; } private unsafe static void WritePlayers(ZPackage pkg, List<PlayerInfo> players) { //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_0016: 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) for (int i = 0; i < players.Count; i++) { PlayerInfo val = players[i]; WriteEntry(pkg, val.m_name, val.m_characterID, ((object)(*(PlatformUserID*)(&val.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString(), val.m_userInfo.m_displayName, val.m_userInfo.m_serverAssignedDisplayName, val.m_userInfo.m_playfabId, val.m_publicPosition, val.m_position); } } private static void WriteEntry(ZPackage pkg, string name, ZDOID characterId, string platformId, string displayName, string serverAssignedName, string playfabId, bool publicPosition, Vector3 position) { //IL_0011: 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) pkg.Write(name ?? ""); pkg.Write(characterId); pkg.Write(platformId ?? ""); pkg.Write(displayName ?? ""); pkg.Write(serverAssignedName ?? ""); pkg.Write(playfabId ?? ""); pkg.Write(publicPosition); if (publicPosition) { pkg.Write(position); } } private void WarnChat(string message) { if (!_chatWarned) { _chatWarned = true; _log.LogWarning((object)message); } else if (_config.DebugLogging) { _log.LogInfo((object)message); } } private static string Describe(Exception e) { while (e is TargetInvocationException && e.InnerException != null) { e = e.InnerException; } return e.GetType().Name + ": " + e.Message; } internal static string SanitizeName(string name) { if (string.IsNullOrEmpty(name)) { return "?"; } StringBuilder stringBuilder = new StringBuilder(name.Length); for (int i = 0; i < name.Length; i++) { if (stringBuilder.Length >= 32) { break; } char c = name[i]; if (c != '<' && c != '>' && c != '$' && !char.IsControl(c)) { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (text.Length != 0) { return text; } return "?"; } } [Flags] internal enum ActivityReason { None = 0, Movement = 1, Look = 2, Emote = 4, Equipment = 8, CraftingStation = 0x10, EnterBed = 0x20 } internal sealed class PlayerAfkState { internal long PeerUid; internal string PlayerName = ""; internal bool Initialized; internal bool NeedsBaseline; internal float LastActivityTime; internal float LastObservedTime; internal bool IsAfk; internal ZDOID CharacterId; internal ZDOID Parent; internal Vector3 LastPosition; internal Vector3 LastWorldPosition; internal Vector3 LookReference; internal bool HasLookReference; internal int LastEmoteId; internal string LastEmote = ""; internal int LastEquipmentHash; internal int LastCrafting; internal bool WasInBed; internal int SeenGeneration; } internal static class RaidIntegration { private const string HarmonyId = "torokal.afkmanager.raids"; private static AfkService _service; private static AFKConfig _config; private static ManualLogSource _log; private static bool _errorLogged; private static readonly List<Vector3> _playerPositions = new List<Vector3>(); private static readonly List<bool> _playerAfk = new List<bool>(); internal static void TryEnable(AfkService service, AFKConfig config, ManualLogSource log) { //IL_00a1: 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_00c5: Expected O, but got Unknown _service = service; _config = config; _log = log; if (!config.ExcludeAfkFromRandomRaids) { log.LogInfo((object)"Raid integration disabled by config."); return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(RandEventSystem), "GetValidEventPoints", (Type[])null, (Type[])null); ParameterInfo[] array = ((methodInfo != null) ? methodInfo.GetParameters() : null); if (methodInfo == null || methodInfo.ReturnType != typeof(List<Vector3>) || array.Length < 1 || array[0].ParameterType != typeof(RandomEvent)) { log.LogWarning((object)"Raid integration unavailable: RandEventSystem.GetValidEventPoints(RandomEvent, ...) not found in this game version. Everything else is unaffected."); return; } new Harmony("torokal.afkmanager.raids").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(RaidIntegration), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); log.LogInfo((object)"Raid integration enabled (Postfix on RandEventSystem.GetValidEventPoints)."); } catch (Exception ex) { log.LogWarning((object)("Raid integration could not be enabled (" + ex.GetType().Name + ": " + ex.Message + "). Everything else is unaffected.")); } } private static void Postfix(RandomEvent __0, List<Vector3> __result) { if (__result == null || __result.Count == 0 || __0 == null || _config == null || !_config.Enabled || !_config.ExcludeAfkFromRandomRaids) { return; } try { int num = RemoveAfkOnlyAreas(__0, __result); if (num > 0 && _config.DebugLogging) { _log.LogInfo((object)("Random raid '" + __0.m_name + "': " + num + " AFK-only target area(s) not eligible, " + __result.Count + " left.")); } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; _log.LogError((object)("Raid integration error (vanilla result kept): " + ex)); } } } private static int RemoveAfkOnlyAreas(RandomEvent randomEvent, List<Vector3> points) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || _service == null || !instance.IsServer()) { return 0; } _playerPositions.Clear(); _playerAfk.Clear(); List<ZNetPeer> peers = instance.GetPeers(); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val != null && val.IsReady()) { _playerPositions.Add(val.m_refPos); _playerAfk.Add(_service.IsAfk(val.m_uid)); } } return RemoveAfkOnlyAreas(points, randomEvent.m_eventRange, _playerPositions, _playerAfk); } internal static int RemoveAfkOnlyAreas(List<Vector3> points, float eventRange, List<Vector3> playerPositions, List<bool> playerAfk) { //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_0037: 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) float num = Mathf.Max(eventRange, 1f); float num2 = num * num; int num3 = 0; for (int num4 = points.Count - 1; num4 >= 0; num4--) { Vector3 val = points[num4]; bool flag = false; bool flag2 = false; for (int i = 0; i < playerPositions.Count; i++) { float num5 = playerPositions[i].x - val.x; float num6 = playerPositions[i].z - val.z; if (!(num5 * num5 + num6 * num6 >= num2)) { flag = true; if (!playerAfk[i]) { flag2 = true; break; } } } if (flag && !flag2) { points.RemoveAt(num4); num3++; } } return num3; } } internal static class SleepIntegration { private const string HarmonyId = "torokal.afkmanager.sleep"; private static AfkService _service; private static AFKConfig _config; private static ManualLogSource _log; private static bool _errorLogged; internal static void TryEnable(AfkService service, AFKConfig config, ManualLogSource log) { //IL_0072: 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_0096: Expected O, but got Unknown _service = service; _config = config; _log = log; if (!config.ExcludeAfkFromSleep) { log.LogInfo((object)"Sleep integration disabled by config."); return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(Game), "EverybodyIsTryingToSleep", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo.ReturnType != typeof(bool)) { log.LogWarning((object)"Sleep integration unavailable: Game.EverybodyIsTryingToSleep() not found in this game version. AFK detection is unaffected."); return; } new Harmony("torokal.afkmanager.sleep").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SleepIntegration), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); log.LogInfo((object)"Sleep integration enabled (Postfix on Game.EverybodyIsTryingToSleep)."); MethodInfo methodInfo2 = AccessTools.Method(typeof(Game), "UpdateSleeping", (Type[])null, (Type[])null); Patches val = ((methodInfo2 != null) ? Harmony.GetPatchInfo((MethodBase)methodInfo2) : null); if (val != null && val.Prefixes.Count > 0) { log.LogWarning((object)"Another mod patches Game.UpdateSleeping; if it replaces the vanilla sleep loop, ExcludeAfkFromSleep may have no effect."); } } catch (Exception ex) { log.LogWarning((object)("Sleep integration could not be enabled (" + ex.GetType().Name + ": " + ex.Message + "). AFK detection is unaffected.")); } } private static void Postfix(ref bool __result) { if (__result || _config == null || !_config.Enabled || !_config.ExcludeAfkFromSleep) { return; } try { if (Evaluate()) { __result = true; } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; _log.LogError((object)("Sleep integration error (vanilla result kept): " + ex)); } } } private static bool Evaluate() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; ZDOMan instance2 = ZDOMan.instance; if ((Object)(object)instance == (Object)null || instance2 == null || _service == null || !instance.IsServer()) { return false; } bool result = false; List<ZNetPeer> peers = instance.GetPeers(); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val == null || !val.IsReady() || ((ZDOID)(ref val.m_characterID)).IsNone()) { continue; } ZDO zDO = instance2.GetZDO(val.m_characterID); if (zDO != null) { if (zDO.GetBool(ZDOVars.s_inBed, false)) { result = true; } else if (!_service.IsAfk(val.m_uid)) { return false; } } } return result; } }