using System; using System.IO; using System.Collections.Generic; using UnityEngine; using Newtonsoft.Json; namespace Carbon.Plugins { [Info ("RustGuardian", "WarmMilk", "1.1.1")] [Description ("Admin plugin to help reduce number of cheaters.")] public class RustGuardian : CarbonPlugin { private Dictionary lastFireTime = new Dictionary(); private RustGuardianConfig config; private Dictionary playerStats = new Dictionary(); private string statsFilePath = "playerStats.json"; // stored in server directory class PlayerStats { public string PlayerName { get; set; } public int Kills { get; set; } public int HeadshotKills { get; set; } public int Deaths { get; set; } public float TimePlayed { get; set; } public float KDR => Deaths > 0 ? (float)Kills / Deaths : Kills; } private void Loaded() { string weaponsConfigFileName = "RustGuardianWeaponsConfig.json"; config = LoadConfig(weaponsConfigFileName); Puts("RustGuardian: Weapons configuration loaded succesfully."); Puts("RustGuardian: Plugin loaded successfully."); // Save stats every hour 3600 timer.Every(3600, () => { SaveAllPlayerStats(); Puts("RustGuardian: Player stats saved successfully."); }); LoadAllPlayerStats(); // Load stats from file Puts("RustGuardian: Player stats loaded successfully."); } private void Unload() { SaveAllPlayerStats(); } private void SaveAllPlayerStats() { string json = JsonConvert.SerializeObject(playerStats); File.WriteAllText(statsFilePath, json); } private void SavePlayerStats(ulong playerId, string playerName, PlayerStats stats) { if (!playerStats.TryGetValue(playerId, out _)) { playerStats[playerId] = new PlayerStats(); } stats.PlayerName = playerName; string json = JsonConvert.SerializeObject(playerStats); File.WriteAllText(statsFilePath, json); } private PlayerStats LoadPlayerStats(ulong playerId) { if (!playerStats.TryGetValue(playerId, out PlayerStats stats)) { stats = new PlayerStats(); playerStats[playerId] = stats; } return stats; } private void LoadAllPlayerStats() { if (File.Exists(statsFilePath)) { try { string json = File.ReadAllText(statsFilePath); playerStats = JsonConvert.DeserializeObject>(json); if (playerStats == null) { playerStats = new Dictionary(); } } catch (Exception ex) { Puts($"RustGuardian: Error loading player stats: {ex.Message}"); playerStats = new Dictionary(); } } else { playerStats = new Dictionary(); } } void OnEntityDeath(BaseCombatEntity entity, HitInfo info) { var victim = entity.ToPlayer(); var attacker = info?.InitiatorPlayer; if (attacker != null && victim != null && attacker != victim) { bool isHeadshot = info.isHeadshot; UpdatePlayerStats(attacker.userID, attacker.displayName, isHeadshot); UpdateDeathStats(victim.userID, victim.displayName); } } void UpdatePlayerStats(ulong playerId, string playerName, bool isHeadshot) { var stats = LoadPlayerStats(playerId); // Log the player's name for debugging purposes Puts($"Updating stats for player ID: {playerId}, Name: {playerName ?? "NULL"}"); stats.PlayerName = playerName ?? "Unknown"; // If playerName is null, set as "Unknown" stats.Kills++; if (isHeadshot) stats.HeadshotKills++; SavePlayerStats(playerId, playerName, stats); } void UpdateDeathStats(ulong playerId, string playerName) { var stats = LoadPlayerStats(playerId); stats.Deaths++; SavePlayerStats(playerId, playerName, stats); } private T LoadConfig(string configFileName) where T : new() { string filePath = configFileName; if (File.Exists(filePath)) { string json = File.ReadAllText(filePath); return JsonConvert.DeserializeObject(json); } else { T newConfig = new T(); SaveConfig(newConfig, configFileName); return newConfig; } } private void SaveConfig(T config, string configFileName) { string json = JsonConvert.SerializeObject(config, Formatting.Indented); File.WriteAllText(configFileName, json); } void OnPlayerConnected(BasePlayer player) { // Start tracking playtime when player joins var stats = LoadPlayerStats(player.userID); stats.TimePlayed -= Time.realtimeSinceStartup; // Save player stats SavePlayerStats(player.userID, player.displayName, stats); } void OnPlayerDisconnected(BasePlayer player) { // Update playtime when player leaves var stats = LoadPlayerStats(player.userID); stats.TimePlayed += Time.realtimeSinceStartup; SavePlayerStats(player.userID, player.displayName, stats); } void OnWeaponFired(BaseProjectile weapon, BasePlayer player) { int weaponId = weapon.GetItem().info.itemid; if (!config.MonitoredWeapons.TryGetValue(weaponId, out WeaponConfig weaponConfig) || !weaponConfig.IsMonitored) return; // ignore weapons not monitored string playerWeaponKey = $"{player.userID}_{weaponId}"; float currentTime = Time.realtimeSinceStartup; if (lastFireTime.TryGetValue(playerWeaponKey, out float lastShotTime)) { float allowedInterval = weaponConfig.FireRate; if (currentTime - lastShotTime < allowedInterval) { string weaponName = weapon.GetItem().info.displayName.english; // Retrieve weapon's display name string message = ($"RustGuardian: Rapid fire detected by {player.displayName} using {weaponName}"); // Log to console Puts(message); // Send message to online admins foreach (BasePlayer admin in BasePlayer.activePlayerList) { if (admin.IsAdmin) { admin.SendConsoleCommand("chat.add", 0, message); } } } } lastFireTime[playerWeaponKey] = currentTime; } } public class RustGuardianConfig { public Dictionary MonitoredWeapons { get; set; } public RustGuardianConfig() { MonitoredWeapons = new Dictionary { { -765183617, new WeaponConfig("Double Barrel Shotgun", true, 0.5f) }, // double barrel shotgun, 120 RPM { -1367281941, new WeaponConfig("Waterpipe Shotgun", true, 4.62f) }, // waterpipe shotgun, 13 RPM { 795371088, new WeaponConfig("Pump Shotgun", true, 1.09f) }, // pump shotgun, 55 RPM { -417440462, new WeaponConfig("Spas-12 Shotgun", true, 0.25f) }, // spas-12 shotgun, 240 RPM { 1545779598, new WeaponConfig("Assault Rifle", false, 0.133f) }, // assault rifle, 450 RPM { 1588298435, new WeaponConfig("Bolt Action Rifle", false, 1.714f) }, // bolt action rifle, 35 RPM { 1965232394, new WeaponConfig("Crossbow", false, 3.529f) }, // crossbow, 17 RPM { 1796682209, new WeaponConfig("Custom SMG", false, 0.1f) }, // custom smg, 600 RPM { -75944661, new WeaponConfig("Eoka Pistol", false, 2.0f) }, // eoka pistol, 30 RPM { -1215753368, new WeaponConfig("Flame Thrower", false, 0.25f) }, // flame thrower, 240 RPM { -1214542497, new WeaponConfig("HMLMG", false, 0.125f) }, // hmlmg, 480 RPM { -218009552, new WeaponConfig("Homing Missle Launcher", false, 6.0f) }, // homing missle launcher, 10 RPM { 1443579727, new WeaponConfig("Hunting Bow", false, 1.0f) }, // hunting bow, 60 RPM {-778367295, new WeaponConfig("L96 Rifle", false, 2.609f) }, // l96 rifle, 23 RPM { -1812555177, new WeaponConfig("LR-300 Assault Rifle", false, 0.12f) }, // lr-300 assault rifle, 500 RPM { -2069578888, new WeaponConfig("M249", false, 0.12f) }, // m249, 500 RPM { 28201841, new WeaponConfig("M39 Rifle", false, 0.175f) }, // m39 rifle, 343 RPM { -852563019, new WeaponConfig("M92 Pistol", false, 0.15f) }, // m92 pistol, 400 RPM { 1318558775, new WeaponConfig("MP5A4", false, 0.1f) }, // mp5a4, 600 RPM { -1123473824, new WeaponConfig("Multiple Grenade Launcher", false, 0.4f) }, // multiple grenade launcher, 150 RPM { 1953903201, new WeaponConfig("Nailgun", false, 0.15f) }, // nailgun, 400 RPM { 1914691295, new WeaponConfig("Prototype 17", false, 0.113f) }, // prototype 17, 533 RPM { 1373971859, new WeaponConfig("Python Revolver", false, 0.15f) }, // python revolver, 400 RPM { 649912614, new WeaponConfig("Revolver", false, 0.175f) }, // revolver, 343 RPM { 442886268, new WeaponConfig("Rocket Launcher", false, 6.0f) }, // rocket launcher, 10 RPM { 818877484, new WeaponConfig("Semi-Automatic Pistol", false, 0.15f) }, // semi-automatic pistol, 400 RPM { -904863145, new WeaponConfig("Semi-Automatic Rifle", false, 0.175f) }, // semi-automatic rifle, 343 RPM { -1758372725, new WeaponConfig("Thompson", false, 0.13f) } // thompson, 462 RPM }; } } public class WeaponConfig { public string Name { get; set; } public bool IsMonitored { get; set; } public float FireRate { get; set; } public WeaponConfig(string name, bool isMonitored, float fireRate) { Name = name; IsMonitored = isMonitored; FireRate = fireRate; } } }