Иконка ресурса

FREE SimpleStatus : 1.3.1 1.3.1

Нет прав для скачивания
C#:
using Newtonsoft.Json;
using System;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core;
using ProtoBuf;
using Facepunch;
using Oxide.Plugins.SimpleStatusExtensionMethods;
using System.Linq;
using System.Globalization;
using System.Reflection;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using System.Collections.Generic;
using Network;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("SimpleStatus", "mr01sam & dFxPhoeniX", "1.3.1")]
    [Description("Allows plugins to add custom status displays for the UI.")]
    partial class SimpleStatus : CovalencePlugin {
        /* // Changelog
[v1.2.0]
-SimpleStatus now works together with AdvancedStatus. You can now have both installed on your server and they wont overlap with each other. Might be a few bugs with this integration, just report them if you see them.
-Added progress bars, which mimic the style of the health/thirst/hunger/vehicle durability bars
-Added new hook for OnSimpleStatusReady that is called after the plugin has finished loading and is called when a plugin that uses SimpleStatus is loaded, ideal for initializing statuses
-Added a rank property that can determine the priority of listing custom statuses
-Updated some API endpoints, old ones still exist but the new ones are recommended to use
-Fixed issue where statuses wouldnt account for horse stamina
-Updated rendering so it should be quicker to redraw
[v1.2.1]
-Fixed issue where statuses would be invisible after dying
-Fixed issue where holding a planner would mess up the statuses
[v1.2.2]
-Fixed issue where the SetColor api wouldnt work
-Fixed issue where statuses would appear at the wrong index if the color was changed
-Fixed issue where the title/text woutuld not be translated if changed
[v1.2.3]
-Fixed issue where statuses would not load properly if you were dead and tried to reconnect
-Statuses now disappear visually when you die and sleep
[v1.2.4]
-Fixed null reference issue on PlayerSleepEnded hook
-Fixed issue where hiding the status would cause it to not come back
[v1.2.5]
-Fixed issue where some statuses would not redraw after being removed once
[v1.2.6]
-Fixed issue where some statuses would not show up after respawning in a bed/sleeping bag
-Removed OnSimpleStatusReady hook, I recommend just doing this logic in the ServerInitialized like before
[v1.2.7]
-Brought OnSimpleStatusReady back to not break plugins
-Fixed issue where hooks were not being called, causing statuses not to show up
[v1.2.8]
-Fixed issue where OnCollectiblePickup would sometimes throw an error
-Fixed issue where statuses would not realign properly when using a stamina based vehicle, like bikes
-Fixed issue of statuses overlapping when you build
-Fixed bug with AdvancedStatus integration where SetText would not properly update the SubText color property
[v1.2.9]
-Fix for 3/6/2025 Rust update
[v1.2.10]
-Fixed integration with AdvancedStatus
-Adjusted status height to match new vanilla height
-Fixed overlapping status when holding shield
[v1.2.11]
-Fixed issue where background color would be white when calling CreateStatus for carbon users
[v1.2.12]
-Improved AdvancedStatus integration
-Fixed several issues with status syncing when AdvancedStatus is installed
-Fixed background, icon, and icon color not updating correctly with AdvancedStatus
-Fixed statuses not properly hiding/showing with the toggle command when AdvancedStatus is installed
-Fixed status refresh issues after sleep/wake
[v1.2.19]
-Fixed custom statuses briefly overlapping native Rust HUD rows
-Added immediate two-stage layout refreshes for crafting, item notifications, building actions, active-item changes, mounting, and dismounting
-Updated crafting tracking for current Rust versions without using the removed ItemCraftTask.owner field
-Added exact note.inv tracking for native Rust inventory notification rows
-Updated standalone native status counting for poison, pet, buffs, debuffs, mounts, metabolism, workbench, crafting, safe zone, shield, and building privilege
-Fixed cumulative spacing when multiple vanilla statuses are visible
-Added one consistent 2px gap between the vanilla status group and the SimpleStatus group
-Separated the 28px vanilla row step from the 30px SimpleStatus row step
-Aligned note.inv tracking to the next server tick and reused the existing expiry polling
-Fixed hidden status preferences not being restored after reload
-Fixed /ts changes not being saved immediately
-Fixed status position updates not respecting Rank order
-Fixed standalone localization using the real player user ID
-Fixed culture-dependent progress, alpha, and UI offset formatting
-Fixed delayed AdvancedStatus initialization and runtime mode migration
-Fixed finite-duration status tracking while AdvancedStatus is active
-Fixed stale AdvancedStatus bars and standalone UI remaining after plugin unload
-Fixed expired saved statuses modifying their collection while being restored
-Fixed stale saved status entries when their owning plugin is no longer loaded
-Fixed malformed JSON when status titles or text contain quotes, backslashes, or line breaks
-Clamped standalone progress values to a valid 0-1 range
-Improved API null safety, inactive-status updates, corrupted-data recovery, color parsing, and native layout refresh consistency
[v1.3.0]
-Added native CustomVitals support for SimpleStatus statuses
-Added internal CustomVitals wrapper using Facepunch SendCustomVitals without requiring Carbon or CarbonAliases
-Added optional Carbon and CarbonAliases CustomVitalManager support when the internal wrapper is disabled
-Added batched CustomVitals rendering so multiple active SimpleStatus statuses are sent together in Rank order
-Added automatic Legacy CUI fallback for progress bars and unsupported CustomVitals icon types
-Added direct image URL support for SimpleStatus icons through ImageLibrary with automatic CustomVitals refresh after download
-Added one-time compatibility warnings identifying the plugin, status, and unsupported feature that causes Legacy CUI fallback
-Added configuration options "UseCustomVitals", "UseInternalCustomVitalsWrapper", "ForceCustomVitalsForUnsupportedStatuses", and "IgnoreAdvancedStatus"
-Added runtime backend switching between AdvancedStatus and standalone CustomVitals or Legacy CUI
-Added informational AdvancedStatus detection messages and warnings for unsupported or unavailable CustomVitals backend configurations
-Added public CustomVitals API with plugin ownership, cleanup, Rank ordering, and timer-safe batching through the internal SimpleStatus wrapper
-Fixed SimpleStatus initialization after load * when ImageLibrary loads after SimpleStatus, removing the need for a manual SimpleStatus reload
-Fixed transient plugin unload/load cycles preserving and restoring registered statuses instead of deleting them immediately
-Added ImageLibrary load detection messages and clarified CustomStatusFramework conflict detection to match the existing AdvancedStatus integration messages
-Removed the unused "warning" localization key
[v1.3.1]
-Fixed AdvancedStatus Rank integration by mapping SimpleStatus Rank to AdvancedStatus Order
-Fixed AdvancedStatus progress initialization, text transparency, and full redraws for properties that require layout or style recreation
-Fixed AdvancedStatus raw ImageLibrary icons, item ID icon resolution, and empty icon handling
-Added ImageLibrary reload refreshes so active status icons recover without reloading SimpleStatus
-Added runtime CustomStatusFramework conflict detection when it is loaded after SimpleStatus
-Fixed AdvancedStatus per-player override semantics for empty text and icon values to match standalone and CustomVitals rendering
-Fixed culture-dependent AdvancedStatus progress overrides
-Removed unused legacy status type metadata and stale commented code
*/
        public static SimpleStatus PLUGIN;
        [PluginReference]
        private readonly Plugin ImageLibrary, CustomStatusFramework, AdvancedStatus;
        private readonly bool Debugging = false;
        // If you are a developer, you can enable this to get console logs.
        private readonly int[] DebugCodes = new int[] {
        }
        ;
        // Add codes here for specific debug statements you want to see, empty array will show all.
        private const string NoteInventoryCommand = "note.inv";
        private const float LayoutVerificationDelay = 0.25f;
        private const float NativeNotificationDuration = 3.7f;
        private const float ImageUrlLoadCheckInterval = 1f;
        private const int ImageUrlLoadMaxChecks = 120;
        private const float PluginDataCleanupDelay = 300f;
        private readonly HashSet<ulong> _pendingLayoutRefresh = new HashSet<ulong>();
        private readonly Dictionary<ItemCraftTask, ulong> _craftTaskPlayers = new Dictionary<ItemCraftTask, ulong>();
        private long _nativeNotificationSequence;
        private CustomVitalsBridge _customVitalsBridge;
        private bool _customVitalsAvailable;
        private readonly Dictionary<ulong, Dictionary<string, object>> _customVitalHandles = new Dictionary<ulong, Dictionary<string, object>>();
        private readonly HashSet<string> _customVitalsWarnings = new HashSet<string>();
        private readonly HashSet<string> _pendingImageUrls = new HashSet<string>(StringComparer.Ordinal);
        private readonly HashSet<ulong> _legacyCustomVitalsFallbackPlayers = new HashSet<ulong>();
        private readonly Dictionary<ulong, Dictionary<string, InternalCustomVitalRegistration>> _internalCustomVitalRegistrations = new Dictionary<ulong, Dictionary<string, InternalCustomVitalRegistration>>();
        private readonly Dictionary<string, long> _pendingPluginDataCleanup = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);
        private long _internalCustomVitalSequence;
        private long _pluginDataCleanupSequence;
        private bool _simpleStatusReady;
        private bool _dataLoaded;
        private bool _imageLibraryMissingWarningShown;
        private bool _customStatusFrameworkWarningShown;
        private bool _advancedStatusDetectedMessageShown;
        private bool _advancedStatusIgnoredWarningShown;
        private bool _internalCustomVitalsAdvancedStatusWarningShown;
        private bool UsingCustomVitals => config?.UseCustomVitals == true && _customVitalsAvailable && !UsingAdvancedStatus;
        private bool InternalCustomVitalsWrapperAvailable => config?.UseCustomVitals == true && config?.UseInternalCustomVitalsWrapper == true && _customVitalsAvailable && _customVitalsBridge?.UsesInternalWrapper == true;
