C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Turret Loadouts", "WhiteThunder", "1.1.1")]
[Description("Automatically fills turrets with weapons, attachments and ammo, using configurable loadouts.")]
internal class TurretLoadouts : CovalencePlugin
{
#region Fields
private static TurretLoadouts _pluginInstance;
private const int LoadoutNameMaxLength = 20;
private const string Permission_AutoAuth = "turretloadouts.autoauth";
private const string Permission_AutoToggle = "turretloadouts.autotoggle";
private const string Permission_AutoToggleSamSite = "turretloadouts.autotoggle.samsite";
private const string Permission_Manage = "turretloadouts.manage";
private const string Permission_ManageCustom = "turretloadouts.manage.custom";
private const string Permission_RulesetPrefix = "turretloadouts.ruleset";
private const string Permission_DefaultLoadoutPrefix = "turretloadouts.default";
private const string Permission_DefaultFlameTurretLoadoutPrefix = "turretloadouts.flameturret.default";
private const string Permission_DefaultShotgunTrapLoadoutPrefix = "turretloadouts.shotguntrap.default";
private const string Permission_DefaultSamSiteLoadoutPrefix = "turretloadouts.samsite.default";
private readonly Dictionary<string, PlayerData> _playerDataCache = new Dictionary<string, PlayerData>();
private Configuration _pluginConfig;
#endregion
#region Hooks
private void Init()
{
_pluginInstance = this;
permission.RegisterPermission(Permission_AutoAuth, this);
permission.RegisterPermission(Permission_AutoToggle, this);
permission.RegisterPermission(Permission_AutoToggleSamSite, this);
permission.RegisterPermission(Permission_Manage, this);
permission.RegisterPermission(Permission_ManageCustom, this);
_pluginConfig.Init(this);
if (!_pluginConfig.LockAutoFilledTurrets)
{
Unsubscribe(nameof(OnTurretToggle));
Unsubscribe(nameof(CanMoveItem));
Unsubscribe(nameof(OnDropContainerEntity));
}
}
private void OnServerInitialized()
{
// Update locked turrets so they can be picked up and don't drop loot.// This is done even if the config option for locked turrets is off
// because there could be locked turrets lingering from a previous configuration.// TBD if there are other plugins locking inventories which could conflict.foreach (var entity in BaseNetworkable.serverEntities)
{
if (entity is AutoTurret || entity is SamSite)
{
var container = entity as ContainerIOEntity;
if (IsLocked(container))
SetupLockedContainer(container);
}
else if (entity is FlameTurret || entity is GunTrap)
{
var container = entity as StorageContainer;
if (IsLocked(container))
SetupLockedContainer(container);
}
}
}
private void Unload()
{
_pluginInstance = null;
}
private void OnEntityBuilt(Planner plan, GameObject go)
{
BasePlayer ownerPlayer = plan?.GetOwnerPlayer();
if (ownerPlayer == null)
return;
BaseEntity entity = go?.ToBaseEntity();
if (entity == null)
return;
AutoTurret turret = entity as AutoTurret;
if (turret != null)
{
MaybeAuthTurret(turret, ownerPlayer);
var loadout = GetPlayerActiveLoadout(ownerPlayer.UserIDString);
if (loadout != null)
{
if (loadout.Peacekeeper)
turret.SetPeacekeepermode(true);
var heldItem = AddHeldEntity(turret, ownerPlayer, loadout);
if (heldItem != null)
{
AddReserveAmmo(turret.inventory, loadout, ownerPlayer, firstSlot: 1);
turret.UpdateTotalAmmo();
turret.EnsureReloaded();
var isInstrument = (heldItem.GetHeldEntity() as HeldEntity)?.IsInstrument() ?? false;
if ((isInstrument || GetTotalAmmo(turret) > 0) && HasPermissionAny(ownerPlayer, Permission_AutoToggle))
{
turret.InitiateStartup();
var turretSwitch = turret.GetComponentInChildren<ElectricSwitch>();
if (turretSwitch != null)
turretSwitch.SetSwitch(true);
}
if (_pluginConfig.LockAutoFilledTurrets)
{
heldItem.contents.SetLocked(true);
turret.inventory.SetLocked(true);
SetupLockedContainer(turret);
}
if (HasPermissionAny(ownerPlayer, Permission_Manage, Permission_ManageCustom))
ChatMessage(ownerPlayer, "Generic.FilledFromLoadout", loadout.GetDisplayName(ownerPlayer.UserIDString));
}
}
turret.SendNetworkUpdate();
return;
}
var samSite = entity as SamSite;
if (samSite != null)
{
var loadout = GetPlayerLastAllowedProfile(_pluginConfig.DefaultSamSiteLoadouts, ownerPlayer.UserIDString);
if (loadout != null)
{
AddReserveAmmo(samSite.inventory, loadout, ownerPlayer);
if (HasPermissionAny(ownerPlayer, Permission_AutoToggleSamSite))
samSite.SetFlag(IOEntity.Flag_HasPower, true);
if (_pluginConfig.LockAutoFilledTurrets)
{
samSite.inventory.SetLocked(true);
SetupLockedContainer(samSite);
}
}
return;
}
var flameTurret = entity as FlameTurret;
if (flameTurret != null)
{
var loadout = GetPlayerLastAllowedProfile(_pluginConfig.DefaultFlameTurretLoadouts, ownerPlayer.UserIDString);
if (loadout != null)
{
AddReserveAmmo(flameTurret.inventory, loadout, ownerPlayer);
if (_pluginConfig.LockAutoFilledTurrets)
{
flameTurret.inventory.SetLocked(true);
SetupLockedContainer(flameTurret);
}
}
return;
}
var gunTrap = entity as GunTrap;
if (gunTrap != null)
{
var loadout = GetPlayerLastAllowedProfile(_pluginConfig.DefaultShotgunTrapLoadouts, ownerPlayer.UserIDString);
if (loadout != null)
{
AddReserveAmmo(gunTrap.inventory, loadout, ownerPlayer);
if (_pluginConfig.LockAutoFilledTurrets)
{
gunTrap.inventory.SetLocked(true);
SetupLockedContainer(gunTrap);
}
}
return;
}
}
private void OnEntityPickedUp(StorageContainer container)
{
if ((container is GunTrap || container is FlameTurret) && IsLocked(container))
container.inventory.Clear();
}
private void OnEntityPickedUp(FlameTurret flameTurret)
{
if (IsLocked(flameTurret))
flameTurret.inventory.Clear();
}
private void OnTurretToggle(AutoTurret turret)
{
// Remove items if powering down while locked and out of ammo
// Otherwise, the turret would be unusable other than picking it up
if (turret != null && turret.IsOnline() && IsLocked(turret) && GetTotalAmmo(turret) == 0)
{
turret.inventory.Clear();
turret.inventory.SetLocked(false);
}
}
private bool? CanMoveItem(Item item)
{
if (item.parent == null)
return null;
// Fix issue where right-clicking an item in a locked turret inventory allows moving it.var containerEntity = item.parent.entityOwner;
if ((containerEntity is AutoTurret || containerEntity is SamSite) && item.parent.IsLocked())
return false;
return null;
}
// Compatibility with plugin: Remover Tool (RemoverTool)
private bool? OnDropContainerEntity(ContainerIOEntity container)
{
// Prevent Remover Tool from explicitly dropping the inventory
if ((container is AutoTurret || container is SamSite) && IsLocked(container))
return false;
return null;
}
// Compatibility with plugin: Remover Tool (RemoverTool)
private bool? OnDropContainerEntity(StorageContainer container)
{
// Prevent Remover Tool from explicitly dropping the inventory
if ((container is FlameTurret || container is GunTrap) && IsLocked(container))
return false;
return null;
}
#endregion
#region Commands
[Command("tl")]
private void MainCommand(IPlayer player, string cmd, string[] args)
{
if (player.IsServer)
return;
if (args.Length == 0)
{
SubCommandDefault(player);
return;
}
var playerData = GetPlayerData(player);
switch (args[0].ToLower())
{
case "help":
SubCommandHelp(player);
return;
case "list":
SubCommandList(player);
return;
case "save":
SubCommandSave(player, args.Skip(1).ToArray());
return;
case "update":
SubCommandUpdate(player, args.Skip(1).ToArray());
return;
case "rename":
SubCommandRename(player, args.Skip(1).ToArray());
return;
case "delete":
SubCommandDelete(player, args.Skip(1).ToArray());
return;
default:
SubCommandActivate(player, args);
return;
}
}
private void SubCommandDefault(IPlayer player)
{
if (!VerifyPermissionAny(player, Permission_Manage, Permission_ManageCustom))
return;
var sb = new StringBuilder();
var loadout = GetPlayerActiveLoadout(player.Id);
if (loadout == null)
sb.AppendLine(GetMessage(player, "Command.Default.NoActive"));
else
{
sb.AppendLine(GetMessage(player, "Command.Default.Active", loadout.GetDisplayName(player.Id)));
sb.Append(PrintLoadoutDetails(player, loadout));
sb.AppendLine();
}
sb.AppendLine(GetMessage(player, "Command.Default.HelpHint"));
player.Reply(sb.ToString());
}
private void SubCommandHelp(IPlayer player)
{
if (!VerifyPermissionAny(player, Permission_Manage, Permission_ManageCustom))
return;
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Generic.Header"));
sb.AppendLine(GetMessage(player, "Command.Help.Details"));
sb.AppendLine(GetMessage(player, "Command.Help.List"));
sb.AppendLine(GetMessage(player, "Command.Help.Activate"));
if (player.HasPermission(Permission_ManageCustom))
{
sb.AppendLine(GetMessage(player, "Command.Help.Save"));
sb.AppendLine(GetMessage(player, "Command.Help.Update"));
sb.AppendLine(GetMessage(player, "Command.Help.Rename"));
sb.AppendLine(GetMessage(player, "Command.Help.Delete"));
}
player.Reply(sb.ToString());
}
private void SubCommandList(IPlayer player)
{
if (!VerifyPermissionAny(player, Permission_Manage, Permission_ManageCustom))
return;
var playerData = GetPlayerData(player);
// Prune loadouts that are no longer valid
// For example, if the player no longer has permission to the weapon type
playerData.RestrictAndPruneLoadouts(GetPlayerLoadoutRuleset(player));
var defaultLoadout = GetPlayerLastAllowedProfile(_pluginConfig.DefaultLoadouts, player.Id);
if (playerData.Loadouts.Count == 0 && defaultLoadout == null)
{
ReplyToPlayer(player, "Command.List.NoLoadouts");
return;
}
var loadouts = playerData.Loadouts.ToArray();
Array.Sort(loadouts, SortLoadoutNames);
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Generic.Header"));
if (defaultLoadout != null)
AddListItem(sb, player, defaultLoadout, playerData.ActiveLoadout);
foreach (var loadout in loadouts)
AddListItem(sb, player, loadout, playerData.ActiveLoadout);
sb.AppendLine();
sb.AppendLine(GetMessage(player, "Command.List.ToggleHint"));
player.Reply(sb.ToString());
}
private void AddListItem(StringBuilder sb, IPlayer player, TurretLoadout loadout, string activeLoadout)
{
var weaponDefinition = ItemManager.FindItemDefinition(loadout.Weapon);
var activeString = loadout.IsDefault && activeLoadout == null || activeLoadout == loadout.Name ? GetMessage(player, "Command.List.Item.Active") : string.Empty;
var attachmentAbbreviations = AbbreviateAttachments(player, loadout);
var attachmentsString = attachmentAbbreviations == null ? string.Empty : string.Format(" ({0})", string.Join(", ", attachmentAbbreviations));
sb.AppendLine(GetMessage(player, "Command.List.Item", activeString, loadout.GetDisplayName(player.Id), weaponDefinition.displayName.translated, attachmentsString));
}
private IEnumerable<string> AbbreviateAttachments(IPlayer player, TurretLoadout loadout)
{
if (loadout.Attachments == null || loadout.Attachments.Count == 0)
return null;
return loadout.Attachments.Select(attachmentName =>
{
var langKey = string.Format("Abbreviation.{0}", attachmentName);
var abbreviated = GetMessage(player, langKey);
return abbreviated == langKey ? attachmentName : abbreviated;
});
}
private void SubCommandSave(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, Permission_ManageCustom))
return;
if (args.Length == 0)
{
ReplyToPlayer(player, "Command.Save.Error.Syntax");
return;
}
var loadoutName = args[0];
if (!VerifyLoadoutNameLength(player, loadoutName))
return;
if (MatchesDefaultLoadoutName(player, loadoutName))
{
ReplyToPlayer(player, "Generic.Error.DefaultLoadout");
return;
}
var playerData = GetPlayerData(player);
var loadoutRuleset = GetPlayerLoadoutRuleset(player);
playerData.RestrictAndPruneLoadouts(loadoutRuleset);
if (playerData.HasLoadout(loadoutName))
{
ReplyToPlayer(player, "Command.Save.Error.LoadoutExists", loadoutName);
return;
}
if (playerData.Loadouts.Count >= _pluginConfig.MaxLoadoutsPerPlayer)
{
ReplyToPlayer(player, "Command.Save.Error.TooManyLoadouts", _pluginConfig.MaxLoadoutsPerPlayer);
return;
}
var basePlayer = player.Object as BasePlayer;
AutoTurret turret;
TurretLoadout loadout;
if (!VerifyTurretFound(basePlayer, out turret) ||
!VerifyTurretLoadoutValid(player, turret, out loadout))
return;
var disallowedItems = new Dictionary<string, int>();
if (playerData.ValidateAndPossiblyReduceLoadout(loadout, loadoutRuleset, disallowedItems) == LoadoutManager.ValidationResult.DisallowedWeapon)
{
var itemDefinition = ItemManager.FindItemDefinition(loadout.Weapon);
ReplyToPlayer(player, "Generic.Error.WeaponNotAllowed", itemDefinition.displayName.translated);
return;
}
loadout.Name = loadoutName;
playerData.SaveLoadout(loadout);
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Command.Save.Success", loadoutName));
sb.Append(PrintLoadoutDetails(player, loadout));
if (disallowedItems != null && !disallowedItems.IsEmpty())
sb.Append(PrintDisallowedItems(player, disallowedItems));
player.Reply(sb.ToString());
}
private void SubCommandUpdate(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, Permission_ManageCustom))
return;
if (args.Length < 1)
{
ReplyToPlayer(player, "Command.Update.Error.Syntax");
return;
}
var loadoutName = args[0];
if (MatchesDefaultLoadoutName(player, loadoutName))
{
ReplyToPlayer(player, "Generic.Error.DefaultLoadout");
return;
}
var basePlayer = player.Object as BasePlayer;
AutoTurret turret;
TurretLoadout newLoadout;
if (!VerifyTurretFound(basePlayer, out turret) ||
!VerifyTurretLoadoutValid(player, turret, out newLoadout))
return;
var playerData = GetPlayerData(player);
var loadoutPermission = GetPlayerLoadoutRuleset(player);
playerData.RestrictAndPruneLoadouts(loadoutPermission);
TurretLoadout existingLoadout;
if (!VerifyHasLoadout(player, loadoutName, out existingLoadout))
return;
var disallowedItems = new Dictionary<string, int>();
if (playerData.ValidateAndPossiblyReduceLoadout(newLoadout, loadoutPermission, disallowedItems) == LoadoutManager.ValidationResult.DisallowedWeapon)
{
var itemDefinition = ItemManager.FindItemDefinition(newLoadout.Weapon);
ReplyToPlayer(player, "Generic.Error.WeaponNotAllowed", itemDefinition.displayName.translated);
return;
}
newLoadout.Name = loadoutName;
GetPlayerData(player).TryUpdateLoadout(newLoadout);
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Command.Update.Success", loadoutName));
sb.Append(PrintLoadoutDetails(player, newLoadout));
if (disallowedItems != null && !disallowedItems.IsEmpty())
sb.Append(PrintDisallowedItems(player, disallowedItems));
player.Reply(sb.ToString());
}
private string PrintDisallowedItems(IPlayer player, Dictionary<string, int> disallowedItems)
{
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Generic.RestrictedItems"));
foreach (var entry in disallowedItems)
sb.AppendLine(string.Format(" {0} {1}", entry.Value, GetItemDisplayName(entry.Key)));
return sb.ToString();
}
private string PrintLoadoutDetails(IPlayer player, TurretLoadout loadout)
{
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, loadout.Peacekeeper ? "Command.Default.Mode.Peacekeeper" : "Command.Default.Mode.AttackAll"));
var ammoString = loadout.Ammo != null && loadout.Ammo.Amount > 0
? string.Format(" ({0} {1})", loadout.Ammo.Amount, GetItemDisplayName(loadout.Ammo.Name))
: string.Empty;
sb.AppendLine(GetMessage(player, "Command.Default.Weapon", GetItemDisplayName(loadout.Weapon), ammoString));
if (loadout.Attachments != null && loadout.Attachments.Count > 0)
{
sb.AppendLine(GetMessage(player, "Command.Default.Attachments"));
foreach (var attachmentName in loadout.Attachments)
sb.AppendLine(string.Format(" {0}", GetItemDisplayName(attachmentName)));
}
if (loadout.ReserveAmmo != null && !loadout.ReserveAmmo.IsEmpty())
{
sb.AppendLine(GetMessage(player, "Command.Default.ReserveAmmo"));
foreach (var ammo in loadout.ReserveAmmo)
{
if (ammo.Amount > 0)
sb.AppendLine(string.Format(" {0} {1}", ammo.Amount, GetItemDisplayName(ammo.Name)));
}
}
return sb.ToString();
}
private void SubCommandRename(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, Permission_ManageCustom))
return;
if (args.Length < 2)
{
ReplyToPlayer(player, "Command.Rename.Error.Syntax");
return;
}
var oldName = args[0];
var newName = args[1];
if (MatchesDefaultLoadoutName(player, oldName) || MatchesDefaultLoadoutName(player, newName))
{
ReplyToPlayer(player, "Generic.Error.DefaultLoadout");
return;
}
TurretLoadout loadout;
if (!VerifyHasLoadout(player, oldName, out loadout) ||
!VerifyLoadoutNameLength(player, newName))
return;
var playerData = GetPlayerData(player);
var existingLoadoutWithNewName = playerData.FindByName(newName);
// Allow renaming if just changing case
if (existingLoadoutWithNewName != null && loadout != existingLoadoutWithNewName)
{
ReplyToPlayer(player, "Command.Rename.Error.LoadoutNameTaken", existingLoadoutWithNewName.Name);
return;
}
var actualOldLoadoutName = loadout.Name;
playerData.RenameLoadout(loadout, newName);
if (playerData.ActiveLoadout == actualOldLoadoutName)
playerData.ActiveLoadout = newName;
ReplyToPlayer(player, "Command.Rename.Success", actualOldLoadoutName, newName);
}
private void SubCommandDelete(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, Permission_ManageCustom))
return;
if (args.Length < 1)
{
ReplyToPlayer(player, "Command.Delete.Error.Syntax");
return;
}
var loadoutName = args[0];
TurretLoadout loadout;
if (!VerifyHasLoadout(player, loadoutName, out loadout))
return;
if (loadout.IsDefault)
{
ReplyToPlayer(player, "Generic.Error.DefaultLoadout");
return;
}
GetPlayerData(player).DeleteLoadout(loadout);
ReplyToPlayer(player, "Command.Delete.Success", loadout.Name);
}
private void SubCommandActivate(IPlayer player, string[] args)
{
if (!VerifyPermissionAny(player, Permission_Manage, Permission_ManageCustom))
return;
if (args.Length < 1)
{
ReplyToPlayer(player, "Command.Activate.Error.Syntax");
return;
}
var loadoutName = args[0];
TurretLoadout loadout;
if (!VerifyHasLoadout(player, loadoutName, out loadout, matchPartial: true))
return;
var playerData = GetPlayerData(player);
if (loadout.IsDefault)
playerData.ActiveLoadout = playerData.ActiveLoadout == null ? string.Empty : null;
else
playerData.ActiveLoadout = playerData.ActiveLoadout == loadout.Name ? string.Empty : loadout.Name;
playerData.SaveData();
if (playerData.ActiveLoadout == string.Empty)
ReplyToPlayer(player, "Command.Activate.Success.Deactivated", loadout.GetDisplayName(player.Id));
else
{
var sb = new StringBuilder();
sb.AppendLine(GetMessage(player, "Command.Default.Active", loadout.GetDisplayName(player.Id)));
sb.Append(PrintLoadoutDetails(player, loadout));
ReplyToPlayer(player, sb.ToString());
}
}
#endregion
#region Helper Methods - Command Checks
private bool VerifyPermissionAny(IPlayer player, params string[] permissionNames)
{
if (HasPermissionAny(player, permissionNames))
return true;
ReplyToPlayer(player, "Generic.Error.NoPermission");
return false;
}
private bool VerifyTurretFound(BasePlayer player, out AutoTurret turret)
{
turret = GetLookEntity(player, 3) as AutoTurret;
if (turret != null)
return true;
ReplyToPlayer(player.IPlayer, "Command.Save.Error.NoTurretFound");
return false;
}
private bool VerifyTurretLoadoutValid(IPlayer player, AutoTurret turret, out TurretLoadout loadout)
{
loadout = CreateLoadout(turret);
if (loadout != null)
return true;
ReplyToPlayer(player, "Generic.Error.NoTurretWeapon");
return false;
}
private bool VerifyHasLoadout(IPlayer player, string loadoutName, out TurretLoadout loadout, bool matchPartial = false)
{
if (MatchesDefaultLoadoutName(player, loadoutName, matchPartial))
loadout = GetPlayerLastAllowedProfile(_pluginConfig.DefaultLoadouts, player.Id);
else
loadout = GetPlayerData(player).FindByName(loadoutName, matchPartial);
if (loadout != null)
return true;
ReplyToPlayer(player, "Generic.Error.LoadoutNotFound", loadoutName);
return false;
}
private bool VerifyLoadoutNameLength(IPlayer player, string loadoutName)
{
if (loadoutName.Length <= LoadoutNameMaxLength)
return true;
ReplyToPlayer(player, "Generic.Error.LoadoutNameLength", LoadoutNameMaxLength);
return false;
}
#endregion
#region Helper Methods - Turrets
private void SetupLockedContainer(ContainerIOEntity container)
{
container.dropsLoot = false;
container.pickup.requireEmptyInv = false;
}
private void SetupLockedContainer(StorageContainer container)
{
container.dropsLoot = false;
container.pickup.requireEmptyInv = false;
}
private bool IsLocked(IItemContainerEntity containerEntity) =>
containerEntity.inventory?.IsLocked() ?? false;
private int GetTotalAmmo(AutoTurret turret)
{
if (turret == null || turret.inventory == null)
return 0;
var weapon = turret.GetAttachedWeapon();
if (weapon == null)
return 0;
// AutoTurret.GetTotalAmmo() only includes the reserve ammo, not the loaded ammo
return weapon.primaryMagazine.contents + turret.GetTotalAmmo();
}
private void MaybeAuthTurret(AutoTurret turret, BasePlayer ownerPlayer)
{
if (!HasPermissionAny(ownerPlayer, Permission_AutoAuth))
return;
if (turret.IsAuthed(ownerPlayer))
return;
turret.authorizedPlayers.Add(new PlayerNameID
{
userid = ownerPlayer.userID,
username = ownerPlayer.UserIDString
});
}
private TurretLoadout CreateLoadout(AutoTurret turret)
{
var heldEntity = turret.AttachedWeapon;
if (heldEntity == null)
return null;
var weaponItem = turret.inventory.GetSlot(0);
if (weaponItem == null)
return null;
var loadout = new TurretLoadout()
{
Weapon = weaponItem.info.shortname,
Skin = weaponItem.skin,
Peacekeeper = turret.PeacekeeperMode()
};
if (weaponItem.contents != null)
{
var attachments = new List<string>();
for (var slot = 0; slot < weaponItem.contents.capacity; slot++)
{
var attachmentItem = weaponItem.contents.GetSlot(slot);
if (attachmentItem != null)
attachments.Add(attachmentItem.info.shortname);
}
if (attachments.Count > 0)
loadout.Attachments = attachments;
}
var weapon = heldEntity as BaseProjectile;
if (weapon != null && weapon.primaryMagazine.contents > 0)
{
loadout.Ammo = new AmmoAmount
{
Name = weapon.primaryMagazine.ammoType.shortname,
Amount = weapon.primaryMagazine.contents,
};
}
var reserveAmmo = new List<AmmoAmount>();
for (var slot = 1; slot <= 6; slot++)
{
var ammoItem = turret.inventory.GetSlot(slot);
if (ammoItem == null || ammoItem.amount <= 0)
continue;
reserveAmmo.Add(new AmmoAmount
{
Name = ammoItem.info.shortname,
Amount = ammoItem.amount
});
}
if (reserveAmmo.Count > 0)
loadout.ReserveAmmo = reserveAmmo;
return loadout;
}
private Item AddHeldEntity(AutoTurret turret, BasePlayer ownerPlayer, TurretLoadout loadout)
{
var heldItem = ItemManager.CreateByName(loadout.Weapon, 1, loadout.Skin);
if (heldItem == null)
{
LogError($"Weapon '{loadout.Weapon}' is not a valid item.Unable to add weapon to turret for player {ownerPlayer.userID}.");
return null;
}
if (loadout.Attachments != null)
{
foreach (var attachmentName in loadout.Attachments)
{
var attachmentItem = ItemManager.CreateByName(attachmentName);
if (attachmentItem == null)
{
LogError($"Attachment '{attachmentName}' is not a valid item.Unable to add to turret weapon for player {ownerPlayer.userID}.");
}
else if (!attachmentItem.MoveToContainer(heldItem.contents))
{
attachmentItem.Remove();
}
}
}
if (!heldItem.MoveToContainer(turret.inventory, 0))
{
heldItem.Remove();
return null;
}
var weapon = heldItem.GetHeldEntity() as BaseProjectile;
if (weapon != null)
{
// Must unload the weapon first or the turret will unload it and the ammo will temporarily take up inventory space
weapon.primaryMagazine.contents = 0;
turret.UpdateAttachedWeapon();
turret.CancelInvoke(turret.UpdateAttachedWeapon);
if (loadout.Ammo != null)
{
ItemDefinition loadedAmmoItemDefinition = ItemManager.FindItemDefinition(loadout.Ammo.Name);
if (loadedAmmoItemDefinition == null)
{
LogError($"Ammo type '{loadout.Ammo.Name}' is not a valid item.Unable to add ammo to turret for player {ownerPlayer.userID}.");
return heldItem;
}
weapon.primaryMagazine.ammoType = loadedAmmoItemDefinition;
weapon.primaryMagazine.contents = Math.Min(weapon.primaryMagazine.capacity, loadout.Ammo.Amount);
}
}
return heldItem;
}
private void AddReserveAmmo(ItemContainer container, BaseLoadout loadout, BasePlayer ownerPlayer, int firstSlot = 0)
{
if (loadout.ReserveAmmo == null)
return;
var slot = firstSlot;
var maxSlot = container.capacity - 1;
foreach (var ammo in loadout.ReserveAmmo)
{
if (slot > maxSlot)
break;
if (ammo.Amount <= 0)
continue;
var itemDefinition = ItemManager.FindItemDefinition(ammo.Name);
if (itemDefinition == null)
{
LogError($"Ammo type '{ammo.Name}' is not a valid item.Unable to add ammo to turret for player {ownerPlayer.userID}.");
continue;
}
// Allow default loadouts to bypass max stack size
var amountToAdd = loadout.IsDefault ? ammo.Amount : Math.Min(ammo.Amount, itemDefinition.stackable);
var ammoItem = ItemManager.Create(itemDefinition, amountToAdd);
if (!ammoItem.MoveToContainer(container, slot))
ammoItem.Remove();
if (ammoItem.parent != container)
{
// The item was split due to max stack size.if (loadout.IsDefault)
{
var destinationItem = container.GetSlot(slot);
if (destinationItem != null)
{
destinationItem.amount = amountToAdd;
destinationItem.MarkDirty();
}
}
ammoItem.Remove();
}
slot++;
}
}
#endregion
#region Helper Methods - Misc
private bool HasPermissionAny(BasePlayer basePlayer, params string[] permissionNames) =>
HasPermissionAny(basePlayer.UserIDString, permissionNames);
private bool HasPermissionAny(IPlayer player, params string[] permissionNames) =>
HasPermissionAny(player.Id, permissionNames);
private bool HasPermissionAny(string userId, params string[] permissionNames)
{
foreach (var perm in permissionNames)
if (permission.UserHasPermission(userId, perm))
return true;
return false;
}
private bool MatchesDefaultLoadoutName(IPlayer player, string loadoutName, bool matchPartial = false)
{
var defaultLoadoutName = GetDefaultLoadoutName(player.Id);
return matchPartial
? defaultLoadoutName.IndexOf(loadoutName, StringComparison.CurrentCultureIgnoreCase) >= 0
: defaultLoadoutName.Equals(loadoutName, StringComparison.CurrentCultureIgnoreCase);
}
private string GetDefaultLoadoutName(string userIdString) =>
GetMessage(userIdString, "Generic.DefaultLoadoutName");
private int SortLoadoutNames(TurretLoadout a, TurretLoadout b) =>
a.Name.ToLower().CompareTo(b.Name.ToLower());
private string GetItemDisplayName(string shortname)
{
var itemDefinition = ItemManager.FindItemDefinition(shortname);
return itemDefinition == null ? shortname : itemDefinition.displayName.translated;
}
private BaseEntity GetLookEntity(BasePlayer basePlayer, int maxDistance)
{
RaycastHit hit;
return !Physics.Raycast(basePlayer.eyes.HeadRay(), out hit, maxDistance) ? null : hit.GetEntity();
}
private static void AddToDictKey(Dictionary<string, int> dict, string key, int amount)
{
if (dict.ContainsKey(key))
dict[key] += amount;
else
dict[key] = amount;
}
#endregion
#region Data Management
private PlayerData GetPlayerData(IPlayer player) =>
GetPlayerData(player.Id);
private PlayerData GetPlayerData(string userIdString)
{
PlayerData data;
if (_playerDataCache.TryGetValue(userIdString, out data))
return data;
data = PlayerData.Get(userIdString);
_playerDataCache[userIdString] = data;
return data;
}
private class PlayerData : LoadoutManager
{
public static PlayerData Get(string ownerId)
{
var filepath = GetFilepath(ownerId);
var data = Interface.Oxide.DataFileSystem.ExistsDatafile(filepath) ?
Interface.Oxide.DataFileSystem.ReadObject<PlayerData>(filepath) :
new PlayerData(ownerId);
return data;
}
private static string GetFilepath(string ownerId) => $"{_pluginInstance.Name}/{ownerId}";
[JsonProperty("OwnerId")]
public string OwnerId { get; private set; }
[JsonProperty("ActiveLoadout", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string ActiveLoadout;
public PlayerData(string ownerId)
{
this.OwnerId = ownerId;
}
public override void SaveData() =>
Interface.Oxide.DataFileSystem.WriteObject(GetFilepath(OwnerId), this);
// Remove loadouts where the player no longer has permission to the weapon type
// Update other loadouts to remove disallowed items
public void RestrictAndPruneLoadouts(LoadoutRuleset ruleset)
{
var changed = false;
for (var i = 0; i < Loadouts.Count; i++)
{
var loadout = Loadouts[i];
var validationResult = ValidateAndPossiblyReduceLoadout(loadout, ruleset);
if (validationResult == ValidationResult.InvalidWeapon)
_pluginInstance.LogWarning($"Removed turret loadout '{loadout.Name}' for player '{OwnerId}' because weapon '{loadout.Weapon}' is not a valid item.");
else if (validationResult == ValidationResult.DisallowedWeapon)
_pluginInstance.LogWarning($"Removed turret loadout '{loadout.Name}' for player '{OwnerId}' because they are no longer allowed to use loadouts with weapon '{loadout.Weapon}'.");
if (validationResult == ValidationResult.InvalidWeapon || validationResult == ValidationResult.DisallowedWeapon)
{
Loadouts.RemoveAt(i);
i--;
}
if (validationResult != ValidationResult.Valid)
changed = true;
}
if (changed)
SaveData();
}
}
private abstract class LoadoutManager
{
public enum ValidationResult { Valid, Changed, InvalidWeapon, DisallowedWeapon }
private static bool MatchesLoadout(TurretLoadout loadout, string name, bool matchPartial = false) =>
matchPartial
? loadout.Name.IndexOf(name, StringComparison.CurrentCultureIgnoreCase) >= 0
: loadout.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase);
[JsonProperty("Loadouts")]
public List<TurretLoadout> Loadouts = new List<TurretLoadout>();
public TurretLoadout FindByName(string loadoutName, bool matchPartial = false)
{
if (Loadouts == null)
return null;
foreach (var currentLoadout in Loadouts)
{
if (MatchesLoadout(currentLoadout, loadoutName))
return currentLoadout;
}
if (matchPartial)
{
// Perform partial matching in a second pass so the first pass can get a closer match
foreach (var currentLoadout in Loadouts)
{
if (MatchesLoadout(currentLoadout, loadoutName, matchPartial: true))
return currentLoadout;
}
}
return null;
}
public bool HasLoadout(string loadoutName) => FindByName(loadoutName) != null;
public void SaveLoadout(TurretLoadout loadout)
{
Loadouts.Add(loadout);
SaveData();
}
public bool TryUpdateLoadout(TurretLoadout newLoadout)
{
var existingLoadout = FindByName(newLoadout.Name);
if (existingLoadout == null)
return false;
Loadouts[Loadouts.IndexOf(existingLoadout)] = newLoadout;
SaveData();
return true;
}
public void RenameLoadout(TurretLoadout loadout, string newName)
{
loadout.Name = newName;
SaveData();
}
public void DeleteLoadout(TurretLoadout loadout)
{
Loadouts.Remove(loadout);
SaveData();
}
// Removes items from the loadout not currently allowed by the ruleset
// The loadout may no longer be valid due to containing an invalid or disallowed weapon
// This can be determined by checking the validation result
public ValidationResult ValidateAndPossiblyReduceLoadout(TurretLoadout loadout, LoadoutRuleset loadoutRuleset, Dictionary<string, int> disallowedItems = null)
{
if (disallowedItems == null)
disallowedItems = new Dictionary<string, int>();
var weaponDefinition = ItemManager.FindItemDefinition(loadout.Weapon);
if (weaponDefinition == null)
return ValidationResult.InvalidWeapon;
if (!loadoutRuleset.IsWeaponAllowed(loadout.Weapon))
{
disallowedItems[loadout.Weapon] = 1;
return ValidationResult.DisallowedWeapon;
}
if (loadout.Attachments != null)
{
for (var i = 0; i < loadout.Attachments.Count; i++)
{
var attachmentName = loadout.Attachments[i];
if (!loadoutRuleset.IsAttachmentAllowed(attachmentName))
{
disallowedItems[attachmentName] = 1;
loadout.Attachments.RemoveAt(i);
i--;
}
}
}
var allowedAmmo = loadoutRuleset.AllowedAmmo;
var countedAmmo = new Dictionary<string, int>();
// Don't impose ammo limits if allowed ammo is null
if (loadout.Ammo != null && allowedAmmo != null)
{
var ammo = loadout.Ammo;
// Make sure ammo name exists
if (ItemManager.FindItemDefinition(ammo.Name) == null)
{
_pluginInstance.LogWarning($"Ammo type '{ammo.Name}' is not a valid item.Removing from loadout.");
ammo.Amount = 0;
}
else if (allowedAmmo.ContainsKey(ammo.Name))
{
var allowedAmount = allowedAmmo[ammo.Name];
// Don't impose a limit if the allowed amount is negative
if (allowedAmount >= 0 && ammo.Amount > allowedAmount)
{
// Reduce ammo to the allowed amount
AddToDictKey(disallowedItems, ammo.Name, ammo.Amount - allowedAmount);
ammo.Amount = allowedAmount;
}
}
else
{
// Ammo not allowed
AddToDictKey(disallowedItems, ammo.Name, ammo.Amount);
ammo.Amount = 0;
}
if (ammo.Amount <= 0)
loadout.Ammo = null;
else
AddToDictKey(countedAmmo, ammo.Name, ammo.Amount);
}
// Don't impose ammo limits if allowed ammo is null
if (loadout.ReserveAmmo != null && allowedAmmo != null)
{
for (var i = 0; i < loadout.ReserveAmmo.Count; i++)
{
var ammo = loadout.ReserveAmmo[i];
// Make sure ammo name exists
if (ItemManager.FindItemDefinition(ammo.Name) == null)
{
_pluginInstance.LogWarning($"Ammo type '{ammo.Name}' is not a valid item.Removing from loadout.");
ammo.Amount = 0;
}
else if (allowedAmmo.ContainsKey(ammo.Name))
{
// Don't impose a limit if the allowed amount is negative
if (allowedAmmo[ammo.Name] >= 0)
{
var countedAmount = countedAmmo.ContainsKey(ammo.Name) ? countedAmmo[ammo.Name] : 0;
var remainingAllowedAmount = allowedAmmo[ammo.Name] - countedAmount;
if (ammo.Amount > remainingAllowedAmount)
{
// Reduce ammo to the allowed amount
AddToDictKey(disallowedItems, ammo.Name, ammo.Amount - remainingAllowedAmount);
// ... [Code preview truncated due to length. Full source in attached file] ...