C#:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Repair Control", "Choanie", "1.0.0")]
[Description("Stops vanilla's permanent max-condition penalty on repair, with a per-item blacklist and optional permission gating.")]
public class RepairControl : RustPlugin
{
#region Fields
private Configuration _config;
private HashSet<string> _blacklist;
private const float ConditionEpsilon = 0.01f;
#endregion
#region Config
protected override void LoadDefaultConfig() => _config = Configuration.Default();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null) throw new Exception();
}
catch
{
PrintWarning("Config file is corrupt or missing, creating a new one.");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(_config);
private class Configuration
{
public Settings Settings = new();
public static Configuration Default() => new();
}
private class Settings
{
public bool Enabled = true;
public bool UsePermission = false;
public string Permission = "repaircontrol.use";
public List<string> ItemBlacklist = new();
}
#endregion
#region Lifecycle
private void Init()
{
permission.RegisterPermission(_config.Settings.Permission, this);
BuildBlacklist();
}
private void OnServerInitialized()
{
ValidateBlacklist();
ApplyEngineFlags();
if (_config.Settings.Enabled)
FixExistingItems();
}
private void Unload()
{
foreach (var definition in ItemManager.GetItemDefinitions())
{
if (definition?.condition == null) continue;
definition.condition.maintainMaxCondition = false;
}
}
private void OnPlayerConnected(BasePlayer player)
{
if (!_config.Settings.Enabled || !PlayerPermitted(player)) return;
FixPlayerInventory(player);
}
#endregion
#region Commands
[ChatCommand("repaircontrol")]
private void CmdRepairControl(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin)
{
player.ChatMessage("You don't have permission to use this command.");
return;
}
if (args.Length == 0)
{
player.ChatMessage(CommandUsage);
return;
}
switch (args[0].ToLowerInvariant())
{
case "list":
player.ChatMessage(BlacklistStatus());
break;
case "blacklist":
player.ChatMessage(args.Length < 2 ? "Usage: /repaircontrol blacklist <shortname>" : AddToBlacklist(args[1]));
break;
case "unblacklist":
player.ChatMessage(args.Length < 2 ? "Usage: /repaircontrol unblacklist <shortname>" : RemoveFromBlacklist(args[1]));
break;
default:
player.ChatMessage(CommandUsage);
break;
}
}
private const string CommandUsage = "Usage: /repaircontrol <list|blacklist <shortname>|unblacklist <shortname>>";
private string BlacklistStatus() =>
$"Blacklisted (vanilla penalty applies): {(_blacklist.Count == 0 ? "(none)" : string.Join(", ", _blacklist))}";
private string AddToBlacklist(string shortname)
{
shortname = shortname.Trim().ToLowerInvariant();
if (string.IsNullOrEmpty(shortname)) return "Shortname cannot be empty.";
if (ItemManager.FindItemDefinition(shortname) == null) return $"'{shortname}' does not match any known item shortname.";
if (_config.Settings.ItemBlacklist.Any(s => string.Equals(s, shortname, StringComparison.OrdinalIgnoreCase)))
return $"'{shortname}' is already blacklisted.";
_config.Settings.ItemBlacklist.Add(shortname);
ApplyBlacklistChange();
return $"'{shortname}' is now blacklisted — it will lose max condition on repair like vanilla.";
}
private string RemoveFromBlacklist(string shortname)
{
shortname = shortname.Trim().ToLowerInvariant();
if (_config.Settings.ItemBlacklist.RemoveAll(s => string.Equals(s, shortname, StringComparison.OrdinalIgnoreCase)) == 0)
return $"'{shortname}' was not blacklisted.";
ApplyBlacklistChange();
return $"'{shortname}' removed from the blacklist — it's protected again.";
}
private void ApplyBlacklistChange()
{
SaveConfig();
BuildBlacklist();
ApplyEngineFlags();
if (_config.Settings.Enabled)
FixExistingItems();
}
#endregion
#region Item filter
private void BuildBlacklist()
{
_blacklist = new HashSet<string>(
_config.Settings.ItemBlacklist
.Where(s => !string.IsNullOrWhiteSpace(s))
.Select(s => s.Trim().ToLowerInvariant()));
}
private void ValidateBlacklist()
{
foreach (var shortname in _config.Settings.ItemBlacklist)
{
if (string.IsNullOrWhiteSpace(shortname)) continue;
if (ItemManager.FindItemDefinition(shortname.Trim()) == null)
PrintWarning($"ItemBlacklist entry '{shortname}' does not match any known item shortname.");
}
}
private bool IsProtected(ItemDefinition definition)
{
if (definition?.shortname == null) return false;
return !_blacklist.Contains(definition.shortname.ToLowerInvariant());
}
#endregion
#region Engine flags
private void ApplyEngineFlags()
{
foreach (var definition in ItemManager.GetItemDefinitions())
{
if (definition?.condition == null) continue;
definition.condition.maintainMaxCondition =
_config.Settings.Enabled && !_config.Settings.UsePermission && IsProtected(definition);
}
}
#endregion
#region Repair hook
private void OnItemRepair(BasePlayer player, Item item)
{
if (!_config.Settings.Enabled || item?.info == null) return;
if (!IsProtected(item.info)) return;
if (_config.Settings.UsePermission &&
(player == null || !permission.UserHasPermission(player.UserIDString, _config.Settings.Permission)))
return;
NextTick(() => RestoreMaxCondition(item));
}
private void RestoreMaxCondition(Item item)
{
if (item?.info?.condition == null) return;
if (item.conditionNormalized < 1f) return;
if (item.maxCondition >= item.info.condition.max - ConditionEpsilon) return;
item.maxCondition = item.info.condition.max;
item.condition = item.maxCondition;
item.GetHeldEntity()?.SetFlag(BaseEntity.Flags.Broken, false);
item.MarkDirty();
}
#endregion
#region Retroactive fix
private void FixExistingItems()
{
var itemsFixed = 0;
var playersScanned = 0;
foreach (var player in BasePlayer.activePlayerList)
{
if (!PlayerPermitted(player)) continue;
itemsFixed += FixPlayerInventory(player);
playersScanned++;
}
foreach (var player in BasePlayer.sleepingPlayerList)
{
if (!PlayerPermitted(player)) continue;
itemsFixed += FixPlayerInventory(player);
playersScanned++;
}
var containersScanned = 0;
if (!_config.Settings.UsePermission)
{
foreach (var storage in UnityEngine.Object.FindObjectsOfType<StorageContainer>())
{
try
{
itemsFixed += FixContainer(storage.inventory);
containersScanned++;
}
catch (Exception ex)
{
PrintWarning($"Failed to scan storage container {storage?.PrefabName}: {ex.Message}");
}
}
foreach (var dropped in UnityEngine.Object.FindObjectsOfType<DroppedItemContainer>())
{
try
{
itemsFixed += FixContainer(dropped.inventory);
containersScanned++;
}
catch (Exception ex)
{
PrintWarning($"Failed to scan dropped item container: {ex.Message}");
}
}
}
if (itemsFixed > 0)
Puts($"Restored max condition on {itemsFixed} existing item(s) across {playersScanned} player(s) and {containersScanned} world container(s).");
}
private bool PlayerPermitted(BasePlayer player)
{
if (player?.inventory == null) return false;
return !_config.Settings.UsePermission || permission.UserHasPermission(player.UserIDString, _config.Settings.Permission);
}
private int FixPlayerInventory(BasePlayer player)
{
var fixedCount = FixContainer(player.inventory.containerMain);
fixedCount += FixContainer(player.inventory.containerBelt);
fixedCount += FixContainer(player.inventory.containerWear);
return fixedCount;
}
private int FixContainer(ItemContainer container)
{
if (container?.itemList == null) return 0;
var fixedCount = 0;
foreach (var item in container.itemList)
fixedCount += FixItemRecursive(item);
return fixedCount;
}
private int FixItemRecursive(Item item)
{
if (item?.info?.condition == null) return 0;
var fixedCount = 0;
var pristineMax = item.info.condition.max;
if (IsProtected(item.info) && item.maxCondition < pristineMax - ConditionEpsilon)
{
var oldMax = item.maxCondition;
if (oldMax > 0f)
item.condition = Math.Min(item.condition * (pristineMax / oldMax), pristineMax);
item.maxCondition = pristineMax;
item.MarkDirty();
fixedCount++;
}
if (item.contents?.itemList != null)
foreach (var sub in item.contents.itemList)
fixedCount += FixItemRecursive(sub);
return fixedCount;
}
#endregion
}
}