using System; using Newtonsoft.Json; using Oxide.Core; using System.Collections.Generic; using System.Linq; using UnityEngine; using Oxide.Core.Plugins; namespace Oxide.Plugins { [Info("The Door Knocker", "Cannabis", "1.4.4")] [Description("Allows players to shoot multiple rockets at once with configurable settings")] class TheDoorKnocker : RustPlugin { #region Declarations private const string useperm = "thedoorknocker.use"; private SavedData data; #endregion #region Oxide Hooks void Init() { // Register permissions foreach (var kvp in config.maxRocketPermission) { permission.RegisterPermission(kvp.Key, this); } permission.RegisterPermission(useperm, this); // Register language messages and command lang.RegisterMessages(_messages, this); cmd.AddChatCommand(config.rocketamtcmd, this, nameof(CmdHandleRockets)); // Load data data = SavedData.Load(); // Clean old data if enabled if (config.cleanOldData) { timer.Once(5f, () => { foreach (var userID in data.playerData.Keys.ToList()) { if (data.playerData[userID]?.lastSeen == null) continue; TimeSpan timeDifference = DateTime.UtcNow - data.playerData[userID].lastSeen; if (timeDifference.TotalDays >= config.daysSinceLastSeenToClean) { data.playerData.Remove(userID); } } data.Save(); }); } } void OnPlayerConnected(BasePlayer player) { if (player == null || !permission.UserHasPermission(player.UserIDString, useperm) || data.playerData.ContainsKey(player.userID)) return; InitializePlayerData(player.userID); } void OnUserPermissionGranted(string id, string permName) { if (!ulong.TryParse(id, out ulong userID) || (permName != useperm && !config.maxRocketPermission.ContainsKey(permName))) return; if (!data.playerData.ContainsKey(userID)) { InitializePlayerData(userID); } } void OnUserGroupAdded(string playerId, string groupName) { if (!ulong.TryParse(playerId, out ulong userID)) return; string[] permissions = permission.GetGroupPermissions(groupName); if (!permissions.Contains(useperm) || data.playerData.ContainsKey(userID)) return; InitializePlayerData(userID); } void OnNewSave(string filename) { if (config.wipeOnWipe) { data = new SavedData(); data.Save(); } } void OnPlayerDisconnected(BasePlayer player, string reason) { if (player != null && data.playerData.ContainsKey(player.userID)) { data.playerData[player.userID].lastSeen = DateTime.UtcNow; data.Save(); } } void OnUserPermissionRevoked(string id, string permName) { if (!ulong.TryParse(id, out ulong userID)) return; BasePlayer player = BasePlayer.FindByID(userID); if (player == null) return; if (permName == useperm && data.playerData.ContainsKey(userID)) { data.playerData.Remove(userID); data.Save(); } else if (config.maxRocketPermission.ContainsKey(permName) && permission.UserHasPermission(id, useperm) && data.playerData.ContainsKey(userID)) { data.playerData[userID].playerRockets = config.defaultPlayerData.amount; data.Save(); } } void OnRocketLaunched(BasePlayer player, BaseEntity entity) { if (player == null || !permission.UserHasPermission(player.UserIDString, useperm) || !data.playerData.ContainsKey(player.userID) || !data.playerData[player.userID].rocketsEnabled) return; Item heldItem = player.GetActiveItem(); if (heldItem?.info?.shortname != "rocket.launcher") return; BaseProjectile.Magazine rocketMagazine = player.GetHeldEntity()?.GetComponent()?.primaryMagazine; if (rocketMagazine == null) return; int rocketsSent = Math.Min(data.playerData[player.userID].playerRockets, GetMaxRockets(player)); if (rocketsSent < 1) return; int rocketType; string ammoType = rocketMagazine.ammoType?.shortname ?? string.Empty; switch (ammoType) { case "ammo.rocket.basic": if (config.blockTypes.Contains("basic") || !data.playerData[player.userID].playerPreferences.basicRocket) return; rocketType = 1; break; case "ammo.rocket.hv": if (config.blockTypes.Contains("hv") || !data.playerData[player.userID].playerPreferences.hvRocket) return; rocketType = 2; break; case "ammo.rocket.fire": if (config.blockTypes.Contains("fire") || !data.playerData[player.userID].playerPreferences.fireRocket) return; rocketType = 3; break; case "ammo.rocket.smoke": if (config.blockTypes.Contains("smoke") || !data.playerData[player.userID].playerPreferences.smokeRocket) return; rocketType = 4; break; default: if (!data.playerData[player.userID].playerPreferences.basicRocket) return; rocketType = 1; break; } SpawnRockets(player, rocketsSent, rocketType); } void Unload() { data.Save(); } #endregion #region Commands private void CmdHandleRockets(BasePlayer player, string command, string[] args) { if (player == null || !permission.UserHasPermission(player.UserIDString, useperm)) { PrintToChat(player, lang.GetMessage("NoPermission", this, player.UserIDString)); return; } if (!data.playerData.ContainsKey(player.userID)) { InitializePlayerData(player.userID); } if (args.Length == 0 || args.Length > 2) { PrintToChat(player, lang.GetMessage("NoArgs", this, player.UserIDString)); return; } int maxRockets = GetMaxRockets(player); if (maxRockets == 0) { PrintToChat(player, lang.GetMessage("InvalidRockets", this, player.UserIDString)); return; } if (int.TryParse(args[0], out int amount)) { 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; } data.UpdateAmount(player.userID, amount); PrintToChat(player, string.Format(lang.GetMessage("ChangeRockets", this, player.UserIDString), amount)); return; } if (bool.TryParse(args[0], out bool enabled)) { data.UpdateEnabled(player.userID, enabled); PrintToChat(player, lang.GetMessage(enabled ? "TurnedOn" : "TurnedOff", this, player.UserIDString)); return; } if (args[0].ToLower() == "toggle" && args.Length == 2) { string type = args[1].ToLower(); if (config.blockTypes.Contains(type)) { PrintToChat(player, string.Format(lang.GetMessage("TypeDisabledTryToggle", this, player.UserIDString), type)); return; } bool newState; string displayName = type; switch (type) { case "basic": newState = !data.playerData[player.userID].playerPreferences.basicRocket; data.playerData[player.userID].playerPreferences.basicRocket = newState; break; case "hv": newState = !data.playerData[player.userID].playerPreferences.hvRocket; data.playerData[player.userID].playerPreferences.hvRocket = newState; break; case "fire": newState = !data.playerData[player.userID].playerPreferences.fireRocket; data.playerData[player.userID].playerPreferences.fireRocket = newState; displayName = "incendiary"; break; case "smoke": newState = !data.playerData[player.userID].playerPreferences.smokeRocket; data.playerData[player.userID].playerPreferences.smokeRocket = newState; break; default: PrintToChat(player, lang.GetMessage("InvalidTypeToggle", this, player.UserIDString)); return; } PrintToChat(player, string.Format(lang.GetMessage("TypeToggled", this, player.UserIDString), newState ? "enabled" : "disabled", displayName)); data.Save(); } } #endregion #region Helpers private void InitializePlayerData(ulong userID) { data.playerData.Add(userID, new TheDoorKnockerData { lastSeen = DateTime.UtcNow, playerPreferences = new TheDoorKnockerPlayerPreferences { basicRocket = true, hvRocket = true, fireRocket = false, smokeRocket = false }, playerRockets = config.defaultPlayerData.amount, rocketsEnabled = config.defaultPlayerData.enabled }); data.Save(); } private int GetMaxRockets(BasePlayer player) { int max = config.defaultPlayerData.amount; foreach (var kvp in config.maxRocketPermission) { if (permission.UserHasPermission(player.UserIDString, kvp.Key)) { max = Math.Max(max, kvp.Value); } } return max; } private void SpawnRockets(BasePlayer player, int amount, int type) { if (amount <= 1) return; Item item = player.GetActiveItem(); if (item?.info?.shortname != "rocket.launcher" || item.condition <= 0) return; string rocketPrefab; int rocketId; float 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: return; } for (int i = 0; i < amount - 1; i++) { if (config.takeDurability) { item.LoseCondition(config.durabilityAmount); if (item.condition <= 0) return; } Vector3 aimDirection = Quaternion.Euler(player.serverInput.current.aimAngles) * Vector3.forward; aimDirection = Quaternion.Euler( UnityEngine.Random.Range(-config.aimCone, config.aimCone), UnityEngine.Random.Range(-config.aimCone, config.aimCone), 0) * aimDirection; Quaternion rocketRotation = Quaternion.Euler(player.serverInput.current.aimAngles); TimedExplosive rocket = GameManager.server.CreateEntity(rocketPrefab, player.eyes.position + aimDirection * 1.5f, rocketRotation) as TimedExplosive; if (rocket == null) continue; rocket.creatorEntity = player; ServerProjectile rocketProjectile = rocket.GetComponent(); if (rocketProjectile != null) { rocketProjectile.InitializeVelocity(aimDirection * velocity); } rocket.Spawn(); } } #endregion #region Configuration private static ConfigData config; private class ConfigData { [JsonProperty(PropertyName = "Permissions and allowed max rockets per permission")] public Dictionary maxRocketPermission { get; set; } [JsonProperty(PropertyName = "Chat command")] public string rocketamtcmd { get; set; } [JsonProperty(PropertyName = "Rocket spread (degrees)")] public float aimCone { get; set; } [JsonProperty(PropertyName = "Blocked rocket types")] public List blockTypes { get; set; } [JsonProperty(PropertyName = "Default player data")] public TheDoorKnockerDefaultPlayerData defaultPlayerData { get; set; } [JsonProperty(PropertyName = "Clean up old player data")] public bool cleanOldData { get; set; } [JsonProperty(PropertyName = "Days before cleaning old data")] public double daysSinceLastSeenToClean { get; set; } [JsonProperty(PropertyName = "Take durability per extra rocket")] public bool takeDurability { get; set; } [JsonProperty(PropertyName = "Durability taken per rocket")] public int durabilityAmount { get; set; } [JsonProperty(PropertyName = "Wipe data on map wipe")] public bool wipeOnWipe { get; set; } [JsonProperty(PropertyName = "Custom rocket speeds")] public TheDoorKnockerRocketSpeeds rocketSpeeds { get; set; } } private ConfigData GetDefaultConfig() { return new ConfigData { maxRocketPermission = new Dictionary { {"thedoorknocker.basic", 2}, {"thedoorknocker.vip", 3}, {"thedoorknocker.example", 69} }, rocketamtcmd = "tdk", aimCone = 4.0f, blockTypes = new List { "smoke", "fire" }, cleanOldData = true, daysSinceLastSeenToClean = 7, takeDurability = false, durabilityAmount = 1, wipeOnWipe = true, defaultPlayerData = new TheDoorKnockerDefaultPlayerData { amount = 2, enabled = true }, rocketSpeeds = new TheDoorKnockerRocketSpeeds { basicVelocity = 30, fireVelocity = 30, hvVelocity = 60, smokeVelocity = 30 } }; } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { LoadDefaultConfig(); } } catch { PrintError("Configuration file is corrupt! Loading default config..."); LoadDefaultConfig(); } ValidateConfig(); SaveConfig(); } protected override void LoadDefaultConfig() { config = GetDefaultConfig(); } protected override void SaveConfig() { Config.WriteObject(config); } private void ValidateConfig() { bool changed = false; if (config.maxRocketPermission == null) { config.maxRocketPermission = GetDefaultConfig().maxRocketPermission; changed = true; } if (string.IsNullOrEmpty(config.rocketamtcmd)) { config.rocketamtcmd = "tdk"; changed = true; } if (config.blockTypes == null) { config.blockTypes = GetDefaultConfig().blockTypes; changed = true; } if (config.defaultPlayerData == null) { config.defaultPlayerData = GetDefaultConfig().defaultPlayerData; changed = true; } if (config.rocketSpeeds == null) { config.rocketSpeeds = GetDefaultConfig().rocketSpeeds; changed = true; } // Add more validations if necessary if (changed) { PrintWarning("Invalid config values detected! Corrected and saving changes."); SaveConfig(); } } public class TheDoorKnockerDefaultPlayerData { [JsonProperty(PropertyName = "Enable TDK by default")] public bool enabled { get; set; } [JsonProperty(PropertyName = "Default rocket amount")] public int amount { get; set; } } public class TheDoorKnockerRocketSpeeds { [JsonProperty(PropertyName = "Basic rocket speed")] public int basicVelocity { get; set; } [JsonProperty(PropertyName = "HV rocket speed")] public int hvVelocity { get; set; } [JsonProperty(PropertyName = "Smoke rocket speed")] public int smokeVelocity { get; set; } [JsonProperty(PropertyName = "Fire rocket speed")] public int fireVelocity { get; set; } } #endregion #region Data Handling class SavedData { [JsonProperty("Player data")] public Dictionary playerData = new Dictionary(); public static SavedData Load() => Interface.Oxide.DataFileSystem.ReadObject("TheDoorKnocker/TDK_playerData") ?? new SavedData(); public void Save() => Interface.Oxide.DataFileSystem.WriteObject("TheDoorKnocker/TDK_playerData", this); public void UpdateAmount(ulong userId, int rocketAmount) { if (playerData.ContainsKey(userId)) { playerData[userId].playerRockets = rocketAmount; Save(); } } public void UpdateEnabled(ulong userId, bool enabled) { if (playerData.ContainsKey(userId)) { playerData[userId].rocketsEnabled = enabled; Save(); } } } public class TheDoorKnockerData { [JsonProperty(PropertyName = "Rocket count")] public int playerRockets { get; set; } [JsonProperty(PropertyName = "Rockets enabled")] public bool rocketsEnabled { get; set; } [JsonProperty(PropertyName = "Last seen")] public DateTime lastSeen { get; set; } [JsonProperty(PropertyName = "Player preferences")] public TheDoorKnockerPlayerPreferences playerPreferences { get; set; } } public class TheDoorKnockerPlayerPreferences { public bool basicRocket { get; set; } public bool hvRocket { get; set; } public bool fireRocket { get; set; } public bool smokeRocket { get; set; } } #endregion #region Language Dictionary _messages = new Dictionary { { "NoPermission", "You lack permission to use this command." }, { "TurnedOn", "You have enabled multiple rockets per shot." }, { "TurnedOff", "You have disabled multiple rockets per shot." }, { "ChangeRockets", "Successfully changed rockets to {0}"}, { "NoArgs", "Available arguments: true/false | # of rockets | toggle [basic/hv/fire/smoke]"}, { "InvalidRockets", "You lack permission to launch multiple rockets.\nContact an administrator if you believe this is an error."}, { "TooMany", "You cannot set more than {0} rockets."}, { "NoZero", "Disable TheDoorKnocker instead of setting rockets to 0."}, { "InvalidTypeToggle", "Invalid type. Valid types: basic, hv, fire, smoke"}, { "TypeDisabledTryToggle", "Cannot toggle {0} rockets as they are disabled."}, { "TypeToggled", "You have {0} {1} rockets."} }; #endregion } }