C#:
using System;
using System.Collections.Generic;
using HarmonyLib;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("SwitchGuard", "romzar.games", "1.3.0")]
[Description("Prevents unauthorized players from toggling Industrial Conveyors and Fluid Switches")]
public class SwitchGuard : RustPlugin
{
#region Constants
private const string PERM_BYPASS = "switchguard.bypass";
private const string PERM_ADMIN = "switchguard.admin";
#endregion
#region Plugin References
[PluginReference] private Plugin Friends, Clans;
#endregion
#region Static Instance
private static SwitchGuard _instance;
#endregion
#region Configuration
private PluginConfig _config;
private class PluginConfig
{
[JsonProperty("Admin can always toggle (server.IsAdmin bypass)")]
public bool AdminCanToggle { get; set; } = false;
[JsonProperty("Use Friends API")]
public bool UseFriendsAPI { get; set; } = true;
[JsonProperty("Use Clans API")]
public bool UseClansAPI { get; set; } = true;
[JsonProperty("Use Rust Teams")]
public bool UseTeams { get; set; } = true;
[JsonProperty("Use TC Authorization")]
public bool UseTCAuth { get; set; } = true;
[JsonProperty("Protect Industrial Conveyors")]
public bool ProtectConveyors { get; set; } = true;
[JsonProperty("Protect Fluid Switches")]
public bool ProtectFluidSwitches { get; set; } = true;
[JsonProperty("Message cooldown (seconds)")]
public float MessageCooldown { get; set; } = 3.0f;
}
protected override void LoadDefaultConfig()
{
_config = new PluginConfig();
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<PluginConfig>() ?? new PluginConfig();
}
catch (Exception ex)
{
PrintError($"Failed to load config: {ex.Message}");
_config = new PluginConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(_config);
#endregion
#region State
private readonly Dictionary<ulong, float> _messageCooldowns = new Dictionary<ulong, float>();
#endregion
#region Localization
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["NotAuthorized"] = "You are not authorized to toggle this device.",
["NoPermission"] = "You do not have permission to use this."
}, this, "en");
lang.RegisterMessages(new Dictionary<string, string>
{
["NotAuthorized"] = "Voce nao tem autorizacao para ligar/desligar este dispositivo.",
["NoPermission"] = "Voce nao tem permissao para usar isso."
}, this, "pt-BR");
lang.RegisterMessages(new Dictionary<string, string>
{
["NotAuthorized"] = "No tienes autorizacion para activar/desactivar este dispositivo.",
["NoPermission"] = "No tienes permiso para usar esto."
}, this, "es-ES");
}
private string Lang(string key, BasePlayer player = null)
{
return lang.GetMessage(key, this, player?.UserIDString);
}
#endregion
#region Lifecycle
private void Init()
{
_instance = this;
permission.RegisterPermission(PERM_BYPASS, this);
permission.RegisterPermission(PERM_ADMIN, this);
}
private void Unload()
{
_instance = null;
_messageCooldowns.Clear();
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
_messageCooldowns.Remove((ulong)player.userID);
}
#endregion
#region Harmony Patches
[AutoPatch]
[HarmonyPatch(typeof(IndustrialConveyor), "SvSwitch")]
public static class Patch_ConveyorSvSwitch
{
public static bool Prefix(IndustrialConveyor __instance, BaseEntity.RPCMessage msg)
{
if (_instance == null || !_instance._config.ProtectConveyors) return true;
if (__instance == null || __instance.IsDestroyed) return true;
var player = msg.player;
if (player == null) return true;
return _instance.CanToggleEntity(player, __instance);
}
}
[AutoPatch]
[HarmonyPatch(typeof(ElectricSwitch), "RPC_Switch")]
public static class Patch_FluidSwitchRpcSwitch
{
public static bool Prefix(ElectricSwitch __instance, BaseEntity.RPCMessage msg)
{
if (_instance == null || !_instance._config.ProtectFluidSwitches) return true;
if (!(__instance is FluidSwitch)) return true;
if (__instance.IsDestroyed) return true;
var player = msg.player;
if (player == null) return true;
return _instance.CanToggleEntity(player, __instance);
}
}
#endregion
#region Authorization Logic
private bool CanToggleEntity(BasePlayer player, BaseEntity entity)
{
if (player == null || entity == null || entity.IsDestroyed) return true;
ulong playerId = (ulong)player.userID;
string playerIdString = player.UserIDString;
if (permission.UserHasPermission(playerIdString, PERM_BYPASS))
return true;
if (permission.UserHasPermission(playerIdString, PERM_ADMIN))
return true;
if (_config.AdminCanToggle && player.IsAdmin)
return true;
ulong ownerId = entity.OwnerID;
if (ownerId == 0UL) return true;
if (playerId == ownerId) return true;
if (_config.UseTCAuth && IsAuthedOnTC(player, entity))
return true;
if (_config.UseClansAPI && IsClanMemberOrAlly(ownerId, playerId))
return true;
if (_config.UseFriendsAPI && IsFriend(ownerId, playerId))
return true;
if (_config.UseTeams && IsTeammate(ownerId, playerId))
return true;
SendCooldownMessage(player, "NotAuthorized");
return false;
}
#endregion
#region Friend/Clan/Team Checks
private bool IsAuthedOnTC(BasePlayer player, BaseEntity entity)
{
if (player == null || entity == null) return false;
var priv = player.GetBuildingPrivilege(entity.WorldSpaceBounds());
if (priv == null) return false;
return priv.IsAuthed(player);
}
private bool IsClanMemberOrAlly(ulong ownerId, ulong playerId)
{
if (Clans == null) return false;
try
{
var result = Clans.Call("IsMemberOrAlly", ownerId.ToString(), playerId.ToString());
if (result is bool b) return b;
}
catch (Exception ex)
{
PrintWarning($"Clans API error: {ex.Message}");
}
return false;
}
private bool IsFriend(ulong ownerId, ulong playerId)
{
if (Friends == null) return false;
try
{
var result = Friends.Call("AreFriends", ownerId, playerId);
if (result is bool b) return b;
}
catch (Exception ex)
{
PrintWarning($"Friends API error: {ex.Message}");
}
return false;
}
private bool IsTeammate(ulong ownerId, ulong playerId)
{
var rm = RelationshipManager.ServerInstance;
if (rm == null) return false;
if (!rm.playerToTeam.TryGetValue(playerId, out var team))
return false;
return team.members.Contains(ownerId);
}
#endregion
#region Helpers
private void SendCooldownMessage(BasePlayer player, string langKey)
{
if (player == null || !player.IsConnected) return;
ulong playerId = (ulong)player.userID;
float now = Time.realtimeSinceStartup;
if (_messageCooldowns.TryGetValue(playerId, out float lastTime))
{
if (now - lastTime < _config.MessageCooldown)
return;
}
_messageCooldowns[playerId] = now;
SendReply(player, Lang(langKey, player));
}
#endregion
}
}