using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Oxide.Core; using Oxide.Core.Libraries.Covalence; namespace Oxide.Plugins { [Info("C4Watch", "Goose22", "1.1.2")] [Description("Monitors excessive C4 Placement")] internal class C4Watch : CovalencePlugin { #region Configuration private Configuration config; public class Configuration { [JsonProperty("C4 Limit (0 to disable)")] public int maxC4 = 0; [JsonProperty("Limit RF C4")] public bool applytoRF = false; [JsonProperty("Limit Regular C4")] public bool applytoC4 = false; public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonConvert.DeserializeObject>(ToJson()); } protected override void LoadDefaultConfig() => config = new Configuration(); protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { throw new JsonException(); } if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys)) { LogWarning("Configuration appears to be outdated; updating and saving"); SaveConfig(); } } catch { LogWarning($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { LogWarning($"Configuration changes saved to {Name}.json"); Config.WriteObject(config, true); } #endregion Configuration #region initialization private void Init() { c4prefabId = StringPool.Get(c4prefab); permission.RegisterPermission(bypassPermission, this); } #endregion initialization #region Lang //Method inspired from Friends by MrBlue private void RespondToUser(BasePlayer player, string langMessage, params object[] args) { if (!player.IsConnected) return; string response = GetLang(langMessage, player.UserIDString, args); if (response == langMessage) response = GetLang(langMessage, null, args); player.SendConsoleCommand("gametip.showtoast", GameTip.Styles.Blue_Normal, response); } private string GetLang(string langKey, string playerId = null, params object[] args) { return string.Format(lang.GetMessage(langKey, this, playerId), args); } //Registers the lang for this file protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { ["MaxReached"] = "YOU HAVE REACHED YOUR LIMIT OF {0} REMOTE C4!" }, this); } #endregion #region Stored Data //C4 Prefab which will be used as a reference private readonly string c4prefab = "assets/prefabs/tools/c4/explosive.timed.deployed.prefab"; private readonly string c4Shortname = "explosive.timed"; private readonly string bypassPermission = "c4watch.bypass"; private readonly string _Message = "YOU HAVE REACHED YOUR LIMIT OF {0} REMOTE C4!"; //A variable which will ultimately hold the uint of the C4 prefab for use private uint c4prefabId; //A collection of player ids mapped to individual's "throw" count, or active C4 entities // private Dictionary playerData = new Dictionary(); //A temporary collection of currently active C4 entities and their owners // public Dictionary entityDatabase = new Dictionary(); #endregion Stored Data #region helpers //Checks to see if a player's ID is present in the playerData Dictionary void playerCheck(string id) { if (!playerData.ContainsKey(id)) { playerData.Add(id, 0); } } //checks if a player has the bypass permission bool hasPerm(IPlayer player) { return player.HasPermission(bypassPermission); } //A method that updates the entitydatabase with respect to the active C4 entities on the server void updateData(ulong entity, string id, bool add) { if (add) { entityDatabase.Add(entity, id); } else { entityDatabase.Remove(entity); } } //Replaces C4 when a player throws one after being over the limit void sendNewC4(BasePlayer player, RFTimedExplosive entity, bool rf) { if (rf) { Item explo = ItemManager.CreateByName(c4Shortname); explo.SetFlag(Item.Flag.IsOn, true); explo.instanceData.dataInt = entity.GetFrequency(); explo.MarkDirty(); player.GiveItem(explo); } else { player.GiveItem(ItemManager.CreateByName(c4Shortname)); } } //Decides if the player is under or past the set C4 limit void limit(BasePlayer player, RFTimedExplosive entity, bool rf) { playerCheck(player.UserIDString); if (playerData[player.UserIDString] >= config.maxC4) { entity.Kill(); RespondToUser(player, "MaxReached", config.maxC4); sendNewC4(player, entity, rf); } else { updateData(entity.net.ID.Value, player.UserIDString, true); playerData[player.UserIDString]++; } } #endregion helpers #region hooks //When an explosive is thrown, depending on the thrown entity type, maxC4 in config, and the player's permissions //the thrown entity may be allowed to live and added to the entitydatabase, or it will be killed. The player's //active entity count will be increased void OnExplosiveThrown(BasePlayer player, RFTimedExplosive entity, ThrownWeapon item) { if (player == null || entity == null || entity.prefabID != c4prefabId || config.maxC4 <= 0 || hasPerm(player.IPlayer)) return; bool isRf = entity.GetFrequency() > 0; if ((config.applytoRF && isRf) || (config.applytoC4 && !isRf)) { limit(player, entity, isRf); } } //Once an entity dies, if it is a C4 then the entity id will be traced to it's owner //and their "active entity" count will be reduced void OnEntityKill(TimedExplosive entity) { if (entity == null || entity.prefabID != c4prefabId || entity.net == null) return; ulong entityName = entity.net.ID.Value; if (!entityDatabase.ContainsKey(entityName)) return; string id = entityDatabase[entityName]; updateData(entityName, id, false); playerData[id]--; } //when a player disconnects, their id will be removed from the collection. //no data is stored beyond a players playtime void OnUserDisconnected(IPlayer player) { if (playerData.ContainsKey(player.Id)) { playerData.Remove(player.Id); } } // When a player connects, they are added to the playerData dictionary // this ensures they are consistently and properly tracked void OnUserConnected(IPlayer player) { // Bug fix on 06/09/2024 // Need to keep track of all players upon joining playerCheck(player.Id); } // GC assistance void Unload() { playerData = null; entityDatabase = null; } #endregion hooks } }