C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("FCleaner", "kamAp", "2.3.0")]
public class FCleaner : RustPlugin
{
public class Configuration
{
[JsonProperty("Префикс сообщения")]
public string Prefix { get; set; } = "[FCleaner]";
[JsonProperty("Цвет префикса")]
public string PrefixColor { get; set; } = "#34eb4f";
[JsonProperty("Интервал очистки (в секундах)")]
public float CleanInterval { get; set; } = 200f;
[JsonProperty("Очищать выброшенные предметы")]
public bool CleanDroppedItems { get; set; } = true;
[JsonProperty("Очищать трупы игроков")]
public bool CleanPlayerCorpses { get; set; } = true;
[JsonProperty("Очищать рюкзаки NPC")]
public bool CleanNPCBackpacks { get; set; } = true;
[JsonProperty("Очищать рюкзаки из предметов")]
public bool CleanDropBackpacks { get; set; } = true;
[JsonProperty("Очищать трупы с лутом")]
public bool CleanLootableCorpses { get; set; } = true;
[JsonProperty("Очищать неактивные сущности")]
public bool CleanInactiveEntities { get; set; } = true;
[JsonProperty("Время уведомления перед очисткой (сек)")]
public float CleanNotificationTime { get; set; } = 30f;
[JsonProperty("Список исключённых предметов")]
public List<string> ExcludedItems { get; set; } = new List<string>();
[JsonProperty("Автоматически добавлять карточки в исключённые предметы")]
public bool AutoAddKeycardsToExcluded { get; set; } = false;
[JsonProperty("Включить бан за спам выбрасыванием предметов")]
public bool BanEnabled { get; set; } = true;
[JsonProperty("Порог предупреждения при выбросе")]
public int ItemDropWarningThreshold { get; set; } = 30;
[JsonProperty("Порог бана при выбросе")]
public int ItemDropBanThreshold { get; set; } = 50;
[JsonProperty("Время сброса счётчика (сек)")]
public float ItemDropResetTime { get; set; } = 60f;
[JsonProperty("Длительность бана (в минутах)")]
public int BanDuration { get; set; } = 60;
[JsonProperty("Сообщение предупреждения при выбросе")]
public string WarningMessage { get; set; } = "Внимание: Вы выбросили {0} предметов. Вас забанят, если вы продолжите.";
[JsonProperty("Причина бана")]
public string BanReason { get; set; } = "Слишком много выброшенных предметов за короткий срок.";
[JsonProperty("Разрешение администратора")]
public string AdminPermission { get; set; } = "fcleaner.admin";
[JsonProperty("Разрешение на уведомление")]
public string NotifyPermission { get; set; } = "fcleaner.notify";
[JsonProperty("Показывать метки на карте")]
public bool ShowMarkersOnMap { get; set; } = true;
[JsonProperty("Тип маркера на карте (только vending поддерживается)")]
public string MapMarkerType { get; set; } = "vending";
[JsonProperty("Название маркера на карте")]
public string MapMarkerName { get; set; } = "Зона Очистки";
[JsonProperty("Включить уведомления об очистке (в чат)")]
public bool EnableCleanupMessages { get; set; } = true;
[JsonProperty("Включить уведомления о выбросе (бан/предупреждения)")]
public bool EnableDropWarnings { get; set; } = true;
[JsonProperty("Отключить коллизии у предметов")]
public bool DisableItemCollisions { get; set; } = false;
[JsonProperty("Логировать изменение слоёв предметов")]
public bool LogLayerChanges { get; set; } = false;
[JsonProperty("Включить авто-очистку по типам предметов")]
public bool AutoCleanByTypeEnabled { get; set; } = false;
[JsonProperty("Порог авто-очистки по типам предметов")]
public int AutoCleanTypeThreshold { get; set; } = 100;
[JsonProperty("Включить пошаговую очистку")]
public bool EnablePartialCleanup { get; set; } = true;
[JsonProperty("Размер пакета очистки")]
public int CleanupChunkSize { get; set; } = 50;
[JsonProperty("Задержка между пакетами очистки (сек)")]
public float CleanupChunkDelay { get; set; } = 0.5f;
[JsonProperty("Кастомная команда для бана")]
public string CustomBanCommand { get; set; } = "banid";
[JsonProperty("Кастомные предметы для очистки на нулевых координатах")]
public List<string> CustomZeroPointItems { get; set; } = new List<string>
{
"boat",
"tugboat",
"rhib"
};
[JsonProperty("Использовать расширенную систему бана (IP/Steam)")]
public bool UseExtendedBanSystem { get; set; } = false;
[JsonProperty("Тип расширенного бана (IP/Steam)")]
public string ExtendedBanType { get; set; } = "IP";
}
private Configuration config;
private Dictionary<ulong, bool> playerDroppedItems = new Dictionary<ulong, bool>();
protected override void LoadDefaultConfig()
{
config = new Configuration();
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<Configuration>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Ошибка загрузки конфигурации. Восстанавливаем стандартные настройки...");
LoadDefaultConfig();
}
config.ExcludedItems = config.ExcludedItems.Distinct().ToList();
config.CustomZeroPointItems = config.CustomZeroPointItems.Distinct().ToList();
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config, true);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["CleaningInterval"] = "Очистка каждые {0} секунд.",
["TotalCleaned"] = "Удалено объектов: ({0}) всего, ({1}) выброшенных предметов, ({2}) трупов игроков, ({3}) рюкзаков NPC, ({4}) рюкзаков с предметами, ({5}) добычных трупов, ({6}) неактивных сущностей.",
["CleaningNotification"] = "Очистка начнется через {0} секунд в отмеченных местах.",
["DropWarning"] = "Внимание: Вы выбросили {0} предметов. Вас забанят, если вы продолжите.",
["DropBan"] = "Игрок {0} был забанен по причине: {1}",
["AdminNotify"] = "Игрок {0} ({1}) близок к бану за выброс предметов.",
["ForceCleanTriggered"] = "Принудительная очистка запущена!"
}, this, "ru");
lang.RegisterMessages(new Dictionary<string, string>
{
["CleaningInterval"] = "Cleanup every {0} seconds.",
["TotalCleaned"] = "Removed objects: ({0}) total, ({1}) dropped items, ({2}) player corpses, ({3}) NPC backpacks, ({4}) item backpacks, ({5}) lootable corpses, ({6}) inactive entities.",
["CleaningNotification"] = "Cleanup will start in {0} seconds in marked areas.",
["DropWarning"] = "Warning: You have dropped {0} items. You will be banned if you continue.",
["DropBan"] = "Player {0} was banned for reason: {1}",
["AdminNotify"] = "Player {0} ({1}) is close to a ban for item dropping.",
["ForceCleanTriggered"] = "Forced cleanup started!"
}, this, "en");
lang.RegisterMessages(new Dictionary<string, string>
{
["CleaningInterval"] = "Nettoyage toutes les {0} secondes.",
["TotalCleaned"] = "Objets supprimés : ({0}) au total, ({1}) objets jetés, ({2}) cadavres de joueurs, ({3}) sacs à dos PNJ, ({4}) sacs à dos d'objets, ({5}) cadavres ouvrables, ({6}) entités inactives.",
["CleaningNotification"] = "Le nettoyage commencera dans {0} secondes dans les zones marquées.",
["DropWarning"] = "Attention : vous avez jeté {0} objets. Vous serez banni si vous continuez.",
["DropBan"] = "Le joueur {0} a été banni pour la raison : {1}",
["AdminNotify"] = "Le joueur {0} ({1}) est proche d'un bannissement pour avoir jeté des objets.",
["ForceCleanTriggered"] = "Nettoyage forcé lancé !"
}, this, "fr");
}
public interface ICleanerModule
{
void Initialize();
void OnServerInitialized();
void Unload();
void OnEntitySpawned(BaseNetworkable entity);
void OnItemAction(Item item, string action, BasePlayer player);
void ForceCleanup();
}
public void BroadcastToAll(string message)
{
foreach (var player in BasePlayer.activePlayerList)
player.ChatMessage(message);
}
public void RunServerCommand(string command)
{
ConsoleSystem.Run(ConsoleSystem.Option.Server, command);
}
private bool IsJetpackItem(DroppedItem droppedItem)
{
return droppedItem != null && droppedItem.item != null &&
droppedItem.item.info.shortname == "largebackpack" &&
droppedItem.children.Any(x => x is BaseMountable);
}
private bool IsEntityJetpack(BaseNetworkable entity)
{
if (entity is DroppedItem droppedItem)
{
return IsJetpackItem(droppedItem);
}
return false;
}
public class CollisionModule : ICleanerModule
{
private FCleaner plugin;
public CollisionModule(FCleaner plugin) { this.plugin = plugin; }
public void Initialize() {}
public void OnServerInitialized()
{
if (!plugin.config.DisableItemCollisions) return;
Physics.IgnoreLayerCollision(17, 17, true);
Physics.IgnoreLayerCollision(17, 21, false);
Physics.IgnoreLayerCollision(17, 8, false);
Physics.IgnoreLayerCollision(17, 0, false);
Physics.IgnoreLayerCollision(29, 29, true);
Physics.IgnoreLayerCollision(29, 21, false);
Physics.IgnoreLayerCollision(29, 8, false);
Physics.IgnoreLayerCollision(29, 0, false);
}
public void Unload() {}
public void OnEntitySpawned(BaseNetworkable entity)
{
if (!plugin.config.DisableItemCollisions) return;
if (entity is DroppedItem di)
{
int oldLayer = di.gameObject.layer;
di.gameObject.layer = 17;
if (plugin.config.LogLayerChanges)
plugin.Puts($"[FCleaner] DroppedItem '{di.ShortPrefabName}' layer {oldLayer} -> 17");
}
}
public void OnItemAction(Item item, string action, BasePlayer player) {}
public void ForceCleanup() {}
}
public class AutoCleanByTypeModule : ICleanerModule
{
private FCleaner plugin;
private Dictionary<string, int> droppedItemCountByType = new Dictionary<string, int>();
public AutoCleanByTypeModule(FCleaner plugin) { this.plugin = plugin; }
public void Initialize() {}
public void OnServerInitialized() {}
public void Unload() { droppedItemCountByType.Clear(); }
public void OnEntitySpawned(BaseNetworkable entity)
{
if (!plugin.config.AutoCleanByTypeEnabled || plugin.config.AutoCleanTypeThreshold <= 0) return;
if (entity is DroppedItem droppedItem)
{
if (plugin.IsJetpackItem(droppedItem)) return;
var shortName = droppedItem.item?.info?.shortname;
if (string.IsNullOrEmpty(shortName)) return;
if (plugin.config.ExcludedItems.Contains(shortName)) return;
if (!droppedItemCountByType.ContainsKey(shortName))
droppedItemCountByType[shortName] = 0;
droppedItemCountByType[shortName]++;
if (droppedItemCountByType[shortName] >= plugin.config.AutoCleanTypeThreshold)
{
var allDropped = UnityEngine.Object.FindObjectsOfType<DroppedItem>()
.Where(d => d?.item?.info?.shortname == shortName).ToList();
if (allDropped.Count > 0)
{
foreach (var di in allDropped)
di?.Kill();
plugin.Puts($"Авто-очистка по типу '{shortName}' (удалено {allDropped.Count} шт.) Порог: {plugin.config.AutoCleanTypeThreshold}");
}
droppedItemCountByType[shortName] = 0;
}
}
}
public void OnItemAction(Item item, string action, BasePlayer player) {}
public void ForceCleanup()
{
if (!plugin.config.AutoCleanByTypeEnabled || plugin.config.AutoCleanTypeThreshold <= 0) return;
var items = UnityEngine.Object.FindObjectsOfType<DroppedItem>();
var itemCount = new Dictionary<string, int>();
foreach (var i in items)
{
if (i?.item?.info == null) continue;
if (plugin.IsJetpackItem(i)) continue;
var shortName = i.item.info.shortname;
if (plugin.config.ExcludedItems.Contains(shortName)) continue;
if (!itemCount.ContainsKey(shortName))
itemCount[shortName] = 0;
itemCount[shortName]++;
}
foreach (var kvp in itemCount)
{
if (kvp.Value >= plugin.config.AutoCleanTypeThreshold)
{
var toRemove = items.Where(x => x?.item?.info?.shortname == kvp.Key).ToList();
foreach (var r in toRemove)
r?.Kill();
plugin.Puts($"Авто-очистка по типу '{kvp.Key}' (удалено {toRemove.Count} шт.) Порог: {plugin.config.AutoCleanTypeThreshold}");
}
}
}
}
public class BanSpamModule : ICleanerModule
{
private FCleaner plugin;
private Dictionary<string, int> playerItemDropCounts = new Dictionary<string, int>();
private Dictionary<string, Timer> resetTimers = new Dictionary<string, Timer>();
public BanSpamModule(FCleaner plugin) { this.plugin = plugin; }
public void Initialize()
{
plugin.permission.RegisterPermission(plugin.config.AdminPermission, plugin);
plugin.permission.RegisterPermission(plugin.config.NotifyPermission, plugin);
}
public void OnServerInitialized() {}
public void Unload()
{
foreach (var t in resetTimers.Values) t.Destroy();
resetTimers.Clear();
playerItemDropCounts.Clear();
}
public void OnEntitySpawned(BaseNetworkable entity) {}
public void OnItemAction(Item item, string action, BasePlayer player)
{
if (action != "drop" || player == null) return;
if (!plugin.config.BanEnabled) return;
if (plugin.permission.UserHasPermission(player.UserIDString, plugin.config.AdminPermission)) return;
string pid = player.UserIDString;
if (!playerItemDropCounts.ContainsKey(pid))
{
playerItemDropCounts[pid] = 1;
resetTimers[pid] = plugin.timer.Once(plugin.config.ItemDropResetTime, () => ResetDropCount(pid));
}
else
{
playerItemDropCounts[pid]++;
}
int drops = playerItemDropCounts[pid];
if (drops >= plugin.config.ItemDropWarningThreshold && drops < plugin.config.ItemDropBanThreshold)
{
if (plugin.config.EnableDropWarnings)
{
string warnMsg = plugin.lang.GetMessage("DropWarning", plugin, player.UserIDString);
player.ChatMessage(string.Format(warnMsg, drops));
}
NotifyAdmins(player, drops);
}
else if (drops >= plugin.config.ItemDropBanThreshold)
{
BanPlayer(player, plugin.config.BanReason);
}
}
public void ForceCleanup() {}
private void ResetDropCount(string pid)
{
playerItemDropCounts.Remove(pid);
if (resetTimers.ContainsKey(pid))
{
resetTimers[pid].Destroy();
resetTimers.Remove(pid);
}
}
private void NotifyAdmins(BasePlayer p, int dropCount)
{
string msg = string.Format(plugin.lang.GetMessage("AdminNotify", plugin, p.UserIDString),
p.displayName, p.UserIDString);
foreach (var bp in BasePlayer.activePlayerList)
{
if (bp == null) continue;
if (plugin.permission.UserHasPermission(bp.UserIDString, plugin.config.NotifyPermission))
bp.ChatMessage(msg);
}
plugin.Puts(msg + $" (DropCount={dropCount})");
}
private void BanPlayer(BasePlayer bp, string reason)
{
string steamId = bp.UserIDString;
var ip = bp.net?.connection?.ipaddress;
if (!string.IsNullOrEmpty(ip))
{
var split = ip.Split(':');
ip = split[0];
}
plugin.Puts(string.Format(plugin.lang.GetMessage("DropBan", plugin, steamId), bp.displayName, reason));
if (plugin.config.UseExtendedBanSystem && plugin.config.ExtendedBanType.Equals("IP", StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrEmpty(ip))
{
plugin.RunServerCommand($"{plugin.config.CustomBanCommand} {ip} {plugin.config.BanDuration} \"{reason}\"");
return;
}
}
if (!string.IsNullOrEmpty(plugin.config.CustomBanCommand))
{
plugin.RunServerCommand($"{plugin.config.CustomBanCommand} {steamId} {plugin.config.BanDuration} \"{reason}\"");
}
else
{
bp.IPlayer.Ban(reason);
}
}
}
public class MarkersModule : ICleanerModule
{
private FCleaner plugin;
private List<BaseEntity> activeMarkers = new List<BaseEntity>();
public MarkersModule(FCleaner plugin) { this.plugin = plugin; }
public void Initialize() {}
public void OnServerInitialized() {}
public void Unload()
{
RemoveAllMarkers();
}
public void OnEntitySpawned(BaseNetworkable entity) {}
public void OnItemAction(Item item, string action, BasePlayer player) {}
public void ForceCleanup()
{
RemoveAllMarkers();
}
public void AddMarker(Vector3 position)
{
if (!plugin.config.ShowMarkersOnMap) return;
var vendingMarker = GameManager.server.CreateEntity(
"assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab",
position) as VendingMachineMapMarker;
if (vendingMarker != null)
{
vendingMarker.markerShopName = plugin.config.MapMarkerName;
vendingMarker.Spawn();
activeMarkers.Add(vendingMarker);
}
}
public void RemoveAllMarkers()
{
foreach (var m in activeMarkers)
m?.Kill();
activeMarkers.Clear();
}
}
public class CleanupModule : ICleanerModule
{
private FCleaner plugin;
private MarkersModule markersModule;
private Timer cleanTimer;
private List<DroppedItem> pendingDroppedItems = new List<DroppedItem>();
private List<PlayerCorpse> pendingPlayerCorpses = new List<PlayerCorpse>();
private List<NPCPlayerCorpse> pendingNPCCorpses = new List<NPCPlayerCorpse>();
private List<BaseNetworkable> pendingDropBackpacks = new List<BaseNetworkable>();
private List<LootableCorpse> pendingLootableCorpses = new List<LootableCorpse>();
private List<BaseNetworkable> pendingInactive = new List<BaseNetworkable>();
private List<BaseNetworkable> pendingZeroPoint = new List<BaseNetworkable>();
private List<Vector3> markerPositions = new List<Vector3>();
public CleanupModule(FCleaner plugin, MarkersModule markersModule)
{
this.plugin = plugin;
this.markersModule = markersModule;
}
public void Initialize() {}
public void OnServerInitialized()
{
cleanTimer = plugin.timer.Repeat(plugin.config.CleanInterval, 0, PrepareForCleanup);
if (plugin.config.EnableCleanupMessages)
{
string msg = string.Format(plugin.lang.GetMessage("CleaningInterval", plugin, null),
plugin.config.CleanInterval);
plugin.BroadcastToAll($"<color={plugin.config.PrefixColor}>{plugin.config.Prefix}</color> {msg}");
}
}
public void Unload()
{
cleanTimer?.Destroy();
}
public void OnEntitySpawned(BaseNetworkable entity) {}
public void OnItemAction(Item item, string action, BasePlayer player) {}
public void ForceCleanup()
{
plugin.Puts(plugin.lang.GetMessage("ForceCleanTriggered", plugin, null));
if (plugin.config.EnableCleanupMessages)
{
plugin.BroadcastToAll($"<color={plugin.config.PrefixColor}>{plugin.config.Prefix}</color> {plugin.lang.GetMessage("ForceCleanTriggered", plugin, null)}");
}
PreparePendingLists();
CleanUpAll();
}
private void PrepareForCleanup()
{
PreparePendingLists();
if (plugin.config.ShowMarkersOnMap)
{
foreach (var pos in markerPositions)
markersModule.AddMarker(pos);
}
if (plugin.config.EnableCleanupMessages)
{
string notification = string.Format(plugin.lang.GetMessage("CleaningNotification", plugin, null),
plugin.config.CleanNotificationTime);
plugin.BroadcastToAll($"<color={plugin.config.PrefixColor}>{plugin.config.Prefix}</color> {notification}");
}
plugin.timer.Once(plugin.config.CleanNotificationTime, CleanUpAll);
}
private void PreparePendingLists()
{
pendingDroppedItems.Clear();
pendingPlayerCorpses.Clear();
pendingNPCCorpses.Clear();
pendingDropBackpacks.Clear();
pendingLootableCorpses.Clear();
pendingInactive.Clear();
pendingZeroPoint.Clear();
markerPositions.Clear();
foreach (var ent in BaseNetworkable.serverEntities)
{
if (ent == null || ent.IsDestroyed) continue;
if (plugin.IsEntityJetpack(ent)) continue;
bool hasTransform = ent.transform != null;
if (plugin.config.CleanInactiveEntities && (!ent.gameObject.activeSelf || ent.IsDestroyed))
{
pendingInactive.Add(ent);
if (hasTransform) markerPositions.Add(ent.transform.position);
continue;
}
if (plugin.config.CleanDroppedItems && ent is DroppedItem di)
{
var shortName = di.item?.info?.shortname;
if (!string.IsNullOrEmpty(shortName))
{
if (plugin.config.ExcludedItems.Contains(shortName)) continue;
if (plugin.config.AutoAddKeycardsToExcluded && shortName.Contains("keycard_"))
{
bool droppedByPlayer;
ulong uidVal = di.item.uid.Value;
if (!plugin.playerDroppedItems.TryGetValue(uidVal, out droppedByPlayer) || !droppedByPlayer)
continue;
}
pendingDroppedItems.Add(di);
if (hasTransform) markerPositions.Add(ent.transform.position);
}
continue;
}
if (plugin.config.CleanPlayerCorpses && ent is PlayerCorpse pc)
{
pendingPlayerCorpses.Add(pc);
if (hasTransform) markerPositions.Add(ent.transform.position);
continue;
}
if (plugin.config.CleanNPCBackpacks && ent is NPCPlayerCorpse npcC)
{
pendingNPCCorpses.Add(npcC);
if (hasTransform) markerPositions.Add(ent.transform.position);
continue;
}
if (plugin.config.CleanDropBackpacks && ent.ShortPrefabName.Equals("item_drop_backpack", StringComparison.OrdinalIgnoreCase))
{
pendingDropBackpacks.Add(ent);
if (hasTransform) markerPositions.Add(ent.transform.position);
continue;
}
if (plugin.config.CleanLootableCorpses && ent is LootableCorpse lc)
{
if (lc.playerSteamID == 0)
{
pendingLootableCorpses.Add(lc);
if (hasTransform) markerPositions.Add(ent.transform.position);
}
continue;
}
if (hasTransform &&
Vector3.Distance(ent.transform.position, Vector3.zero) < 10f &&
plugin.config.CustomZeroPointItems.Any(x => ent.ShortPrefabName.Contains(x)))
{
pendingZeroPoint.Add(ent);
markerPositions.Add(ent.transform.position);
}
}
}
private void CleanUpAll()
{
markersModule.RemoveAllMarkers();
int cleanedDropped = pendingDroppedItems.Count;
int cleanedPlayerCorpses = pendingPlayerCorpses.Count;
int cleanedNPCBackpacks = pendingNPCCorpses.Count;
int cleanedDropBackpacks = pendingDropBackpacks.Count;
int cleanedLootableCorpses = pendingLootableCorpses.Count;
int cleanedInactive = pendingInactive.Count;
int cleanedZeroPoint = pendingZeroPoint.Count;
int totalCleaned = cleanedDropped + cleanedPlayerCorpses + cleanedNPCBackpacks + cleanedDropBackpacks +
cleanedLootableCorpses + cleanedInactive + cleanedZeroPoint;
if (plugin.config.EnablePartialCleanup)
{
CleanUpEntitiesInChunks(pendingDroppedItems, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay);
CleanUpEntitiesInChunks(pendingPlayerCorpses, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay);
CleanUpEntitiesInChunks(pendingNPCCorpses, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay);
CleanUpEntitiesInChunks(pendingDropBackpacks, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay);
CleanUpEntitiesInChunks(pendingLootableCorpses, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay);
CleanUpEntitiesInChunks(pendingInactive, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay);
CleanUpEntitiesInChunks(pendingZeroPoint, plugin.config.CleanupChunkSize, plugin.config.CleanupChunkDelay,
() => NotifyCleanupResult(totalCleaned, cleanedDropped, cleanedPlayerCorpses, cleanedNPCBackpacks, cleanedDropBackpacks, cleanedLootableCorpses, cleanedInactive, cleanedZeroPoint));
}
else
{
KillAll(pendingDroppedItems);
KillAll(pendingPlayerCorpses);
KillAll(pendingNPCCorpses);
KillAll(pendingDropBackpacks);
KillAll(pendingLootableCorpses);
KillAll(pendingInactive);
KillAll(pendingZeroPoint);
NotifyCleanupResult(totalCleaned, cleanedDropped, cleanedPlayerCorpses, cleanedNPCBackpacks, cleanedDropBackpacks, cleanedLootableCorpses, cleanedInactive, cleanedZeroPoint);
}
RemoveDroppedItemRecords(pendingDroppedItems);
}
private void CleanUpEntitiesInChunks<T>(List<T> entities, int chunkSize, float delay, Action onComplete = null) where T : BaseNetworkable
{
if (entities == null || entities.Count == 0)
{
onComplete?.Invoke();
return;
}
var localList = new List<T>(entities);
void CleanChunk()
{
if (localList.Count == 0)
{
onComplete?.Invoke();
return;
}
int count = Mathf.Min(chunkSize, localList.Count);
var chunk = localList.GetRange(0, count);
foreach (var e in chunk) e?.Kill();
localList.RemoveRange(0, count);
if (localList.Count > 0) plugin.timer.Once(delay, CleanChunk);
else onComplete?.Invoke();
}
CleanChunk();
}
private void KillAll<T>(List<T> list) where T : BaseNetworkable
{
foreach (var e in list) e?.Kill();
}
private void NotifyCleanupResult(int total, int dropped, int playerCorpses, int npcBackpacks,
int dropBackpacks, int lootableCorpses, int inactive, int zeroPoint)
{
if (!plugin.config.EnableCleanupMessages) return;
string msg = string.Format(
plugin.lang.GetMessage("TotalCleaned", plugin, null),
total, dropped, playerCorpses, npcBackpacks, dropBackpacks, lootableCorpses, inactive
);
plugin.BroadcastToAll($"<color={plugin.config.PrefixColor}>{plugin.config.Prefix}</color> {msg}");
}
private void RemoveDroppedItemRecords(List<DroppedItem> droppedItems)
{
foreach (var di in droppedItems)
{
if (di?.item == null) continue;
ulong uidVal = di.item.uid.Value;
if (plugin.playerDroppedItems.ContainsKey(uidVal))
plugin.playerDroppedItems.Remove(uidVal);
}
}
}
private List<ICleanerModule> modules = new List<ICleanerModule>();
private CollisionModule collisionModule;
private AutoCleanByTypeModule autoCleanByTypeModule;
private BanSpamModule banSpamModule;
private MarkersModule markersModule;
private CleanupModule cleanupModule;
private void OnServerInitialized()
{
LoadConfig();
collisionModule = new CollisionModule(this);
autoCleanByTypeModule = new AutoCleanByTypeModule(this);
banSpamModule = new BanSpamModule(this);
markersModule = new MarkersModule(this);
cleanupModule = new CleanupModule(this, markersModule);
modules.Add(collisionModule);
modules.Add(autoCleanByTypeModule);
modules.Add(banSpamModule);
modules.Add(markersModule);
modules.Add(cleanupModule);
foreach (var m in modules)
{
m.Initialize();
m.OnServerInitialized();
}
}
private void Unload()
{
foreach (var m in modules)
m.Unload();
}
private void OnEntitySpawned(BaseNetworkable entity)
{
foreach (var m in modules)
m.OnEntitySpawned(entity);
}
private void OnItemAction(Item item, string action, BasePlayer player)
{
foreach (var m in modules)
m.OnItemAction(item, action, player);
if (action == "drop" && item != null)
{
ulong itemUid = item.uid.Value;
playerDroppedItems[itemUid] = true;
}
}
[ConsoleCommand("fcleaner.forceclean")]
private void ForceCleanCommand(ConsoleSystem.Arg arg)
{
foreach (var m in modules)
m.ForceCleanup();
arg?.ReplyWith(lang.GetMessage("ForceCleanTriggered", this, null));
}
}
}