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

FREE LootBouncer : 1.4.2 1.4.2

Нет прав для скачивания
C#:
/*
Modified & Updated Branch of Lootbouncer by VisEntities. The readme notes can be found at https://github.com/SeesAll/
I take no credit for the original plugin found on uMod. I merely changed a few things to improve upon it.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
namespace Oxide.Plugins {
    [Info("Loot Bouncer", "Sorrow/Arainrr, enhanced by SeesAll", "1.4.2")]
    [Description("Automatically clears abandoned loot containers and optional junkpiles when players leave items behind")]
    public class LootBouncer : RustPlugin {
#region Fields
        [PluginReference]
        private Plugin Slap, Trade, MonumentAddons, RaidableBases;
        private const string AdminPermission = "lootbouncer.admin";
        private readonly Dictionary<ulong, LootTrackData> _trackedLoot = new Dictionary<ulong, LootTrackData>();
        private readonly Dictionary<ulong, Timer> _junkPileCleanupTimers = new Dictionary<ulong, Timer>();
        private readonly Dictionary<ulong, Timer> _roadsideVehicleCleanupTimers = new Dictionary<ulong, Timer>();
        private readonly Dictionary<ulong, JunkPile> _junkPileAssociations = new Dictionary<ulong, JunkPile>();
        private readonly Dictionary<ulong, BaseEntity> _roadsideVehicleAssociations = new Dictionary<ulong, BaseEntity>();
        private readonly Dictionary<ulong, GroupCleanupState> _junkPileCleanupStates = new Dictionary<ulong, GroupCleanupState>();
        private readonly Dictionary<ulong, GroupCleanupState> _roadsideVehicleCleanupStates = new Dictionary<ulong, GroupCleanupState>();
        private readonly Dictionary<ulong, Timer> _droppedItemCleanupTimers = new Dictionary<ulong, Timer>();
        private readonly HashSet<ulong> _protectedEntityIds = new HashSet<ulong>();
        private readonly HashSet<string> _barrelShortPrefabNames = new HashSet<string>(StringComparer.Ordinal);
        private readonly SortedDictionary<long, CleanupStats> _hourlyStats = new SortedDictionary<long, CleanupStats>();
        private bool _configRecoveryMode;
#endregion Fields
#region Data Classes
        private class LootTrackData {
            public LootContainer Container;
            public InventorySnapshot InitialInventory;
            public bool PendingCleanup;
            public double LastInteractionTime;
            public int TimerGeneration;
            public readonly HashSet<ulong> ActiveLooterIds = new HashSet<ulong>();
            public readonly HashSet<ulong> ResponsiblePlayerIds = new HashSet<ulong>();
            public Timer CleanupTimer;
        }
        private struct InventorySnapshot {
            public int StackCount;
            public long ItemAmount;
        }
        private class GroupCleanupState {
            public readonly HashSet<ulong> OriginalContainerIds = new HashSet<ulong>();
            public int RetryCount;
        }
        private class CleanupStats {
            public long ContainersRemoved;
            public long ItemStacksRemoved;
            public long ItemAmountRemoved;
            public long JunkPilesRecycled;
            public long RoadsideGroupsRecycled;
            public long DroppedWorldItemsRemoved;
            public long DroppedWorldItemAmountRemoved;
            public long ProtectedSkips;
            public long ReopenCancellations;
        }
        private enum CleanupReason {
            Container, JunkPile, RoadsideVehicle
        }
#endregion Data Classes
#region Oxide Hooks
        private void Init() {
            permission.RegisterPermission(AdminPermission, this);
            Unsubscribe(nameof(OnEntityDeath));
            Unsubscribe(nameof(OnPlayerAttack));
        }
        private void OnServerInitialized() {
            RefreshContainerCaches();
            if (configData.slapPlayer && Slap == null) {
                PrintError("Slap is not loaded, get it at https://umod.org/plugins/slap");
            }
            if (_barrelShortPrefabNames.Any(shortPrefabName => IsContainerEnabled(shortPrefabName))) {
                Subscribe(nameof(OnEntityDeath));
                Subscribe(nameof(OnPlayerAttack));
            }
        }
        private void Unload() {
            foreach (var track in _trackedLoot.Values) {
                track.CleanupTimer?.Destroy();
            }
            foreach (var cleanupTimer in _junkPileCleanupTimers.Values) {
                cleanupTimer?.Destroy();
            }
            foreach (var cleanupTimer in _roadsideVehicleCleanupTimers.Values) {
                cleanupTimer?.Destroy();
            }
            foreach (var cleanupTimer in _droppedItemCleanupTimers.Values) {
                cleanupTimer?.Destroy();
            }
            _trackedLoot.Clear();
            _junkPileCleanupTimers.Clear();
            _roadsideVehicleCleanupTimers.Clear();
            _junkPileAssociations.Clear();
            _roadsideVehicleAssociations.Clear();
            _junkPileCleanupStates.Clear();
            _roadsideVehicleCleanupStates.Clear();
            _droppedItemCleanupTimers.Clear();
            _protectedEntityIds.Clear();
            _barrelShortPrefabNames.Clear();
            _hourlyStats.Clear();
        }
        private void OnLootEntity(BasePlayer player, LootContainer lootContainer) {
            if (!CanProcessLootContainer(player, lootContainer)) {
                return;
            }
            var entityId = lootContainer.net.ID.Value;
            var track = GetOrCreateTrack(entityId);
            track.Container = lootContainer;
            if (track.ActiveLooterIds.Count == 0) {
                track.InitialInventory = GetInventorySnapshot(lootContainer);
            }
            track.LastInteractionTime = GetNow();
            track.ActiveLooterIds.Add(player.userID);
            if (track.CleanupTimer != null && !track.CleanupTimer.Destroyed) {
                AddStat(stats => stats.ReopenCancellations++);
            }
            CancelLootCleanupTimer(track);
        }
        private void OnLootEntityEnd(BasePlayer player, LootContainer lootContainer) {
            if (player == null || lootContainer == null || lootContainer.net == null) {
                return;
            }
            var entityId = lootContainer.net.ID.Value;
            LootTrackData track;
            if (!_trackedLoot.TryGetValue(entityId, out track)) {
                return;
            }
            track.LastInteractionTime = GetNow();
            track.ActiveLooterIds.Remove(player.userID);
            var currentInventory = GetInventorySnapshot(lootContainer);
            if (currentInventory.StackCount <= 0) {
                track.PendingCleanup = false;
                ClearLootTrackingIfUnused(entityId, track);
                return;
            }
            var removedItems = HasInventoryDecreased(track.InitialInventory, currentInventory);
            if (removedItems) {
                track.PendingCleanup = true;
                track.ResponsiblePlayerIds.Add(player.userID);
            }
            if (removedItems || track.PendingCleanup) {
                if (track.ActiveLooterIds.Count == 0) {
                    ScheduleLootCleanup(entityId, lootContainer, track);
                    EvaluateAssociatedSpawnBlocking(lootContainer);
                    ScheduleAssociatedGroupCleanup(lootContainer);
                }
                return;
            }
            ClearLootTrackingIfUnused(entityId, track);
        }
        private void OnPlayerDisconnected(BasePlayer player) {
            if (player == null) {
                return;
            }
            foreach (var entry in _trackedLoot) {
                var track = entry.Value;
                if (!track.ActiveLooterIds.Remove(player.userID) || track.ActiveLooterIds.Count > 0 || !track.PendingCleanup) {
                    continue;
                }
                if (track.Container != null && !track.Container.IsDestroyed) {
                    ScheduleLootCleanup(entry.Key, track.Container, track);
                }
            }
        }
        private void OnItemDropped(Item item, BaseEntity entity) {
            if (!configData.cleanupDroppedWorldLoot || item == null || entity == null || entity.net == null) {
                return;
            }
            var worldItem = entity as WorldItem;
            if (worldItem == null || IsExcludedDroppedItem(item)) {
                return;
            }
            NextTick(() => TryScheduleDroppedWorldItemCleanup(item, worldItem));
        }
        private void OnPlayerAttack(BasePlayer attacker, HitInfo info) {
            if (attacker == null || !attacker.userID.IsSteamId()) {
                return;
            }
            var lootContainer = info?.HitEntity as LootContainer;
            if (lootContainer == null || lootContainer.net == null) {
                return;
            }
            if (!_barrelShortPrefabNames.Contains(lootContainer.ShortPrefabName) || !IsEligibleForCleanup(lootContainer, true)) {
                return;
            }
            var entityId = lootContainer.net.ID.Value;
            var track = GetOrCreateTrack(entityId);
            track.Container = lootContainer;
            track.ResponsiblePlayerIds.Add(attacker.userID);
            track.LastInteractionTime = GetNow();
            ScheduleLootCleanup(entityId, lootContainer, track);
            EvaluateAssociatedSpawnBlocking(lootContainer);
            ScheduleAssociatedGroupCleanup(lootContainer);
        }
        private void OnEntityDeath(LootContainer lootContainer, HitInfo info) {
            if (lootContainer == null || lootContainer.net == null) {
                return;
            }
            if (!_barrelShortPrefabNames.Contains(lootContainer.ShortPrefabName)) {
                return;
            }
            var attacker = info?.InitiatorPlayer;
            if (attacker == null || !attacker.userID.IsSteamId()) {
                return;
            }
            LootTrackData track;
            if (!_trackedLoot.TryGetValue(lootContainer.net.ID.Value, out track)) {
                return;
            }
            track.ActiveLooterIds.Remove(attacker.userID);
            ClearLootTrackingIfUnused(lootContainer.net.ID.Value, track);
        }
        private void OnEntityKill(BaseNetworkable entity) {
            var lootContainer = entity as LootContainer;
            if (lootContainer != null && lootContainer.net != null) {
                _protectedEntityIds.Remove(lootContainer.net.ID.Value);
                HandleLootContainerKill(lootContainer);
                return;
            }
            var junkPile = entity as JunkPile;
            if (junkPile != null && junkPile.net != null) {
                HandleJunkPileKill(junkPile);
                return;
            }
            var baseEntity = entity as BaseEntity;
            if (baseEntity != null && baseEntity.net != null) {
                CancelDroppedItemCleanupTimer(baseEntity.net.ID.Value);
            }
            if (baseEntity != null && baseEntity.net != null && LooksLikeRoadsideVehicleAnchor(baseEntity.ShortPrefabName)) {
                HandleRoadsideVehicleKill(baseEntity);
            }
        }
        [ChatCommand("lbstatus")]
        private void CommandLootBouncerStatus(BasePlayer player, string command, string[] args) {
            if (player == null) {
                return;
            }
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, AdminPermission)) {
                Print(player, Lang("NoPermission", player.UserIDString));
                return;
            }
            Print(player, BuildHourlyStatus());
        }
        [ConsoleCommand("lootbouncer.status")]
        private void ConsoleLootBouncerStatus(ConsoleSystem.Arg arg) {
            var player = arg.Connection?.player as BasePlayer;
            if (player != null && !player.IsAdmin && !permission.UserHasPermission(player.UserIDString, AdminPermission)) {
                arg.ReplyWith(Lang("NoPermission", player.UserIDString));
                return;
            }
            arg.ReplyWith(BuildHourlyStatus());
        }
#endregion Oxide Hooks
#region Methods
        private LootTrackData GetOrCreateTrack(ulong entityId) {
            LootTrackData track;
            if (!_trackedLoot.TryGetValue(entityId, out track)) {
                track = new LootTrackData();
                _trackedLoot.Add(entityId, track);
            }
            return track;
        }
        private bool CanProcessLootContainer(BasePlayer player, LootContainer lootContainer) {
            if (player == null || lootContainer == null || lootContainer.net == null) {
                return false;
            }
            return IsEligibleForCleanup(lootContainer, true);
        }
        private bool IsEligibleForCleanup(LootContainer lootContainer, bool countProtectedSkip) {
            if (_configRecoveryMode || lootContainer == null || lootContainer.net == null || lootContainer.IsDestroyed) {
                return false;
            }
            if (!IsContainerEnabled(lootContainer.ShortPrefabName)) {
                return false;
            }
            var tradeResult = Trade?.Call("IsTradeBox", lootContainer);
            if (tradeResult is bool && (bool)tradeResult) {
                return false;
            }
            if (IsEventProtected(lootContainer)) {
                if (countProtectedSkip) {
                    AddStat(stats => stats.ProtectedSkips++);
                }
                return false;
            }
            return true;
        }
        private bool IsEventProtected(LootContainer lootContainer) {
            var entityId = lootContainer.net.ID.Value;
            if (_protectedEntityIds.Contains(entityId)) {
                return true;
            }
            if (configData.protectAirfieldEventContainers && string.Equals(lootContainer.name, "airfieldcrate", StringComparison.OrdinalIgnoreCase)) {
                return true;
            }
            if (configData.protectMonumentAddonsContainers && MonumentAddons != null) {
                var monumentResult = MonumentAddons.Call("API_IsMonumentEntity", lootContainer);
                if (monumentResult is bool && (bool)monumentResult) {
                    return true;
                }
            }
            if (configData.protectRaidableBasesContainers && RaidableBases != null) {
                var raidResult = RaidableBases.Call("EventTerritory", lootContainer.transform.position, 0f);
                if (raidResult is bool && (bool)raidResult) {
                    return true;
                }
            }
            var hookResult = Interface.CallHook("CanLootBouncerProcess", lootContainer);
            return hookResult is bool && !(bool)hookResult;
        }
        private bool IsContainerEnabled(string shortPrefabName) {
            bool enabled;
            return configData.lootContainers.TryGetValue(shortPrefabName, out enabled) ? enabled : configData.enableNewContainersByDefault;
        }
        private InventorySnapshot GetInventorySnapshot(LootContainer lootContainer) {
            var snapshot = new InventorySnapshot();
            var items = lootContainer?.inventory?.itemList;
            if (items == null) {
                return snapshot;
            }
            snapshot.StackCount = items.Count;
            foreach (var item in items) {
                if (item != null) {
                    snapshot.ItemAmount += Math.Max(0, item.amount);
                }
            }
            return snapshot;
        }
        private int GetInventoryItemCount(LootContainer lootContainer) {
            return GetInventorySnapshot(lootContainer).StackCount;
        }
        private static bool HasInventoryDecreased(InventorySnapshot before, InventorySnapshot after) {
            return after.StackCount < before.StackCount || after.ItemAmount < before.ItemAmount;
        }
        [HookMethod("API_ProtectEntity")]
        public bool API_ProtectEntity(BaseEntity entity) {
            return entity != null && entity.net != null && _protectedEntityIds.Add(entity.net.ID.Value);
        }
        [HookMethod("API_UnprotectEntity")]
        public bool API_UnprotectEntity(BaseEntity entity) {
            return entity != null && entity.net != null && _protectedEntityIds.Remove(entity.net.ID.Value);
        }
        private double GetNow() {
            return Interface.Oxide.Now;
        }
        private void AddStat(Action<CleanupStats> update) {
            if (!configData.enableHourlyStatistics || update == null) {
                return;
            }
            var currentMinute = (long)Math.Floor(GetNow() / 60d);
            CleanupStats stats;
            if (!_hourlyStats.TryGetValue(currentMinute, out stats)) {
                stats = new CleanupStats();
                _hourlyStats[currentMinute] = stats;
            }
            update(stats);
            PruneHourlyStats(currentMinute);
        }
        private void PruneHourlyStats(long currentMinute) {
            var oldestMinute = currentMinute - 59L;
            while (_hourlyStats.Count > 0 && _hourlyStats.First().Key < oldestMinute) {
                _hourlyStats.Remove(_hourlyStats.First().Key);
            }
        }
        private CleanupStats GetHourlyStats() {
            var currentMinute = (long)Math.Floor(GetNow() / 60d);
            PruneHourlyStats(currentMinute);
            var total = new CleanupStats();
            foreach (var stats in _hourlyStats.Values) {
                total.ContainersRemoved += stats.ContainersRemoved;
                total.ItemStacksRemoved += stats.ItemStacksRemoved;
                total.ItemAmountRemoved += stats.ItemAmountRemoved;
                total.JunkPilesRecycled += stats.JunkPilesRecycled;
                total.RoadsideGroupsRecycled += stats.RoadsideGroupsRecycled;
                total.DroppedWorldItemsRemoved += stats.DroppedWorldItemsRemoved;
                total.DroppedWorldItemAmountRemoved += stats.DroppedWorldItemAmountRemoved;
                total.ProtectedSkips += stats.ProtectedSkips;
                total.ReopenCancellations += stats.ReopenCancellations;
            }
            return total;
        }
        private string BuildHourlyStatus() {
            if (!configData.enableHourlyStatistics) {
                return "Hourly cleanup statistics are disabled in the configuration.";
            }
            var stats = GetHourlyStats();
            return string.Format("Last 60 minutes: {0:N0} container(s) removed, {1:N0} container stack(s) / {2:N0} total container item(s) cleaned, {3:N0} junkpile(s) and {4:N0} roadside group(s) recycled. Discarded world loot: {5:N0} stack(s) / {6:N0} item(s). Protected skips: {7:N0}. Reopen cancellations: {8:N0}.", stats.ContainersRemoved, stats.ItemStacksRemoved, stats.ItemAmountRemoved, stats.JunkPilesRecycled, stats.RoadsideGroupsRecycled, stats.DroppedWorldItemsRemoved, stats.DroppedWorldItemAmountRemoved, stats.ProtectedSkips, stats.ReopenCancellations);
        }
        private bool IsExcludedDroppedItem(Item item) {
            if (item?.info == null) {
                return true;
            }
            if (configData.excludedDroppedItemShortNames.Contains(item.info.shortname, StringComparer.OrdinalIgnoreCase)) {
                return true;
            }
            if (!configData.preserveRareDroppedItems) {
                return false;
            }
            var rarity = item.info.rarity.ToString();
            return rarity.IndexOf("Rare", StringComparison.OrdinalIgnoreCase) >= 0;
        }
        private void TryScheduleDroppedWorldItemCleanup(Item item, WorldItem worldItem) {
            if (item == null || worldItem == null || worldItem.IsDestroyed || worldItem.net == null || IsExcludedDroppedItem(item) || IsPositionInPlayerBase(worldItem.transform.position) || IsDroppedItemEventProtected(item, worldItem)) {
                return;
            }
            if (!IsNearTrackedWorldLootContainer(worldItem.transform.position)) {
                return;
            }
            var entityId = worldItem.net.ID.Value;
            CancelDroppedItemCleanupTimer(entityId);
            var originalPosition = worldItem.transform.position;
            var originalItem = item;
            _droppedItemCleanupTimers[entityId] = timer.Once(Math.Max(30f, configData.droppedWorldLootLifetime), () => {
                _droppedItemCleanupTimers.Remove(entityId);
                if (worldItem == null || worldItem.IsDestroyed || worldItem.net == null || originalItem == null || !ReferenceEquals(worldItem.item, originalItem) || IsExcludedDroppedItem(originalItem)) {
                    return;
                }
                if (UnityEngine.Vector3.Distance(originalPosition, worldItem.transform.position) > configData.droppedWorldLootMovementTolerance || IsPositionInPlayerBase(worldItem.transform.position) || IsDroppedItemEventProtected(originalItem, worldItem)) {
                    return;
                }
                var amount = Math.Max(0, originalItem.amount);
                AddStat(stats => {
                    stats.DroppedWorldItemsRemoved++;
                    stats.DroppedWorldItemAmountRemoved += amount;
                }
                );
                worldItem.Kill();
            }
            );
        }
        private bool IsNearTrackedWorldLootContainer(UnityEngine.Vector3 position) {
            var containers = Pool.Get<List<LootContainer>>();
            try {
                Vis.Entities(position, Math.Max(1f, configData.droppedWorldLootTriggerRadius), containers, Layers.Solid);
                foreach (var container in containers) {
                    if (container != null && container.net != null && _trackedLoot.ContainsKey(container.net.ID.Value) && IsEligibleForCleanup(container, false)) {
                        return true;
                    }
                }
            }
            finally {
                Pool.FreeUnmanaged(ref containers);
            }
            return false;
        }
        private bool IsPositionInPlayerBase(UnityEngine.Vector3 position) {
            var entities = Pool.Get<List<BaseEntity>>();
            try {
                Vis.Entities(position, Math.Max(5f, configData.droppedWorldLootBaseExclusionRadius), entities, Layers.Solid);
                return entities.Any(entity => entity is BuildingBlock || entity is BuildingPrivlidge);
            }
            finally {
                Pool.FreeUnmanaged(ref entities);
            }
        }
        private bool IsDroppedItemEventProtected(Item item, WorldItem worldItem) {
            if (configData.protectRaidableBasesContainers && RaidableBases != null) {
                var raidResult = RaidableBases.Call("EventTerritory", worldItem.transform.position, 0f);
                if (raidResult is bool && (bool)raidResult) {
                    return true;
                }
            }
            var hookResult = Interface.CallHook("CanLootBouncerCleanDroppedItem", item, worldItem);
            return hookResult is bool && !(bool)hookResult;
        }
        private void CancelDroppedItemCleanupTimer(ulong entityId) {
            Timer cleanupTimer;
            if (!_droppedItemCleanupTimers.TryGetValue(entityId, out cleanupTimer)) {
                return;
            }
            cleanupTimer?.Destroy();
            _droppedItemCleanupTimers.Remove(entityId);
        }
        private float GetRoadsideCleanupRadius() {
            return Math.Max(1f, configData.maximumCleanupRadiusForRoadsideGroups);
        }
        private double GetStaleLootAgeThreshold() {
            return Math.Max(5d, configData.timeBeforeLootEmpty);
        }
        private void CancelJunkPileCleanupTimer(ulong junkPileId) {
            Timer cleanupTimer;
            if (!_junkPileCleanupTimers.TryGetValue(junkPileId, out cleanupTimer)) {
                return;
            }
            cleanupTimer?.Destroy();
            _junkPileCleanupTimers.Remove(junkPileId);
        }
        private void CancelRoadsideVehicleCleanupTimer(ulong entityId) {
            Timer cleanupTimer;
            if (!_roadsideVehicleCleanupTimers.TryGetValue(entityId, out cleanupTimer)) {
                return;
            }
            cleanupTimer?.Destroy();
            _roadsideVehicleCleanupTimers.Remove(entityId);
        }
        private static SpawnGroup GetSpawnGroup(BaseEntity entity) {
            return entity?.GetComponent<SpawnPointInstance>()?.parentSpawnPointUser as SpawnGroup;
        }
        private bool IsTrackedContainerStale(LootContainer lootContainer, LootTrackData track, double now) {
            if (track == null || lootContainer == null || lootContainer.IsDestroyed || GetInventoryItemCount(lootContainer) <= 0) {
                return false;
            }
            if (track.PendingCleanup) {
                return true;
            }
            return now - track.LastInteractionTime >= GetStaleLootAgeThreshold();
        }
        private bool TryGetJunkPileContainers(JunkPile junkPile, List<LootContainer> results) {
            if (junkPile == null || junkPile.net == null || junkPile.IsDestroyed || results == null) {
                return false;
            }
            results.Clear();
            Vis.Entities(junkPile.transform.position, GetRoadsideCleanupRadius(), results, Layers.Solid);
            return true;
        }
        private bool IsContainerInJunkPileGroup(LootContainer lootContainer, JunkPile junkPile) {
            if (lootContainer == null || junkPile == null || junkPile.spawngroups == null) {
                return false;
            }
            var lootSpawnGroup = GetSpawnGroup(lootContainer);
            return lootSpawnGroup != null && junkPile.spawngroups.Contains(lootSpawnGroup);
        }
        private static bool IsEntityInSpawnGroup(BaseEntity entity, SpawnGroup spawnGroup) {
            return entity != null && spawnGroup != null && ReferenceEquals(GetSpawnGroup(entity), spawnGroup);
        }
        private bool LooksLikeRoadsideVehicleAnchor(string shortPrefabName) {
            bool enabled;
            return !string.IsNullOrEmpty(shortPrefabName) && configData.roadsideVehicleAnchors.TryGetValue(shortPrefabName, out enabled) && enabled;
        }
        private static bool IsKnownRoadsideVehicleAnchorSeed(string shortPrefabName) {
            // Seed only a confirmed static roadside anchor. Broad fragments such as
            // "van" also match unrelated names like "advanced" and "vanity".
            return string.Equals(shortPrefabName, "shreddable_pickuptruck", StringComparison.Ordinal);
        }
        private bool TryFindAssociatedRoadsideVehicleAnchor(LootContainer lootContainer, out BaseEntity anchorEntity, out SpawnGroup spawnGroup) {
            anchorEntity = null;
            spawnGroup = GetSpawnGroup(lootContainer);
            if (lootContainer == null || lootContainer.net == null || spawnGroup == null) {
                return false;
            }
            var entityId = lootContainer.net.ID.Value;
            if (_roadsideVehicleAssociations.TryGetValue(entityId, out anchorEntity)) {
                if (anchorEntity != null && !anchorEntity.IsDestroyed && anchorEntity.net != null && IsEntityInSpawnGroup(anchorEntity, spawnGroup)) {
                    return true;
                }
                _roadsideVehicleAssociations.Remove(entityId);
                anchorEntity = null;
            }
            var nearbyEntities = Pool.Get<List<BaseEntity>>();
            nearbyEntities.Clear();
            try {
                Vis.Entities(lootContainer.transform.position, GetRoadsideCleanupRadius(), nearbyEntities, Layers.Solid);
                foreach (var nearbyEntity in nearbyEntities) {
                    if (nearbyEntity == null || nearbyEntity.IsDestroyed || nearbyEntity.net == null) {
                        continue;
                    }
                    if (nearbyEntity == lootContainer || !LooksLikeRoadsideVehicleAnchor(nearbyEntity.ShortPrefabName) || !IsEntityInSpawnGroup(nearbyEntity, spawnGroup)) {
                        continue;
                    }
                    anchorEntity = nearbyEntity;
                    _roadsideVehicleAssociations[entityId] = anchorEntity;
                    return true;
                }
            }
            finally {
                Pool.FreeUnmanaged(ref nearbyEntities);
            }
            return false;
        }
        private bool TryGetRoadsideVehicleGroupContainers(BaseEntity anchorEntity, SpawnGroup spawnGroup, List<LootContainer> results) {
            if (anchorEntity == null || anchorEntity.net == null || anchorEntity.IsDestroyed || spawnGroup == null || results == null) {
                return false;
            }
            results.Clear();
            Vis.Entities(anchorEntity.transform.position, GetRoadsideCleanupRadius(), results, Layers.Solid);
            results.RemoveAll(container => container == null || container.IsDestroyed || container.net == null || !IsEntityInSpawnGroup(container, spawnGroup));
            return true;
        }
        private void ScheduleAssociatedSpawnBlockingCleanup(IEnumerable<LootContainer> nearbyLootContainers, Func<LootContainer, bool> belongsToGroup) {
            if (nearbyLootContainers == null || belongsToGroup == null) {
                return;
            }
            var now = GetNow();
            foreach (var nearbyLootContainer in nearbyLootContainers) {
                if (nearbyLootContainer == null || nearbyLootContainer.IsDestroyed || nearbyLootContainer.net == null) {
                    continue;
                }
                if (!belongsToGroup(nearbyLootContainer) || !IsEligibleForCleanup(nearbyLootContainer, false)) {
                    continue;
                }
                var relatedEntityId = nearbyLootContainer.net.ID.Value;
                LootTrackData relatedTrack;
                if (!_trackedLoot.TryGetValue(relatedEntityId, out relatedTrack)) {
                    continue;
                }
                if (!IsTrackedContainerStale(nearbyLootContainer, relatedTrack, now)) {
                    continue;
                }
                ScheduleLootCleanup(relatedEntityId, nearbyLootContainer, relatedTrack);
            }
        }
        private void EvaluateAssociatedSpawnBlocking(LootContainer sourceContainer) {
            var junkPile = FindAssociatedJunkPile(sourceContainer);
            if (junkPile != null) {
                var nearbyLootContainers = Pool.Get<List<LootContainer>>();
                try {
                    if (!TryGetJunkPileContainers(junkPile, nearbyLootContainers)) {
                        return;
                    }
                    ScheduleAssociatedSpawnBlockingCleanup(nearbyLootContainers, nearbyLootContainer => IsContainerInJunkPileGroup(nearbyLootContainer, junkPile));
                    return;
                }
                finally {
                    Pool.FreeUnmanaged(ref nearbyLootContainers);
                }
            }
            BaseEntity anchorEntity;
            SpawnGroup spawnGroup;
            if (!TryFindAssociatedRoadsideVehicleAnchor(sourceContainer, out anchorEntity, out spawnGroup)) {
                return;
            }
            var nearbyVehicleLootContainers = Pool.Get<List<LootContainer>>();
            try {
                if (!TryGetRoadsideVehicleGroupContainers(anchorEntity, spawnGroup, nearbyVehicleLootContainers)) {
                    return;
                }
                ScheduleAssociatedSpawnBlockingCleanup(nearbyVehicleLootContainers, nearbyLootContainer => IsEntityInSpawnGroup(nearbyLootContainer, spawnGroup));
            }
            finally {
                Pool.FreeUnmanaged(ref nearbyVehicleLootContainers);
            }
        }
        private void ScheduleLootCleanup(ulong entityId, LootContainer lootContainer, LootTrackData track) {
            if (track == null || lootContainer == null || lootContainer.IsDestroyed) {
                return;
            }
            track.PendingCleanup = true;
            track.Container = lootContainer;
            if (track.ActiveLooterIds.Count > 0) {
                return;
            }
            if (track.CleanupTimer != null && !track.CleanupTimer.Destroyed) {
                return;
            }
            var generation = ++track.TimerGeneration;
            track.CleanupTimer = timer.Once(Math.Max(1f, configData.timeBeforeLootEmpty), () => {
                track.CleanupTimer = null;
                LootTrackData currentTrack;
                if (!_trackedLoot.TryGetValue(entityId, out currentTrack) || !ReferenceEquals(currentTrack, track) || currentTrack.TimerGeneration != generation || currentTrack.ActiveLooterIds.Count > 0) {
                    return;
                }
                if (!IsEligibleForCleanup(lootContainer, true)) {
                    currentTrack.PendingCleanup = false;
                    ClearLootTrackingIfUnused(entityId, currentTrack);
                    return;
                }
                DropItems(lootContainer, CleanupReason.Container);
            }
            );
        }
        private void CancelLootCleanupTimer(LootTrackData track) {
            if (track == null || track.CleanupTimer == null) {
                return;
            }
            track.CleanupTimer.Destroy();
            track.CleanupTimer = null;
            track.TimerGeneration++;
        }
        private void ClearLootTrackingIfUnused(ulong entityId, LootTrackData track) {
            if (track == null) {
                return;
            }
            if (track.ActiveLooterIds.Count > 0) {
                return;
            }
            if (track.PendingCleanup) {
                return;
            }
            if (track.CleanupTimer != null && !track.CleanupTimer.Destroyed) {
                return;
            }
            _trackedLoot.Remove(entityId);
            _junkPileAssociations.Remove(entityId);
            _roadsideVehicleAssociations.Remove(entityId);
        }
        private void HandleLootContainerKill(LootContainer lootContainer) {
            var entityId = lootContainer.net.ID.Value;
            LootTrackData track;
            if (_trackedLoot.TryGetValue(entityId, out track)) {
                CancelLootCleanupTimer(track);
                _trackedLoot.Remove(entityId);
                if (configData.slapPlayer && Slap != null) {
                    foreach (var playerId in track.ResponsiblePlayerIds) {
                        var player = BasePlayer.FindByID(playerId);
                        if (player == null || player.IPlayer == null) {
                            continue;
                        }
                        Slap.Call("SlapPlayer", player.IPlayer);
                        Print(player, Lang("SlapMessage", player.UserIDString));
                    }
                }
            }
            _junkPileAssociations.Remove(entityId);
            _roadsideVehicleAssociations.Remove(entityId);
        }
        private void HandleJunkPileKill(JunkPile junkPile) {
            var junkPileId = junkPile.net.ID.Value;
            CancelJunkPileCleanupTimer(junkPileId);
            _junkPileCleanupStates.Remove(junkPileId);
            var associatedContainerIds = Pool.Get<List<ulong>>();
            associatedContainerIds.Clear();
            try {
                foreach (var entry in _junkPileAssociations) {
                    if (entry.Value == junkPile) {
                        associatedContainerIds.Add(entry.Key);
                    }
                }
                foreach (var containerId in associatedContainerIds) {
                    _junkPileAssociations.Remove(containerId);
                }
            }
            finally {
                Pool.FreeUnmanaged(ref associatedContainerIds);
            }
        }
        private void HandleRoadsideVehicleKill(BaseEntity anchorEntity) {
            var entityId = anchorEntity.net.ID.Value;
            CancelRoadsideVehicleCleanupTimer(entityId);
            _roadsideVehicleCleanupStates.Remove(entityId);
            var associatedContainerIds = Pool.Get<List<ulong>>();
            associatedContainerIds.Clear();
            try {
                foreach (var entry in _roadsideVehicleAssociations) {
                    if (entry.Value == anchorEntity) {
                        associatedContainerIds.Add(entry.Key);
                    }
                }
                foreach (var containerId in associatedContainerIds) {
                    _roadsideVehicleAssociations.Remove(containerId);
                }
            }
            finally {
                Pool.FreeUnmanaged(ref associatedContainerIds);
            }
        }
        private void DropItems(LootContainer lootContainer, CleanupReason reason) {
            if (lootContainer == null || lootContainer.IsDestroyed) {
                return;
            }
            if (!IsEligibleForCleanup(lootContainer, true)) {
                return;
            }
            if (lootContainer.net != null) {
                LootTrackData track;
                if (_trackedLoot.TryGetValue(lootContainer.net.ID.Value, out track)) {
                    track.PendingCleanup = false;
                }
            }
            var inventory = lootContainer.inventory;
            var removedStacks = 0;
            long removedAmount = 0;
            if (inventory != null) {
                var snapshot = GetInventorySnapshot(lootContainer);
                removedStacks = snapshot.StackCount;
                removedAmount = snapshot.ItemAmount;
                if (configData.removeItems) {
                    inventory.Clear();
                }
                else if (inventory.itemList != null && inventory.itemList.Count > 0) {
                    DropUtil.DropItems(inventory, lootContainer.GetDropPosition());
                }
            }
            AddStat(stats => {
                stats.ContainersRemoved++;
                stats.ItemStacksRemoved += removedStacks;
                stats.ItemAmountRemoved += removedAmount;
            }
            );
            lootContainer.RemoveMe();
        }
        private void ScheduleAssociatedGroupCleanup(LootContainer lootContainer) {
            if (!configData.emptyJunkpile) {
                return;
            }
            if (FindAssociatedJunkPile(lootContainer) != null) {
                ScheduleJunkPileCleanup(lootContainer);
                return;
            }
            ScheduleRoadsideVehicleCleanup(lootContainer);
        }
        private bool HasProtectedOrActiveJunkPileContainer(JunkPile junkPile, IEnumerable<LootContainer> containers, out bool hasProtected, out bool hasActive) {
            hasProtected = false;
            hasActive = false;
            foreach (var container in containers) {
                if (container == null || container.net == null || !IsContainerInJunkPileGroup(container, junkPile)) {
                    continue;
                }
                if (IsEventProtected(container)) {
                    hasProtected = true;
                }
                LootTrackData track;
                if (_trackedLoot.TryGetValue(container.net.ID.Value, out track) && track.ActiveLooterIds.Count > 0) {
                    hasActive = true;
                }
            }
            return hasProtected || hasActive;
        }
        private bool HasProtectedOrActiveRoadsideContainer(SpawnGroup spawnGroup, IEnumerable<LootContainer> containers, out bool hasProtected, out bool hasActive) {
            hasProtected = false;
            hasActive = false;
            foreach (var container in containers) {
                if (container == null || container.net == null || !IsEntityInSpawnGroup(container, spawnGroup)) {
                    continue;
                }
                if (IsEventProtected(container)) {
                    hasProtected = true;
                }
                LootTrackData track;
                if (_trackedLoot.TryGetValue(container.net.ID.Value, out track) && track.ActiveLooterIds.Count > 0) {
                    hasActive = true;
                }
            }
            return hasProtected || hasActive;
        }
        private void RecordImplicitGroupCleanup(IEnumerable<LootContainer> containers, Func<LootContainer, bool> belongsToGroup) {
            long containerCount = 0;
            long stackCount = 0;
            long itemAmount = 0;
            foreach (var container in containers) {
                if (container == null || container.IsDestroyed || container.net == null || !belongsToGroup(container) || !IsEligibleForCleanup(container, false)) {
                    continue;
                }
                var snapshot = GetInventorySnapshot(container);
                containerCount++;
                stackCount += snapshot.StackCount;
                itemAmount += snapshot.ItemAmount;
            }
            AddStat(stats => {
                stats.ContainersRemoved += containerCount;
                stats.ItemStacksRemoved += stackCount;
                stats.ItemAmountRemoved += itemAmount;
            }
            );
        }
        private void ScheduleJunkPileCleanup(LootContainer lootContainer) {
            if (!configData.emptyJunkpile) {
                return;
            }
            var junkPile = FindAssociatedJunkPile(lootContainer);
            if (junkPile == null || junkPile.net == null || junkPile.IsDestroyed) {
                return;
            }
            var junkPileId = junkPile.net.ID.Value;
            GroupCleanupState state;
            if (!_junkPileCleanupStates.TryGetValue(junkPileId, out state)) {
                state = CaptureJunkPileState(junkPile);
                _junkPileCleanupStates[junkPileId] = state;
            }
            ScheduleJunkPileTimer(junkPile, state, configData.timeBeforeJunkpileEmpty);
        }
        private GroupCleanupState CaptureJunkPileState(JunkPile junkPile) {
            var state = new GroupCleanupState();
            var containers = Pool.Get<List<LootContainer>>();
            try {
                if (TryGetJunkPileContainers(junkPile, containers)) {
                    foreach (var container in containers) {
                        if (container != null && container.net != null && IsContainerInJunkPileGroup(container, junkPile) && IsEligibleForCleanup(container, false)) {
                            state.OriginalContainerIds.Add(container.net.ID.Value);
                        }
                    }
                }
            }
            finally {
                Pool.FreeUnmanaged(ref containers);
            }
            return state;
        }
        private void ScheduleJunkPileTimer(JunkPile junkPile, GroupCleanupState state, float delay) {
            if (junkPile == null || junkPile.net == null || junkPile.IsDestroyed) {
                return;
            }
            var junkPileId = junkPile.net.ID.Value;
            Timer existingTimer;
            if (_junkPileCleanupTimers.TryGetValue(junkPileId, out existingTimer)) {
                if (existingTimer != null && !existingTimer.Destroyed) {
                    return;
                }
                _junkPileCleanupTimers.Remove(junkPileId);
            }
            _junkPileCleanupTimers.Add(junkPileId, timer.Once(Math.Max(5f, delay), () => {
                _junkPileCleanupTimers.Remove(junkPileId);
                if (junkPile == null || junkPile.IsDestroyed || junkPile.net == null) {
                    return;
                }
                var nearbyLootContainers = Pool.Get<List<LootContainer>>();
                try {
                    if (!TryGetJunkPileContainers(junkPile, nearbyLootContainers)) {
                        return;
                    }
                    if (HasProtectedOrActiveJunkPileContainer(junkPile, nearbyLootContainers, out var hasProtected, out var hasActive)) {
                        if (hasProtected) {
                            _junkPileCleanupStates.Remove(junkPileId);
                            return;
                        }
                        if (hasActive) {
                            ScheduleJunkPileTimer(junkPile, state, configData.groupRetryDelay);
                            return;
                        }
                    }
                    var remainingContainersWithLoot = 0;
                    foreach (var nearbyLootContainer in nearbyLootContainers) {
                        if (nearbyLootContainer == null || nearbyLootContainer.IsDestroyed || nearbyLootContainer.net == null) {
                            continue;
                        }
                        if (!IsContainerInJunkPileGroup(nearbyLootContainer, junkPile) || !state.OriginalContainerIds.Contains(nearbyLootContainer.net.ID.Value) || !IsEligibleForCleanup(nearbyLootContainer, false)) {
                            continue;
                        }
                        if (GetInventoryItemCount(nearbyLootContainer) > 0) {
                            remainingContainersWithLoot++;
                        }
                    }
                    var originalCount = state.OriginalContainerIds.Count;
                    var lootedCount = Math.Max(0, originalCount - remainingContainersWithLoot);
                    var lootedRatio = originalCount == 0 ? 1f : (float)lootedCount / originalCount;
                    var shouldDestroyJunkPile = remainingContainersWithLoot == 0 || lootedRatio >= configData.junkPileCleanupThreshold || state.RetryCount >= configData.maximumGroupCleanupRetries;
                    if (!shouldDestroyJunkPile) {
                        state.RetryCount++;
                        ScheduleJunkPileTimer(junkPile, state, configData.groupRetryDelay);
                        return;
                    }
                    if (configData.dropNearbyLoot) {
                        DropNearbyJunkPileLoot(junkPile);
                    }
                    else {
                        RecordImplicitGroupCleanup(nearbyLootContainers, container => IsContainerInJunkPileGroup(container, junkPile));
                    }
                    CancelJunkPileCleanupTimer(junkPileId);
                    _junkPileCleanupStates.Remove(junkPileId);
                    AddStat(stats => stats.JunkPilesRecycled++);
                    junkPile.SinkAndDestroy();
                }
                finally {
                    Pool.FreeUnmanaged(ref nearbyLootContainers);
                }
            }
            ));
        }
        private void ScheduleRoadsideVehicleCleanup(LootContainer lootContainer) {
            BaseEntity anchorEntity;
// ... [Code preview truncated due to length. Full source in attached file] ...

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