#region Init
        private void Init() {
            UnsubscribeAll(nameof(OnPlayerSleepEnded), nameof(OnPlayerSleep), nameof(OnPlayerDeath), nameof(CanPickupEntity), nameof(OnItemPickup), nameof(OnStructureUpgrade), nameof(OnStructureRepair), nameof(OnCupboardAuthorize), nameof(OnCupboardDeauthorize), nameof(OnDispenserGather), nameof(OnCollectiblePickup), nameof(OnPlayerRespawn), nameof(OnItemCraft), nameof(OnItemCraftFinished), nameof(OnItemCraftCancelled), nameof(OnActiveItemChanged), nameof(OnEntityMounted), nameof(OnEntityDismounted), nameof(OnSendCommand) );
            PLUGIN = this;
        }
        private void OnServerInitialized() {
            TryInitializeSimpleStatus();
        }
        private bool TryInitializeSimpleStatus() {
            if (_simpleStatusReady) {
                return true;
            }
            if (!ImageLibrary?.IsLoaded ?? true) {
                if (!_imageLibraryMissingWarningShown) {
                    _imageLibraryMissingWarningShown = true;
                    PrintWarning("ImageLibrary is required for SimpleStatus. Waiting for ImageLibrary to load before initializing.");
                }
                return false;
            }
            if (_imageLibraryMissingWarningShown) {
                Puts("ImageLibrary loaded. Continuing SimpleStatus initialization.");
            }
            else {
                Puts("ImageLibrary detected.");
            }
            _imageLibraryMissingWarningShown = false;
            if (CustomStatusFramework?.IsLoaded ?? false) {
                if (!_customStatusFrameworkWarningShown) {
                    _customStatusFrameworkWarningShown = true;
                    PrintError("CustomStatusFramework detected. SimpleStatus cannot initialize while CustomStatusFramework is loaded because both plugins provide the same status UI functionality. Please unload CustomStatusFramework.");
                }
                return false;
            }
            _customStatusFrameworkWarningShown = false;
            AddCovalenceCommand(config.ToggleStatusCommand, nameof(CmdToggleStatus));
            InitializeCustomVitals();
            LoadData();
            _dataLoaded = true;
            LogAdvancedStatusState();
            SubscribeAll();
            foreach (var player in BasePlayer.activePlayerList) {
                OnPlayerConnected(player);
                OnPlayerSleepEnded(player);
            }
            _simpleStatusReady = true;
            Interface.CallHook("OnSimpleStatusReady");
            ScheduleUnloadedPluginDataCleanup();
            return true;
        }
        private bool IsAdvancedStatusReady() {
            if (!UsingAdvancedStatus) {
                return false;
            }
            var result = AdvancedStatus?.Call("IsReady");
            return result is bool && (bool)result;
        }
        private void OnAdvancedStatusLoaded(VersionNumber version) {
            LogAdvancedStatusState();
            if (!UsingAdvancedStatus) {
                return;
            }
            foreach (var player in BasePlayer.activePlayerList) {
                if (player == null) {
                    continue;
                }
                RemoveCustomVitals(player);
                _legacyCustomVitalsFallbackPlayers.Remove(player.userID);
                BehaviorManager.HideBehavior(player);
                Behaviours .GetValueOrDefault(player.UserIDString) ?.UpdateAdvancedTrackingState();
                if (!Data.PlayersHiding.Contains(player.UserIDString) && !player.IsDead() && !player.IsSleeping()) {
                    AS_ShowAllBars(player);
                }
            }
        }
        private void Unload() {
            _simpleStatusReady = false;
            if (_dataLoaded) {
                SaveData();
            }
            _internalCustomVitalRegistrations.Clear();
            var advancedStatusReady = IsAdvancedStatusReady();
            foreach (var player in BasePlayer.activePlayerList.ToArray()) {
                if (player == null) {
                    continue;
                }
                if (advancedStatusReady) {
                    AS_HideAllBars(player);
                }
                RemoveCustomVitals(player);
                CuiHelper.DestroyUi(player, UI_Base_ID);
                BehaviorManager.RemoveBehavior(player.UserIDString);
            }
            _pendingLayoutRefresh.Clear();
            _craftTaskPlayers.Clear();
            _customVitalHandles.Clear();
            _customVitalsWarnings.Clear();
            _pendingImageUrls.Clear();
            _legacyCustomVitalsFallbackPlayers.Clear();
            _pendingPluginDataCleanup.Clear();
            _internalCustomVitalSequence = 0;
            _pluginDataCleanupSequence = 0;
        }
        private void OnPluginLoaded(Plugin plugin) {
            if (plugin == null || plugin.Name == Name) {
                return;
            }
            var name = plugin.Name;
            _pendingPluginDataCleanup.Remove(name);
            if (!_simpleStatusReady) {
                NextTick(() => TryInitializeSimpleStatus());
                return;
            }
            if (name.Equals(nameof(CustomStatusFramework), StringComparison.OrdinalIgnoreCase)) {
                if (!_customStatusFrameworkWarningShown) {
                    _customStatusFrameworkWarningShown = true;
                    PrintError("CustomStatusFramework was loaded while SimpleStatus is already active. Both plugins provide overlapping status UI functionality and should not run together. Please unload CustomStatusFramework or SimpleStatus.");
                }
                return;
            }
            if (name.Equals(nameof(ImageLibrary), StringComparison.OrdinalIgnoreCase)) {
                _imageLibraryMissingWarningShown = false;
                _pendingImageUrls.Clear();
                NextTick(RefreshAllStatusRenderers);
                return;
            }
            NextTick(() => {
                if (!_simpleStatusReady || !plugin.IsLoaded) {
                    return;
                }
                plugin.Call("OnSimpleStatusReady");
                RefreshPluginStatuses(name);
            }
            );
        }
        private void OnPluginUnloaded(Plugin plugin) {
            if (plugin == null) {
                return;
            }
            var name = plugin.Name;
            if (name == Name) {
                return;
            }
            if (name.Equals(nameof(AdvancedStatus), StringComparison.OrdinalIgnoreCase)) {
                RemoveAllInternalCustomVitals(name);
                _advancedStatusDetectedMessageShown = false;
                _advancedStatusIgnoredWarningShown = false;
                if (config?.IgnoreAdvancedStatus != true) {
                    NextTick(RestoreStandaloneStatuses);
                }
                return;
            }
            RemoveAllInternalCustomVitals(name);
            if (_dataLoaded && Data.Statuses.Values.Any(x => x != null && x.PluginName == name)) {
                SuspendPluginStatuses(name);
                SchedulePluginDataCleanup(name);
            }
            if (name.Equals(nameof(CustomStatusFramework), StringComparison.OrdinalIgnoreCase)) {
                _customStatusFrameworkWarningShown = false;
                if (!_simpleStatusReady) {
                    NextTick(() => TryInitializeSimpleStatus());
                }
            }
        }
        private void ScheduleUnloadedPluginDataCleanup() {
            foreach (var pluginName in Data.Statuses.Values .Where(status => status != null && !string.IsNullOrWhiteSpace(status.PluginName) && !status.PluginIsLoaded) .Select(status => status.PluginName) .Distinct(StringComparer.OrdinalIgnoreCase)) {
                SchedulePluginDataCleanup(pluginName);
            }
        }
        private void SchedulePluginDataCleanup(string pluginName) {
            if (!_simpleStatusReady || string.IsNullOrWhiteSpace(pluginName)) {
                return;
            }
            var sequence = ++_pluginDataCleanupSequence;
            _pendingPluginDataCleanup[pluginName] = sequence;
            timer.Once(PluginDataCleanupDelay, () => {
                long currentSequence;
                if (!_simpleStatusReady || !_pendingPluginDataCleanup.TryGetValue(pluginName, out currentSequence) || currentSequence != sequence) {
                    return;
                }
                _pendingPluginDataCleanup.Remove(pluginName);
                var plugin = Interface.uMod.RootPluginManager.GetPlugin(pluginName);
                if (plugin?.IsLoaded ?? false) {
                    return;
                }
                if (Data.Statuses.Values.Any(status => status != null && status.PluginName == pluginName)) {
                    RemovePluginData(pluginName);
                }
            }
            );
        }
        private void SuspendPluginStatuses(string pluginName) {
            var statusIds = new HashSet<string>(Data.Statuses .Where(entry => entry.Value != null && entry.Value.PluginName == pluginName) .Select(entry => entry.Key));
            if (statusIds.Count == 0) {
                return;
            }
            foreach (var basePlayer in BasePlayer.activePlayerList.ToArray()) {
                if (basePlayer == null) {
                    continue;
                }
                var playerData = Data.Player.GetValueOrDefault(basePlayer.UserIDString);
                if (playerData == null || !statusIds.Any(playerData.ContainsKey)) {
                    continue;
                }
                foreach (var statusId in statusIds) {
                    var data = playerData.GetValueOrDefault(statusId);
                    if (data != null) {
                        data.Drawn = false;
                    }
                }
                if (UsingAdvancedStatus) {
                    foreach (var statusId in statusIds) {
                        var status = Data.Statuses.GetValueOrDefault(statusId);
                        if (status != null) {
                            AS_DeleteBar(basePlayer.userID, status);
                        }
                    }
                    Behaviours.GetValueOrDefault(basePlayer.UserIDString)?.UpdateAdvancedTrackingState();
                    continue;
                }
                if (UsingCustomVitals) {
                    RefreshCustomVitals(basePlayer);
                    continue;
                }
                foreach (var statusId in statusIds) {
                    CuiHelper.DestroyUi(basePlayer, string.Format(UI_Status_ID, statusId));
                }
                var behaviour = Behaviours.GetValueOrDefault(basePlayer.UserIDString);
                if (behaviour != null) {
                    behaviour.rowsNeedUpdate = true;
                    behaviour.forceCheckMods = true;
                    behaviour.ForceLayoutRefresh();
                }
            }
        }
        private void RefreshPluginStatuses(string pluginName) {
            var statusIds = new HashSet<string>(Data.Statuses .Where(entry => entry.Value != null && entry.Value.PluginName == pluginName && entry.Value.PluginIsLoaded) .Select(entry => entry.Key));
            if (statusIds.Count == 0) {
                return;
            }
            foreach (var basePlayer in BasePlayer.activePlayerList.ToArray()) {
                if (basePlayer == null) {
                    continue;
                }
                var playerData = Data.Player.GetValueOrDefault(basePlayer.UserIDString);
                if (playerData == null || !statusIds.Any(playerData.ContainsKey)) {
                    continue;
                }
                foreach (var statusId in statusIds) {
                    var data = playerData.GetValueOrDefault(statusId);
                    if (data != null) {
                        data.Drawn = false;
                    }
                }
                if (Data.PlayersHiding.Contains(basePlayer.UserIDString) || basePlayer.IsDead() || basePlayer.IsSleeping()) {
                    continue;
                }
                var behaviour = BehaviorManager.AddBehavior(basePlayer);
                if (UsingAdvancedStatus) {
                    AS_ShowAllBars(basePlayer);
                    behaviour?.UpdateAdvancedTrackingState();
                    continue;
                }
                BehaviorManager.ShowBehavior(basePlayer);
                behaviour?.ResumeStandalone();
            }
        }
        private void RefreshAllStatusRenderers() {
            if (!_simpleStatusReady || !(ImageLibrary?.IsLoaded ?? false)) {
                return;
            }
            foreach (var player in BasePlayer.activePlayerList.ToArray()) {
                if (player == null || !player.IsConnected) {
                    continue;
                }
                if (Data.PlayersHiding.Contains(player.UserIDString) || player.IsDead() || player.IsSleeping()) {
                    continue;
                }
                var behaviour = BehaviorManager.AddBehavior(player);
                if (UsingAdvancedStatus) {
                    AS_HideAllBars(player);
                    AS_ShowAllBars(player);
                    behaviour?.UpdateAdvancedTrackingState();
                    continue;
                }
                if (UsingCustomVitals) {
                    RefreshCustomVitals(player);
                    continue;
                }
                ResetStandaloneDrawState(player);
                CuiHelper.DestroyUi(player, UI_Base_ID);
                BehaviorManager.ShowBehavior(player);
                behaviour?.ResumeStandalone();
            }
        }
        private void RestoreStandaloneStatuses() {
            foreach (var player in BasePlayer.activePlayerList) {
                if (player == null || player.IsDead() || player.IsSleeping() || Data.PlayersHiding.Contains(player.UserIDString)) {
                    continue;
                }
                var behaviour = BehaviorManager.AddBehavior(player);
                BehaviorManager.ShowBehavior(player);
                behaviour?.ResumeStandalone();
            }
        }
