using System; using Newtonsoft.Json; using Oxide.Core; using System.Collections.Generic; using System.Linq; //using Oxide.Game.Rust.Cui; using UnityEngine; using Random = Oxide.Core.Random; // For changelog /* * Added support to initialize player data when they are added to a group that contains TDK permissions * Added support for server owners to change the velocity of each rocket type the player can fire */ namespace Oxide.Plugins { [Info("The Door Knocker", "Cannabis", "1.4.3")] [Description("Allows players to shoot more than 1 rocket at a time")] internal class TheDoorKnocker : RustPlugin { #region Declarations //private readonly string limitperm = "thedoorknocker.amount."; private readonly string useperm = "thedoorknocker.use"; SavedData data; //SavedData dataCache; #endregion #region Oxide Hooks void Init() { foreach (var kvp in config.maxRocketPermission) { permission.RegisterPermission(kvp.Key, this); } permission.RegisterPermission(useperm, this); lang.RegisterMessages(_messages, this); cmd.AddChatCommand(config.rocketamtcmd, this, nameof(CmdHandleRockets)); //cmd.AddChatCommand(config.rocketCuiCmd, this, nameof(CUI_CmdOpenSettings)); data = SavedData.Load(); //dataCache = data; if (config.cleanOldData) { timer.Once(5f, () => { foreach (var userID in data.playerData.Keys.ToList()) { DateTime lastDisconnectTime = data.playerData[userID].lastSeen; TimeSpan timeDifference = DateTime.UtcNow - lastDisconnectTime; double daysSinceLastSeen = timeDifference.TotalDays; if (!(daysSinceLastSeen >= config.daysSinceLastSeenToClean)) continue; data.playerData.Remove(userID); data.Save(); } }); } } void OnPlayerConnected(BasePlayer player) { if (!permission.UserHasPermission(player.UserIDString, useperm) || data.playerData.ContainsKey(player.userID)) return; data.playerData.Add(player.userID, new TheDoorKnockerData() { lastSeen = DateTime.Now, playerPreferences = new TheDoorKnockerPlayerPreferences() { basicRocket = true, fireRocket = false, hvRocket = true, smokeRocket = false }, playerRockets = config.defaultPlayerData.amount, rocketsEnabled = config.defaultPlayerData.enabled }); data.Save(); } void OnUserPermissionGranted(string id, string permName) { bool a = config.maxRocketPermission.ContainsKey(permName); ulong userID; if ((permName != useperm && !a) || !ulong.TryParse(id, out userID)) return; if (data.playerData.ContainsKey(userID)) return; data.playerData.Add(userID, new TheDoorKnockerData() { lastSeen = DateTime.Now, playerRockets = config.defaultPlayerData.amount, rocketsEnabled = config.defaultPlayerData.enabled, playerPreferences = new TheDoorKnockerPlayerPreferences() { basicRocket = true, fireRocket = false, hvRocket = true, smokeRocket = false } }); data.Save(); } void OnNewSave(string filename) { if (!config.wipeOnWipe) return; data = new SavedData(); data.Save(); } void OnPlayerDisconnected(BasePlayer player, string reason) { if (!data.playerData.ContainsKey(player.userID)) return; data.playerData[player.userID].lastSeen = DateTime.Now; data.Save(); } void OnUserPermissionRevoked(string id, string permName) { BasePlayer player = BasePlayer.FindByID(ulong.Parse(id)); if (player == null) return; if (permName == useperm) { if (!data.playerData.ContainsKey(player.userID)) return; data.playerData.Remove(player.userID); data.Save(); }; foreach (var kvp in config.maxRocketPermission) { if (permName != kvp.Key || !permission.UserHasPermission(id, useperm)) continue; data.playerData[player.userID].playerRockets = config.defaultPlayerData.amount; data.Save(); } } void OnUserGroupAdded(string playerId, string groupName) { string[] permissions = permission.GetGroupPermissions(groupName); BasePlayer player = BasePlayer.FindByID(ulong.Parse(playerId)); if (player == null) return; if (permissions.Contains(useperm) && !data.playerData.ContainsKey(player.userID)) { data.playerData.Add(player.userID, new TheDoorKnockerData() { lastSeen = DateTime.Now, playerPreferences = new TheDoorKnockerPlayerPreferences() { basicRocket = true, fireRocket = false, hvRocket = true, smokeRocket = false }, playerRockets = config.defaultPlayerData.amount, rocketsEnabled = config.defaultPlayerData.enabled }); data.Save(); } } void OnRocketLaunched(BasePlayer player, BaseEntity entity) { if (!permission.UserHasPermission(player.UserIDString, useperm) || data.playerData[player.userID].rocketsEnabled == false) return; int rockettype; BaseProjectile.Magazine rocketMagazine = player.GetHeldEntity()?.GetComponent()?.primaryMagazine; Item heldItem = player.GetActiveItem(); int rocketsSent = data.playerData[player.userID].playerRockets; if (rocketsSent > GetMaxRockets(player)) rocketsSent = GetMaxRockets(player); switch (rocketMagazine?.ammoType.shortname) { case "ammo.rocket.basic": if (config.blockTypes.Contains("basic") || data.playerData[player.userID].playerPreferences.basicRocket == false) return; rockettype = 1; break; case "ammo.rocket.hv": if (config.blockTypes.Contains("hv") || data.playerData[player.userID].playerPreferences.hvRocket == false) return; rockettype = 2; break; case "ammo.rocket.fire": if (config.blockTypes.Contains("fire") || data.playerData[player.userID].playerPreferences.fireRocket == false) return; rockettype = 3; break; case "ammo.rocket.smoke": if (config.blockTypes.Contains("smoke") || data.playerData[player.userID].playerPreferences.smokeRocket == false) return; rockettype = 4; break; default: if (data.playerData[player.userID].playerPreferences.basicRocket == false) return; rockettype = 1; break; } if (rocketMagazine != null && heldItem.info.shortname == "rocket.launcher" && rocketsSent > 0) { SpawnRockets(player, rocketsSent, rockettype); } } void Unload() { data.Save(); } #endregion #region Commands private void CmdHandleRockets(BasePlayer player, string command, string[] args) { // Checks if the player is null (isn't there) or if the player doesn't have permission if (player == null || !permission.UserHasPermission(player.UserIDString, useperm)) { // If they don't have permission they will be alerted and nothing further will happen. PrintToChat(player, string.Format(lang.GetMessage("NoPermission", this, player.UserIDString))); return; } // If there are no args or if there's too many arguments let the player know if (args.Length == 0 || args.Length > 2) { PrintToChat(player, lang.GetMessage("NoArgs", this, player.UserIDString)); return; } // Get the max rockets available to the player int maxRockets = GetMaxRockets(player); // Check if they do not have a permission to allow extra rockets and alert the player if (maxRockets == 0) { PrintToChat(player, lang.GetMessage("InvalidRockets", this, player.UserIDString)); return; } // Try to parse the first argument as an int and continue through with changing rocket amount int amount; if (int.TryParse(args[0], out amount)) { // Check if the player is trying to set rocket amount higher than they have permission for if (amount > maxRockets) { PrintToChat(player, string.Format(lang.GetMessage("TooMany", this, player.UserIDString), maxRockets)); return; } if (amount == 0) { PrintToChat(player, lang.GetMessage("NoZero", this, player.UserIDString)); return; } // Update player rockets in data for persistent storage. data.UpdateAmount(player.userID, amount); PrintToChat(player, string.Format(lang.GetMessage("ChangeRockets", this, player.UserIDString), amount)); return; } // Try to parse the first argument as a boolean and continue through with enabling/disabling extra rockets being sent bool enabled; if (bool.TryParse(args[0], out enabled)) { // Update whether or not the player has TDK enabled data.UpdateEnabled(player.userID, enabled); // Catch whether they turned on or off TDK and let them know of the change switch (enabled) { case true: PrintToChat(player, lang.GetMessage("TurnedOn", this, player.UserIDString)); return; case false: PrintToChat(player, lang.GetMessage("TurnedOff", this, player.UserIDString)); return; } } if (args[0].ToLower() == "toggle") { switch (args[1].ToLower()) { case "basic": if (config.blockTypes.Contains(args[1])) { PrintToChat(player, string.Format(lang.GetMessage("TypeDisabledTryToggle", this, player.UserIDString), "basic")); break; } data.playerData[player.userID].playerPreferences.basicRocket = !data.playerData[player.userID].playerPreferences.basicRocket; switch (data.playerData[player.userID].playerPreferences.basicRocket) { case true: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "enabled", "basic")); break; default: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "disabled", "basic")); break; } data.Save(); break; case "hv": if (config.blockTypes.Contains(args[1])) { PrintToChat(player, string.Format(lang.GetMessage("TypeDisabledTryToggle", this, player.UserIDString), "hv")); break; } data.playerData[player.userID].playerPreferences.hvRocket = !data.playerData[player.userID].playerPreferences.hvRocket; switch (data.playerData[player.userID].playerPreferences.hvRocket) { case true: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "enabled", "hv")); break; default: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "disabled", "hv")); break; } data.Save(); break; case "fire": if (config.blockTypes.Contains(args[1])) { PrintToChat(player, string.Format(lang.GetMessage("TypeDisabledTryToggle", this, player.UserIDString), "incendiary")); break; } data.playerData[player.userID].playerPreferences.fireRocket = !data.playerData[player.userID].playerPreferences.fireRocket; switch (data.playerData[player.userID].playerPreferences.fireRocket) { case true: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "enabled", "incendiary")); break; default: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "disabled", "incendiary")); break; } data.Save(); break; case "smoke": if (config.blockTypes.Contains(args[1])) { PrintToChat(player, string.Format(lang.GetMessage("TypeDisabledTryToggle", this, player.UserIDString), "smoke")); break; } data.playerData[player.userID].playerPreferences.smokeRocket = !data.playerData[player.userID].playerPreferences.smokeRocket; switch (data.playerData[player.userID].playerPreferences.smokeRocket) { case true: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "enabled", "smoke")); break; default: PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), "disabled", "smoke")); break; } data.Save(); break; default: PrintToChat(player, lang.GetMessage("InvalidTypeToggle", this, player.UserIDString)); break; } } } #endregion #region CUI //TODO Get a CUI design for a nice and easy way for players to configure their own settings and move away from chat commands. /*public void CUI_CmdOpenSettings(BasePlayer player) { } [ConsoleCommand("thedoorknocker.cui.toggle")] public void CUI_CmdButtonToggle(ConsoleSystem.Arg args, string command, string[] arg) { BasePlayer player = args.Player(); if (player == null) return; CUI_CmdOpenSettings(player); } [ConsoleCommand("thedoorknocker.cui.closesettings")] public void CUI_CmdSettingsClose(BasePlayer player) { CuiHelper.DestroyUi(player, "placeholder"); }*/ #endregion #region Helpers private int GetMaxRockets(BasePlayer player) { int max = config.defaultPlayerData.amount; foreach (var kvp in config.maxRocketPermission) { if (permission.UserHasPermission(player.UserIDString, kvp.Key)) { max = Mathf.Max(max, kvp.Value); } } return max > 0 ? max : 0; } /* assets/prefabs/ammo/rocket/ammo_rocket_basic.item.prefab assets/prefabs/ammo/rocket/ammo_rocket_fire.item.prefab assets/prefabs/ammo/rocket/ammo_rocket_hv.item.prefab assets/prefabs/ammo/rocket/ammo_rocket_smoke.item.prefab */ private void SpawnRockets(BasePlayer player, int amount, int type) { Item item = player.GetActiveItem(); string rocketprefab; int rocketid; int velocity; switch (type) { case 1: rocketprefab = "assets/prefabs/ammo/rocket/rocket_basic.prefab"; rocketid = -742865266; velocity = config.rocketSpeeds.basicVelocity; break; case 2: rocketprefab = "assets/prefabs/ammo/rocket/rocket_hv.prefab"; rocketid = -1841918730; velocity = config.rocketSpeeds.hvVelocity; break; case 3: rocketprefab = "assets/prefabs/ammo/rocket/rocket_fire.prefab"; rocketid = 1638322904; velocity = config.rocketSpeeds.fireVelocity; break; case 4: rocketprefab = "assets/prefabs/ammo/rocket/rocket_smoke.prefab"; rocketid = -17123659; velocity = config.rocketSpeeds.smokeVelocity; break; default: rocketprefab = "assets/prefabs/ammo/rocket/rocket_basic.prefab"; rocketid = -742865266; velocity = config.rocketSpeeds.basicVelocity; break; } for (int i = 1; i <= amount; i++) { int rocketsInInventory = player.inventory.GetAmount(rocketid); if (rocketsInInventory < 1 || item.info.shortname != "rocket.launcher" || item.condition <= 0) return; if (config.takeDurability) { item.LoseCondition(config.durabilityAmount); } Vector3 aimDirection = Quaternion.Euler(player.serverInput.current.aimAngles) * Vector3.forward; aimDirection = Quaternion.Euler(Random.Range(-config.aimCone, config.aimCone), Random.Range(-config.aimCone, config.aimCone), 0) * aimDirection; TimedExplosive rocket = GameManager.server.CreateEntity(rocketprefab, player.eyes.position + player.eyes.HeadForward(), player.transform.rotation) as TimedExplosive; if (rocket != null) rocket.creatorEntity = player; ServerProjectile rocketProjectile = rocket?.GetComponent(); rocketProjectile?.InitializeVelocity(aimDirection * velocity); rocket?.Spawn(); player.inventory.Take(null, rocketid, 1); } } #endregion #region Configuration private static ConfigData config; private class ConfigData { [JsonProperty(PropertyName = "Permissions and allowed max rockets per permission")] public Dictionary maxRocketPermission; [JsonProperty(PropertyName = "Chat based command (/{command} {bool | # | toggle [basic, hv, fire, smoke]}")] public string rocketamtcmd; /*[JsonProperty(PropertyName = "Command to bring up CUI")] public string rocketCuiCmd;*/ [JsonProperty(PropertyName = "Rocket spread (Larger number means more spread lower number means less spread)")] public float aimCone; [JsonProperty(PropertyName = "Rocket types to block?")] public List blockTypes; [JsonProperty(PropertyName = "Default player data")] public TheDoorKnockerDefaultPlayerData defaultPlayerData; [JsonProperty(PropertyName = "Clean up old player data?")] public bool cleanOldData; [JsonProperty(PropertyName = "How many days since last connection should data be erased?")] public double daysSinceLastSeenToClean; [JsonProperty(PropertyName = "Should TDK take durability per extra rocket?")] public bool takeDurability; [JsonProperty(PropertyName = "Amount of durability taken per 1 rocket")] public int durabilityAmount; /* [JsonProperty(PropertyName = "Enable toggle CUI button on screen?")] public bool showToggle; [JsonProperty(PropertyName = "Toggle button image URL")] public string toggleButtonImageURL;*/ [JsonProperty(PropertyName = "Wipe data every map wipe?")] public bool wipeOnWipe; [JsonProperty(PropertyName = "Custom rocket speeds (For additional rockets fired)")] public TheDoorKnockerRocketSpeeds rocketSpeeds; } private ConfigData GetDefaultConfig() { return new ConfigData { maxRocketPermission = new Dictionary() { {"thedoorknocker.basic", 2}, {"thedoorknocker.vip", 3}, {"thedoorknocker.example", 69} }, rocketamtcmd = "tdk", //rocketCuiCmd = "tdk.cui", blockTypes = new List() { "smoke", "fire" }, aimCone = 4.0f, cleanOldData = true, daysSinceLastSeenToClean = 7, takeDurability = false, durabilityAmount = 1, //showToggle = true, //toggleButtonImageURL = "https://cdn.rustbyte.dev/u/vh0v1j.webp", wipeOnWipe = true, defaultPlayerData = new TheDoorKnockerDefaultPlayerData() { amount = 1, enabled = true }, rocketSpeeds = new TheDoorKnockerRocketSpeeds() { basicVelocity = 22, fireVelocity = 22, hvVelocity = 44, smokeVelocity = 22 } }; } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { LoadDefaultConfig(); } } catch { PrintError("Configuration file is corrupt! Unloading plugin..."); Interface.Oxide.RootPluginManager.RemovePlugin(this); return; } SaveConfig(); } protected override void LoadDefaultConfig() { config = GetDefaultConfig(); } protected override void SaveConfig() { Config.WriteObject(config); } public class TheDoorKnockerDefaultPlayerData { [JsonProperty(PropertyName = "Enable TDK by default?")] public bool enabled; [JsonProperty(PropertyName = "Amount of rockets sent by default")] public int amount; } public class TheDoorKnockerRocketSpeeds { [JsonProperty(PropertyName = "Basic rocket speed")] public int basicVelocity; [JsonProperty(PropertyName = "HV rocket speed")] public int hvVelocity; [JsonProperty(PropertyName = "Smoke rocket speed")] public int smokeVelocity; [JsonProperty(PropertyName = "Fire rocket speed")] public int fireVelocity; } #endregion #region Data Handling class SavedData { [JsonProperty("Player data")] public Dictionary playerData = new Dictionary(); //instance.Name public static SavedData Load() => Interface.Oxide.DataFileSystem.ReadObject("The Door Knocker\\TDK_playerData") ?? new SavedData(); public void Save() => Interface.Oxide.DataFileSystem.WriteObject("The Door Knocker\\TDK_playerData", this); public void UpdateAmount(ulong userId, int rocketAmount) { if (!playerData.ContainsKey(userId)) return; playerData[userId].playerRockets = rocketAmount; Save(); } public void UpdateEnabled(ulong userId, bool enabled) { if (!playerData.ContainsKey(userId)) return; playerData[userId].rocketsEnabled = enabled; Save(); } } public class TheDoorKnockerData { [JsonProperty(PropertyName = "# of rockets set")] public int playerRockets; [JsonProperty(PropertyName = "Rockets enabled?")] public bool rocketsEnabled; [JsonProperty(PropertyName = "Time last seen")] public DateTime lastSeen; [JsonProperty(PropertyName = "Player preferences")] public TheDoorKnockerPlayerPreferences playerPreferences; } public class TheDoorKnockerPlayerPreferences { public bool basicRocket; public bool hvRocket; public bool fireRocket; public bool smokeRocket; } #endregion #region Language Dictionary _messages = new Dictionary() { { "NoPermission", "You lack permission to use this command." }, { "TurnedOn", "You have enabled more rockets per shot." }, { "TurnedOff", "You have disabled more rockets per shot." }, { "ChangeRockets", "Successfully changed rockets sent to {0}"}, { "NoArgs", "Available arguments are true/false | # of rockets | toggle [basic/hv/fire/smoke]"}, { "InvalidRockets", "You lack permission to launch more than one rocket.\nIf you believe this is a mistake please contact your administrator."}, { "TooMany", "You cannot choose more than {0} rockets."}, { "NoAmount", "You do not have a configured amount of rockets set. Please set your rockets with /{0}"}, { "NoZero", "Please disable TheDoorKnocker instead of setting your rockets sent to 0."}, { "InvalidTypeToggle", "Type is unrecognized, valid types are: basic, hv, fire, smoke, homing"}, { "TypeDisabledTryToggle", "You're unable to toggle {0} rockets as they are disabled."}, { "TypeToggled", "You have {0} {1} rockets."} }; #endregion } }