C#:
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<BaseProjectile>()?.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<ServerProjectile>();
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<string, int> 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<string> 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<string, int>()
{
{"thedoorknocker.basic", 2},
{"thedoorknocker.vip", 3},
{"thedoorknocker.example", 69}
},
rocketamtcmd = "tdk",
//rocketCuiCmd = "tdk.cui",
blockTypes = new List<string>()
{
"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<ConfigData>();
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<ulong, TheDoorKnockerData> playerData = new Dictionary<ulong, TheDoorKnockerData>();
//instance.Name
public static SavedData Load() =>
Interface.Oxide.DataFileSystem.ReadObject<SavedData>("The Door Knocker\\TDK_playerData") ?? new SavedData();
public void Save() =>
Interface.Oxide.DataFileSystem.WriteObject<SavedData>("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<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> more rockets per shot." },
{ "TurnedOff", "You have <color=#FF0000>disabled</color> more rockets per shot." },
{ "ChangeRockets", "Successfully changed rockets sent to <color=#0394fc>{0}</color>"},
{ "NoArgs", "Available arguments are <color=#03f8fc>true/false | # of rockets | toggle [basic/hv/fire/smoke]</color>"},
{ "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
}
}