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<BaseProjectile>()?.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<ServerProjectile>();
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<string, int> 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<string> 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<string, int>
{
{"thedoorknocker.basic", 2},
{"thedoorknocker.vip", 3},
{"thedoorknocker.example", 69}
},
rocketamtcmd = "tdk",
aimCone = 4.0f,
blockTypes = new List<string> { "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<ConfigData>();
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<ulong, TheDoorKnockerData> playerData = new Dictionary<ulong, TheDoorKnockerData>();
public static SavedData Load() =>
Interface.Oxide.DataFileSystem.ReadObject<SavedData>("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<string, string> _messages = new Dictionary<string, string>
{
{ "NoPermission", "You lack <color=#FF0000>permission</color> to use this command." },
{ "TurnedOn", "You have <color=#00FBFF>enabled</color> multiple rockets per shot." },
{ "TurnedOff", "You have <color=#FF0000>disabled</color> multiple rockets per shot." },
{ "ChangeRockets", "Successfully changed rockets to <color=#0394fc>{0}</color>"},
{ "NoArgs", "Available arguments: <color=#03f8fc>true/false | # of rockets | toggle [basic/hv/fire/smoke]</color>"},
{ "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
}
}
TheDoorKnocker.cs