C#:
/*
* Copyright (C) 2025 NexCore/CrypticRedHat - All Rights Reserved
* Unauthorized copying of this file, via any medium, is strictly prohibited.
* This file is proprietary and confidential, created and maintained by NexCore (CrypticRedHat).
*
* Plugin Name: BaseGuardian
* Version: 1.1.1
* Last Updated: April 2025
* Contact: zaneful@proton.me
*
* Description:
* This software is provided as part of a Rust server mod/plugin. By using this file,
* you agree to the following terms. You may not, without explicit written permission
* from the Developer:
* - Copy, distribute, or disclose any part of this software in any medium.
* - Alter, merge, publish, sublicense, or sell copies of this software, in whole or in part.
*
* Warranty and Liability:
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH
* THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* Any breach of these terms will be pursued to the fullest extent of applicable laws.
*/
using System.Collections.Generic;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Libraries;
using Oxide.Plugins;
using System;
namespace Oxide.Plugins
{
[Info("BaseGuardian", "NexCore", "1.1.1")]
[Description("Closes doors and tops up TCs, with crash detection and network drop checks.")]
public class BaseGuardian : RustPlugin
{
private class BaseGuardianConfig
{
public bool EnableCrashCheck { get; set; } = true;
public bool CloseDoorsOnCrash { get; set; } = true;
public bool TopUpTCOnCrash { get; set; } = false;
public bool EnableNetworkDropCheck { get; set; } = false;
public string NetworkCheckHost { get; set; } = "https://www.google.com";
public int PingFrequencySeconds { get; set; } = 600;
public int MaxPingFailures { get; set; } = 3;
public bool CloseDoorsOnNetDrop { get; set; } = true;
public bool TopUpTCOnNetDrop { get; set; } = false;
public float TCUpkeepHoursToTopup { get; set; } = 12f;
}
private class BaseGuardianData
{
public bool GracefulShutdown = true;
}
private BaseGuardianConfig config;
private BaseGuardianData data;
private const string PermDoors = "baseguardian.doors";
private const string PermTC = "baseguardian.tc";
private int consecutiveFailures;
private Timer netCheckTimer;
protected override void LoadDefaultConfig()
{
PrintWarning("Creating a new configuration file...");
config = new BaseGuardianConfig();
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
config = Config.ReadObject<BaseGuardianConfig>();
if (config == null)
{
PrintWarning("Configuration file is invalid; creating a new one.");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
Config.WriteObject(config, true);
}
private void LoadData()
{
data = Interface.Oxide.DataFileSystem.ReadObject<BaseGuardianData>(Name);
if (data == null) data = new BaseGuardianData();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, data);
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NoPermission"] = "You do not have permission to use this command.",
["ClosedDoors"] = "Closed {0} doors on the server.",
["ToppedCupboards"] = "Topped up {0} tool cupboards to 12 hours of upkeep.",
["NoCupboardsFound"] = "No valid tool cupboards were found to top up."
}, this, "en");
}
private string Msg(string key, string userId = null) => lang.GetMessage(key, this, userId);
private void Init()
{
permission.RegisterPermission(PermDoors, this);
permission.RegisterPermission(PermTC, this);
LoadData();
}
private void Unload()
{
data.GracefulShutdown = true;
SaveData();
}
private void OnServerInitialized()
{
LoadData();
if (config.EnableCrashCheck && !data.GracefulShutdown)
{
if (config.CloseDoorsOnCrash) CloseAllDoors();
if (config.TopUpTCOnCrash) TopUpAllTCs();
}
data.GracefulShutdown = false;
SaveData();
if (config.EnableNetworkDropCheck)
{
StartNetworkMonitor();
}
}
[ChatCommand("bg.doors")]
[ConsoleCommand("bg.doors")]
private void CmdCloseDoors(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermDoors))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
CloseAllDoors(player);
}
private void CloseAllDoors(BasePlayer player = null)
{
int closedDoors = 0;
foreach (var net in BaseNetworkable.serverEntities)
{
var entity = net as BaseEntity;
if (entity == null) continue;
string shortName = entity.ShortPrefabName?.ToLower() ?? "";
if (shortName.Contains("door") || shortName.Contains("gate") || shortName.Contains("shutter") || shortName.Contains("hatch"))
{
if (entity.HasFlag(BaseEntity.Flags.Open))
{
entity.SetFlag(BaseEntity.Flags.Open, false);
entity.SendNetworkUpdateImmediate();
closedDoors++;
}
}
}
if (player != null) SendReply(player, string.Format(Msg("ClosedDoors", player.UserIDString), closedDoors));
else Puts(string.Format(Msg("ClosedDoors"), closedDoors));
}
[ChatCommand("bg.tc")]
[ConsoleCommand("bg.tc")]
private void CmdTopUpTC(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PermTC))
{
SendReply(player, Msg("NoPermission", player.UserIDString));
return;
}
TopUpAllTCs(player);
}
private void TopUpAllTCs(BasePlayer player = null)
{
int cupboardsTopped = 0;
foreach (var net in BaseNetworkable.serverEntities)
{
var tc = net as BuildingPrivlidge;
if (tc == null) continue;
float currentMinutes = tc.GetProtectedMinutes();
if (currentMinutes <= 0f) continue;
float currentHours = currentMinutes / 60f;
float targetHours = config.TCUpkeepHoursToTopup;
if (currentHours >= targetHours) continue;
float neededHours = targetHours - currentHours;
float neededSeconds = neededHours * 3600f;
var dailyCosts = new List<ItemAmount>();
tc.CalculateUpkeepCostAmounts(dailyCosts);
float fraction = neededHours / 24f;
foreach (var itemAmt in dailyCosts) itemAmt.amount *= fraction;
GiveCostsToTCInventory(tc, dailyCosts);
tc.PurchaseUpkeepTime(neededSeconds);
float newCoverage = tc.GetProtectedMinutes() / 60f;
if (newCoverage >= config.TCUpkeepHoursToTopup) cupboardsTopped++;
}
if (cupboardsTopped > 0)
{
if (player != null)
SendReply(player, string.Format(Msg("ToppedCupboards", player.UserIDString), cupboardsTopped));
else
Puts(string.Format(Msg("ToppedCupboards"), cupboardsTopped));
}
else
{
if (player != null)
SendReply(player, Msg("NoCupboardsFound", player.UserIDString));
else
Puts(Msg("NoCupboardsFound"));
}
}
private void GiveCostsToTCInventory(BuildingPrivlidge tc, List<ItemAmount> costs)
{
if (tc.inventory == null) return;
foreach (var itemAmt in costs)
{
if (itemAmt?.itemDef == null) continue;
int quantity = Mathf.CeilToInt(itemAmt.amount);
if (quantity <= 0) continue;
var item = ItemManager.CreateByItemID(itemAmt.itemDef.itemid, quantity);
if (item != null) tc.inventory.GiveItem(item);
}
}
private void StartNetworkMonitor()
{
if (netCheckTimer != null && !netCheckTimer.Destroyed) netCheckTimer.Destroy();
netCheckTimer = timer.Repeat(config.PingFrequencySeconds, 0, () => CheckNetwork());
}
private void CheckNetwork()
{
webrequest.EnqueueGet(
config.NetworkCheckHost,
(code, response) =>
{
if (code == 200)
{
consecutiveFailures = 0;
}
else
{
consecutiveFailures++;
if (consecutiveFailures >= config.MaxPingFailures)
{
Puts($"Network drop detected after {consecutiveFailures} consecutive failures. Host {config.NetworkCheckHost}");
netCheckTimer?.Destroy();
if (config.CloseDoorsOnNetDrop) CloseAllDoors();
if (config.TopUpTCOnNetDrop) TopUpAllTCs();
}
}
},
this,
null,
10f
);
}
}
}