using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Oxide.Core; using Oxide.Game.Rust.Cui; using UnityEngine; // ____ _ _ _ // / ___|(_) __ _(_) | ___ // \___ \| |/ _` | | |/ _ \ // ___) | | (_| | | | (_) | // |____/|_|\__, |_|_|\___/ // |___/ // ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾ // ✦ RUST PLUGINS ✦ // ✦ Sigilo.dev ✦ // ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾ namespace Oxide.Plugins { [Info("Wipe Raid Timer", "Sigilo", "1.0.7")] [Description("Block raid items for a configurable time after wipe")] public class WipeRaidTimer : RustPlugin { private const string UiTimerBarName = "wiperaid.timerbar"; private const float NOTIFICATION_DURATION = 5f; private Dictionary activeReloads = new Dictionary(); private Dictionary playerAmmoChecks = new Dictionary(); private Timer statusBarUpdateTimer; private bool droneRangeModified = false; private bool previousBlockActiveState = false; private const string STATUS_BAR_BG_COLOR = "0.1 0.1 0.1 0.7"; private const string STATUS_BAR_BORDER_COLOR = "0.3 0.3 0.3 0.7"; private const string STATUS_BAR_FILL_COLOR = "0.7 0.2 0.2 0.9"; private const string STATUS_BAR_TEXT_COLOR = "1 1 1 0.9"; private const string STATUS_BAR_ANCHOR_MIN = "0.10 0.03"; private const string STATUS_BAR_ANCHOR_MAX = "0.28 0.05"; private readonly HashSet ATTACK_HELI_ITEMS = new HashSet { "attackhelicopter", "vehicle.attackhelicopter" }; private ConfigData configData; #region Oxide Hooks void OnServerInitialized() { permission.RegisterPermission("wiperaidtimer.bypass", this); Puts($"Config loaded: Block Time = {configData.BlockTimeAfterWipe} seconds, Block Attack Helicopter = {configData.BlockAttackHelicopter}, Block MLRS = {configData.BlockMlrs}, Block Drones = {configData.BlockDrones}, Drone Restore Range = {configData.DroneRestoreRange}"); foreach (var attackHelicopterEntity in BaseNetworkable.serverEntities.OfType()) { if (ShouldBlockAttackHelicopter()) { if (attackHelicopterEntity.OwnerID != 0) { var player = BasePlayer.FindByID(attackHelicopterEntity.OwnerID); if (player != null && !HasBypassPermission(player)) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); } } attackHelicopterEntity.Kill(); } } if (configData.BlockDrones) { UpdateDroneRange(); } if (IsWipeBlockActive()) { StartStatusBarUpdateTimer(); } } private void Init() { LoadConfig(); foreach (var turret in BaseNetworkable.serverEntities.OfType()) { if (turret is not NPCAutoTurret && turret.OwnerID.IsSteamId()) { HandleTurretInit(turret); } } timer.Every(0.5f, () => { foreach (var player in BasePlayer.activePlayerList) { CheckPlayerWeapons(player); } }); } private void OnPlayerConnected(BasePlayer player) { if (player == null || HasBypassPermission(player)) return; if (IsWipeBlockActive()) { UpdateStatusBar(player); } } private void OnPlayerDisconnected(BasePlayer player) { if (player == null) return; CuiHelper.DestroyUi(player, UiTimerBarName); } private void OnEntitySpawned(BaseEntity entity) { if (entity == null) return; if (entity is AutoTurret turret && turret is not NPCAutoTurret && turret.OwnerID.IsSteamId()) { HandleTurretInit(turret); return; } if (IsAttackHelicopter(entity) && ShouldBlockAttackHelicopter()) { NextTick(() => { if (entity != null && !entity.IsDestroyed) { if (entity.OwnerID != 0) { var player = BasePlayer.FindByID(entity.OwnerID); if (player != null) { if (HasBypassPermission(player)) return; string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); } } entity.Kill(); } }); return; } } private object OnEntityBuilt(Planner planner, GameObject go) { if (planner == null || go == null) return null; var entity = go.ToBaseEntity(); if (entity == null) return null; var player = planner.GetOwnerPlayer(); if (player == null || HasBypassPermission(player)) return null; if (IsAttackHelicopter(entity) && ShouldBlockAttackHelicopter()) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); NextTick(() => { if (entity != null && !entity.IsDestroyed) { entity.Kill(); } }); return false; } return null; } private void Unload() { foreach (var player in BasePlayer.activePlayerList) { CuiHelper.DestroyUi(player, UiTimerBarName); } statusBarUpdateTimer?.Destroy(); statusBarUpdateTimer = null; foreach (var timer in activeReloads.Values.Concat(playerAmmoChecks.Values).Where(t => t != null)) { timer.Destroy(); } activeReloads.Clear(); playerAmmoChecks.Clear(); RestoreOriginalDroneRange(); } private object CanEquipItem(PlayerInventory inventory, Item item, int targetPos) { return CanWearItem(inventory, item, targetPos); } private object OnWeaponReload(BaseProjectile projectile, BasePlayer player) { if (player == null || projectile == null) return null; ScheduleAmmoCheck(player); return OnMagazineReload(projectile, -1, player); } private object CanWearItem(PlayerInventory inventory, Item item, int targetPos) { var player = inventory.GetComponent(); return CanUseItem(player, item.info.shortname) ? null : (object)false; } private object OnMagazineReload(BaseProjectile projectile, int desiredAmount, BasePlayer player) { if (projectile == null || player == null) return null; ScheduleAmmoCheck(player); if (activeReloads.TryGetValue(projectile, out var existingTimer) && existingTimer != null) { existingTimer.Destroy(); } activeReloads[projectile] = timer.Once(projectile.reloadTime + 0.2f, () => { VerifyWeaponAmmo(player, projectile); ScheduleAmmoCheck(player); }); string weaponShortname = projectile.ShortPrefabName; if (!string.IsNullOrEmpty(weaponShortname) && weaponShortname.Contains("rifle.ak")) { NextTick(() => CheckGun(player, projectile)); } if (projectile.primaryMagazine != null && projectile.primaryMagazine.ammoType != null) { return CanUseItem(player, projectile.primaryMagazine.ammoType.shortname) ? null : (object)true; } return null; } private object CanDeployItem(Deployer deployer, BaseEntity entity) { if (deployer == null || entity == null) return null; BasePlayer player = deployer.GetOwnerPlayer(); if (player == null || HasBypassPermission(player)) return null; if (IsAttackHelicopter(entity) && ShouldBlockAttackHelicopter()) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } if (IsMlrs(entity) && ShouldBlockMlrs()) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } return null; } private object CanMountEntity(BasePlayer player, BaseMountable entity) { if (player == null || entity == null || HasBypassPermission(player)) return null; if (IsMlrs(entity) && ShouldBlockMlrs()) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); Puts($"Blocked {player.displayName} from mounting MLRS: {entity.name} / {entity.ShortPrefabName}"); return false; } return null; } private object OnItemAction(Item item, string action, BasePlayer player) { if (player == null || HasBypassPermission(player) || item == null || item.info == null) return null; if (action == "drop") return null; if (ShouldBlockAttackHelicopter()) { var shortname = item.info.shortname; if (shortname != null && ( shortname.Contains("attackhelicopter") || ATTACK_HELI_ITEMS.Contains(shortname))) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } } if (ShouldBlockMlrs() && IsMlrsItem(item.info.shortname)) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } if (IsItemBlocked(item.info.shortname)) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } return null; } private object OnUseNPC(BasePlayer player, BaseCombatEntity entity) { if (player == null || entity == null || HasBypassPermission(player)) return null; if (entity.ShortPrefabName?.Contains("bandit") == true || entity.ShortPrefabName?.Contains("outpost") == true) { return null; } return null; } private object CanUseItem(BasePlayer player, Item item) { if (player == null || item == null || HasBypassPermission(player)) return null; if (ShouldBlockAttackHelicopter()) { var shortname = item.info.shortname; if (ATTACK_HELI_ITEMS.Contains(shortname) || shortname.Contains("attackhelicopter")) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } } if (ShouldBlockMlrs() && IsMlrsItem(item.info.shortname)) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } if (IsItemBlocked(item.info.shortname)) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } return null; } private object CanUseMLRS(BasePlayer player) { if (player == null || HasBypassPermission(player)) return null; if (ShouldBlockMlrs()) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } return null; } private void OnItemAddedToContainer(ItemContainer container, Item item) { if (item == null || container == null) return; if (container.playerOwner != null) { ScheduleAmmoCheck(container.playerOwner); } if (ShouldBlockAttackHelicopter() && container.playerOwner != null && !HasBypassPermission(container.playerOwner)) { var shortname = item?.info?.shortname; if (shortname != null && ( ATTACK_HELI_ITEMS.Contains(shortname) || shortname.Contains("attackhelicopter"))) { NextTick(() => { if (item != null && item.IsValid() && item.parent == container) { item.Remove(); string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(container.playerOwner, timeString); } }); return; } } if (IsMlrsContainer(container) && IsMlrsItem(item?.info?.shortname) && ShouldBlockMlrs()) { NextTick(() => { if (item != null && item.IsValid() && item.parent == container) { RemoveItemFromContainer(item, container); } }); return; } } private void OnItemRemovedFromContainer(ItemContainer container, Item item) { if (container?.playerOwner != null) { ScheduleAmmoCheck(container.playerOwner); } } private object CanDropItem(BasePlayer player, Item item) { return null; } #endregion #region Status Bar UI private void StartStatusBarUpdateTimer() { statusBarUpdateTimer?.Destroy(); previousBlockActiveState = IsWipeBlockActive(); statusBarUpdateTimer = timer.Every(1f, () => { bool isBlockActive = IsWipeBlockActive(); if (isBlockActive != previousBlockActiveState) { UpdateDroneRange(); previousBlockActiveState = isBlockActive; } if (!isBlockActive) { DestroyAllStatusBars(); statusBarUpdateTimer?.Destroy(); statusBarUpdateTimer = null; return; } foreach (var player in BasePlayer.activePlayerList) { if (player != null && !HasBypassPermission(player)) { UpdateStatusBar(player); } } }); } private void DestroyAllStatusBars() { foreach (var player in BasePlayer.activePlayerList) { if (player != null) { CuiHelper.DestroyUi(player, UiTimerBarName); } } } private void UpdateStatusBar(BasePlayer player) { if (player == null) return; int remainingTime = GetRemainingBlockTime(); float progress = (float)remainingTime / configData.BlockTimeAfterWipe; string timeString = FormatTimeString(remainingTime); var elements = new CuiElementContainer(); elements.Add(new CuiElement { Name = UiTimerBarName, Parent = "Hud", Components = { new CuiImageComponent { Color = STATUS_BAR_BG_COLOR }, new CuiRectTransformComponent { AnchorMin = STATUS_BAR_ANCHOR_MIN, AnchorMax = STATUS_BAR_ANCHOR_MAX }, new CuiOutlineComponent { Color = STATUS_BAR_BORDER_COLOR, Distance = "1 1" } } }); elements.Add(new CuiElement { Name = $"{UiTimerBarName}.fill", Parent = UiTimerBarName, Components = { new CuiImageComponent { Color = STATUS_BAR_FILL_COLOR }, new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = $"{progress} 1" } } }); elements.Add(new CuiElement { Name = $"{UiTimerBarName}.text", Parent = UiTimerBarName, Components = { new CuiTextComponent { Text = $"Raid Block: {timeString}", Align = TextAnchor.MiddleCenter, FontSize = 12, Color = STATUS_BAR_TEXT_COLOR, Font = "robotocondensed-regular.ttf" }, new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" } } }); CuiHelper.DestroyUi(player, UiTimerBarName); CuiHelper.AddUi(player, elements); } #endregion #region Helper Methods private bool IsWipeBlockActive() { return GetSecondsSinceWipe() < configData.BlockTimeAfterWipe; } private int GetRemainingBlockTime() { int timeElapsed = GetSecondsSinceWipe(); return Math.Max(0, configData.BlockTimeAfterWipe - timeElapsed); } private bool ShouldBlockAttackHelicopter() { return configData.BlockAttackHelicopter && IsWipeBlockActive(); } private bool ShouldBlockMlrs() { return configData.BlockMlrs && IsWipeBlockActive(); } private bool IsItemBlocked(string shortname) { return configData.BlockedItems.Contains(shortname) && IsWipeBlockActive(); } private bool HasBypassPermission(BasePlayer player) { return player.IsAdmin || permission.UserHasPermission(player.UserIDString, "wiperaidtimer.bypass"); } private bool IsAttackHelicopter(BaseEntity entity) { if (entity == null) return false; return entity is AttackHelicopter; } private void HandleTurretInit(AutoTurret turret) { NextTick(() => { if (turret.IsValid() && turret.inventory != null) { turret.inventory.onItemAddedRemoved += (item, added) => OnTurretItemChanged(turret, item, added); } }); } private void OnTurretItemChanged(AutoTurret turret, Item item, bool added) { if (!added || item.parent == null) return; if (IsItemBlocked(item.info.shortname)) { item.Drop(turret.transform.position + new Vector3(0, 1, 0), turret.GetDropVelocity()); } } private bool CanUseItem(BasePlayer player, string shortName) { if (player == null || HasBypassPermission(player)) return true; if (IsItemBlocked(shortName)) { string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); return false; } return true; } private bool IsMlrs(BaseEntity entity) { if (entity == null) return false; string prefabName = entity.ShortPrefabName?.ToLower(); return !string.IsNullOrEmpty(prefabName) && prefabName.Contains("mlrs"); } private bool IsMlrsContainer(ItemContainer container) { var parent = container?.entityOwner; if (parent == null) return false; string prefabName = parent.ShortPrefabName?.ToLower(); return !string.IsNullOrEmpty(prefabName) && prefabName.Contains("mlrs"); } private bool IsMlrsItem(string shortname) { return shortname == "ammo.rocket.mlrs" || shortname == "aiming.module.mlrs"; } private void ScheduleAmmoCheck(BasePlayer player) { if (player == null) return; if (playerAmmoChecks.TryGetValue(player.userID, out var existingTimer) && existingTimer != null) { existingTimer.Destroy(); } playerAmmoChecks[player.userID] = timer.Repeat(0.1f, 10, () => CheckPlayerWeapons(player)); } private void CheckPlayerWeapons(BasePlayer player) { if (player == null || player.IsDestroyed || !player.IsConnected) return; if (player.GetActiveItem()?.GetHeldEntity() is BaseProjectile activeWeapon) { VerifyWeaponAmmo(player, activeWeapon); } foreach (var container in new[] { player.inventory.containerMain, player.inventory.containerBelt, player.inventory.containerWear }) { if (container == null) continue; foreach (var item in container.itemList) { if (item?.GetHeldEntity() is BaseProjectile weapon) { VerifyWeaponAmmo(player, weapon); } } } } private void VerifyWeaponAmmo(BasePlayer player, BaseProjectile weapon) { if (player == null || weapon == null || weapon.primaryMagazine == null) return; var magazine = weapon.primaryMagazine; if (magazine.contents > 0 && magazine.ammoType != null && IsItemBlocked(magazine.ammoType.shortname)) { Item weaponItem = null; foreach (var container in new[] { player.inventory.containerMain, player.inventory.containerBelt }) { weaponItem = container?.itemList.FirstOrDefault(x => x.GetHeldEntity() == weapon); if (weaponItem != null) break; } if (weaponItem != null) { magazine.contents = 0; weapon.SendNetworkUpdateImmediate(); string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); } } } private void CheckGun(BasePlayer player, BaseProjectile weapon) { if (weapon?.primaryMagazine == null) return; var magazine = weapon.primaryMagazine; if (magazine.contents > 0 && magazine.ammoType != null && IsItemBlocked(magazine.ammoType.shortname)) { var item = player.inventory.containerMain?.itemList.FirstOrDefault(x => x.GetHeldEntity() == weapon); if (item != null) { item._condition = 0f; item._maxCondition = 0f; item.MarkDirty(); magazine.contents = 0; magazine.capacity = 0; } } } private void RemoveItemFromContainer(Item item, ItemContainer container) { if (item == null || container == null) return; var player = container.playerOwner; if (player == null) { item.Remove(); return; } if (!item.MoveToContainer(player.inventory.containerMain)) { item.Drop(player.transform.position, player.GetDropVelocity()); string timeString = FormatTimeString(GetRemainingBlockTime()); SendChatMessage(player, timeString); } } private void SendChatMessage(BasePlayer player, string timeString) { player.ChatMessage(GetMessage("ItemBlocked", player.UserIDString, timeString)); } private void UpdateDroneRange() { if (!configData.BlockDrones) return; try { if (IsWipeBlockActive()) { if (!droneRangeModified) { ConsoleSystem.Run(ConsoleSystem.Option.Server, "drone.maxcontrolrange", "1"); droneRangeModified = true; Puts("Raid block active: Set drone.maxcontrolrange to 1"); } } else { RestoreOriginalDroneRange(); } } catch (Exception ex) { PrintError($"Error updating drone range: {ex.Message}"); } } private void RestoreOriginalDroneRange() { if (!configData.BlockDrones) return; try { if (droneRangeModified) { ConsoleSystem.Run(ConsoleSystem.Option.Server, "drone.maxcontrolrange", configData.DroneRestoreRange.ToString()); droneRangeModified = false; Puts($"Raid block expired: Restored drone.maxcontrolrange to {configData.DroneRestoreRange}"); } } catch (Exception ex) { PrintError($"Error restoring original drone range: {ex.Message}"); } } #endregion #region Time Utilities private int GetSecondsSinceWipe() { return (int)(DateTime.UtcNow - SaveRestore.SaveCreatedTime).TotalSeconds; } private string FormatTimeString(int seconds) { var timeComponents = new List(); int days = seconds / 86400; seconds %= 86400; if (days > 0) timeComponents.Add($"{days}d"); int hours = seconds / 3600; seconds %= 3600; if (hours > 0) timeComponents.Add($"{hours}h"); int minutes = seconds / 60; seconds %= 60; if (minutes > 0) timeComponents.Add($"{minutes}m"); if (seconds > 0 || timeComponents.Count == 0) timeComponents.Add($"{seconds}s"); return string.Join(", ", timeComponents); } #endregion #region Localization protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { {"ItemBlocked", "Raid item blocked for {0}!"}, {"WipeTimer", "Raid Block Timer: {0}"} }, this); } private string GetMessage(string messageKey, string playerID, params object[] args) { return string.Format(lang.GetMessage(messageKey, this, playerID), args); } #endregion #region Configuration private class ConfigData { [JsonProperty("Blocked Items (List of items blocked during wipe timer)")] public HashSet BlockedItems { get; set; } = new HashSet(); [JsonProperty("Block Time After Wipe (seconds)")] public int BlockTimeAfterWipe { get; set; } = 86400; [JsonProperty("Block Attack Helicopter")] public bool BlockAttackHelicopter { get; set; } = true; [JsonProperty("Block MLRS")] public bool BlockMlrs { get; set; } = true; [JsonProperty("Block Drones (Set drone.maxcontrolrange to 1 during raid block)")] public bool BlockDrones { get; set; } = true; [JsonProperty("Drone Restore Range (Value to restore drone.maxcontrolrange to after raid block expires)")] public float DroneRestoreRange { get; set; } = 750f; } protected override void LoadConfig() { base.LoadConfig(); try { configData = Config.ReadObject(); if (configData == null) { LoadDefaultConfig(); } } catch { PrintError("Configuration file is corrupt! Check your config file format."); LoadDefaultConfig(); return; } ValidateConfig(); SaveConfig(); } private void ValidateConfig() { if (configData.BlockedItems.Count == 0) { configData.BlockedItems = new HashSet { "grenade.beancan", "grenade.molotov", "catapult.ammo.incendiary", "catapult.ammo.explosive", "flamethrower", "catapult", "batteringram", "ballista.static", "ballista.mounted", "rocket.launcher", "multiplegrenadelauncher", "explosive.satchel", "explosive.timed", "ammo.grenadelauncher.he", "ammo.rifle.explosive", "ammo.rocket.basic", "ammo.rocket.fire", "ammo.rocket.hv", "grenade.f1" }; } if (configData.BlockMlrs) { configData.BlockedItems.Add("ammo.rocket.mlrs"); configData.BlockedItems.Add("aiming.module.mlrs"); } } protected override void LoadDefaultConfig() { configData = new ConfigData(); } protected override void SaveConfig() { Config.WriteObject(configData, true); } #endregion #region API private Dictionary API_GetRemainingBlockTime() { int time = GetRemainingBlockTime(); return new Dictionary { { "RemainingSeconds", time } }; } #endregion } }