C#:
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using System.Linq;
namespace Oxide.Plugins
{
[Info("LockOnRockets", "https://topplugin.ru/", "0.3.7")]
class LockOnRockets : RustPlugin
{
#region Fields
private static LockOnRockets Instance { get; set; }
private const int TARGET_LAYERS = ~(1 << 3 | 1 << 10 | 1 << 18 | 1 << 28 | 1 << 29);
private const int LAUNCHER_ITEMID = 442886268;
private const int SMOKE_ITEMID = -17123659;
private const float CAST_RADIUS = 0.25f;
private const float MAX_LOS_ANGLE = 5.5f;
private const string EXPLOSION_PREFAB = "assets/prefabs/tools/c4/effects/c4_explosion.prefab";
private const string SMOKE_PREFAB = "assets/bundled/prefabs/fx/smoke_signal_full.prefab";
private const string BEEP_PREFAB = "assets/prefabs/locks/keypad/effects/lock.code.unlock.prefab";
private const string UI_PANEL = "UI_LockOn";
#endregion
#region Oxide Hooks
private void Loaded()
{
permission.RegisterPermission("lockonrockets.craft", this);
lang.RegisterMessages(Messages, this);
Instance = this;
}
private void OnServerInitialized()
{
foreach (BasePlayer player in BasePlayer.activePlayerList)
OnPlayerConnected(player);
}
private void Unload()
{
LockOnPlayer[] lockOnPlayers = UnityEngine.Object.FindObjectsOfType<LockOnPlayer>();
for (int i = 0; i < lockOnPlayers?.Length; i++)
UnityEngine.Object.Destroy(lockOnPlayers[i]);
HomingRocket[] homingRockets = UnityEngine.Object.FindObjectsOfType<HomingRocket>();
for (int i = 0; i < homingRockets?.Length; i++)
UnityEngine.Object.Destroy(homingRockets[i]);
configData = null;
Instance = null;
}
private void OnItemAddedToContainer(ItemContainer container, Item item)
{
if (item?.info.itemid == -17123659 && container.playerOwner != null)
{
SendReply(container.playerOwner, msg("inventory", container.playerOwner.UserIDString));
}
}
private void OnPlayerConnected(BasePlayer player)
{
if (GetPlayer(player) == null)
player.gameObject.AddComponent<LockOnPlayer>();
}
private void OnPlayerDisconnected(BasePlayer player)
{
LockOnPlayer lockOnPlayer = GetPlayer(player);
if (lockOnPlayer != null)
UnityEngine.Object.Destroy(lockOnPlayer);
}
private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem)
{
if (newItem?.info.itemid != LAUNCHER_ITEMID)
return;
BaseProjectile baseProjectile = newItem.GetHeldEntity() as BaseProjectile;
if (baseProjectile == null)
return;
OnReloadWeapon(player, baseProjectile);
}
private void OnReloadWeapon(BasePlayer player, BaseProjectile baseProjectile)
{
if (baseProjectile?.primaryMagazine.contents > 0 && baseProjectile?.primaryMagazine.ammoType.itemid == SMOKE_ITEMID)
SendReply(player, msg("loaded", player.UserIDString));
}
private void OnRocketLaunched(BasePlayer player, BaseEntity entity)
{
LockOnPlayer lockOnPlayer = GetPlayer(player);
if (lockOnPlayer == null)
return;
if (lockOnPlayer.TargetLocked)
{
TimedExplosive timedExplosive = entity.GetComponent<TimedExplosive>();
entity.GetComponent<ServerProjectile>().gravityModifier = 0f;
timedExplosive.damageTypes.Clear();
timedExplosive.SetFuse(configData.DetonationTime);
timedExplosive.explosionEffect.guid = string.Empty;
HomingRocket homingRocket = entity.gameObject.AddComponent<HomingRocket>();
homingRocket.Initialize(lockOnPlayer.Target, player);
lockOnPlayer.Reset(false);
}
}
#endregion
#region Functions
private LockOnPlayer GetPlayer(BasePlayer player) => player?.GetComponent<LockOnPlayer>();
private static void RadiusDamage(BaseEntity attackingPlayer, BaseEntity weaponPrefab, Vector3 pos)
{
List<HitInfo> hitInfo = new List<HitInfo>();
List<BaseCombatEntity> baseCombatEntities = Facepunch.Pool.GetList<BaseCombatEntity>();
Vis.Entities(pos, 3.8f, baseCombatEntities);
baseCombatEntities = baseCombatEntities.Distinct().ToList();
for (int i = 0; i < baseCombatEntities.Count; i++)
{
BaseCombatEntity item = baseCombatEntities[i];
if (item != null && !item.IsDestroyed)
{
Vector3 closestPoint = item.ClosestPoint(pos);
float distance = Vector3.Distance(closestPoint, pos);
float damageScale = Mathf.Clamp01((distance - 1.5f) / (3.8f - 1.5f));
if (damageScale <= 1f)
{
float scale = 1f - damageScale;
HitInfo info = new HitInfo()
{
Initiator = attackingPlayer,
WeaponPrefab = weaponPrefab
};
info.damageTypes.Add(new List<Rust.DamageTypeEntry>
{
new Rust.DamageTypeEntry
{
amount = (item is BaseHelicopter || item is CH47Helicopter) ? configData.RocketDamage * configData.HelicopterModifiers.DamageModifier : configData.RocketDamage,
type = Rust.DamageType.Explosion
},
new Rust.DamageTypeEntry
{
amount = 75,
type = Rust.DamageType.Blunt
}
});
info.damageTypes.ScaleAll(scale);
info.HitPositionWorld = closestPoint;
info.HitNormalWorld = (pos - closestPoint).normalized;
info.PointStart = pos;
info.PointEnd = info.HitPositionWorld;
hitInfo.Add(info);
}
}
}
for (int j = 0; j < baseCombatEntities.Count; j++)
{
BaseCombatEntity baseCombatEntity = baseCombatEntities[j];
HitInfo info = hitInfo[j];
if (baseCombatEntity is BaseHelicopter && (baseCombatEntity as BaseHelicopter).enabled && baseCombatEntity.health - info.damageTypes.Total() <= 0)
(baseCombatEntity as BaseHelicopter).Die(info);
else
{
baseCombatEntity.Hurt(info);
baseCombatEntity.DoHitNotify(info);
}
}
Facepunch.Pool.FreeList(ref baseCombatEntities);
}
#endregion
#region UI
public class UI
{
public static CuiElementContainer Container(string panelName, string color, string aMin, string aMax, bool useCursor = false)
{
CuiElementContainer container = new CuiElementContainer()
{
{
new CuiPanel
{
Image = {Color = color},
RectTransform = {AnchorMin = aMin, AnchorMax = aMax},
CursorEnabled = useCursor
},
new CuiElement().Parent = "Overlay",
panelName
}
};
return container;
}
public static void Label(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter)
{
container.Add(new CuiLabel
{
Text = { Color = color, FontSize = size, Align = align, Text = text },
RectTransform = { AnchorMin = aMin, AnchorMax = aMax }
},
panel);
}
}
private static void CreateUIOverlay(BasePlayer player, string message)
{
CuiElementContainer container = UI.Container(UI_PANEL, "0 0 0 0", "0.55 0.45", "0.95 0.55");
UI.Label(ref container, UI_PANEL, "", message, 16, "0 0", "1 1", TextAnchor.MiddleLeft);
CuiHelper.DestroyUi(player, UI_PANEL);
CuiHelper.AddUi(player, container);
}
#endregion
#region Components
private class LockOnPlayer : MonoBehaviour
{
private BasePlayer player;
private RaycastHit raycastHit;
private BaseEntity lastTarget;
private Vector3 targetOffset;
private float timeLocked;
private bool isBeeping;
private bool isLocked;
private bool activeUI;
private bool activeDDraw;
public bool TargetLocked { get; private set; }
public BaseEntity Target { get { return lastTarget; } }
private void Awake()
{
player = GetComponent<BasePlayer>();
}
private void Update()
{
if (player == null || player.IsDead())
return;
if (player.serverInput.IsDown(BUTTON.FIRE_SECONDARY) && HasValidWeapon())
{
TryLockTarget();
DDrawOverlay();
}
else Reset(false);
}
private bool HasValidWeapon()
{
Item item = player.GetActiveItem();
if (item?.info.itemid != LAUNCHER_ITEMID)
return false;
BaseProjectile baseProjectile = item.GetHeldEntity() as BaseProjectile;
if (baseProjectile == null)
return false;
return baseProjectile?.primaryMagazine.contents > 0 && baseProjectile?.primaryMagazine.ammoType.itemid == SMOKE_ITEMID;
}
private void TryLockTarget()
{
if (lastTarget != null && !lastTarget.IsDestroyed)
{
float angle = Vector3.Angle(player.eyes.HeadForward(), lastTarget.transform.TransformPoint(targetOffset) - player.eyes.position);
if (angle <= MAX_LOS_ANGLE)
{
if (GamePhysics.LineOfSight(player.eyes.position + player.eyes.HeadForward(), lastTarget.transform.TransformPoint(targetOffset), TARGET_LAYERS, CAST_RADIUS))
{
OnTargetTick();
return;
}
}
}
if (Physics.SphereCast(player.eyes.HeadRay(), CAST_RADIUS, out raycastHit, 2000, TARGET_LAYERS, QueryTriggerInteraction.Collide))
{
BaseEntity hitEntity = raycastHit.GetEntity();
if (hitEntity != null)
{
if (hitEntity != lastTarget)
{
if (CanTargetEntity(hitEntity, Vector3.Distance(player.transform.position, hitEntity.transform.position)))
{
lastTarget = hitEntity;
targetOffset = lastTarget.transform.InverseTransformPoint(raycastHit.point);
}
Reset(true);
return;
}
OnTargetTick();
return;
}
}
Reset(false);
}
private void OnTargetTick()
{
if (!isBeeping)
Beep();
if (!activeUI)
{
CreateUIOverlay(player, msg("aquiring", player.UserIDString));
activeUI = true;
}
timeLocked += Time.deltaTime;
if (timeLocked >= configData.TimeToLockOn)
{
TargetLocked = true;
CreateUIOverlay(player, msg("locked", player.UserIDString));
}
}
private bool CanTargetEntity(BaseEntity entity, float distance)
{
if (entity is CargoPlane)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Plane];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BasePlayer)
{
if ((entity as BasePlayer).IsNpc)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.NPC];
return lockType.Enabled && distance <= lockType.Distance;
}
else
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Player];
return lockType.Enabled && distance <= lockType.Distance;
}
}
else if (entity is BaseNpc || entity is BaseRidableAnimal)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Animal];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is AutoTurret || entity is FlameTurret || entity is GunTrap)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.GunTrap];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BradleyAPC)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.APC];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is ScrapTransportHelicopter)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.TransportHelicopter];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is MiniCopter)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Minicopter];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is CH47Helicopter)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.CH47];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BaseHelicopter)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.AttackHelicopter];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BasicCar)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Car];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is RHIB)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.RHIB];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BaseBoat)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Boat];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BuildingPrivlidge)
return false;
else if ((entity is LootContainer || entity is StorageContainer) && !(entity is StashContainer))
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Loot];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is ResourceEntity)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Resource];
return lockType.Enabled && distance <= lockType.Distance;
}
else if (entity is BuildingBlock || entity is SimpleBuildingBlock)
{
ConfigData.LockType lockType = configData.LockOnTypes[LockTypes.Structure];
return lockType.Enabled && distance <= lockType.Distance;
}
return false;
}
private void Beep()
{
isBeeping = true;
Effect.server.Run(BEEP_PREFAB, player.transform.position + player.transform.forward);
InvokeHandler.Invoke(this, Beep, TargetLocked ? 0.25f : 1f);
}
private void DDrawOverlay()
{
if (activeDDraw || lastTarget == null)
return;
bool tempAdmin = false;
if (!player.HasPlayerFlag(BasePlayer.PlayerFlags.IsAdmin))
{
tempAdmin = true;
player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
}
player.SendConsoleCommand("ddraw.box", 0.2f, (TargetLocked ? Color.green : Color.red), lastTarget.transform.TransformPoint(targetOffset), 0.5f);
if (tempAdmin)
player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
activeDDraw = true;
InvokeHandler.Invoke(this, () => activeDDraw = false, 0.2f);
}
internal void Reset(bool keepTarget)
{
if (!keepTarget)
lastTarget = null;
if (timeLocked > 0)
{
timeLocked = 0;
TargetLocked = false;
if (isBeeping)
{
isBeeping = false;
InvokeHandler.CancelInvoke(this, Beep);
}
if (activeUI)
{
activeUI = false;
CuiHelper.DestroyUi(player, UI_PANEL);
}
activeDDraw = false;
}
}
}
private class HomingRocket : MonoBehaviour
{
private ServerProjectile projectile;
private BaseEntity target;
private BasePlayer owner;
private Vector3 targetPosition;
private float totalDistance;
private float fraction;
private void Awake()
{
projectile = GetComponent<ServerProjectile>();
enabled = false;
}
public void Initialize(BaseEntity target, BasePlayer owner)
{
this.target = target;
this.owner = owner;
projectile.speed = (target is BaseHelicopter || target is CH47Helicopter) ? configData.RocketSpeed * configData.HelicopterModifiers.SpeedModifier : configData.RocketSpeed;
totalDistance = Vector3.Distance(projectile.transform.position, target.transform.position);
fraction = 0;
if (configData.RocketBeep)
Beep();
InvokeHandler.Invoke(this, () => enabled = true, 0.5f);
}
private void Update()
{
if (target == null || target.IsDestroyed)
return;
Vector3 position = target.transform.position + new Vector3(0, target.bounds.center.y / 2, 0);
if (position != targetPosition)
{
targetPosition = position;
float distance = Vector3.Distance(projectile.transform.position, targetPosition);
if (distance <= 0.25f)
{
Destroy(this);
return;
}
Vector3 direction = (targetPosition - projectile.transform.position).normalized;
projectile.InitializeVelocity(direction * projectile.speed);
float remaining = totalDistance - distance;
if (remaining > 0 && totalDistance > 0)
fraction = remaining / totalDistance;
}
}
private void OnDestroy()
{
RadiusDamage(owner, projectile.GetComponent<BaseEntity>(), projectile.transform.position);
Effect.server.Run(EXPLOSION_PREFAB, projectile.transform.position);
}
private void Beep()
{
Effect.server.Run(BEEP_PREFAB, projectile.transform.position + Vector3.up);
InvokeHandler.Invoke(this, Beep, 1f - fraction);
}
}
#endregion
#region Crafting
private void ValidateCraftingConfig()
{
foreach (ConfigData.CraftingItems craftingItem in configData.CraftCost)
{
ItemDefinition itemDefinition;
if (!ItemManager.itemDictionaryByName.TryGetValue(craftingItem.Shortname, out itemDefinition))
{
PrintError($"An invalid item shortname has been set in the crafting section of the config, crafting has been disabled: {craftingItem.Shortname}");
configData.EnableCrafting = false;
}
}
}
[ChatCommand("lockon")]
private void cmdCraftLockon(BasePlayer player, string command, string[] args)
{
if (!configData.EnableCrafting)
return;
if (!permission.UserHasPermission(player.UserIDString, "lockonrockets.craft"))
{
SendReply(player, msg("noPerms", player.UserIDString));
return;
}
int amount = 1;
if (args.Length > 0)
{
if (!int.TryParse(args[0], out amount))
amount = 1;
}
if (!HasResourcesForCrafting(player, amount))
{
SendReply(player, string.Format(msg("notEnoughResources", player.UserIDString), GetRequiredResources()));
return;
}
TakeAllResources(player, amount);
player.GiveItem(ItemManager.CreateByItemID(-17123659, amount, 0), BaseEntity.GiveItemReason.PickedUp);
}
private bool HasResourcesForCrafting(BasePlayer player, int amount)
{
foreach (ConfigData.CraftingItems craftingItem in configData.CraftCost)
{
ItemDefinition itemDefinition = ItemManager.itemDictionaryByName[craftingItem.Shortname];
if (!HasEnoughResources(player, itemDefinition.itemid, craftingItem.Amount * amount))
return false;
}
return true;
}
private bool HasEnoughResources(BasePlayer player, int itemid, int amount) => player.inventory.GetAmount(itemid) >= amount;
private string GetRequiredResources()
{
string message = string.Empty;
for (int i = 0; i < configData.CraftCost.Count; i++)
{
ItemDefinition itemDefinition = ItemManager.itemDictionaryByName[configData.CraftCost[i].Shortname];
message += $"{configData.CraftCost[i].Amount} x {itemDefinition.displayName.english}";
if (i < configData.CraftCost.Count - 1)
message += ", ";
}
return message;
}
private void TakeAllResources(BasePlayer player, int amount)
{
foreach (ConfigData.CraftingItems craftingItem in configData.CraftCost)
{
ItemDefinition itemDefinition = ItemManager.itemDictionaryByName[craftingItem.Shortname];
TakeResources(player, itemDefinition.itemid, craftingItem.Amount * amount);
}
}
private void TakeResources(BasePlayer player, int itemid, int amount) => player.inventory.Take(null, itemid, amount);
#endregion
#region Config
internal enum LockTypes { AttackHelicopter, CH47, Minicopter, TransportHelicopter, RHIB, Boat, Car, APC, Plane, NPC, Player, Animal, Structure, Resource, Loot, GunTrap }
private static ConfigData configData;
private class ConfigData
{
[JsonProperty(PropertyName = "Amount of time to acquire target lock")]
public float TimeToLockOn { get; set; }
[JsonProperty(PropertyName = "Amount of time before the rocket self detonates")]
public float DetonationTime { get; set; }
[JsonProperty(PropertyName = "Enable beeping sfx on the rocket as it approaches the target")]
public bool RocketBeep { get; set; }
[JsonProperty(PropertyName = "Base speed of the rocket")]
public float RocketSpeed { get; set; }
[JsonProperty(PropertyName = "Base damage of the rocket")]
public float RocketDamage { get; set; }
[JsonProperty(PropertyName = "Modifiers when targeting a helicopter")]
public HelicopterMods HelicopterModifiers { get; set; }
[JsonProperty(PropertyName = "Allow smoke rocket crafting")]
public bool EnableCrafting { get; set; }
[JsonProperty(PropertyName = "Crafting Costs")]
public List<CraftingItems> CraftCost { get; set; }
[JsonProperty(PropertyName = "Targeting Types")]
public Dictionary<LockTypes, LockType> LockOnTypes { get; set; }
public class LockType
{
public bool Enabled { get; set; }
[JsonProperty(PropertyName = "Maximum distance")]
public float Distance { get; set; }
}
public class HelicopterMods
{
[JsonProperty(PropertyName = "Rocket damage modifier")]
public float DamageModifier { get; set; }
[JsonProperty(PropertyName = "Rocket speed modifier")]
public float SpeedModifier { get; set; }
}
public class CraftingItems
{
public string Shortname { get; set; }
public int Amount { get; set; }
}
public Oxide.Core.VersionNumber Version { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
configData = Config.ReadObject<ConfigData>();
if (configData.Version < Version)
UpdateConfigValues();
Config.WriteObject(configData, true);
}
protected override void LoadDefaultConfig() => configData = GetBaseConfig();
private ConfigData GetBaseConfig()
{
return new ConfigData
{
TimeToLockOn = 3f,
DetonationTime = 30f,
RocketBeep = true,
RocketDamage = 300,
RocketSpeed = 40,
HelicopterModifiers = new ConfigData.HelicopterMods
{
DamageModifier = 5.0f,
SpeedModifier = 2.5f
},
EnableCrafting = true,
CraftCost = new List<ConfigData.CraftingItems>
{
new ConfigData.CraftingItems
{
Shortname = "ammo.rocket.basic",
Amount = 1
},
new ConfigData.CraftingItems
{
Shortname = "techparts",
Amount = 3
}
},
LockOnTypes = new Dictionary<LockTypes, ConfigData.LockType>
{
[LockTypes.AttackHelicopter] = new ConfigData.LockType() { Enabled = true, Distance = 350 },
[LockTypes.CH47] = new ConfigData.LockType() { Enabled = true, Distance = 350 },
[LockTypes.Minicopter] = new ConfigData.LockType() { Enabled = true, Distance = 350 },
[LockTypes.TransportHelicopter] = new ConfigData.LockType() { Enabled = true, Distance = 350 },
[LockTypes.RHIB] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Boat] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Car] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.APC] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Plane] = new ConfigData.LockType() { Enabled = true, Distance = 1000 },
[LockTypes.NPC] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Player] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Animal] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Structure] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Resource] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.Loot] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
[LockTypes.GunTrap] = new ConfigData.LockType() { Enabled = true, Distance = 250 },
},
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(configData, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
ConfigData baseConfig = GetBaseConfig();
if (configData.Version < new Core.VersionNumber(0, 3, 0))
configData = baseConfig;
if (configData.Version < new Core.VersionNumber(0, 3, 4))
configData.LockOnTypes = baseConfig.LockOnTypes;
configData.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Localization
private static string msg(string key, string playerId = null) => Instance.lang.GetMessage(key, Instance, playerId);
private Dictionary<string, string> Messages = new Dictionary<string, string>
{
["locked"] = ">><color=#00E500> Target Locked </color><<",
["aquiring"] = ">><color=#E50000> Aquiring Target </color><<",
["loaded"] = "<color=#939393>A </color><color=#C4FF00>lock-on rocket</color><color=#939393> is loaded in your rocket launcher!</color>",
["inventory"] = "<color=#939393>You have a </color><color=#C4FF00>lock-on rocket</color><color=#939393> in your inventory! To use it load the smoke rocket into your rocket launcher</color>",
["noPerms"] = "You do not have permission to use this command",
["notEnoughResources"] = "You do not have the required resources to craft a lock-on rocket\nResources required per rocket: {0}",
["craftSuccess"] = "You have crafted a lock-on rocket!"
};
#endregion
}
}