#endregion
#region Subscribing
        public List<string> SubscribedHooks = new List<string>();
        private void SubscribeAll() {
            foreach (var hook in SubscribedHooks) {
                Subscribe(hook);
            }
        }
        private void UnsubscribeAll(params string[] hooks) {
            foreach (var hook in hooks) {
                SubscribedHooks.Add(hook);
                Unsubscribe(hook);
            }
        }
#endregion
#region Oxide Hooks
        private void OnPlayerConnected(BasePlayer basePlayer) {
            if (basePlayer == null) {
                return;
            }
            BehaviorManager.AddBehavior(basePlayer);
            if (InternalCustomVitalsWrapperAvailable && HasInternalCustomVitalRegistrations(basePlayer.userID)) {
                var userId = basePlayer.userID;
                NextTick(() => RefreshInternalCustomVitals(userId));
            }
        }
        private void OnPlayerDisconnected(BasePlayer basePlayer) {
            if (basePlayer == null) {
                return;
            }
            RemoveCustomVitals(basePlayer, basePlayer.IsConnected);
            CuiHelper.DestroyUi(basePlayer, UI_Base_ID);
            _pendingLayoutRefresh.Remove(basePlayer.userID);
            _legacyCustomVitalsFallbackPlayers.Remove(basePlayer.userID);
            foreach (var task in _craftTaskPlayers .Where(entry => entry.Value == basePlayer.userID) .Select(entry => entry.Key) .ToArray()) {
                _craftTaskPlayers.Remove(task);
            }
            NextTick(() => {
                BehaviorManager.RemoveBehavior(basePlayer.UserIDString);
            }
            );
            Debug($"Disconnecting {basePlayer.displayName} has {(Data.Player.ContainsKey(basePlayer.UserIDString) ? Data.Player[basePlayer.UserIDString].Count : 0)} statuses: {Data.Player.GetValueOrDefault(basePlayer.UserIDString)?.Keys.ToSentence()}");
        }
        private void OnPlayerSleepEnded(BasePlayer basePlayer) {
            if (basePlayer == null) {
                return;
            }
            if (!Data.PlayersHiding.Contains(basePlayer.UserIDString)) {
                if (UsingAdvancedStatus) {
                    if (IsAdvancedStatusReady()) {
                        AS_ShowAllBars(basePlayer);
                    }
                }
                else {
                    BehaviorManager.ShowBehavior(basePlayer);
                }
            }
        }
        private void OnPlayerSleep(BasePlayer basePlayer) {
            if (basePlayer == null) {
                return;
            }
            if (UsingAdvancedStatus) AS_HideAllBars(basePlayer);
            else BehaviorManager.HideBehavior(basePlayer);
        }
        private void OnPlayerRespawn(BasePlayer basePlayer, BasePlayer.SpawnPoint spawnPoint) {
            if (basePlayer == null) {
                return;
            }
            OnPlayerConnected(basePlayer);
        }
        private void OnPlayerRespawn(BasePlayer basePlayer, SleepingBag sleepingBag2) {
            if (basePlayer == null) {
                return;
            }
            OnPlayerConnected(basePlayer);
        }
        private void OnPlayerDeath(BasePlayer basePlayer, HitInfo info) => OnPlayerDisconnected(basePlayer);
        private void OnSendCommand(Connection connection, string command, object[] args) {
            if (UsingAdvancedStatus || connection == null || string.IsNullOrEmpty(command)) {
                return;
            }
            var isExactCommand = command.Equals(NoteInventoryCommand, StringComparison.OrdinalIgnoreCase);
            if (!isExactCommand && !command.StartsWith(NoteInventoryCommand + " ", StringComparison.OrdinalIgnoreCase)) {
                return;
            }
            int itemId;
            if (!TryGetInventoryNotificationItemId(command, args, out itemId)) {
                return;
            }
            var basePlayer = BasePlayer.FindByID(connection.userid);
            if (basePlayer == null || !basePlayer.IsConnected || UsesCustomVitalsForPlayer(basePlayer)) {
                return;
            }
            var behaviour = Behaviours.GetValueOrDefault(basePlayer.UserIDString);
            if (behaviour == null) {
                return;
            }
            var itemDefinition = ItemManager.FindItemDefinition(itemId);
            var isStackable = itemDefinition != null && itemDefinition.stackable > 1;
            var key = isStackable ? $"note:{itemId}" : $"note:{itemId}:{++_nativeNotificationSequence}";
            var userId = basePlayer.userID;
            NextTick(() => {
                var currentPlayer = BasePlayer.FindByID(userId);
                if (currentPlayer == null || !currentPlayer.IsConnected) {
                    return;
                }
                var currentBehaviour = Behaviours.GetValueOrDefault(currentPlayer.UserIDString);
                if (currentBehaviour == null) {
                    return;
                }
                var visibleUntil = Time.realtimeSinceStartup + NativeNotificationDuration;
                float existingVisibleUntil;
                if (!currentBehaviour.itemStatuses.TryGetValue(key, out existingVisibleUntil) || visibleUntil > existingVisibleUntil) {
                    currentBehaviour.itemStatuses[key] = visibleUntil;
                }
                currentBehaviour.ForceLayoutRefresh();
            }
            );
        }
        private static bool TryGetInventoryNotificationItemId(string command, object[] args, out int itemId) {
            itemId = 0;
            if (command.Equals(NoteInventoryCommand, StringComparison.OrdinalIgnoreCase)) {
                if (args == null || args.Length == 0) {
                    return false;
                }
                return int.TryParse(Convert.ToString(args[0], CultureInfo.InvariantCulture), NumberStyles.Integer, CultureInfo.InvariantCulture, out itemId);
            }
            var parts = command.Split(new[] {
                ' '
            }
            , StringSplitOptions.RemoveEmptyEntries);
            return parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out itemId);
        }
        private bool? OnItemCraft(ItemCraftTask task, BasePlayer basePlayer, Item item) {
            if (UsingAdvancedStatus || UsesCustomVitalsForPlayer(basePlayer)) {
                return null;
            }
            if (task != null && basePlayer != null) {
                _craftTaskPlayers[task] = basePlayer.userID;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
            return null;
        }
        private void OnItemCraftFinished(ItemCraftTask task, Item item) {
            var basePlayer = FindCraftTaskPlayer(task);
            if (UsingAdvancedStatus || UsesCustomVitalsForPlayer(basePlayer)) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
            ScheduleCraftTaskCleanup(task, basePlayer);
        }
        private void OnItemCraftCancelled(ItemCraftTask task) {
            var basePlayer = FindCraftTaskPlayer(task);
            if (UsingAdvancedStatus || UsesCustomVitalsForPlayer(basePlayer)) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
            if (task != null) {
                _craftTaskPlayers.Remove(task);
            }
        }
        private void OnActiveItemChanged(BasePlayer basePlayer, Item oldItem, Item newItem) {
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnEntityMounted(BaseMountable entity, BasePlayer basePlayer) {
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnEntityDismounted(BaseMountable entity, BasePlayer basePlayer) {
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private BasePlayer FindCraftTaskPlayer(ItemCraftTask task) {
            if (task == null) {
                return null;
            }
            ulong userId;
            if (_craftTaskPlayers.TryGetValue(task, out userId)) {
                return BasePlayer.FindByID(userId);
            }
            foreach (var basePlayer in BasePlayer.activePlayerList) {
                var queue = basePlayer?.inventory?.crafting?.queue;
                if (queue != null && queue.Any(queuedTask => ReferenceEquals(queuedTask, task))) {
                    _craftTaskPlayers[task] = basePlayer.userID;
                    return basePlayer;
                }
            }
            return null;
        }
        private void ScheduleCraftTaskCleanup(ItemCraftTask task, BasePlayer basePlayer) {
            if (task == null) {
                return;
            }
            var userId = basePlayer?.userID ?? 0UL;
            timer.Once(1f, () => {
                var currentPlayer = userId == 0UL ? null : BasePlayer.FindByID(userId);
                var queue = currentPlayer?.inventory?.crafting?.queue;
                var stillQueued = queue != null && queue.Any(queuedTask => ReferenceEquals(queuedTask, task));
                if (!stillQueued) {
                    _craftTaskPlayers.Remove(task);
                }
            }
            );
        }
        private void ScheduleStandaloneLayoutRefresh(BasePlayer basePlayer) {
            if (basePlayer == null || UsingAdvancedStatus || UsesCustomVitalsForPlayer(basePlayer)) {
                return;
            }
            var userId = basePlayer.userID;
            if (!_pendingLayoutRefresh.Add(userId)) {
                return;
            }
            NextTick(() => RefreshStandaloneLayout(userId));
            timer.Once(LayoutVerificationDelay, () => {
                _pendingLayoutRefresh.Remove(userId);
                RefreshStandaloneLayout(userId);
            }
            );
        }
        private void RefreshStandaloneLayout(ulong userId) {
            var basePlayer = BasePlayer.FindByID(userId);
            if (basePlayer == null || !basePlayer.IsConnected) {
                return;
            }
            var behaviour = Behaviours.GetValueOrDefault(basePlayer.UserIDString);
            behaviour?.ForceLayoutRefresh();
        }
        private void CanPickupEntity(BasePlayer basePlayer, BaseEntity entity) {
            if (basePlayer == null || entity == null) {
                return;
            }
            NextTick(() => ScheduleStandaloneLayoutRefresh(basePlayer));
        }
        private void OnEntityBuilt(Planner plan, GameObject go) {
            if (plan == null || go == null) {
                return;
            }
            var basePlayer = plan.GetOwnerPlayer();
            if (basePlayer == null) {
                return;
            }
            var buildingBlock = go.ToBaseEntity() as BuildingBlock;
            if (buildingBlock == null) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnItemPickup(Item item, BasePlayer basePlayer) {
            if (basePlayer == null || item == null) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnStructureUpgrade(BaseCombatEntity entity, BasePlayer basePlayer, BuildingGrade.Enum grade) {
            if (basePlayer == null) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnStructureRepair(BuildingBlock entity, BasePlayer basePlayer) {
            NextTick(() => {
                if (entity != null && basePlayer != null) {
                    ScheduleStandaloneLayoutRefresh(basePlayer);
                }
            }
            );
        }
        private void OnCupboardAuthorize(BuildingPrivlidge privilege, BasePlayer basePlayer) {
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnCupboardDeauthorize(BuildingPrivlidge privilege, BasePlayer basePlayer) {
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnDispenserGather(ResourceDispenser dispenser, BasePlayer basePlayer, Item item) {
            if (basePlayer == null || item == null) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
        private void OnCollectiblePickup(CollectibleEntity collectible, BasePlayer basePlayer) {
            if (basePlayer == null || collectible == null) {
                return;
            }
            ScheduleStandaloneLayoutRefresh(basePlayer);
        }
#endregion
#region Status Info
        protected class SavedData {
            public Dictionary<string, StatusInfo> Statuses = new Dictionary<string, StatusInfo>();
            public HashSet<string> PlayersHiding = new HashSet<string>();
            public Dictionary<string, Dictionary<string, PlayerStatusInfo>> Player = new Dictionary<string, Dictionary<string, PlayerStatusInfo>>();
        }
        private static string GetImageData(string path) {
            if (string.IsNullOrWhiteSpace(path)) {
                return string.Empty;
            }
            else if (path.IsAssetPath()) {
                return path;
            }
            else if (path.IsItemId()) {
                return path.AsItemId();
            }
            else if (path.IsRawImage()) {
                return PLUGIN.ImageLibrary?.Call<string>("GetImage", path.AsRawImage()) ?? string.Empty;
            }
            else if (path.IsImageUrl()) {
                return PLUGIN.GetImageUrlData(path);
            }
            return PLUGIN.ImageLibrary?.Call<string>("GetImage", path) ?? string.Empty;
        }
        protected class PlayerStatusInfo {
            public int Duration;
            public string Color = null;
            public string Title = null;
            public string TitleColor = null;
            public string Text = null;
            public string TextColor = null;
            public string ImagePathOrUrl = null;
            public string IconColor = null;
            public string Progress = null;
            public string ProgressColor = null;
            public string UserId = "0";
            [JsonIgnore]
            public DateTime? EndTime = null;
            [JsonIgnore]
            public bool IsPastEndTime => EndTime.HasValue && EndTime.Value < DateTime.Now;
            [JsonIgnore]
            public int DurationUntilEndTime => !EndTime.HasValue ? Duration : (int)Math.Floor((EndTime.Value.Subtract(DateTime.Now)).TotalSeconds);
            [JsonIgnore]
            public bool Drawn = false;
        }
        private SavedData _data = new SavedData();
        protected static SavedData Data => PLUGIN._data;
        private void RemovePluginData(string pluginName) {
            Debug($"Removing {pluginName} because its no longer loaded");
            var statuses = Data.Statuses .Where(entry => entry.Value != null && entry.Value.PluginName == pluginName) .Select(entry => entry.Key) .ToArray();
            foreach (var status in statuses) {
                Data.Statuses.Remove(status);
            }
            var userIdsToUpdate = new HashSet<string>();
            foreach (var playerData in Data.Player.ToArray()) {
                var userId = playerData.Key;
                if (playerData.Value == null) {
                    Data.Player.Remove(userId);
                    continue;
                }
                foreach (var key in playerData.Value.Keys.ToArray()) {
                    if (statuses.Contains(key)) {
                        playerData.Value.Remove(key);
                    }
                }
                if (playerData.Value.Count == 0) {
                    Data.Player.Remove(userId);
                }
                userIdsToUpdate.Add(userId);
            }
            foreach (var userId in userIdsToUpdate) {
                var behavior = Behaviours.GetValueOrDefault(userId);
                if (behavior == null) {
                    continue;
                }
                behavior.rowsNeedUpdate = true;
                behavior.UpdateAdvancedTrackingState();
                behavior.UpdateCustomVitalsTrackingState();
                var basePlayer = BasePlayer.Find(userId);
                if (basePlayer != null) {
                    if (UsingCustomVitals) {
                        RefreshCustomVitals(basePlayer);
                    }
                    else {
                        foreach(var status in statuses) {
                            CuiHelper.DestroyUi(basePlayer, string.Format(UI_Status_ID, status));
                        }
                    }
                }
            }
        }
        private void SaveData() {
            Interface.Oxide.DataFileSystem.WriteObject($"{Name}/data", Data);
        }
        private void LoadData() {
            Debug("Load data called");
            SavedData data;
            try {
                data = Interface.Oxide.DataFileSystem .ReadObject<SavedData>($"{Name}/data") ?? new SavedData();
            }
            catch (Exception exception) {
                PrintError($"Could not read the data file. Starting with clean data: " + exception.Message);
                data = new SavedData();
            }
            if (data.Statuses == null) {
                data.Statuses = new Dictionary<string, StatusInfo>();
            }
            if (data.Player == null) {
                data.Player = new Dictionary<string, Dictionary<string, PlayerStatusInfo>>();
            }
            if (data.PlayersHiding == null) {
                data.PlayersHiding = new HashSet<string>();
            }
            _data.Player = data.Player;
            _data.PlayersHiding = data.PlayersHiding;
            foreach (var status in data.Statuses.ToArray()) {
                if (status.Value == null || string.IsNullOrWhiteSpace(status.Value.PluginName)) {
                    foreach (var playerData in _data.Player.ToArray()) {
                        playerData.Value?.Remove(status.Key);
                        if (playerData.Value == null || playerData.Value.Count == 0) {
                            _data.Player.Remove(playerData.Key);
                        }
                    }
                    continue;
                }
                if (!_data.Statuses.ContainsKey(status.Key)) {
                    Debug("Assigned new status from load");
                    status.Value.Id = status.Key;
                    _data.Statuses[status.Key] = status.Value;
                }
            }
        }
        private static string NormalizeOverride(string value) {
            return value == RESET_KEYWORD ? null : value;
        }
        private void SyncStatusDataForAdvanced(PlayerStatusInfo data, Dictionary<string, object> properties) {
            foreach (var key in properties.Keys) {
                switch (key) {
                    case "color" : data.Color = NormalizeOverride(properties.GetProperty<string>("color"));
                    break;
                    case "title" : data.Title = NormalizeOverride(properties.GetProperty<string>("title"));
                    break;
                    case "titleColor" : data.TitleColor = NormalizeOverride(properties.GetProperty<string>("titleColor"));
                    break;
                    case "text" : data.Text = NormalizeOverride(properties.GetProperty<string>("text"));
                    break;
                    case "textColor" : data.TextColor = NormalizeOverride(properties.GetProperty<string>("textColor"));
                    break;
                    case "icon" : data.ImagePathOrUrl = NormalizeOverride(properties.GetProperty<string>("icon"));
                    break;
                    case "iconColor" : data.IconColor = NormalizeOverride(properties.GetProperty<string>("iconColor"));
                    break;
                    case "progress" : {
                        var progressObj = properties.GetValueOrDefault("progress", null);
                        var progressStr = progressObj == null ? null : Convert.ToString(progressObj, CultureInfo.InvariantCulture);
                        data.Progress = NormalizeOverride(progressStr);
                        break;
                    }
                    case "progressColor" : data.ProgressColor = NormalizeOverride(properties.GetProperty<string>("progressColor"));
                    break;
                }
            }
        }
        private void NotifyStatusUpdates(string userId, string statusId, Dictionary<string, object> properties) {
            if (properties == null) {
                return;
            }
            foreach (var property in properties) {
                switch (property.Key) {
                    case "color" : case "title" : case "titleColor" : case "text" : case "textColor" : case "icon" : case "iconColor" : case "progress" : case "progressColor" : Interface.CallHook("OnStatusUpdate", userId, statusId, property.Key, Convert.ToString(property.Value, CultureInfo.InvariantCulture) ?? string.Empty);
                    break;
                }
            }
        }
        private static void Debug(string message) {
            if (PLUGIN == null || !PLUGIN.Debugging) {
                return;
            }
            var code = message.Split(" ").First().GetHashCode();
            if (PLUGIN.DebugCodes != null && PLUGIN.DebugCodes.Length > 0 && !PLUGIN.DebugCodes.Contains(code)) {
                return;
            }
            PLUGIN?.Puts($"DEBUG({code}): {message}");
        }
        protected class StatusInfo {
            [JsonIgnore]
            public Plugin Plugin => Interface.uMod.RootPluginManager.GetPlugin(PluginName);
            [JsonIgnore]
            public bool PluginIsLoaded => Plugin?.IsLoaded ?? false;
            public string PluginName;
            public string Id;
            public string Color;
            public string Title;
            public string TitleColor;
            public string Text = null;
            public string TextColor;
            public string ImageLibraryNameOrAssetPath;
  
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху