Created
December 5, 2024 21:49
-
-
Save acidumirae/f9333f9995753863c987437c9e9df05e to your computer and use it in GitHub Desktop.
Quick and dirty fix for 'PlayerInventory' does not contain a definition for 'AllItemsNoAlloc'
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Facepunch; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Converters; | |
using Oxide.Core; | |
using Oxide.Core.Configuration; | |
using Oxide.Core.Libraries; | |
using Oxide.Core.Plugins; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
using System.Text; | |
using Newtonsoft.Json.Serialization; | |
using Oxide.Ext.Chaos; | |
using Oxide.Ext.Chaos.UIFramework; | |
using ProtoBuf; | |
using UnityEngine; | |
using Color = Oxide.Ext.Chaos.UIFramework.Color; | |
namespace Oxide.Plugins | |
{ | |
[Info("SkinBox", "k1lly0u", "2.2.15")] | |
[Description("Allows you to reskin item's by placing it in the SkinBox and selecting a new skin")] | |
class SkinBox : ChaosPlugin | |
{ | |
#region Fields | |
private Hash<ulong, string> _skinPermissions = new Hash<ulong, string>(); | |
private readonly Hash<string, HashSet<ulong>> _skinList = new Hash<string, HashSet<ulong>>(); | |
private readonly Hash<ulong, LootHandler> _activeSkinBoxes = new Hash<ulong, LootHandler>(); | |
private readonly Hash<string, string> _shortnameToDisplayname = new Hash<string, string>(); | |
private readonly Hash<ulong, string> _skinSearchLookup = new Hash<ulong, string>(); | |
private readonly Hash<ulong, string> _skinNameLookup = new Hash<ulong, string>(); | |
private readonly Hash<ulong, double> _cooldownTimes = new Hash<ulong, double>(); | |
private bool _apiKeyMissing = false; | |
private bool _skinsLoaded = false; | |
private static SkinBox Instance { get; set; } | |
private static Func<string, BasePlayer, string> GetMessage; | |
private const ulong SPRAYCAN_SKIN = 2937962221UL; | |
private const string LOOT_PANEL = "generic_resizable"; | |
private const int SCRAP_ITEM_ID = -932201673; | |
private const int SET_SLOTS = 12; | |
[JsonConverter(typeof(StringEnumConverter))] | |
private enum CostType { Scrap, ServerRewards, Economics } | |
[JsonConverter(typeof(StringEnumConverter))] | |
public enum SortBy { Config = 0, ConfigReversed = 1, Alphabetical = 2 } | |
#endregion | |
#region Oxide Hooks | |
private void Loaded() | |
{ | |
Instance = this; | |
InitializeUI(); | |
Configuration.Permission.RegisterPermissions(permission, this); | |
Configuration.Command.RegisterCommands(cmd, this); | |
if (!Configuration.Command.SprayCan) | |
{ | |
Unsubscribe(nameof(OnSprayCreate)); | |
Unsubscribe(nameof(OnActiveItemChanged)); | |
} | |
if (Configuration.Favourites.Enabled) | |
StoredData.Load(); | |
GetMessage = GetString;// (string key, ulong userId) => lang.GetMessage(key, this, userId.ToString()); | |
} | |
private void OnServerInitialized() | |
{ | |
UnsubscribeHooks(); | |
if (string.IsNullOrEmpty(Configuration.Skins.APIKey)) | |
{ | |
_apiKeyMissing = true; | |
SendAPIMissingWarning(); | |
return; | |
} | |
if (Configuration.Skins.UseApproved || Configuration.Skins.ApprovedItems.Count > 0) | |
CheckSteamDefinitionsUpdated(); | |
else StartSkinRequest(); | |
} | |
private object CanAcceptItem(ItemContainer container, Item item) | |
{ | |
if (container.entityOwner) | |
{ | |
LootHandler lootHandler = container.entityOwner.GetComponent<LootHandler>(); | |
if (!lootHandler) | |
return null; | |
if (Configuration.ReskinItemBlocklist.Contains(item.info.shortname)) | |
{ | |
lootHandler.PopupMessage(GetMessage("BlockedItem.Item", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
if (Configuration.ReskinSkinBlocklist.Contains(item.skin)) | |
{ | |
lootHandler.PopupMessage(GetMessage("BlockedItem.Skin", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
if (lootHandler is SkinSetHandler skinSetHandler) | |
{ | |
skinSetHandler.OnCanAcceptItem(item); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
if (item.isBroken) | |
{ | |
lootHandler.PopupMessage(GetMessage("BrokenItem", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
if (lootHandler.HasItem) | |
{ | |
lootHandler.PopupMessage(GetMessage("HasItem", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
if (!HasItemPermissions(lootHandler.Looter, item)) | |
{ | |
lootHandler.PopupMessage(GetMessage("InsufficientItemPermission", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
string shortname = GetRedirectedShortname(item.info.shortname); | |
if (!Configuration.Skins.UseRedirected && !shortname.Equals(item.info.shortname, StringComparison.OrdinalIgnoreCase)) | |
{ | |
lootHandler.PopupMessage(GetMessage("RedirectsDisabled", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
if (!_skinList.TryGetValue(shortname, out HashSet<ulong> skins) || skins.Count == 0) | |
{ | |
lootHandler.PopupMessage(GetMessage("NoSkinsForItem", lootHandler.Looter)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
int reskinCost = GetReskinCost(item); | |
if (reskinCost > 0 && !CanAffordToUse(lootHandler.Looter, reskinCost)) | |
{ | |
lootHandler.PopupMessage(string.Format(GetMessage("NotEnoughBalanceUse", lootHandler.Looter), | |
reskinCost, | |
GetCostType(lootHandler.Looter), | |
item.info.displayName.english)); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
string result = Interface.Call<string>("SB_CanAcceptItem", lootHandler.Looter, item); | |
if (!string.IsNullOrEmpty(result)) | |
{ | |
lootHandler.PopupMessage(result); | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
} | |
if (item.parent?.entityOwner) | |
{ | |
SkinSetHandler lootHandler = item.parent.entityOwner.GetComponent<SkinSetHandler>(); | |
if (lootHandler) | |
return ItemContainer.CanAcceptResult.CannotAccept; | |
} | |
return null; | |
} | |
private void OnItemSplit(Item item, int amount) | |
{ | |
if (item.parent?.entityOwner) | |
{ | |
LootHandler lootHandler = item.parent.entityOwner.GetComponent<LootHandler>(); | |
if (lootHandler != null) | |
lootHandler.CheckItemHasSplit(item); | |
} | |
} | |
private object CanMoveItem(Item item, PlayerInventory inventory, ItemContainerId targetContainerID, int targetSlot, int amount, ItemMoveModifier itemMoveModifier) | |
{ | |
if (item.parent?.entityOwner) | |
{ | |
LootHandler lootHandler = item.parent.entityOwner.GetComponent<LootHandler>(); | |
if (lootHandler) | |
{ | |
if (lootHandler is SkinSetHandler skinSetHandler) | |
{ | |
if (targetSlot == -1) | |
skinSetHandler.RemoveItemFromSet(item); | |
return false; | |
} | |
if (lootHandler.InputAmount > 1) | |
{ | |
if (targetContainerID == default(ItemContainerId) || targetSlot == -1) | |
return false; | |
ItemContainer targetContainer = inventory.FindContainer(targetContainerID); | |
if (targetContainer?.GetSlot(targetSlot) != null) | |
return false; | |
} | |
} | |
} | |
return null; | |
} | |
private object OnItemAction(Item item, string action, BasePlayer player) | |
{ | |
if (!_activeSkinBoxes.TryGetValue(player.userID, out LootHandler lootHandler)) | |
return null; | |
const string DROP_ACTION = "drop"; | |
if (!action.Equals(DROP_ACTION, StringComparison.OrdinalIgnoreCase)) | |
return null; | |
if (lootHandler.Entity.inventory?.itemList?.Contains(item) ?? false) | |
return true; | |
return null; | |
} | |
private void OnItemAddedToContainer(ItemContainer container, Item item) | |
{ | |
if (container?.entityOwner == null || container.entityOwner.IsDestroyed) | |
return; | |
LootHandler lootHandler = container.entityOwner.GetComponent<LootHandler>(); | |
if (lootHandler != null) | |
lootHandler.OnItemAdded(item); | |
} | |
private void OnItemRemovedFromContainer(ItemContainer container, Item item) | |
{ | |
if (container?.entityOwner == null || container.entityOwner.IsDestroyed) | |
return; | |
LootHandler lootHandler = container.entityOwner.GetComponent<LootHandler>(); | |
if (lootHandler != null) | |
lootHandler.OnItemRemoved(item); | |
} | |
private void OnLootEntityEnd(BasePlayer player, StorageContainer storageContainer) | |
{ | |
if (_activeSkinBoxes.TryGetValue(player.userID, out LootHandler lootHandler)) | |
UnityEngine.Object.DestroyImmediate(lootHandler); | |
} | |
private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo) | |
{ | |
UnityEngine.Object.Destroy(player.GetComponent<SprayCanWatcher>()); | |
if (!_activeSkinBoxes.TryGetValue(player.userID, out LootHandler lootHandler)) | |
return; | |
lootHandler.ReturnItemInstantly(); | |
player.EndLooting(); | |
UnityEngine.Object.DestroyImmediate(lootHandler); | |
} | |
private void OnPlayerDisconnected(BasePlayer player) | |
{ | |
UnityEngine.Object.Destroy(player.GetComponent<SprayCanWatcher>()); | |
} | |
private void OnServerSave() | |
{ | |
if (Configuration.Favourites.Enabled) | |
StoredData.Save(); | |
} | |
private void Unload() | |
{ | |
SprayCanWatcher[] sprayCanWatchers = UnityEngine.Object.FindObjectsOfType<SprayCanWatcher>(); | |
for (int i = 0; i < sprayCanWatchers.Length; i++) | |
UnityEngine.Object.Destroy(sprayCanWatchers[i]); | |
LootHandler[] lootHandlers = UnityEngine.Object.FindObjectsOfType<LootHandler>(); | |
for (int i = 0; i < lootHandlers.Length; i++) | |
{ | |
LootHandler lootHandler = lootHandlers[i]; | |
if (lootHandler.Looter != null) | |
lootHandler.Looter.EndLooting(); | |
UnityEngine.Object.Destroy(lootHandler); | |
} | |
Configuration = null; | |
Instance = null; | |
} | |
#endregion | |
#region Spraycan Shit | |
private object OnSprayCreate(SprayCan sprayCan, Vector3 position, Quaternion rotation) | |
{ | |
Item item = sprayCan.GetItem(); | |
if (item.skin == SPRAYCAN_SKIN) | |
return false; | |
return null; | |
} | |
private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem) | |
{ | |
if (oldItem?.skin == SPRAYCAN_SKIN) | |
UnityEngine.Object.Destroy(player.GetComponent<SprayCanWatcher>()); | |
if (newItem?.skin == SPRAYCAN_SKIN) | |
player.gameObject.AddComponent<SprayCanWatcher>(); | |
} | |
#endregion | |
#region Functions | |
private void SendAPIMissingWarning() | |
{ | |
Debug.LogWarning("You must enter a Steam API key in the config!\nYou can get a API key here -> https://steamcommunity.com/dev/apikey \nOnce you have your API key copy it to the 'Skin Options/Steam API Key' field in your SkinBox.json config file"); | |
} | |
private void ChatMessage(BasePlayer player, string key, params object[] args) | |
{ | |
if (args == null) | |
player.ChatMessage(lang.GetMessage(key, this, player.UserIDString)); | |
else player.ChatMessage(string.Format(lang.GetMessage(key, this, player.UserIDString), args)); | |
} | |
private LootHandler CreateSkinBox<T>(BasePlayer player, DeployableHandler.ReskinTarget reskinTarget, Action<LootHandler> onOpened = null) where T : LootHandler | |
{ | |
const string COFFIN_PREFAB = "assets/prefabs/misc/halloween/coffin/coffinstorage.prefab"; | |
StorageContainer container = GameManager.server.CreateEntity(COFFIN_PREFAB, player.transform.position + (Vector3.down * (typeof(T) == typeof(SkinSetHandler) ? 240f : 250f))) as StorageContainer; | |
container.limitNetworking = true; | |
container.enableSaving = false; | |
container.inventorySlots = typeof(T) == typeof(SkinSetHandler) ? SET_SLOTS : 48; | |
UnityEngine.Object.Destroy(container.GetComponent<DestroyOnGroundMissing>()); | |
UnityEngine.Object.Destroy(container.GetComponent<GroundWatch>()); | |
container.Spawn(); | |
T lootHandler = container.gameObject.AddComponent<T>(); | |
player.inventory.loot.Clear(); | |
player.inventory.loot.SendImmediate(); | |
timer.In(0.05f, ()=> | |
{ | |
lootHandler.Looter = player; | |
if (reskinTarget != null && lootHandler is DeployableHandler deployableHandler) | |
deployableHandler.Target = reskinTarget; | |
player.inventory.loot.PositionChecks = false; | |
player.inventory.loot.entitySource = container; | |
player.inventory.loot.itemSource = null; | |
player.inventory.loot.MarkDirty(); | |
player.inventory.loot.AddContainer(container.inventory); | |
player.inventory.loot.SendImmediate(); | |
player.ClientRPCPlayer(null, player, "RPC_OpenLootPanel", LOOT_PANEL); | |
container.SendNetworkUpdate(BasePlayer.NetworkQueue.Update); | |
if (Configuration.Cost.Enabled && !player.HasPermission(Configuration.Permission.NoCost)) | |
{ | |
lootHandler.PopupMessage(string.Format(GetMessage("CostToUse2", lootHandler.Looter), Configuration.Cost.Deployable, | |
Configuration.Cost.Weapon, | |
Configuration.Cost.Attire, | |
GetCostType(player))); | |
} | |
onOpened?.Invoke(lootHandler); | |
}); | |
_activeSkinBoxes[player.userID] = lootHandler; | |
ToggleHooks(); | |
return lootHandler; | |
} | |
#endregion | |
#region Hook Subscriptions | |
private void ToggleHooks() | |
{ | |
if (_activeSkinBoxes.Count > 0) | |
SubscribeHooks(); | |
else UnsubscribeHooks(); | |
} | |
private void SubscribeHooks() | |
{ | |
Subscribe(nameof(OnLootEntityEnd)); | |
Subscribe(nameof(OnItemRemovedFromContainer)); | |
Subscribe(nameof(OnItemAddedToContainer)); | |
Subscribe(nameof(CanMoveItem)); | |
Subscribe(nameof(OnItemSplit)); | |
Subscribe(nameof(CanAcceptItem)); | |
} | |
private void UnsubscribeHooks() | |
{ | |
Unsubscribe(nameof(OnLootEntityEnd)); | |
Unsubscribe(nameof(OnItemRemovedFromContainer)); | |
Unsubscribe(nameof(OnItemAddedToContainer)); | |
Unsubscribe(nameof(CanMoveItem)); | |
Unsubscribe(nameof(OnItemSplit)); | |
Unsubscribe(nameof(CanAcceptItem)); | |
} | |
#endregion | |
#region Helpers | |
private void GetSkinsFor(BasePlayer player, string shortname, ref List<ulong> list) | |
{ | |
list.Clear(); | |
List<ulong> skinOverrides = Interface.Call<List<ulong>>("SB_GetSkinOverrides", player, shortname); | |
if (skinOverrides != null && skinOverrides.Count > 0) | |
{ | |
list.AddRange(skinOverrides); | |
goto SORT_FAVOURITES; | |
} | |
if (_skinList.TryGetValue(shortname, out HashSet<ulong> skins)) | |
{ | |
foreach(ulong skinId in skins) | |
{ | |
if (_skinPermissions.TryGetValue(skinId, out string perm) && !player.HasPermission(perm)) | |
continue; | |
if (Configuration.Blacklist.Contains(skinId) && player.net.connection.authLevel < Configuration.Other.BlacklistAuth) | |
continue; | |
list.Add(skinId); | |
} | |
} | |
SORT_FAVOURITES: | |
if (Configuration.Favourites.Enabled) | |
Data.SortForPlayer(player, shortname, ref list); | |
} | |
private bool HasItemPermissions(BasePlayer player, Item item) | |
{ | |
switch (item.info.category) | |
{ | |
case ItemCategory.Weapon: | |
case ItemCategory.Tool: | |
return player.HasPermission(Configuration.Permission.Weapon); | |
case ItemCategory.Construction: | |
case ItemCategory.Items: | |
return player.HasPermission(Configuration.Permission.Deployable); | |
case ItemCategory.Attire: | |
return player.HasPermission(Configuration.Permission.Attire); | |
default: | |
return true; | |
} | |
} | |
#endregion | |
#region Usage Costs | |
private static string GetCostType(BasePlayer player) => GetMessage($"Cost.{Configuration.Cost.Currency}", player); | |
private static int GetReskinCost(Item item) | |
{ | |
if (!Configuration.Cost.Enabled) | |
return 0; | |
return GetReskinCost(item.info); | |
} | |
private static int GetReskinCost(ItemDefinition itemDefinition) | |
{ | |
switch (itemDefinition.category) | |
{ | |
case ItemCategory.Weapon: | |
case ItemCategory.Tool: | |
return Configuration.Cost.Weapon; | |
case ItemCategory.Construction: | |
case ItemCategory.Items: | |
return Configuration.Cost.Deployable; | |
case ItemCategory.Attire: | |
return Configuration.Cost.Attire; | |
default: | |
return 0; | |
} | |
} | |
private bool CanAffordToUse(BasePlayer player, int amount) | |
{ | |
if (!Configuration.Cost.Enabled || amount == 0 || player.HasPermission(Configuration.Permission.NoCost)) | |
return true; | |
switch (Configuration.Cost.Currency) | |
{ | |
case CostType.Scrap: | |
return player.inventory.GetAmount(SCRAP_ITEM_ID) >= amount; | |
case CostType.ServerRewards: | |
return ServerRewards.IsLoaded && ServerRewards.CheckPoints(player.userID) >= amount; | |
case CostType.Economics: | |
return Economics.IsLoaded && Economics.Balance(player.userID) >= amount; | |
} | |
return false; | |
} | |
private static bool ChargePlayer(BasePlayer player, ItemCategory itemCategory) | |
{ | |
if (!Configuration.Cost.Enabled || player.HasPermission(Configuration.Permission.NoCost)) | |
return true; | |
int amount = itemCategory is ItemCategory.Weapon or ItemCategory.Tool ? Configuration.Cost.Weapon : | |
itemCategory is ItemCategory.Items or ItemCategory.Construction ? Configuration.Cost.Deployable : | |
itemCategory == ItemCategory.Attire ? Configuration.Cost.Attire : 0; | |
return ChargePlayer(player, amount); | |
} | |
private static bool ChargePlayer(BasePlayer player, int amount) | |
{ | |
if (amount == 0 || !Configuration.Cost.Enabled || player.HasPermission(Configuration.Permission.NoCost)) | |
return true; | |
switch (Configuration.Cost.Currency) | |
{ | |
case CostType.Scrap: | |
if (amount <= player.inventory.GetAmount(SCRAP_ITEM_ID)) | |
{ | |
player.inventory.Take(null, SCRAP_ITEM_ID, amount); | |
return true; | |
} | |
return false; | |
case CostType.ServerRewards: | |
return ServerRewards.IsLoaded && (bool)ServerRewards.TakePoints(player.userID, amount); | |
case CostType.Economics: | |
return Economics.IsLoaded && Economics.Withdraw(player.userID, (double)amount); | |
} | |
return false; | |
} | |
#endregion | |
#region Cooldown | |
private void ApplyCooldown(BasePlayer player) | |
{ | |
if (!Configuration.Cooldown.Enabled || player.HasPermission(Configuration.Permission.NoCooldown)) | |
return; | |
_cooldownTimes[player.userID] = CurrentTime() + Configuration.Cooldown.Time; | |
} | |
private bool IsOnCooldown(BasePlayer player) | |
{ | |
if (!Configuration.Cooldown.Enabled || player.HasPermission(Configuration.Permission.NoCooldown)) | |
return false; | |
if (_cooldownTimes.TryGetValue(player.userID, out double time) && time > CurrentTime()) | |
return true; | |
return false; | |
} | |
private bool IsOnCooldown(BasePlayer player, out double remaining) | |
{ | |
remaining = 0; | |
if (!Configuration.Cooldown.Enabled || player.HasPermission(Configuration.Permission.NoCooldown)) | |
return false; | |
double currentTime = CurrentTime(); | |
if (_cooldownTimes.TryGetValue(player.userID, out double time) && time > currentTime) | |
{ | |
remaining = time - currentTime; | |
return true; | |
} | |
return false; | |
} | |
#endregion | |
#region Approved Skins | |
private float m_SteamDefinitionsWaitStarted = 0; | |
private void CheckSteamDefinitionsUpdated() | |
{ | |
float time = UnityEngine.Time.time; | |
if (m_SteamDefinitionsWaitStarted == 0) | |
m_SteamDefinitionsWaitStarted = time; | |
float timeWaited = time - m_SteamDefinitionsWaitStarted; | |
if (Configuration.Skins.ApprovedTimeout > 0 && timeWaited > Configuration.Skins.ApprovedTimeout) | |
{ | |
Debug.LogWarning($"[SkinBox] - Aborting approved skin processing as timeout period has elapsed ({Configuration.Skins.ApprovedTimeout}s). The server has not yet downloaded the approved skin manifest. Only workshop skins will be available"); | |
if (!Configuration.Skins.UseWorkshop) | |
{ | |
PrintError("You have workshop skins disabled. This leaves no skins available to use in SkinBox!"); | |
return; | |
} | |
VerifyWorkshopSkins(); | |
return; | |
} | |
if ((Steamworks.SteamInventory.Definitions?.Length ?? 0) == 0) | |
{ | |
if (timeWaited % 10f == 0) | |
Debug.LogWarning($"[SkinBox] - Waiting for Steam inventory definitions to be updated. Total wait time : {Math.Round(time, 1)}s"); | |
timer.In(1f, CheckSteamDefinitionsUpdated); | |
return; | |
} | |
StartSkinRequest(); | |
} | |
private void StartSkinRequest() | |
{ | |
UpdateWorkshopNameConversionList(); | |
FindItemRedirects(); | |
FixLR300InvalidShortname(); | |
if ((!Configuration.Skins.UseApproved && Configuration.Skins.ApprovedItems.Count == 0) && !Configuration.Skins.UseWorkshop) | |
{ | |
PrintError("You have approved skins and workshop skins disabled. This leaves no skins available to use in SkinBox!"); | |
return; | |
} | |
if ((!Configuration.Skins.UseApproved && Configuration.Skins.ApprovedItems.Count == 0) && Configuration.Skins.UseWorkshop) | |
{ | |
VerifyWorkshopSkins(); | |
return; | |
} | |
PrintWarning("Retrieving approved skin lists..."); | |
CollectApprovedSkins(); | |
} | |
private void CollectApprovedSkins() | |
{ | |
int count = 0; | |
bool addApprovedPermission = Configuration.Permission.Approved != Configuration.Permission.Use; | |
bool updateConfig = false; | |
List<ulong> list = Pool.Get<List<ulong>>(); | |
List<int> itemSkinDirectory = Pool.Get<List<int>>(); | |
itemSkinDirectory.AddRange(ItemSkinDirectory.Instance.skins.Select(x => x.id)); | |
bool useManuallyAssignedSkinsOnly = !Configuration.Skins.UseApproved && Configuration.Skins.ApprovedItems.Count > 0; | |
foreach (ItemDefinition itemDefinition in ItemManager.itemList) | |
{ | |
list.Clear(); | |
if (useManuallyAssignedSkinsOnly && !Configuration.Skins.ApprovedItems.Contains(itemDefinition.shortname)) | |
continue; | |
foreach (Steamworks.InventoryDef item in Steamworks.SteamInventory.Definitions) | |
{ | |
string shortname = item.GetProperty("itemshortname"); | |
if (string.IsNullOrEmpty(shortname) || item.Id < 100) | |
continue; | |
if (_workshopNameToShortname.ContainsKey(shortname)) | |
shortname = _workshopNameToShortname[shortname]; | |
if (!shortname.Equals(itemDefinition.shortname, StringComparison.OrdinalIgnoreCase)) | |
continue; | |
ulong skinId; | |
if (itemSkinDirectory.Contains(item.Id)) | |
skinId = (ulong)item.Id; | |
else if (!ulong.TryParse(item.GetProperty("workshopid"), out skinId)) | |
continue; | |
if (list.Contains(skinId) || Configuration.Skins.ApprovedLimit > 0 && list.Count >= Configuration.Skins.ApprovedLimit) | |
continue; | |
list.Add(skinId); | |
_skinNameLookup[skinId] = item.Name; | |
_skinSearchLookup[skinId] = $"{skinId} {item.Name}"; | |
} | |
if (list.Count > 1) | |
{ | |
count += list.Count; | |
if (!_skinList.TryGetValue(itemDefinition.shortname, out HashSet<ulong> skins)) | |
skins = _skinList[itemDefinition.shortname] = new HashSet<ulong>(); | |
int removeCount = 0; | |
list.ForEach((ulong skin) => | |
{ | |
if (Configuration.Skins.RemoveApproved && Configuration.SkinList.ContainsKey(itemDefinition.shortname) && | |
Configuration.SkinList[itemDefinition.shortname].Contains(skin)) | |
{ | |
Configuration.SkinList[itemDefinition.shortname].Remove(skin); | |
removeCount++; | |
updateConfig = true; | |
} | |
skins.Add(skin); | |
if (addApprovedPermission) | |
_skinPermissions[skin] = Configuration.Permission.Approved; | |
}); | |
if (removeCount > 0) | |
Debug.Log($"[SkinBox] Removed {removeCount} approved skin ID's for {itemDefinition.shortname} from the config skin list"); | |
} | |
} | |
if (updateConfig) | |
SaveConfiguration(); | |
Pool.FreeUnmanaged(ref list); | |
Pool.FreeUnmanaged(ref itemSkinDirectory); | |
Debug.Log($"[SkinBox] - Loaded {count} approved skins"); | |
if (Configuration.Skins.UseWorkshop && Configuration.SkinList.Sum(x => x.Value.Count) > 0) | |
VerifyWorkshopSkins(); | |
else | |
{ | |
SortSkinLists(); | |
_skinsLoaded = true; | |
Hash<string, HashSet<ulong>> skinList = new Hash<string, HashSet<ulong>>(); | |
foreach (KeyValuePair<string, HashSet<ulong>> kvp in _skinList) | |
{ | |
HashSet<ulong> skins = new HashSet<ulong>(); | |
foreach (ulong skinId in kvp.Value) | |
skins.Add(skinId); | |
skinList.Add(kvp.Key, skins); | |
} | |
Configuration.Permission.ReverseCustomSkinPermissions(ref _skinPermissions); | |
Interface.Oxide.CallHook("OnSkinBoxSkinsLoaded", skinList); | |
Debug.Log($"[SkinBox] - SkinBox has loaded all required skins and is ready to use! ({_skinList.Values.Sum(x => x.Count)} skins acrosss {_skinList.Count} items)"); | |
} | |
} | |
private void SortSkinLists() | |
{ | |
List<ulong> list = Pool.Get<List<ulong>>(); | |
foreach (KeyValuePair<string, HashSet<ulong>> kvp in _skinList) | |
{ | |
list.AddRange(kvp.Value); | |
SortSkinList(kvp.Key, ref list); | |
kvp.Value.Clear(); | |
list.ForEach((ulong skinId) => kvp.Value.Add(skinId)); | |
list.Clear(); | |
} | |
Pool.FreeUnmanaged(ref list); | |
} | |
private void SortSkinList(string shortname, ref List<ulong> list) | |
{ | |
if (Configuration.Skins.Sorting == SortBy.Alphabetical) | |
{ | |
list.Sort((ulong a, ulong b) => | |
{ | |
_skinNameLookup.TryGetValue(a, out string nameA); | |
_skinNameLookup.TryGetValue(b, out string nameB); | |
return nameA.CompareTo(nameB); | |
}); | |
return; | |
} | |
else | |
{ | |
Configuration.SkinList.TryGetValue(shortname, out HashSet<ulong> configList); | |
if (configList != null) | |
{ | |
List<ulong> l = configList.ToList(); | |
list.Sort((ulong a, ulong b) => | |
{ | |
int indexA = l.IndexOf(a); | |
int indexB = l.IndexOf(b); | |
return Configuration.Skins.Sorting == SortBy.Config ? indexA.CompareTo(indexB) : indexA.CompareTo(indexB) * -1; | |
}); | |
} | |
} | |
} | |
#endregion | |
#region Workshop Skins | |
private List<ulong> _skinsToVerify = new List<ulong>(); | |
private Queue<ulong> _collectionsToVerify = new Queue<ulong>(); | |
private const string PUBLISHED_FILE_DETAILS = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/"; | |
private const string COLLECTION_DETAILS = "https://api.steampowered.com/ISteamRemoteStorage/GetCollectionDetails/v1/"; | |
private const string ITEMS_BODY = "?key={0}&itemcount={1}"; | |
private const string ITEM_ENTRY = "&publishedfileids[{0}]={1}"; | |
private const string COLLECTION_BODY = "?key={0}&collectioncount=1&publishedfileids[0]={1}"; | |
private void VerifyWorkshopSkins() | |
{ | |
foreach (HashSet<ulong> list in Configuration.SkinList.Values) | |
_skinsToVerify.AddRange(list); | |
SendWorkshopQuery(); | |
} | |
private void SendWorkshopQuery(int page = 0, int success = 0, ConsoleSystem.Arg arg = null, string perm = null) | |
{ | |
int totalPages = Mathf.CeilToInt((float)_skinsToVerify.Count / 100f); | |
int index = page * 100; | |
int limit = Mathf.Min((page + 1) * 100, _skinsToVerify.Count); | |
string details = string.Format(ITEMS_BODY, Configuration.Skins.APIKey, (limit - index)); | |
for (int i = index; i < limit; i++) | |
{ | |
details += string.Format(ITEM_ENTRY, i - index, _skinsToVerify[i]); | |
} | |
try | |
{ | |
webrequest.Enqueue(PUBLISHED_FILE_DETAILS, details, (code, response) => ServerMgr.Instance.StartCoroutine(ValidateRequiredSkins(code, response, page + 1, totalPages, success, arg, perm)), this, RequestMethod.POST); | |
} | |
catch { } | |
} | |
public enum CollectionAction { AddSkin, RemoveSkin, ExcludeSkin, RemoveExludeSkin } | |
private JsonSerializerSettings m_ErrorHandling = new JsonSerializerSettings() | |
{ | |
Error = delegate(object sender, ErrorEventArgs args) | |
{ | |
Debug.Log($"[SkinBox] Steam response JSON deserialization error;\n{args.ErrorContext.Error.Message}\nThis message can be ignored"); | |
args.ErrorContext.Handled = true; | |
} | |
}; | |
private void SendWorkshopCollectionQuery(ulong collectionId, CollectionAction action, int success, ConsoleSystem.Arg arg = null, string perm = null) | |
{ | |
string details = string.Format(COLLECTION_BODY, Configuration.Skins.APIKey, collectionId); | |
try | |
{ | |
webrequest.Enqueue(COLLECTION_DETAILS, details, (code, response) => ServerMgr.Instance.StartCoroutine(ProcessCollectionRequest(collectionId, code, response, action, success, arg, perm)), this, RequestMethod.POST); | |
} | |
catch { } | |
} | |
private IEnumerator ValidateRequiredSkins(int code, string response, int page, int totalPages, int success, ConsoleSystem.Arg arg, string perm) | |
{ | |
bool hasChanged = false; | |
int newSkins = 0; | |
if (response != null && code == 200) | |
{ | |
QueryResponse queryRespone = JsonConvert.DeserializeObject<QueryResponse>(response, m_ErrorHandling); | |
if (queryRespone != null && queryRespone.response != null && queryRespone.response.publishedfiledetails?.Length > 0) | |
{ | |
SendResponse($"Processing workshop response. Page: {page} / {totalPages}", arg); | |
foreach (PublishedFileDetails publishedFileDetails in queryRespone.response.publishedfiledetails) | |
{ | |
if (publishedFileDetails.tags != null) | |
{ | |
foreach (PublishedFileDetails.Tag tag in publishedFileDetails.tags) | |
{ | |
if (string.IsNullOrEmpty(tag.tag)) | |
continue; | |
ulong workshopid = Convert.ToUInt64(publishedFileDetails.publishedfileid); | |
string adjTag = tag.tag.ToLower().Replace("skin", "").Replace(" ", "").Replace("-", "").Replace(".item", ""); | |
if (_workshopNameToShortname.ContainsKey(adjTag)) | |
{ | |
string shortname = _workshopNameToShortname[adjTag]; | |
if (shortname == "ammo.snowballgun") | |
continue; | |
if (!_skinList.TryGetValue(shortname, out HashSet<ulong> skins)) | |
skins = _skinList[shortname] = new HashSet<ulong>(); | |
if (!skins.Contains(workshopid)) | |
{ | |
skins.Add(workshopid); | |
_skinNameLookup[workshopid] = publishedFileDetails.title; | |
_skinSearchLookup[workshopid] = $"{workshopid} {publishedFileDetails.title}"; | |
} | |
if (!Configuration.SkinList.TryGetValue(shortname, out HashSet<ulong> configSkins)) | |
configSkins = Configuration.SkinList[shortname] = new HashSet<ulong>(); | |
if (!configSkins.Contains(workshopid)) | |
{ | |
hasChanged = true; | |
configSkins.Add(workshopid); | |
newSkins += 1; | |
} | |
if (!string.IsNullOrEmpty(perm) && !Configuration.Permission.Custom[perm].Contains(workshopid)) | |
{ | |
hasChanged = true; | |
Configuration.Permission.Custom[perm].Add(workshopid); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
yield return CoroutineEx.waitForEndOfFrame; | |
yield return CoroutineEx.waitForEndOfFrame; | |
if (hasChanged) | |
SaveConfiguration(); | |
if (page < totalPages) | |
SendWorkshopQuery(page, success + newSkins); | |
else | |
{ | |
if (_collectionsToVerify.Count != 0) | |
{ | |
SendWorkshopCollectionQuery(_collectionsToVerify.Dequeue(), CollectionAction.AddSkin, success + newSkins, arg, perm); | |
yield break; | |
} | |
if (!_skinsLoaded) | |
{ | |
SortSkinLists(); | |
_skinsLoaded = true; | |
Hash<string, HashSet<ulong>> skinList = new Hash<string, HashSet<ulong>>(); | |
foreach (KeyValuePair<string, HashSet<ulong>> kvp in _skinList) | |
{ | |
HashSet<ulong> skins = new HashSet<ulong>(); | |
foreach (ulong skinId in kvp.Value) | |
skins.Add(skinId); | |
skinList.Add(kvp.Key, skins); | |
} | |
Configuration.Permission.ReverseCustomSkinPermissions(ref _skinPermissions); | |
Interface.Oxide.CallHook("OnSkinBoxSkinsLoaded", skinList); | |
Debug.Log($"[SkinBox] - SkinBox has loaded all required skins and is ready to use! ({_skinList.Values.Sum(x => x.Count)} skins acrosss {_skinList.Count} items)"); | |
} | |
else SendResponse($"{success + newSkins} new skins have been added!", arg); | |
} | |
} | |
private IEnumerator ProcessCollectionRequest(ulong collectionId, int code, string response, CollectionAction action, int success, ConsoleSystem.Arg arg, string perm) | |
{ | |
if (response != null && code == 200) | |
{ | |
SendResponse($"Processing response for collection {collectionId}", arg); | |
CollectionQueryResponse collectionQuery = JsonConvert.DeserializeObject<CollectionQueryResponse>(response, m_ErrorHandling); | |
if (collectionQuery == null || !(collectionQuery is CollectionQueryResponse)) | |
{ | |
SendResponse("Failed to receive a valid workshop collection response", arg); | |
yield break; | |
} | |
if (collectionQuery.response.resultcount == 0 || collectionQuery.response.collectiondetails == null || | |
collectionQuery.response.collectiondetails.Length == 0 || collectionQuery.response.collectiondetails[0].result != 1) | |
{ | |
SendResponse("Failed to receive a valid workshop collection response", arg); | |
yield break; | |
} | |
_skinsToVerify.Clear(); | |
foreach (CollectionChild child in collectionQuery.response.collectiondetails[0].children) | |
{ | |
try | |
{ | |
switch (child.filetype) | |
{ | |
case 1: | |
_skinsToVerify.Add(Convert.ToUInt64(child.publishedfileid)); | |
break; | |
case 2: | |
_collectionsToVerify.Enqueue(Convert.ToUInt64(child.publishedfileid)); | |
SendResponse($"Collection {collectionId} contains linked collection {child.publishedfileid}", arg); | |
break; | |
default: | |
break; | |
} | |
} | |
catch { } | |
} | |
if (_skinsToVerify.Count == 0) | |
{ | |
if (_collectionsToVerify.Count != 0) | |
{ | |
SendWorkshopCollectionQuery(_collectionsToVerify.Dequeue(), action, success, arg, perm); | |
yield break; | |
} | |
SendResponse("No valid skin ID's in the specified collection", arg); | |
yield break; | |
} | |
switch (action) | |
{ | |
case CollectionAction.AddSkin: | |
SendWorkshopQuery(0, success, arg, perm); | |
break; | |
case CollectionAction.RemoveSkin: | |
RemoveSkins(arg, perm); | |
break; | |
case CollectionAction.ExcludeSkin: | |
AddSkinExclusions(arg); | |
break; | |
case CollectionAction.RemoveExludeSkin: | |
RemoveSkinExclusions(arg); | |
break; | |
} | |
} | |
else SendResponse($"[SkinBox] Collection response failed. Error code {code}", arg); | |
} | |
private void RemoveSkins(ConsoleSystem.Arg arg, string perm = null) | |
{ | |
int removedCount = 0; | |
for (int y = _skinList.Count - 1; y >= 0; y--) | |
{ | |
KeyValuePair<string, HashSet<ulong>> skin = _skinList.ElementAt(y); | |
for (int i = 0; i < _skinsToVerify.Count; i++) | |
{ | |
ulong skinId = _skinsToVerify[i]; | |
if (skin.Value.Contains(skinId)) | |
{ | |
skin.Value.Remove(skinId); | |
Configuration.SkinList[skin.Key].Remove(skinId); | |
removedCount++; | |
if (!string.IsNullOrEmpty(perm)) | |
Configuration.Permission.Custom[perm].Remove(skinId); | |
} | |
} | |
} | |
if (removedCount > 0) | |
SaveConfiguration(); | |
SendReply(arg, $"[SkinBox] - Removed {removedCount} skins"); | |
if (_collectionsToVerify.Count != 0) | |
SendWorkshopCollectionQuery(_collectionsToVerify.Dequeue(), CollectionAction.RemoveSkin, 0, arg, perm); | |
} | |
private void AddSkinExclusions(ConsoleSystem.Arg arg) | |
{ | |
int count = 0; | |
foreach (ulong skinId in _skinsToVerify) | |
{ | |
if (!Configuration.Blacklist.Contains(skinId)) | |
{ | |
Configuration.Blacklist.Add(skinId); | |
count++; | |
} | |
} | |
if (count > 0) | |
{ | |
SendResponse($"Added {count} new skin ID's to the excluded list", arg); | |
SaveConfiguration(); | |
} | |
} | |
private void RemoveSkinExclusions(ConsoleSystem.Arg arg) | |
{ | |
int count = 0; | |
foreach (ulong skinId in _skinsToVerify) | |
{ | |
if (Configuration.Blacklist.Contains(skinId)) | |
{ | |
Configuration.Blacklist.Remove(skinId); | |
count++; | |
} | |
} | |
if (count > 0) | |
{ | |
SendResponse($"Removed {count} skin ID's from the excluded list", arg); | |
SaveConfiguration(); | |
} | |
} | |
private void SendResponse(string message, ConsoleSystem.Arg arg) | |
{ | |
if (arg != null) | |
SendReply(arg, message); | |
else Debug.Log($"[SkinBox] - {message}"); | |
} | |
#endregion | |
#region Workshop Name Conversions | |
private Dictionary<string, string> _workshopNameToShortname = new Dictionary<string, string> | |
{ | |
{"longtshirt", "tshirt.long" }, | |
{"cap", "hat.cap" }, | |
{"beenie", "hat.beenie" }, | |
{"boonie", "hat.boonie" }, | |
{"balaclava", "mask.balaclava" }, | |
{"pipeshotgun", "shotgun.waterpipe" }, | |
{"woodstorage", "box.wooden" }, | |
{"ak47", "rifle.ak" }, | |
{"bearrug", "rug.bear" }, | |
{"boltrifle", "rifle.bolt" }, | |
{"bandana", "mask.bandana" }, | |
{"hideshirt", "attire.hide.vest" }, | |
{"snowjacket", "jacket.snow" }, | |
{"buckethat", "bucket.helmet" }, | |
{"semiautopistol", "pistol.semiauto" }, | |
{"roadsignvest", "roadsign.jacket" }, | |
{"roadsignpants", "roadsign.kilt" }, | |
{"burlappants", "burlap.trousers" }, | |
{"collaredshirt", "shirt.collared" }, | |
{"mp5", "smg.mp5" }, | |
{"sword", "salvaged.sword" }, | |
{"workboots", "shoes.boots" }, | |
{"vagabondjacket", "jacket" }, | |
{"hideshoes", "attire.hide.boots" }, | |
{"deerskullmask", "deer.skull.mask" }, | |
{"minerhat", "hat.miner" }, | |
{"lr300", "rifle.lr300" }, | |
{"lr300.item", "rifle.lr300" }, | |
{"burlapgloves", "burlap.gloves" }, | |
{"burlap.gloves", "burlap.gloves"}, | |
{"leather.gloves", "burlap.gloves"}, | |
{"python", "pistol.python" }, | |
{"m39", "rifle.m39" }, | |
{"l96", "rifle.l96" }, | |
{"woodendoubledoor", "door.double.hinged.wood" } | |
}; | |
private void UpdateWorkshopNameConversionList() | |
{ | |
foreach (ItemDefinition item in ItemManager.itemList) | |
{ | |
_shortnameToDisplayname[item.shortname] = item.displayName.english; | |
string workshopName = item.displayName.english.ToLower().Replace("skin", "").Replace(" ", "").Replace("-", ""); | |
if (!_workshopNameToShortname.ContainsKey(workshopName)) | |
_workshopNameToShortname[workshopName] = item.shortname; | |
if (!_workshopNameToShortname.ContainsKey(item.shortname)) | |
_workshopNameToShortname[item.shortname] = item.shortname; | |
if (!_workshopNameToShortname.ContainsKey(item.shortname.Replace(".", ""))) | |
_workshopNameToShortname[item.shortname.Replace(".", "")] = item.shortname; | |
} | |
foreach (Skinnable skin in Skinnable.All.ToList()) | |
{ | |
if (string.IsNullOrEmpty(skin.Name) || string.IsNullOrEmpty(skin.ItemName) || _workshopNameToShortname.ContainsKey(skin.Name.ToLower())) | |
continue; | |
_workshopNameToShortname[skin.Name.ToLower()] = skin.ItemName.ToLower(); | |
} | |
} | |
private void FixLR300InvalidShortname() | |
{ | |
const string LR300_ITEM = "lr300.item"; | |
const string LR300 = "rifle.lr300"; | |
if (Configuration.SkinList.TryGetValue(LR300_ITEM, out HashSet<ulong> list)) | |
{ | |
Configuration.SkinList.Remove(LR300_ITEM); | |
Configuration.SkinList[LR300] = list; | |
SaveConfiguration(); | |
} | |
} | |
#endregion | |
#region Item Skin Redirects | |
private Hash<string, string> _itemSkinRedirects = new Hash<string, string>(); | |
private void FindItemRedirects() | |
{ | |
bool addApprovedPermission = Configuration.Permission.Approved != Configuration.Permission.Use; | |
foreach (ItemSkinDirectory.Skin skin in ItemSkinDirectory.Instance.skins) | |
{ | |
ItemSkin itemSkin = skin.invItem as ItemSkin; | |
if (itemSkin == null || itemSkin.Redirect == null) | |
continue; | |
_itemSkinRedirects[itemSkin.Redirect.shortname] = itemSkin.itemDefinition.shortname; | |
if (Configuration.Skins.UseRedirected) | |
{ | |
if (!_skinList.TryGetValue(itemSkin.itemDefinition.shortname, out HashSet<ulong> skins)) | |
skins = _skinList[itemSkin.itemDefinition.shortname] = new HashSet<ulong>(); | |
skins.Add((ulong)skin.id); | |
_skinNameLookup[(ulong)skin.id] = itemSkin.displayName.english; | |
_skinSearchLookup[(ulong)skin.id] = $"{(ulong)skin.id} {itemSkin.displayName.english}"; | |
if (addApprovedPermission) | |
_skinPermissions[(ulong)skin.id] = Configuration.Permission.Approved; | |
} | |
} | |
} | |
private string GetRedirectedShortname(string shortname) | |
{ | |
if (_itemSkinRedirects.TryGetValue(shortname, out string redirectedName)) | |
return redirectedName; | |
return shortname; | |
} | |
#endregion | |
#region SkinBox Component | |
private class DeployableHandler : LootHandler | |
{ | |
internal ReskinTarget Target | |
{ | |
set | |
{ | |
reskinTarget = value; | |
Populate(); | |
} | |
} | |
protected override ItemDefinition Definition => reskinTarget.itemDefinition.isRedirectOf ?? reskinTarget.itemDefinition; | |
protected override ulong InputSkin => reskinTarget.entity.skinID; | |
private ReskinTarget reskinTarget; | |
protected override void Awake() | |
{ | |
_filteredSkins = Pool.Get<List<ulong>>(); | |
Entity = GetComponent<StorageContainer>(); | |
if (!Configuration.Other.AllowStacks) | |
{ | |
Entity.maxStackSize = 1; | |
Entity.inventory.maxStackSize = 1; | |
} | |
Entity.SetFlag(BaseEntity.Flags.Open, true, false); | |
} | |
internal void Populate() | |
{ | |
if (HasItem) | |
return; | |
HasItem = true; | |
InputShortname = reskinTarget.itemDefinition.isRedirectOf != null ? reskinTarget.itemDefinition.isRedirectOf.shortname : reskinTarget.itemDefinition.shortname; | |
_availableSkins = reskinTarget.skins; | |
_availableSkins.Remove(0UL); | |
if (InputSkin != 0UL) | |
_availableSkins.Remove(InputSkin); | |
_filteredSkins.AddRange(_availableSkins); | |
_itemsPerPage = InputSkin == 0UL ? Entity.inventory.capacity - 1: Entity.inventory.capacity - 2; | |
_currentPage = 0; | |
_maximumPages = Mathf.Min(Configuration.Skins.MaximumPages, Mathf.CeilToInt((float)_filteredSkins.Count / (float)_itemsPerPage)); | |
CreateOverlay(); | |
CreateFavouritesButton(); | |
CreatePageButtons(true); | |
CreateSearchBar(); | |
ClearContainer(); | |
StartCoroutine(FillContainer()); | |
} | |
internal override void OnItemRemoved(Item item) | |
{ | |
if (!HasItem) | |
return; | |
ChaosUI.Destroy(Looter, UI_OVERLAY); | |
bool skinChanged = item.info != reskinTarget.itemDefinition || (item.skin != InputSkin); | |
bool wasSuccess = false; | |
if (!skinChanged) | |
goto IGNORE_RESKIN; | |
if (skinChanged && !ChargePlayer(Looter, Definition.category)) | |
{ | |
item.skin = InputSkin; | |
if (item.GetHeldEntity() != null) | |
item.GetHeldEntity().skinID = InputSkin; | |
PopupMessage(string.Format(GetMessage("NotEnoughBalanceTake", Looter), item.info.displayName.english, GetCostType(Looter))); | |
goto IGNORE_RESKIN; | |
} | |
string result2 = Interface.Call<string>("SB_CanReskinDeployableWith", Looter, reskinTarget.entity, reskinTarget.itemDefinition, item.skin); | |
if (!string.IsNullOrEmpty(result2)) | |
{ | |
PopupMessage(result2); | |
goto IGNORE_RESKIN; | |
} | |
wasSuccess = ReskinEntity(Looter, reskinTarget.entity, reskinTarget.itemDefinition, item); | |
IGNORE_RESKIN: | |
if (wasSuccess && Configuration.Favourites.Enabled) | |
Data.OnSkinClaimed(Looter, item); | |
ItemDefinition itemDefinition = item.info; | |
ulong itemSkin = item.skin; | |
Looter.Invoke(()=> TakeOrDestroyItem(itemDefinition, itemSkin, item), 0.2f); | |
ClearContainer(); | |
Entity.inventory.MarkDirty(); | |
HasItem = false; | |
reskinTarget = null; | |
if (wasSuccess && Configuration.Cooldown.ActivateOnTake) | |
Instance.ApplyCooldown(Looter); | |
Looter.EndLooting(); | |
} | |
public static bool ReskinEntity(BasePlayer looter, BaseEntity targetEntity, ItemDefinition defaultDefinition, Item targetItem) | |
{ | |
if (!CanEntityBeRespawned(targetEntity, out string reason)) | |
{ | |
Instance.ChatMessage(looter, reason); | |
return false; | |
} | |
if (defaultDefinition != targetItem.info) | |
return ChangeSkinForRedirectedItem(looter, targetEntity, targetItem); | |
return ChangeSkinForExistingItem(targetEntity, targetItem); | |
} | |
private static bool ChangeSkinForRedirectedItem(BasePlayer looter, BaseEntity targetEntity, Item targetItem) | |
{ | |
if (!GetEntityPrefabPath(targetItem.info, out string resourcePath)) | |
{ | |
Instance.ChatMessage(looter, "ReskinError.InvalidResourcePath"); | |
return false; | |
} | |
Vector3 position = targetEntity.transform.position; | |
Quaternion rotation = targetEntity.transform.rotation; | |
BaseEntity parentEntity = targetEntity.GetParentEntity(); | |
float health = targetEntity.Health(); | |
float lastAttackedTime = targetEntity is BaseCombatEntity ? (targetEntity as BaseCombatEntity).lastAttackedTime : 0; | |
ulong owner = targetEntity.OwnerID; | |
float sleepingBagUnlockTime = 0; | |
if (targetEntity is SleepingBag) | |
sleepingBagUnlockTime = (targetEntity as SleepingBag).unlockTime; | |
Rigidbody rb = targetEntity.GetComponent<Rigidbody>(); | |
bool isDoorOrPriv = targetEntity is Door or BuildingPrivlidge; | |
HashSet<PlayerNameID> hashSet = targetEntity is BuildingPrivlidge ? new HashSet<PlayerNameID>((targetEntity as BuildingPrivlidge).authorizedPlayers) : null; | |
Dictionary<ContainerSet, List<Item>> containerSets = new Dictionary<ContainerSet, List<Item>>(); | |
SaveEntityStorage(targetEntity, containerSets, 0); | |
EntityRef[] slots = targetEntity.GetSlots(); | |
List<ChildPreserveInfo> list = Pool.Get<List<ChildPreserveInfo>>(); | |
if (!isDoorOrPriv) | |
{ | |
for (int i = 0; i < targetEntity.children.Count; i++) | |
SaveEntityStorage(targetEntity.children[i], containerSets, i + 1); | |
} | |
else | |
{ | |
foreach (BaseEntity child in targetEntity.children) | |
{ | |
ChildPreserveInfo childPreserveInfo = new ChildPreserveInfo() | |
{ | |
TargetEntity = child, | |
TargetBone = child.parentBone, | |
LocalPosition = child.transform.localPosition, | |
LocalRotation = child.transform.localRotation, | |
Slot = GetEntitySlot(targetEntity, child) | |
}; | |
list.Add(childPreserveInfo); | |
} | |
foreach (ChildPreserveInfo childPreserveInfo in list) | |
childPreserveInfo.TargetEntity.SetParent(null, true, false); | |
} | |
targetEntity.Kill(BaseNetworkable.DestroyMode.None); | |
BaseEntity newEntity = GameManager.server.CreateEntity(resourcePath, position, rotation, true); | |
if (rb) | |
{ | |
if (newEntity.TryGetComponent<Rigidbody>(out Rigidbody rigidbody) && !rigidbody.isKinematic && rigidbody.useGravity) | |
{ | |
rigidbody.useGravity = false; | |
newEntity.Invoke(() => RestoreRigidbody(rigidbody), 0.1f); | |
} | |
} | |
newEntity.SetParent(parentEntity, false, false); | |
newEntity.transform.SetPositionAndRotation(position, rotation); | |
newEntity.skinID = targetItem.skin; | |
newEntity.OwnerID = owner; | |
newEntity.Spawn(); | |
if (newEntity is BaseCombatEntity) | |
{ | |
(newEntity as BaseCombatEntity).SetHealth(health); | |
(newEntity as BaseCombatEntity).lastAttackedTime = lastAttackedTime; | |
} | |
if (newEntity is SleepingBag) | |
{ | |
(newEntity as SleepingBag).unlockTime = sleepingBagUnlockTime; | |
(newEntity as SleepingBag).deployerUserID = owner; | |
} | |
if (newEntity is DecayEntity) | |
(newEntity as DecayEntity).AttachToBuilding(null); | |
if (containerSets.Count > 0) | |
{ | |
RestoreEntityStorage(newEntity, 0, containerSets); | |
if (!isDoorOrPriv) | |
{ | |
for (int j = 0; j < newEntity.children.Count; j++) | |
RestoreEntityStorage(newEntity.children[j], j + 1, containerSets); | |
} | |
foreach (KeyValuePair<ContainerSet, List<Item>> containerSet in containerSets) | |
{ | |
foreach (Item value in containerSet.Value) | |
{ | |
value.Remove(0f); | |
} | |
} | |
} | |
if (newEntity is BuildingPrivlidge buildingPrivlidge) | |
buildingPrivlidge.authorizedPlayers = hashSet; | |
if (isDoorOrPriv) | |
{ | |
foreach (ChildPreserveInfo child in list) | |
{ | |
child.TargetEntity.SetParent(newEntity, child.TargetBone, true, false); | |
child.TargetEntity.transform.localPosition = child.LocalPosition; | |
child.TargetEntity.transform.localRotation = child.LocalRotation; | |
child.TargetEntity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update); | |
} | |
newEntity.SetSlots(slots); | |
} | |
Pool.FreeUnmanaged<ChildPreserveInfo>(ref list); | |
return true; | |
} | |
private static bool ChangeSkinForExistingItem(BaseEntity targetEntity, Item targetItem) | |
{ | |
targetEntity.skinID = targetItem.skin; | |
targetEntity.SendNetworkUpdateImmediate(); | |
targetEntity.ClientRPC<int, NetworkableId>(null, "Client_ReskinResult", 1, targetEntity.net.ID); | |
return true; | |
} | |
private static int GetEntitySlot(BaseEntity rootEntity, BaseEntity childEntity) | |
{ | |
for (int i = 0; i < rootEntity.GetSlots().Length; i++) | |
{ | |
if (rootEntity.GetSlot((BaseEntity.Slot)i) == childEntity) | |
{ | |
return i; | |
} | |
} | |
return -1; | |
} | |
private void TakeOrDestroyItem(ItemDefinition itemDefinition, ulong skinId, Item item) | |
{ | |
if (item != null && item.parent == null) | |
{ | |
List<Item> list = Pool.Get<List<Item>>(); | |
list.AddRange(Looter.inventory.containerBelt.itemList); | |
list.AddRange(Looter.inventory.containerMain.itemList); | |
for (int i = 0; i < list.Count; i++) | |
{ | |
Item y = list[i]; | |
if (y.info == itemDefinition && y.skin == skinId) | |
{ | |
if (y.amount > 1) | |
{ | |
y.amount -= 1; | |
y.MarkDirty(); | |
} | |
else | |
{ | |
item.RemoveFromContainer(); | |
item.Remove(0f); | |
} | |
break; | |
} | |
} | |
Pool.FreeUnmanaged(ref list); | |
} | |
if (item == null) | |
return; | |
item.RemoveFromContainer(); | |
item.Remove(0f); | |
} | |
#region Helpers | |
private static RaycastHit raycastHit; | |
internal static BaseEntity FindReskinTarget(BasePlayer player) | |
{ | |
const int LAYERS = 1 << 0 | 1 << 8 | 1 << 15 | 1 << 16 | 1 << 21; | |
BaseEntity baseEntity = null; | |
if (Physics.Raycast(player.eyes.HeadRay(), out raycastHit, 5f, LAYERS, QueryTriggerInteraction.Ignore)) | |
baseEntity = raycastHit.GetEntity(); | |
return baseEntity; | |
} | |
internal static bool CanEntityBeRespawned(BaseEntity targetEntity, out string reason) | |
{ | |
if (!targetEntity || targetEntity.IsDestroyed) | |
{ | |
reason = "ReskinError.TargetNull"; | |
return false; | |
} | |
BaseMountable baseMountable = targetEntity as BaseMountable; | |
if (baseMountable && baseMountable.AnyMounted()) | |
{ | |
reason = "ReskinError.MountBlocked"; | |
return false; | |
} | |
if (targetEntity.isServer) | |
{ | |
BaseVehicle baseVehicle = targetEntity as BaseVehicle; | |
if (baseVehicle && (baseVehicle.HasDriver() || baseVehicle.AnyMounted())) | |
{ | |
reason = "ReskinError.MountBlocked"; | |
return false; | |
} | |
} | |
IOEntity ioEntity = targetEntity as IOEntity; | |
if (ioEntity && (HasIOConnection(ioEntity.inputs) || HasIOConnection(ioEntity.outputs))) | |
{ | |
reason = "ReskinError.IOConnected"; | |
return false; | |
} | |
reason = null; | |
return true; | |
} | |
private static bool GetEntityPrefabPath(ItemDefinition itemDefinition, out string resourcePath) | |
{ | |
resourcePath = string.Empty; | |
if (itemDefinition.TryGetComponent<ItemModDeployable>(out ItemModDeployable itemModDeployable)) | |
{ | |
resourcePath = itemModDeployable.entityPrefab.resourcePath; | |
return true; | |
} | |
if (itemDefinition.TryGetComponent<ItemModEntity>(out ItemModEntity itemModEntity)) | |
{ | |
resourcePath = itemModEntity.entityPrefab.resourcePath; | |
return true; | |
} | |
if (itemDefinition.TryGetComponent<ItemModEntityReference>(out ItemModEntityReference itemModEntityReference)) | |
{ | |
resourcePath = itemModEntityReference.entityPrefab.resourcePath; | |
return true; | |
} | |
return false; | |
} | |
internal static bool HasIOConnection(IOEntity.IOSlot[] slots) | |
{ | |
for (int i = 0; i < slots.Length; i++) | |
{ | |
if (slots[i].connectedTo.Get(true)) | |
return true; | |
} | |
return false; | |
} | |
internal static bool GetItemDefinitionForEntity(BaseEntity entity, out ItemDefinition itemDefinition, bool useRedirect = true) | |
{ | |
itemDefinition = null; | |
BaseCombatEntity baseCombatEntity = entity as BaseCombatEntity; | |
if (baseCombatEntity) | |
{ | |
if (baseCombatEntity.pickup.enabled && baseCombatEntity.pickup.itemTarget) | |
itemDefinition = baseCombatEntity.pickup.itemTarget; | |
else if (baseCombatEntity.repair.enabled && baseCombatEntity.repair.itemTarget) | |
itemDefinition = baseCombatEntity.repair.itemTarget; | |
} | |
if (useRedirect && itemDefinition && itemDefinition.isRedirectOf) | |
itemDefinition = itemDefinition.isRedirectOf; | |
return itemDefinition; | |
} | |
private static void SaveEntityStorage(BaseEntity baseEntity, Dictionary<ContainerSet, List<Item>> dictionary, int index) | |
{ | |
IItemContainerEntity itemContainerEntity = baseEntity as IItemContainerEntity; | |
if (itemContainerEntity != null) | |
{ | |
ContainerSet containerSet = new ContainerSet() { ContainerIndex = index, PrefabId = index == 0 ? 0 : baseEntity.prefabID }; | |
dictionary.Add(containerSet, new List<Item>()); | |
while(itemContainerEntity.inventory.itemList.Count > 0) | |
{ | |
Item item = itemContainerEntity.inventory.itemList[0]; | |
dictionary[containerSet].Add(item); | |
item.RemoveFromContainer(); | |
} | |
} | |
} | |
private static void RestoreEntityStorage(BaseEntity baseEntity, int index, Dictionary<ContainerSet, List<Item>> copy) | |
{ | |
IItemContainerEntity itemContainerEntity = baseEntity as IItemContainerEntity; | |
if (itemContainerEntity != null) | |
{ | |
ContainerSet containerSet = new ContainerSet() { ContainerIndex = index, PrefabId = index == 0 ? 0 : baseEntity.prefabID }; | |
if (copy.ContainsKey(containerSet)) | |
{ | |
foreach (Item item in copy[containerSet]) | |
item.MoveToContainer(itemContainerEntity.inventory, -1, true, false); | |
copy.Remove(containerSet); | |
} | |
} | |
} | |
private static void RestoreRigidbody(Rigidbody rb) | |
{ | |
if (rb) | |
rb.useGravity = true; | |
} | |
#endregion | |
internal class ReskinTarget | |
{ | |
public BaseEntity entity; | |
public ItemDefinition itemDefinition; | |
public List<ulong> skins; | |
public ReskinTarget(BaseEntity entity, ItemDefinition itemDefinition, List<ulong> skins) | |
{ | |
this.entity = entity; | |
this.itemDefinition = itemDefinition; | |
this.skins = skins; | |
} | |
} | |
private struct ChildPreserveInfo | |
{ | |
public BaseEntity TargetEntity; | |
public uint TargetBone; | |
public Vector3 LocalPosition; | |
public Quaternion LocalRotation; | |
public int Slot; | |
} | |
private struct ContainerSet | |
{ | |
public int ContainerIndex; | |
public uint PrefabId; | |
} | |
} | |
private class LootHandler : MonoBehaviour | |
{ | |
public StorageContainer Entity { get; protected set; } | |
internal BasePlayer m_Looter; | |
internal virtual BasePlayer Looter | |
{ | |
get => m_Looter; | |
set | |
{ | |
m_Looter = value; | |
CreateOverlay(); | |
} | |
} | |
public bool HasItem { get; protected set; } | |
public int InputAmount => inputItem?.amount ?? 0; | |
public string InputShortname { get; protected set; } | |
protected virtual ulong InputSkin => inputItem.skin; | |
protected virtual ItemDefinition Definition => inputItem?.itemDefinition; | |
private InputItem inputItem; | |
protected int _currentPage = 0; | |
protected int _maximumPages = 0; | |
protected int _itemsPerPage; | |
protected List<ulong> _availableSkins; | |
protected List<ulong> _filteredSkins; | |
protected bool _isFillingContainer; | |
protected virtual void Awake() | |
{ | |
_availableSkins = Pool.Get<List<ulong>>(); | |
_filteredSkins = Pool.Get<List<ulong>>(); | |
Entity = GetComponent<StorageContainer>(); | |
if (!Configuration.Other.AllowStacks) | |
{ | |
Entity.maxStackSize = 1; | |
Entity.inventory.maxStackSize = 1; | |
} | |
Entity.SetFlag(BaseEntity.Flags.Open, true, false); | |
} | |
protected virtual void OnDestroy() | |
{ | |
ChaosUI.Destroy(Looter, UI_OVERLAY); | |
ChaosUI.Destroy(Looter, UI_POPUP); | |
Instance?._activeSkinBoxes?.Remove(Looter.userID); | |
if (HasItem && inputItem != null) | |
Looter.GiveItem(inputItem.Create(), BaseEntity.GiveItemReason.PickedUp); | |
Pool.FreeUnmanaged(ref _availableSkins); | |
Pool.FreeUnmanaged(ref _filteredSkins); | |
if (Entity != null && !Entity.IsDestroyed) | |
{ | |
if (Entity.inventory.itemList.Count > 0) | |
ClearContainer(); | |
Entity.Kill(BaseNetworkable.DestroyMode.None); | |
} | |
Instance?.ToggleHooks(); | |
} | |
public void ReturnItemInstantly() | |
{ | |
if (HasItem && Looter && inputItem != null) | |
{ | |
Looter.GiveItem(inputItem.Create(), BaseEntity.GiveItemReason.PickedUp); | |
HasItem = false; | |
} | |
} | |
internal virtual void OnItemAdded(Item item) | |
{ | |
if (HasItem) | |
return; | |
HasItem = true; | |
InputShortname = Instance.GetRedirectedShortname(item.info.shortname); | |
Instance.GetSkinsFor(Looter, InputShortname, ref _availableSkins); | |
_availableSkins.Remove(0UL); | |
if (item.skin != 0UL) | |
_availableSkins.Remove(item.skin); | |
_filteredSkins.Clear(); | |
_filteredSkins.AddRange(_availableSkins); | |
inputItem = new InputItem(InputShortname, item); | |
_itemsPerPage = InputSkin == 0UL ? Entity.inventory.capacity - 1 : Entity.inventory.capacity - 2; | |
_currentPage = 0; | |
_maximumPages = Mathf.Min(Configuration.Skins.MaximumPages, Mathf.CeilToInt((float)_filteredSkins.Count / (float)_itemsPerPage)); | |
CreateFavouritesButton(); | |
CreatePageButtons(false); | |
CreateSearchBar(); | |
if (Configuration.Favourites.Enabled) | |
PopupMessage(GetMessage("FavouritesEnabled", Looter)); | |
RemoveItem(item); | |
ClearContainer(); | |
StartCoroutine(FillContainer()); | |
} | |
internal void ResetSkinOrder() | |
{ | |
Instance.GetSkinsFor(Looter, InputShortname, ref _availableSkins); | |
_availableSkins.Remove(0UL); | |
if (InputSkin != 0UL) | |
_availableSkins.Remove(InputSkin); | |
_filteredSkins.Clear(); | |
_filteredSkins.AddRange(_availableSkins); | |
ChaosUI.Destroy(Looter, UI_FAVOURITES); | |
ChangePage(0); | |
} | |
internal virtual void OnItemRemoved(Item item) | |
{ | |
if (!HasItem) | |
return; | |
bool skinChanged = item.skin != 0UL && item.skin != InputSkin; | |
bool aborted = false; | |
inputItem.CloneTo(item); | |
BaseEntity heldEntity = item.GetHeldEntity(); | |
if (skinChanged && !ChargePlayer(Looter, Definition.category)) | |
{ | |
item.skin = InputSkin; | |
if (heldEntity) | |
heldEntity.skinID = InputSkin; | |
aborted = true; | |
PopupMessage(string.Format(GetMessage("NotEnoughBalanceTake", Looter), item.info.displayName.english, GetCostType(Looter))); | |
} | |
string result = Interface.Call<string>("SB_CanReskinItem", Looter, item, item.skin); | |
if (!string.IsNullOrEmpty(result)) | |
{ | |
item.skin = InputSkin; | |
if (heldEntity) | |
heldEntity.skinID = InputSkin; | |
aborted = true; | |
PopupMessage(result); | |
} | |
if (!aborted && Configuration.Other.ApplyWorkshopName && (item.skin != 0UL || Configuration.Skins.ShowSkinIDs)) | |
Instance._skinNameLookup.TryGetValue(item.skin, out item.name); | |
else item.name = inputItem.name; | |
if (!aborted && Configuration.Favourites.Enabled) | |
Data.OnSkinClaimed(Looter, item); | |
item.MarkDirty(); | |
ClearContainer(); | |
Entity.inventory.MarkDirty(); | |
inputItem.Dispose(); | |
inputItem = null; | |
HasItem = false; | |
if (Configuration.Cooldown.ActivateOnTake) | |
Instance.ApplyCooldown(Looter); | |
if (Instance.IsOnCooldown(Looter)) | |
{ | |
Looter.EndLooting(); | |
} | |
else | |
{ | |
ChaosUI.Destroy(Looter, UI_PAGE); | |
ChaosUI.Destroy(Looter, UI_FAVOURITES); | |
ChaosUI.Destroy(Looter, UI_SEARCH); | |
ChaosUI.Destroy(Looter, UI_POPUP); | |
} | |
} | |
private void ChangePage(int change) | |
{ | |
if (_isFillingContainer) | |
return; | |
_currentPage = (_currentPage + change) % _maximumPages; | |
if (_currentPage < 0) | |
_currentPage = _maximumPages - 1; | |
CreatePageButtons(false); | |
StartCoroutine(RefillContainer()); | |
} | |
private string _searchString = string.Empty; | |
private void SetSearchParameters(string s) | |
{ | |
_searchString = s; | |
_filteredSkins.Clear(); | |
if (string.IsNullOrEmpty(s)) | |
_filteredSkins.AddRange(_availableSkins); | |
else | |
{ | |
for (int i = 0; i < _availableSkins.Count; i++) | |
{ | |
Instance._skinSearchLookup.TryGetValue(_availableSkins[i], out string skinLabel); | |
if (!string.IsNullOrEmpty(skinLabel) && skinLabel.Contains(s, CompareOptions.OrdinalIgnoreCase)) | |
_filteredSkins.Add(_availableSkins[i]); | |
} | |
} | |
_currentPage = 0; | |
_maximumPages = Mathf.Min(Configuration.Skins.MaximumPages, Mathf.CeilToInt((float)_filteredSkins.Count / (float)_itemsPerPage)); | |
ChaosUI.Destroy(Looter, UI_PAGE); | |
CreatePageButtons(false); | |
StartCoroutine(RefillContainer()); | |
} | |
protected IEnumerator RefillContainer() | |
{ | |
ClearContainer(); | |
yield return StartCoroutine(FillContainer()); | |
} | |
protected virtual IEnumerator FillContainer() | |
{ | |
_isFillingContainer = true; | |
ItemDefinition definition = Definition; | |
if (definition) | |
{ | |
CreateItem(definition, 0UL); | |
if (InputSkin != 0UL) | |
CreateItem(definition, InputSkin); | |
for (int i = _currentPage * _itemsPerPage; i < Mathf.Min(_filteredSkins.Count, (_currentPage + 1) * _itemsPerPage); i++) | |
{ | |
if (!HasItem) | |
break; | |
CreateItem(definition, _filteredSkins[i]); | |
if (i % 2 == 0) | |
yield return null; | |
} | |
} | |
_isFillingContainer = false; | |
} | |
protected void ClearContainer() | |
{ | |
for (int i = Entity.inventory.itemList.Count - 1; i >= 0; i--) | |
RemoveItem(Entity.inventory.itemList[i]); | |
} | |
protected Item CreateItem(ItemDefinition definition, ulong skinId) | |
{ | |
Item item = ItemManager.Create(definition, 1, skinId); | |
item.contents?.SetFlag(ItemContainer.Flag.IsLocked, true); | |
BaseProjectile heldEntity = item.GetHeldEntity() as BaseProjectile; | |
if (heldEntity) | |
heldEntity.primaryMagazine.contents = 0; | |
if (skinId != 0UL) | |
{ | |
Instance._skinNameLookup.TryGetValue(skinId, out item.name); | |
if (Configuration.Skins.ShowSkinIDs && Looter.IsAdmin) | |
item.name = $"{item.name} ({skinId})"; | |
} | |
if (!InsertItem(item)) | |
item.Remove(0f); | |
else item.MarkDirty(); | |
return item; | |
} | |
private bool InsertItem(Item item) | |
{ | |
if (Entity.inventory.itemList.Contains(item)) | |
return false; | |
if (Entity.inventory.IsFull()) | |
return false; | |
Entity.inventory.itemList.Add(item); | |
item.parent = Entity.inventory; | |
if (!Entity.inventory.FindPosition(item)) | |
return false; | |
Entity.inventory.MarkDirty(); | |
Entity.inventory.onItemAddedRemoved?.Invoke(item, true); | |
return true; | |
} | |
protected void RemoveItem(Item item) | |
{ | |
if (!Entity.inventory.itemList.Contains(item)) | |
return; | |
Entity.inventory.onPreItemRemove?.Invoke(item); | |
Entity.inventory.itemList.Remove(item); | |
item.parent = null; | |
Entity.inventory.MarkDirty(); | |
Entity.inventory.onItemAddedRemoved?.Invoke(item, false); | |
item.Remove(0f); | |
} | |
internal void CheckItemHasSplit(Item item) => StartCoroutine(CheckItemHasSplit(item, item.amount)); // Item split dupe solution? | |
private IEnumerator CheckItemHasSplit(Item item, int originalAmount) | |
{ | |
yield return null; | |
if (item != null && item.amount != originalAmount) | |
{ | |
int splitAmount = originalAmount - item.amount; | |
Looter.inventory.Take(null, item.info.itemid, splitAmount); | |
item.amount += splitAmount; | |
} | |
} | |
private class InputItem | |
{ | |
public ItemDefinition itemDefinition; | |
public string name; | |
public int amount; | |
public ulong skin; | |
public string text; | |
public float condition; | |
public float maxCondition; | |
public int magazineContents; | |
public int magazineCapacity; | |
public ItemDefinition ammoType; | |
public List<InputItem> contents; | |
internal InputItem(string shortname, Item item) | |
{ | |
if (!item.info.shortname.Equals(shortname)) | |
itemDefinition = ItemManager.FindItemDefinition(shortname); | |
else itemDefinition = item.info; | |
name = item.name; | |
amount = Mathf.Max(item.amount, 1); | |
skin = item.skin; | |
text = item.text; | |
if (item.hasCondition) | |
{ | |
condition = item.condition; | |
maxCondition = item.maxCondition; | |
} | |
BaseProjectile baseProjectile = item.GetHeldEntity() as BaseProjectile; | |
if (baseProjectile != null) | |
{ | |
magazineContents = baseProjectile.primaryMagazine.contents; | |
magazineCapacity = baseProjectile.primaryMagazine.capacity; | |
ammoType = baseProjectile.primaryMagazine.ammoType; | |
} | |
if (item.contents?.itemList?.Count > 0) | |
{ | |
contents = Pool.Get<List<InputItem>>(); | |
for (int i = 0; i < item.contents.itemList.Count; i++) | |
{ | |
Item content = item.contents.itemList[i]; | |
if (content == null) | |
continue; | |
contents.Add(new InputItem(content.info.shortname, content)); | |
} | |
} | |
} | |
internal void Dispose() | |
{ | |
if (contents != null) | |
Pool.FreeUnmanaged(ref contents); | |
} | |
internal Item Create() | |
{ | |
Item item = ItemManager.Create(itemDefinition, amount, skin); | |
item.name = name; | |
item.text = text; | |
if (item.hasCondition) | |
{ | |
item.condition = condition; | |
item.maxCondition = maxCondition; | |
} | |
BaseProjectile baseProjectile = item.GetHeldEntity() as BaseProjectile; | |
if (baseProjectile != null) | |
{ | |
baseProjectile.primaryMagazine.contents = magazineContents; | |
baseProjectile.primaryMagazine.capacity = magazineCapacity; | |
baseProjectile.primaryMagazine.ammoType = ammoType; | |
} | |
if (contents?.Count > 0) | |
{ | |
for (int i = 0; i < contents.Count; i++) | |
{ | |
InputItem content = contents[i]; | |
Item attachment = ItemManager.Create(content.itemDefinition, Mathf.Max(content.amount, 1), content.skin); | |
if (attachment != null) | |
{ | |
if (attachment.hasCondition) | |
{ | |
attachment.condition = content.condition; | |
attachment.maxCondition = content.maxCondition; | |
} | |
attachment.MoveToContainer(item.contents, -1, false); | |
attachment.MarkDirty(); | |
} | |
} | |
item.contents.MarkDirty(); | |
} | |
item.MarkDirty(); | |
return item; | |
} | |
internal void CloneTo(Item item) | |
{ | |
item.contents?.SetFlag(ItemContainer.Flag.IsLocked, false); | |
item.contents?.SetFlag(ItemContainer.Flag.NoItemInput, false); | |
item.amount = amount; | |
item.text = text; | |
if (item.hasCondition) | |
{ | |
item.condition = condition; | |
item.maxCondition = maxCondition; | |
} | |
BaseProjectile baseProjectile = item.GetHeldEntity() as BaseProjectile; | |
if (baseProjectile != null && baseProjectile.primaryMagazine != null) | |
{ | |
baseProjectile.primaryMagazine.contents = magazineContents; | |
baseProjectile.primaryMagazine.capacity = magazineCapacity; | |
baseProjectile.primaryMagazine.ammoType = ammoType; | |
} | |
if (contents?.Count > 0) | |
{ | |
for (int i = 0; i < contents.Count; i++) | |
{ | |
InputItem content = contents[i]; | |
Item attachment = ItemManager.Create(content.itemDefinition, content.amount, content.skin); | |
if (attachment.hasCondition) | |
{ | |
attachment.condition = content.condition; | |
attachment.maxCondition = content.maxCondition; | |
} | |
attachment.MoveToContainer(item.contents, -1, false); | |
attachment.MarkDirty(); | |
} | |
item.contents.MarkDirty(); | |
} | |
item.MarkDirty(); | |
} | |
} | |
#region UI | |
protected virtual void CreateOverlay() | |
{ | |
BaseContainer root = BaseContainer.Create(UI_OVERLAY, Layer.HudMenu, Anchor.FullStretch, Offset.zero) | |
.WithChildren(parent => | |
{ | |
BaseContainer menu = BaseContainer.Create(parent, Anchor.FullStretch, new Offset(17.5f, 17.5f, -17.5f, -17.5f)); | |
BaseContainer container = BaseContainer.Create(menu, Anchor.BottomCenter, new Offset(-580.5f, 64f, 580.5f, 635f)); | |
BaseContainer right = BaseContainer.Create(container, Anchor.RightStretch, new Offset(-380f, 28f, 0f, 5f)); | |
BaseContainer contents = BaseContainer.Create(right, Anchor.BottomStretch, new Offset(0f, 0f, 0f, 950.22f)); | |
BaseContainer loot = BaseContainer.Create(contents, Anchor.TopLeft, new Offset(0f, -950.22f, 380f, -393.22f)); | |
BaseContainer panel = BaseContainer.Create(loot, Anchor.TopLeft, new Offset(0f, -557f, 380f, -8f)) | |
.WithName(UI_PANEL_PARENT); | |
// Header | |
ImageContainer.Create(panel, Anchor.TopLeft, new Offset(-8f, -49f, 372f, -26f)) | |
.WithColor(m_HeaderColor) | |
.WithName(UI_HEADER_PARENT) | |
.WithChildren(header => | |
{ | |
TextContainer.Create(header, Anchor.FullStretch, new Offset(10, 0, 0, 0)) | |
.WithText(GetMessage("UI.Title", Looter)) | |
.WithStyle(m_TextStyle) | |
.WithAlignment(TextAnchor.MiddleLeft); | |
// Create Sets button | |
if (!(this is DeployableHandler)) | |
{ | |
if (Configuration.Sets.Enabled && Looter.HasPermission(Configuration.Permission.Sets)) | |
{ | |
ImageContainer.Create(header, Anchor.CenterRight, new Offset(-41.5f, -10f, -1.5f, 10f)) | |
.WithStyle(m_SetStyle) | |
.WithChildren(prev => | |
{ | |
TextContainer.Create(prev, Anchor.FullStretch, Offset.zero) | |
.WithText(GetMessage("UI.Sets", Looter)) | |
.WithStyle(m_SetStyle); | |
ButtonContainer.Create(prev, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, | |
(arg) => Instance.CreateSkinBox<SkinSetHandler>(Looter, null), $"{Looter.userID}.sets"); | |
}); | |
} | |
} | |
}); | |
}) | |
.DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
protected void CreateFavouritesButton() | |
{ | |
// Favourites system | |
if (HasItem && Configuration.Favourites.Enabled && Data.UserHasFavouritesFor(Looter, InputShortname)) | |
{ | |
BaseContainer root = ImageContainer.Create(UI_FAVOURITES, Layer.HudMenu, Anchor.BottomLeft, new Offset(-2f, -22f, 150f, -2f)) | |
.WithStyle(m_PanelStyle) | |
.WithParent(UI_PANEL_PARENT) | |
.WithChildren(favourites => | |
{ | |
TextContainer.Create(favourites, Anchor.FullStretch, Offset.zero) | |
.WithText(GetMessage("UI.ClearFavourites", Looter)) | |
.WithStyle(m_PanelStyle); | |
ButtonContainer.Create(favourites, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, (arg) => | |
{ | |
Data.ClearForPlayer(Looter, InputShortname); | |
ResetSkinOrder(); | |
}, $"{Looter.userID}.clearfavourites"); | |
}).DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
} | |
protected void CreatePageButtons(bool shouldOffset) | |
{ | |
BaseContainer root = BaseContainer.Create(UI_PAGE, Layer.HudMenu, Anchor.FullStretch, Offset.zero) | |
.WithParent(UI_HEADER_PARENT) | |
.WithChildren(header => | |
{ | |
float offset = 41.5f; | |
if (!shouldOffset && Configuration.Sets.Enabled && Looter.HasPermission(Configuration.Permission.Sets)) | |
offset = 0f; | |
// Next page | |
ImageContainer.Create(header, Anchor.CenterRight, new Offset(-73f + offset, -10f, -43f + offset, 10f)) | |
.WithStyle(m_PageStyle) | |
.WithChildren(next => | |
{ | |
TextContainer.Create(next, Anchor.FullStretch, Offset.zero) | |
.WithText("▶") | |
.WithStyle(m_PageStyle); | |
if (_maximumPages > 1) | |
{ | |
ButtonContainer.Create(next, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, (arg) => ChangePage(1), $"{Looter.userID}.nextpage"); | |
} | |
}); | |
// Page label | |
TextContainer.Create(header, Anchor.CenterRight, new Offset(-123f + offset, -10.5f, -73f + offset, 10.5f)) | |
.WithText($"{_currentPage + 1} / {_maximumPages}") | |
.WithStyle(m_PanelStyle); | |
// Previous page | |
ImageContainer.Create(header, Anchor.CenterRight, new Offset(-153f + offset, -10f, -123f + offset, 10f)) | |
.WithStyle(m_PageStyle) | |
.WithChildren(prev => | |
{ | |
TextContainer.Create(prev, Anchor.FullStretch, Offset.zero) | |
.WithText("◀") | |
.WithStyle(m_PageStyle); | |
if (_maximumPages > 1) | |
{ | |
ButtonContainer.Create(prev, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, (arg) => ChangePage(-1), $"{Looter.userID}.prevpage"); | |
} | |
}); | |
}) | |
.DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
protected void CreateSearchBar() | |
{ | |
if (HasItem) | |
{ | |
// Search bar | |
BaseContainer root = ImageContainer.Create(UI_SEARCH, Layer.HudMenu, Anchor.CenterRight, new Offset(-153f, 13f, 0f, 33f)) | |
.WithStyle(m_PanelStyle) | |
.WithParent(UI_HEADER_PARENT) | |
.WithChildren(search => | |
{ | |
InputFieldContainer.Create(search, Anchor.FullStretch, new Offset(5f, 0f, -5f, 0f)) | |
.WithText(_searchString) | |
.WithStyle(m_InputStyle) | |
.InHudMenu() | |
.WithCallback(m_CallbackHandler, (arg) => | |
SetSearchParameters(arg.Args.Length > 1 ? string.Join(" ", arg.Args.Skip(1)) : string.Empty), $"{Looter.userID}.search"); | |
ImageContainer.Create(search, Anchor.CenterLeft, new Offset(-20, -10, 0, 10)) | |
.WithStyle(m_PanelStyle); | |
RawImageContainer.Create(search, Anchor.CenterLeft, new Offset(-20, -10, 0, 10)) | |
.WithURL(MAGNIFY_URL) | |
.WithColor(m_PanelStyle.FontColor); | |
}) | |
.DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
} | |
internal void PopupMessage(string message) | |
{ | |
BaseContainer root = ImageContainer.Create(UI_POPUP, Layer.HudMenu, Anchor.TopRight, new Offset(-315f, -65f, -5f, -5f)) | |
.WithStyle(m_PanelStyle) | |
.WithChildren(parent => | |
{ | |
TextContainer.Create(parent, Anchor.FullStretch, new Offset(5, 5, -5, -5)) | |
.WithText(message) | |
.WithStyle(m_PanelStyle); | |
ImageContainer.Create(parent, Anchor.TopRight, new Offset(-20f, -20f, 0f, 0f)) | |
.WithStyle(m_SetStyle) | |
.WithChildren(button => | |
{ | |
TextContainer.Create(button, Anchor.FullStretch, Offset.zero) | |
.WithStyle(m_SetStyle) | |
.WithText("✘"); | |
ButtonContainer.Create(button, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, | |
(arg)=> ChaosUI.Destroy(Looter, UI_POPUP), $"{Looter.userID}.dismiss.popup"); | |
}); | |
}) | |
.DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
#endregion | |
} | |
#endregion | |
#region Skin Sets | |
private class SkinSetHandler : LootHandler | |
{ | |
private List<StoredData.SetItem[]> _sets; | |
private List<Item> _itemList; | |
private int _reskinCost; | |
internal override BasePlayer Looter | |
{ | |
get => m_Looter; | |
set | |
{ | |
m_Looter = value; | |
_sets = Data.GetSkinSetsForPlayer(value); | |
_maximumPages = Mathf.Max(Configuration.Sets.NumberSets, 1); | |
if (_sets.Count < _maximumPages) | |
{ | |
int total = _maximumPages - _sets.Count; | |
for (int i = 0; i < total; i++) | |
_sets.Add(new StoredData.SetItem[SET_SLOTS]); | |
} | |
for (int i = 0; i < _sets.Count; i++) | |
{ | |
StoredData.SetItem[] set = _sets[i]; | |
if (set == null) | |
_sets[i] = new StoredData.SetItem[SET_SLOTS]; | |
} | |
_itemList = Pool.Get<List<Item>>(); | |
//Looter.inventory.AllItemsNoAlloc(ref _itemList); | |
//var playerLoot = Looter.inventory.loot; | |
//var lootingContainer = playerLoot.containers.FirstOrDefault(); | |
var lootingContainer = Looter.inventory.containerWear; | |
if (lootingContainer != null) | |
{ | |
for (int i = 0; i < lootingContainer.itemList.Count; i++) | |
{ | |
if (lootingContainer.itemList[i] != null) | |
_itemList.Add(lootingContainer.itemList[i]); | |
} | |
} | |
_itemList.RemoveAll((item) => | |
{ | |
string result = Interface.Call<string>("SB_CanReskinItem", Looter, item, item.skin); | |
return !string.IsNullOrEmpty(result); | |
}); | |
StartCoroutine(RefillContainer()); | |
CreateOverlay(); | |
CreatePageButtons(false); | |
} | |
} | |
protected override void Awake() | |
{ | |
Entity = GetComponent<StorageContainer>(); | |
Entity.maxStackSize = 1; | |
Entity.inventory.maxStackSize = 1; | |
Entity.SetFlag(BaseEntity.Flags.Open, true, false); | |
_itemsPerPage = 12; | |
} | |
protected override void OnDestroy() | |
{ | |
ChaosUI.Destroy(Looter, UI_OVERLAY); | |
ChaosUI.Destroy(Looter, UI_POPUP); | |
Instance?._activeSkinBoxes?.Remove(Looter.userID); | |
Pool.FreeUnmanaged(ref _itemList); | |
if (Entity != null && !Entity.IsDestroyed) | |
{ | |
if (Entity.inventory.itemList.Count > 0) | |
ClearContainer(); | |
Entity.Kill(BaseNetworkable.DestroyMode.None); | |
} | |
Instance?.ToggleHooks(); | |
} | |
public void OnCanAcceptItem(Item item) | |
{ | |
if (item.skin == 0UL || Configuration.Blacklist.Contains(item.skin)) | |
return; | |
int freeIndex = -1; | |
StoredData.SetItem[] currentSet = _sets[_currentPage]; | |
for (int i = 0; i < currentSet.Length; i++) | |
{ | |
StoredData.SetItem setItem = currentSet[i]; | |
if (setItem == null) | |
{ | |
if (freeIndex == -1) | |
freeIndex = i; | |
continue; | |
} | |
if (setItem.Shortname == item.info.shortname) | |
{ | |
if (setItem.SkinId != item.skin) | |
{ | |
setItem.SkinId = item.skin; | |
Item inventoryItem = Entity.inventory.GetSlot(i); | |
inventoryItem.skin = item.skin; | |
inventoryItem.MarkDirty(); | |
} | |
return; | |
} | |
} | |
if (freeIndex != -1) | |
{ | |
currentSet[freeIndex] = new StoredData.SetItem | |
{ | |
Shortname = item.info.shortname, | |
SkinId = item.skin | |
}; | |
Item inventoryItem = CreateItem(item.info, item.skin); | |
inventoryItem.position = freeIndex; | |
inventoryItem.MarkDirty(); | |
UpdateReskinCost(); | |
} | |
} | |
internal override void OnItemAdded(Item item) | |
{ | |
} | |
internal override void OnItemRemoved(Item item) | |
{ | |
} | |
public void RemoveItemFromSet(Item item) | |
{ | |
StartCoroutine(DelayedRemoved()); | |
IEnumerator DelayedRemoved() | |
{ | |
yield return null; | |
if (item != null) | |
{ | |
StoredData.SetItem[] currentSet = _sets[_currentPage]; | |
for (int i = 0; i < currentSet.Length; i++) | |
{ | |
StoredData.SetItem setItem = currentSet[i]; | |
if (setItem != null && setItem.Shortname == item.info.shortname && setItem.SkinId == item.skin) | |
{ | |
currentSet[i] = null; | |
item.RemoveFromContainer(); | |
item.Remove(); | |
UpdateReskinCost(); | |
break; | |
} | |
} | |
} | |
} | |
} | |
#region UI | |
protected override void CreateOverlay() | |
{ | |
BaseContainer root = BaseContainer.Create(UI_OVERLAY, Layer.HudMenu, Anchor.FullStretch, Offset.zero) | |
.WithChildren(parent => | |
{ | |
BaseContainer menu = BaseContainer.Create(parent, Anchor.FullStretch, new Offset(17.5f, 17.5f, -17.5f, -17.5f)); | |
BaseContainer container = BaseContainer.Create(menu, Anchor.BottomCenter, new Offset(-580.5f, 64f, 580.5f, 635f)); | |
BaseContainer right = BaseContainer.Create(container, Anchor.RightStretch, new Offset(-380f, 28f, 0f, 5f)); | |
BaseContainer contents = BaseContainer.Create(right, Anchor.BottomStretch, new Offset(0f, 0f, 0f, 580.11f)); | |
BaseContainer loot = BaseContainer.Create(contents, Anchor.TopLeft, new Offset(0f, -579.5f, 380f, -392.5f)); | |
BaseContainer panel = BaseContainer.Create(loot, Anchor.TopLeft, new Offset(0f, -187f, 380f, -8f)) | |
.WithName(UI_PANEL_PARENT); | |
// Buttons | |
ImageContainer.Create(panel, Anchor.BottomLeft, new Offset(-2f, -25f, 201f, -2f)) | |
.WithStyle(m_PanelStyle) | |
.WithChildren(buttons => | |
{ | |
// Apply set | |
ImageContainer.Create(buttons, Anchor.CenterRight, new Offset(-100f, -10f, -1.5f, 10f)) | |
.WithStyle(m_PageStyle) | |
.WithChildren(apply => | |
{ | |
TextContainer.Create(apply, Anchor.FullStretch, Offset.zero) | |
.WithText(GetMessage("UI.Apply", Looter)) | |
.WithStyle(m_PageStyle); | |
ButtonContainer.Create(apply, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, (arg) => ApplySet(), $"{Looter.userID}.applyset"); | |
}); | |
// Clear set | |
ImageContainer.Create(buttons, Anchor.CenterLeft, new Offset(1.5f, -10f, 100f, 10f)) | |
.WithStyle(m_SetStyle) | |
.WithChildren(clear => | |
{ | |
TextContainer.Create(clear, Anchor.FullStretch, Offset.zero) | |
.WithText(GetMessage("UI.Clear", Looter)) | |
.WithStyle(m_SetStyle); | |
ButtonContainer.Create(clear, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, (arg) => ClearSet(), $"{Looter.userID}.clearset"); | |
}); | |
}); | |
// Header | |
ImageContainer.Create(panel, Anchor.TopLeft, new Offset(-8f, -51f, 372f, -28f)) | |
.WithColor(m_HeaderColor) | |
.WithName(UI_HEADER_PARENT) | |
.WithChildren(header => | |
{ | |
TextContainer.Create(header, Anchor.FullStretch, new Offset(10, 0, 0, 0)) | |
.WithText(GetMessage("UI.Title.Sets", Looter)) | |
.WithStyle(m_TextStyle) | |
.WithAlignment(TextAnchor.MiddleLeft); | |
// SkinBox | |
ImageContainer.Create(header, Anchor.CenterRight, new Offset(-41.5f, -10f, -1.5f, 10f)) | |
.WithStyle(m_SetStyle) | |
.WithChildren(prev => | |
{ | |
TextContainer.Create(prev, Anchor.FullStretch, Offset.zero) | |
.WithText(GetMessage("UI.Box", Looter)) | |
.WithStyle(m_SetStyle); | |
ButtonContainer.Create(prev, Anchor.FullStretch, Offset.zero) | |
.WithColor(Color.Clear) | |
.WithCallback(m_CallbackHandler, | |
(arg) => Instance.CreateSkinBox<LootHandler>(Looter, null), $"{Looter.userID}.box"); | |
}); | |
ImageContainer.Create(header, Anchor.FullStretch, new Offset(0f, 55f, 0f, 105f)) | |
.WithStyle(m_PageStyle) | |
.WithChildren(help => | |
{ | |
TextContainer.Create(help, Anchor.FullStretch, new Offset(5, 5, -5, -5)) | |
.WithStyle(m_PageStyle) | |
.WithAlignment(TextAnchor.MiddleLeft) | |
.WithText(GetMessage("Sets.Help", Looter)); | |
}); | |
}); | |
}) | |
.DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
protected override IEnumerator FillContainer() | |
{ | |
_isFillingContainer = true; | |
StoredData.SetItem[] currentSet = _sets[_currentPage]; | |
for (int i = 0; i < currentSet.Length; i++) | |
{ | |
StoredData.SetItem setItem = currentSet[i]; | |
if (setItem == null) | |
continue; | |
ItemDefinition itemDefinition = setItem.Definition; | |
if (!itemDefinition) | |
{ | |
currentSet[i] = null; | |
continue; | |
} | |
CreateItem(itemDefinition, setItem.SkinId); | |
if (i % 2 == 0) | |
yield return null; | |
} | |
UpdateReskinCost(); | |
_isFillingContainer = false; | |
} | |
private void UpdateReskinCost() | |
{ | |
if (!Configuration.Cost.Enabled) | |
return; | |
_reskinCost = CalculateCost(_sets[_currentPage], _itemList); | |
// Cost info | |
BaseContainer root = ImageContainer.Create(UI_SEARCH, Layer.HudMenu, Anchor.CenterRight, new Offset(-153f, 13f, 0f, 33f)) | |
.WithStyle(m_PanelStyle) | |
.WithParent(UI_HEADER_PARENT) | |
.WithChildren(search => | |
{ | |
TextContainer.Create(search, Anchor.FullStretch, new Offset(5f, 0f, -5f, 0f)) | |
.WithStyle(m_PanelStyle) | |
.WithText(string.Format(GetMessage("Sets.Cost", Looter), _reskinCost, GetCostType(Looter))); | |
}) | |
.DestroyExisting(); | |
ChaosUI.Show(Looter, root); | |
} | |
internal static int CalculateCost(StoredData.SetItem[] currentSet, List<Item> itemList) | |
{ | |
int cost = 0; | |
for (int i = 0; i < currentSet.Length; i++) | |
{ | |
StoredData.SetItem setItem = currentSet[i]; | |
if (setItem == null) | |
continue; | |
ItemDefinition itemDefinition = setItem.Definition; | |
if (itemDefinition) | |
{ | |
if (itemList.Any(x => x.info.shortname == setItem.Shortname && x.skin != setItem.SkinId)) | |
cost += GetReskinCost(itemDefinition); | |
} | |
} | |
return cost; | |
} | |
private void ApplySet() | |
{ | |
if (_reskinCost > 0 && !ChargePlayer(Looter, _reskinCost)) | |
{ | |
PopupMessage(string.Format(GetMessage("NotEnoughBalanceSet", Looter), _reskinCost, GetCostType(Looter))); | |
return; | |
} | |
StoredData.SetItem[] currentSet = _sets[_currentPage]; | |
for (int i = 0; i < currentSet.Length; i++) | |
{ | |
StoredData.SetItem setItem = currentSet[i]; | |
if (setItem == null) | |
continue; | |
_itemList.Sort((a, b) => a.skin.CompareTo(b.skin)); | |
foreach (Item item in _itemList) | |
{ | |
if (item.info.shortname == setItem.Shortname && item.skin != setItem.SkinId) | |
{ | |
item.skin = setItem.SkinId; | |
item.MarkDirty(); | |
if (Configuration.Other.ApplyWorkshopName && (item.skin != 0UL || Configuration.Skins.ShowSkinIDs)) | |
Instance._skinNameLookup.TryGetValue(item.skin, out item.name); | |
BaseEntity heldEntity = item.GetHeldEntity(); | |
if (heldEntity) | |
{ | |
heldEntity.skinID = setItem.SkinId; | |
heldEntity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update); | |
} | |
if (Looter.svActiveItemID == item.uid) | |
{ | |
Looter.UpdateActiveItem(default); | |
int slot = item.position; | |
item.SetParent(null); | |
item.MarkDirty(); | |
Looter.inventory.SendUpdatedInventory(PlayerInventory.Type.Belt, item.parent, false); | |
item.SetParent(Looter.inventory.containerBelt); | |
item.position = slot; | |
item.MarkDirty(); | |
Looter.UpdateActiveItem(item.uid); | |
} | |
break; | |
} | |
} | |
} | |
UpdateReskinCost(); | |
} | |
private void ClearSet() | |
{ | |
StoredData.SetItem[] currentSet = _sets[_currentPage]; | |
for (int i = 0; i < currentSet.Length; i++) | |
currentSet[i] = null; | |
StartCoroutine(RefillContainer()); | |
} | |
#endregion | |
} | |
#endregion | |
#region Spraycan Component | |
private class SprayCanWatcher : MonoBehaviour | |
{ | |
private BasePlayer m_Player; | |
private static RaycastHit m_RaycastHit; | |
private float m_LastPressTime; | |
private const int LAYER_MASK = 1 << (int)Rust.Layer.Reserved1 | 1 << (int) Rust.Layer.Construction | 1 << (int) Rust.Layer.Deployed | 1 << (int)Rust.Layer.Physics_Debris; | |
private void Awake() | |
{ | |
m_Player = GetComponent<BasePlayer>(); | |
} | |
private void LateUpdate() | |
{ | |
if (UnityEngine.Time.time - m_LastPressTime < 1f) | |
return; | |
if (m_Player.serverInput.WasJustPressed(BUTTON.FIRE_PRIMARY)) | |
{ | |
Item activeItem = m_Player.GetActiveItem(); | |
if (!(activeItem is { skin: SPRAYCAN_SKIN })) | |
{ | |
Destroy(this); | |
return; | |
} | |
m_LastPressTime = UnityEngine.Time.time; | |
if (Physics.SphereCast(m_Player.eyes.HeadRay(), 0.1f, out m_RaycastHit, 3f, LAYER_MASK, QueryTriggerInteraction.Ignore)) | |
{ | |
BaseEntity baseEntity = m_RaycastHit.GetEntity(); | |
if (!baseEntity) | |
return; | |
if (baseEntity is WorldItem worldItem) | |
{ | |
if (!Instance.CanOpenSkinBox(m_Player)) | |
return; | |
if (Configuration.ReskinItemBlocklist.Contains(activeItem.info.shortname)) | |
{ | |
Instance.ChatMessage(m_Player, "BlockedItem.Item"); | |
return; | |
} | |
if (Configuration.ReskinSkinBlocklist.Contains(activeItem.skin)) | |
{ | |
Instance.ChatMessage(m_Player, "BlockedItem.Skin"); | |
return; | |
} | |
if (!ChargePlayer(m_Player, Configuration.Cost.Open)) | |
{ | |
Instance.ChatMessage(m_Player, "NotEnoughBalanceOpen", Configuration.Cost.Open, GetCostType(m_Player)); | |
return; | |
} | |
Instance.CreateSkinBox<LootHandler>(m_Player, null, (lootHandler) => | |
{ | |
Item item = worldItem.item; | |
worldItem.RemoveItem(); | |
lootHandler.Entity.inventory.Insert(item); | |
}); | |
} | |
else | |
{ | |
if (!DeployableHandler.GetItemDefinitionForEntity(baseEntity, out _, false)) | |
return; | |
Instance.cmdDeployedSkinBox(m_Player, string.Empty, Array.Empty<string>()); | |
} | |
} | |
} | |
} | |
} | |
[ConsoleCommand("skinbox.spraycan")] | |
private void ccmdSkinBoxSprayCan(ConsoleSystem.Arg arg) | |
{ | |
if (!Configuration.Command.SprayCan) | |
{ | |
Debug.Log($"[SkinBox] The console command 'skinbox.spraycan' was used, but spray cans are disabled in the config"); | |
return; | |
} | |
BasePlayer player = arg.Connection?.player as BasePlayer; | |
if (player) | |
{ | |
if (!player.HasPermission(Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length == 0) | |
{ | |
player.GiveItem(CreateSprayCanItem(), BaseEntity.GiveItemReason.PickedUp); | |
return; | |
} | |
} | |
if (arg.Args == null || arg.Args.Length == 0) | |
{ | |
SendReply(arg, "skinbox.spraycan <playerNameOrID>"); | |
return; | |
} | |
List<BasePlayer> candidates = Pool.Get<List<BasePlayer>>(); | |
string search = arg.GetString(0); | |
foreach (BasePlayer target in BasePlayer.activePlayerList) | |
{ | |
if (target.UserIDString == search) | |
{ | |
candidates.Clear(); | |
candidates.Add(target); | |
break; | |
} | |
if (target.displayName.Contains(search, CompareOptions.OrdinalIgnoreCase)) | |
candidates.Add(target); | |
} | |
if (candidates.Count == 0) | |
{ | |
SendReply(arg, $"No players found with the name or ID '{search}'"); | |
Pool.FreeUnmanaged(ref candidates); | |
return; | |
} | |
if (candidates.Count > 1) | |
{ | |
SendReply(arg, $"Multiple players found with the name or ID '{search}'"); | |
Pool.FreeUnmanaged(ref candidates); | |
return; | |
} | |
candidates[0].GiveItem(CreateSprayCanItem(), BaseEntity.GiveItemReason.PickedUp); | |
SendReply(arg, $"Gave a SkinBox Spray Can to {candidates[0].displayName}"); | |
Pool.FreeUnmanaged(ref candidates); | |
} | |
private Item CreateSprayCanItem() | |
{ | |
const string SPRAYCAN_ITEM = "spraycan"; | |
Item item = ItemManager.CreateByName(SPRAYCAN_ITEM, 1, SPRAYCAN_SKIN); | |
item.name = "SkinBox Spray Can"; | |
return item; | |
} | |
#endregion | |
#region UI | |
private const string UI_OVERLAY = "skinbox.overlay"; | |
private const string UI_FAVOURITES = "skinbox.favourites"; | |
private const string UI_PAGE = "skinbox.pages"; | |
private const string UI_SEARCH = "skinbox.search"; | |
private const string UI_POPUP = "skinbox.popup"; | |
private const string UI_PANEL_PARENT = "skinbox.panel"; | |
private const string UI_HEADER_PARENT = "skinbox.header"; | |
private const string MAGNIFY_URL = "https://chaoscode.io/oxide/Images/magnifyingglass.png"; | |
private static CommandCallbackHandler m_CallbackHandler; | |
private static Style m_TextStyle; | |
private static Style m_PanelStyle; | |
private static Style m_SetStyle; | |
private static Style m_PageStyle; | |
private static Style m_InputStyle; | |
private static Color m_HeaderColor; | |
private void InitializeUI() | |
{ | |
m_CallbackHandler = new CommandCallbackHandler(this); | |
m_HeaderColor = new Color(0.1019608f, 0.1019608f, 0.1019608f, 1f); | |
m_TextStyle = new Style | |
{ | |
FontColor = new Color(0.9686275f, 0.9215686f, 0.8823529f, 0.7843137f), | |
FontSize = 13, | |
Alignment = TextAnchor.MiddleCenter | |
}; | |
m_PanelStyle = new Style | |
{ | |
ImageColor = new Color(0.9686275f, 0.9215686f, 0.8823529f, 0.03921569f), | |
Material = Materials.GreyOut, | |
FontSize = 13, | |
FontColor = new Color(0.9686275f, 0.9215686f, 0.8823529f, 0.7843137f), | |
Alignment = TextAnchor.MiddleCenter | |
}; | |
m_SetStyle = new Style | |
{ | |
ImageColor = new Color(0.6132076f, 0.2283664f, 0.1595001f, 1f), | |
Material = Materials.GreyOut, | |
FontColor = new Color(0.9245283f, 0.5805449f, 0.493414f, 1f), | |
FontSize = 13, | |
Alignment = TextAnchor.MiddleCenter | |
}; | |
m_PageStyle = new Style | |
{ | |
ImageColor = new Color(0.4509804f, 0.5529412f, 0.2705882f, 1f), | |
Material = Materials.GreyOut, | |
FontColor = new Color(0.7098039f, 0.8666667f, 0.4352941f, 1f), | |
FontSize = 13, | |
Alignment = TextAnchor.MiddleCenter | |
}; | |
m_InputStyle = new Style | |
{ | |
FontColor = new Color(0.9686275f, 0.9215686f, 0.8823529f, 0.7843137f), | |
FontSize = 13, | |
Alignment = TextAnchor.MiddleLeft | |
}; | |
} | |
#endregion | |
#region Chat Commands | |
private bool CanOpenSkinBox(BasePlayer player) | |
{ | |
if (_apiKeyMissing) | |
{ | |
SendAPIMissingWarning(); | |
ChatMessage(player, "NoAPIKey"); | |
return false; | |
} | |
if (!_skinsLoaded) | |
{ | |
ChatMessage(player, "SkinsLoading"); | |
return false; | |
} | |
if (player.inventory.loot.IsLooting()) | |
return false; | |
if (Configuration.Other.RequireBuildingPrivilege && !player.IsBuildingAuthed()) | |
{ | |
ChatMessage(player, "NoBuildingAuth"); | |
return false; | |
} | |
if (!player.IsAdmin && !player.HasPermission(Configuration.Permission.Use)) | |
{ | |
ChatMessage(player, "NoPermission"); | |
return false; | |
} | |
if (IsOnCooldown(player, out double cooldownRemaining)) | |
{ | |
ChatMessage(player, "CooldownTime", Mathf.RoundToInt((float)cooldownRemaining)); | |
return false; | |
} | |
string result = Interface.Call<string>("SB_CanUseSkinBox", player); | |
if (!string.IsNullOrEmpty(result)) | |
{ | |
ChatMessage(player, result); | |
return false; | |
} | |
return true; | |
} | |
private void cmdSkinBox(BasePlayer player, string command, string[] args) | |
{ | |
if (!CanOpenSkinBox(player)) | |
return; | |
if (!ChargePlayer(player, Configuration.Cost.Open)) | |
{ | |
ChatMessage(player, "NotEnoughBalanceOpen", Configuration.Cost.Open, GetCostType(player)); | |
return; | |
} | |
if (args.Length > 0) | |
{ | |
if (ulong.TryParse(args[0], out ulong targetSkin)) | |
{ | |
Item activeItem = player.GetActiveItem(); | |
if (activeItem == null) | |
{ | |
ChatMessage(player, "NoItemInHands"); | |
return; | |
} | |
if (targetSkin == activeItem.skin) | |
{ | |
ChatMessage(player, "TargetSkinIsCurrentSkin"); | |
return; | |
} | |
if (Configuration.ReskinItemBlocklist.Contains(activeItem.info.shortname)) | |
{ | |
ChatMessage(player, "BlockedItem.Item"); | |
return; | |
} | |
if (Configuration.ReskinSkinBlocklist.Contains(activeItem.skin)) | |
{ | |
ChatMessage(player, "BlockedItem.Skin"); | |
return; | |
} | |
List<ulong> skins = Pool.Get<List<ulong>>(); | |
GetSkinsFor(player, activeItem.info.shortname, ref skins); | |
bool contains = skins.Contains(targetSkin); | |
Pool.FreeUnmanaged(ref skins); | |
if (!contains && targetSkin != 0UL) | |
{ | |
ChatMessage(player, "TargetSkinNotInList"); | |
return; | |
} | |
bool skinChanged = targetSkin != 0UL && targetSkin != activeItem.skin; | |
if (skinChanged && !ChargePlayer(player, activeItem.info.category)) | |
{ | |
ChatMessage(player, "NotEnoughBalanceTake", activeItem.info.displayName.english, GetCostType(player)); | |
return; | |
} | |
string result = Interface.Call<string>("SB_CanReskinItem", player, activeItem, targetSkin); | |
if (!string.IsNullOrEmpty(result)) | |
{ | |
ChatMessage(player, result); | |
return; | |
} | |
ChatMessage(player, "ApplyingSkin", targetSkin); | |
activeItem.skin = targetSkin; | |
activeItem.MarkDirty(); | |
if (activeItem.skin != 0UL || Configuration.Skins.ShowSkinIDs) | |
Instance._skinNameLookup.TryGetValue(activeItem.skin, out activeItem.name); | |
BaseEntity heldEntity = activeItem.GetHeldEntity(); | |
if (heldEntity != null) | |
{ | |
heldEntity.skinID = targetSkin; | |
heldEntity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update); | |
} | |
player.UpdateActiveItem(default); | |
int slot = activeItem.position; | |
activeItem.SetParent(null); | |
activeItem.MarkDirty(); | |
player.inventory.SendUpdatedInventory(PlayerInventory.Type.Belt, activeItem.parent, false); | |
activeItem.SetParent(player.inventory.containerBelt); | |
activeItem.position = slot; | |
activeItem.MarkDirty(); | |
player.UpdateActiveItem(activeItem.uid); | |
if (Configuration.Cooldown.ActivateOnTake) | |
Instance.ApplyCooldown(player); | |
return; | |
} | |
} | |
timer.In(0.2f, () => CreateSkinBox<LootHandler>(player, null)); | |
} | |
private void cmdDeployedSkinBox(BasePlayer player, string command, string[] args) | |
{ | |
if (!CanOpenSkinBox(player)) | |
return; | |
if (!player.CanBuild()) | |
{ | |
ChatMessage(player, "ReskinError.NoAuth"); | |
return; | |
} | |
BaseEntity entity = DeployableHandler.FindReskinTarget(player); | |
if (!entity || entity.IsDestroyed) | |
{ | |
ChatMessage(player, "ReskinError.NoTarget"); | |
return; | |
} | |
if (!DeployableHandler.CanEntityBeRespawned(entity, out string reason)) | |
{ | |
ChatMessage(player, reason); | |
return; | |
} | |
if (!DeployableHandler.GetItemDefinitionForEntity(entity, out ItemDefinition itemDefinition, false)) | |
{ | |
ChatMessage(player, "NoDefinitionForEntity"); | |
return; | |
} | |
string shortname = GetRedirectedShortname(itemDefinition.shortname); | |
if (!Configuration.Skins.UseRedirected && !shortname.Equals(itemDefinition.shortname, StringComparison.OrdinalIgnoreCase)) | |
{ | |
ChatMessage(player, "RedirectsDisabled"); | |
return; | |
} | |
List<ulong> skins = Pool.Get<List<ulong>>(); | |
GetSkinsFor(player, shortname, ref skins); | |
if (skins.Count == 0) | |
{ | |
ChatMessage(player, "NoSkinsForItem"); | |
return; | |
} | |
if (!ChargePlayer(player, Configuration.Cost.Open)) | |
{ | |
ChatMessage(player, "NotEnoughBalanceOpen", Configuration.Cost.Open, GetCostType(player)); | |
return; | |
} | |
string result = Interface.Call<string>("SB_CanReskinDeployable", player, entity, itemDefinition); | |
if (!string.IsNullOrEmpty(result)) | |
{ | |
ChatMessage(player, result); | |
return; | |
} | |
if (args.Length > 0) | |
{ | |
if (ulong.TryParse(args[0], out ulong targetSkin)) | |
{ | |
if (targetSkin == entity.skinID) | |
{ | |
ChatMessage(player, "TargetSkinIsCurrentSkin"); | |
return; | |
} | |
if (!skins.Contains(targetSkin) && targetSkin != 0UL) | |
{ | |
ChatMessage(player, "TargetSkinNotInList"); | |
return; | |
} | |
Item item = ItemManager.CreateByName(shortname, 1, targetSkin); | |
bool skinChanged = item.info != itemDefinition || (item.skin != entity.skinID); | |
bool wasSuccess = false; | |
if (!skinChanged) | |
goto IGNORE_RESKIN; | |
if (skinChanged && !ChargePlayer(player, itemDefinition.category)) | |
{ | |
ChatMessage(player, "NotEnoughBalanceTake", item.info.displayName.english, GetCostType(player)); | |
goto IGNORE_RESKIN; | |
} | |
string result2 = Interface.Call<string>("SB_CanReskinDeployableWith", player, entity, itemDefinition, item.skin); | |
if (!string.IsNullOrEmpty(result2)) | |
{ | |
ChatMessage(player, result2); | |
goto IGNORE_RESKIN; | |
} | |
wasSuccess = DeployableHandler.ReskinEntity(player, entity, itemDefinition, item); | |
ChatMessage(player, "ApplyingSkin", targetSkin); | |
IGNORE_RESKIN: | |
item.Remove(0f); | |
if (wasSuccess && Configuration.Cooldown.ActivateOnTake) | |
Instance.ApplyCooldown(player); | |
return; | |
} | |
} | |
timer.In(0.2f, () => CreateSkinBox<DeployableHandler>(player, new DeployableHandler.ReskinTarget(entity, itemDefinition, skins))); | |
} | |
private void cmdSkinSetBox(BasePlayer player, string command, string[] args) | |
{ | |
if (!CanOpenSkinBox(player)) | |
return; | |
if (!player.HasPermission(Configuration.Permission.Sets)) | |
{ | |
ChatMessage(player, "NoPermission.Sets"); | |
return; | |
} | |
if (args.Length > 0 && int.TryParse(args[0], out int setId)) | |
{ | |
List<StoredData.SetItem[]> sets = Data.GetSkinSetsForPlayer(player); | |
if (setId < 1 || setId > sets.Count) | |
{ | |
ChatMessage(player, "Sets.Error.InvalidSetID", sets.Count); | |
return; | |
} | |
StoredData.SetItem[] currentSet = sets[setId - 1]; | |
if (currentSet.All(x => x == null)) | |
{ | |
ChatMessage(player, "Sets.Error.EmptySet", setId); | |
return; | |
} | |
List<Item> itemList = Pool.Get<List<Item>>(); | |
//player.inventory.AllItemsNoAlloc(ref itemList); | |
//var playerLoot = player.inventory.loot; | |
//var lootingContainer = playerLoot.containers.FirstOrDefault(); | |
var lootingContainer = player.inventory.containerWear; | |
if (lootingContainer != null) | |
{ | |
for (int i = 0; i < lootingContainer.itemList.Count; i++) | |
{ | |
if (lootingContainer.itemList[i] != null) | |
itemList.Add(lootingContainer.itemList[i]); | |
} | |
} | |
itemList.RemoveAll((item) => | |
{ | |
string result = Interface.Call<string>("SB_CanReskinItem", player, item, item.skin); | |
return !string.IsNullOrEmpty(result); | |
}); | |
int reskinCost = Configuration.Cost.Enabled ? SkinSetHandler.CalculateCost(currentSet, itemList) : 0; | |
if (reskinCost > 0 && !ChargePlayer(player, reskinCost)) | |
{ | |
player.LocalizedMessage(this, "NotEnoughBalanceSet", reskinCost, GetCostType(player)); | |
Pool.FreeUnmanaged(ref itemList); | |
return; | |
} | |
int appliedItemCount = 0; | |
for (int i = 0; i < currentSet.Length; i++) | |
{ | |
StoredData.SetItem setItem = currentSet[i]; | |
if (setItem == null) | |
continue; | |
itemList.Sort((a, b) => a.skin.CompareTo(b.skin)); | |
foreach (Item item in itemList) | |
{ | |
if (item.info.shortname == setItem.Shortname && item.skin != setItem.SkinId) | |
{ | |
item.skin = setItem.SkinId; | |
item.MarkDirty(); | |
BaseEntity heldEntity = item.GetHeldEntity(); | |
if (heldEntity) | |
{ | |
heldEntity.skinID = setItem.SkinId; | |
heldEntity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update); | |
} | |
if (Configuration.Other.ApplyWorkshopName && (item.skin != 0UL || Configuration.Skins.ShowSkinIDs)) | |
Instance._skinNameLookup.TryGetValue(item.skin, out item.name); | |
if (player.svActiveItemID == item.uid) | |
{ | |
player.UpdateActiveItem(default); | |
int slot = item.position; | |
item.SetParent(null); | |
item.MarkDirty(); | |
player.inventory.SendUpdatedInventory(PlayerInventory.Type.Belt, item.parent, false); | |
item.SetParent(player.inventory.containerBelt); | |
item.position = slot; | |
item.MarkDirty(); | |
player.UpdateActiveItem(item.uid); | |
} | |
appliedItemCount++; | |
break; | |
} | |
} | |
} | |
Pool.FreeUnmanaged(ref itemList); | |
if (appliedItemCount == 0) | |
player.LocalizedMessage(this, "Sets.NoItems"); | |
else | |
{ | |
if (reskinCost == 0) | |
player.LocalizedMessage(this, "Sets.Applied", setId, appliedItemCount); | |
else player.LocalizedMessage(this, "Sets.Applied.Cost", setId, appliedItemCount, reskinCost, GetCostType(player)); | |
} | |
return; | |
} | |
timer.In(0.2f, () => CreateSkinBox<SkinSetHandler>(player, null)); | |
} | |
#endregion | |
#region Console Commands | |
[ConsoleCommand("skinbox.cmds")] | |
private void cmdListCmds(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
return; | |
StringBuilder sb = new StringBuilder(); | |
sb.AppendLine("\n> SkinBox command overview <"); | |
TextTable textTable = new TextTable(); | |
textTable.AddColumn("Command"); | |
textTable.AddColumn("Description"); | |
textTable.AddRow(new[] { "skinbox.addskin", "Add one or more skin-id's to the workshop skin list" }); | |
textTable.AddRow(new[] { "skinbox.removeskin", "Remove one or more skin-id's from the workshop skin list" }); | |
textTable.AddRow(new[] { "skinbox.addvipskin", "Add one or more skin-id's to the workshop skin list for the specified permission" }); | |
textTable.AddRow(new[] { "skinbox.removevipskin", "Remove one or more skin-id's from the workshop skin list for the specified permission" }); | |
textTable.AddRow(new[] { "skinbox.addexcluded", "Add one or more skin-id's to the exclusion list (for players)" }); | |
textTable.AddRow(new[] { "skinbox.removeexcluded", "Remove one or more skin-id's from the exclusion list" }); | |
textTable.AddRow(new[] { "skinbox.addcollectionexclusion", "Add a skin collection to the exclusion list (for players)" }); | |
textTable.AddRow(new[] { "skinbox.removecollectionexclusion", "Remove a skin collection from the exclusion list (for players)" }); | |
textTable.AddRow(new[] { "skinbox.addcollection", "Adds a whole skin-collection to the workshop skin list"}); | |
textTable.AddRow(new[] { "skinbox.removecollection", "Removes a whole collection from the workshop skin list" }); | |
textTable.AddRow(new[] { "skinbox.addvipcollection", "Adds a whole skin-collection to the workshop skin list for the specified permission" }); | |
textTable.AddRow(new[] { "skinbox.removevipcollection", "Removes a whole collection from the workshop skin list for the specified permission" }); | |
sb.AppendLine(textTable.ToString()); | |
SendReply(arg, sb.ToString()); | |
} | |
#region Add/Remove Skins | |
[ConsoleCommand("skinbox.addskin")] | |
private void consoleAddSkin(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in one or more workshop skin ID's"); | |
return; | |
} | |
_skinsToVerify.Clear(); | |
for (int i = 0; i < arg.Args.Length; i++) | |
{ | |
if (!ulong.TryParse(arg.Args[i], out ulong fileId)) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it's not a number"); | |
continue; | |
} | |
else | |
{ | |
if (arg.Args[i].Length < 9 || arg.Args[i].Length > 10) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it is not the correct length (9 - 10 digits)"); | |
continue; | |
} | |
_skinsToVerify.Add(fileId); | |
} | |
} | |
if (_skinsToVerify.Count > 0) | |
SendWorkshopQuery(0, 0, arg); | |
else SendReply(arg, "No valid skin ID's were entered"); | |
} | |
[ConsoleCommand("skinbox.removeskin")] | |
private void consoleRemoveSkin(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in one or more workshop skin ID's"); | |
return; | |
} | |
_skinsToVerify.Clear(); | |
for (int i = 0; i < arg.Args.Length; i++) | |
{ | |
if (!ulong.TryParse(arg.Args[i], out ulong fileId)) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it's not a number"); | |
continue; | |
} | |
else | |
{ | |
if (arg.Args[i].Length < 9 || arg.Args[i].Length > 10) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it is not the correct length (9 - 10 digits)"); | |
continue; | |
} | |
_skinsToVerify.Add(fileId); | |
} | |
} | |
if (_skinsToVerify.Count > 0) | |
RemoveSkins(arg); | |
else SendReply(arg, "No valid skin ID's were entered"); | |
} | |
[ConsoleCommand("skinbox.addvipskin")] | |
private void consoleAddVIPSkin(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 2) | |
{ | |
SendReply(arg, "You need to type in a permission and one or more workshop skin ID's"); | |
return; | |
} | |
string perm = arg.Args[0]; | |
if (!Configuration.Permission.Custom.ContainsKey(perm)) | |
{ | |
SendReply(arg, $"The permission {perm} does not exist in the custom permission section of the config"); | |
return; | |
} | |
_skinsToVerify.Clear(); | |
for (int i = 1; i < arg.Args.Length; i++) | |
{ | |
if (!ulong.TryParse(arg.Args[i], out ulong fileId)) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it's not a number"); | |
continue; | |
} | |
else | |
{ | |
if (arg.Args[i].Length < 9 || arg.Args[i].Length > 10) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it is not the correct length (9 - 10 digits)"); | |
continue; | |
} | |
_skinsToVerify.Add(fileId); | |
} | |
} | |
if (_skinsToVerify.Count > 0) | |
SendWorkshopQuery(0, 0, arg, perm); | |
else SendReply(arg, "No valid skin ID's were entered"); | |
} | |
[ConsoleCommand("skinbox.removevipskin")] | |
private void consoleRemoveVIPSkin(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 2) | |
{ | |
SendReply(arg, "You need to type in a permission and one or more workshop skin ID's"); | |
return; | |
} | |
string perm = arg.Args[0]; | |
if (!Configuration.Permission.Custom.ContainsKey(perm)) | |
{ | |
SendReply(arg, $"The permission {perm} does not exist in the custom permission section of the config"); | |
return; | |
} | |
_skinsToVerify.Clear(); | |
for (int i = 1; i < arg.Args.Length; i++) | |
{ | |
if (!ulong.TryParse(arg.Args[i], out ulong fileId)) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it's not a number"); | |
continue; | |
} | |
else | |
{ | |
if (arg.Args[i].Length < 9 || arg.Args[i].Length > 10) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it is not the correct length (9 - 10 digits)"); | |
continue; | |
} | |
_skinsToVerify.Add(fileId); | |
} | |
} | |
if (_skinsToVerify.Count > 0) | |
RemoveSkins(arg, perm); | |
else SendReply(arg, "No valid skin ID's were entered"); | |
} | |
[ConsoleCommand("skinbox.validatevipskins")] | |
private void consoleValidateVIPSkins(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
_skinsToVerify.Clear(); | |
foreach (List<ulong> list in Configuration.Permission.Custom.Values) | |
{ | |
foreach (ulong skinId in list) | |
{ | |
if (skinId == 0UL) | |
continue; | |
ItemSkinDirectory.Skin skin = ItemSkinDirectory.Instance.skins.FirstOrDefault<ItemSkinDirectory.Skin>((ItemSkinDirectory.Skin x) => x.id == (int)skinId); | |
if (skin.invItem != null) | |
continue; | |
if (!Configuration.SkinList.Values.Any(x => x.Contains(skinId))) | |
_skinsToVerify.Add(skinId); | |
} | |
} | |
if (_skinsToVerify.Count > 0) | |
{ | |
SendReply(arg, $"Found {_skinsToVerify.Count} permission based skin IDs that are not in the skin list. Sending workshop request"); | |
SendWorkshopQuery(0, 0, arg); | |
} | |
else SendReply(arg, "No permission based skin ID's are missing from the skin list"); | |
} | |
#endregion | |
#region Add/Remove Collections | |
[ConsoleCommand("skinbox.addcollection")] | |
private void consoleAddCollection(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in a skin collection ID"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[0], out ulong collectionId)) | |
{ | |
SendReply(arg, $"{arg.Args[0]} is an invalid collection ID"); | |
return; | |
} | |
SendWorkshopCollectionQuery(collectionId, CollectionAction.AddSkin, 0, arg); | |
} | |
[ConsoleCommand("skinbox.removecollection")] | |
private void consoleRemoveCollection(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in a skin collection ID"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[0], out ulong collectionId)) | |
{ | |
SendReply(arg, $"{arg.Args[0]} is an invalid collection ID"); | |
return; | |
} | |
SendWorkshopCollectionQuery(collectionId, CollectionAction.RemoveSkin, 0, arg); | |
} | |
[ConsoleCommand("skinbox.addvipcollection")] | |
private void consoleAddVIPCollection(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 2) | |
{ | |
SendReply(arg, "You need to type in a permission and one or more workshop skin ID's"); | |
return; | |
} | |
string perm = arg.Args[0]; | |
if (!Configuration.Permission.Custom.ContainsKey(perm)) | |
{ | |
SendReply(arg, $"The permission {perm} does not exist in the custom permission section of the config"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[1], out ulong collectionId)) | |
{ | |
SendReply(arg, $"{arg.Args[1]} is an invalid collection ID"); | |
return; | |
} | |
SendWorkshopCollectionQuery(collectionId, CollectionAction.AddSkin, 0, arg, perm); | |
} | |
[ConsoleCommand("skinbox.removevipcollection")] | |
private void consoleRemoveVIPCollection(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 2) | |
{ | |
SendReply(arg, "You need to type in a permission and one or more workshop skin ID's"); | |
return; | |
} | |
string perm = arg.Args[0]; | |
if (!Configuration.Permission.Custom.ContainsKey(perm)) | |
{ | |
SendReply(arg, $"The permission {perm} does not exist in the custom permission section of the config"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[1], out ulong collectionId)) | |
{ | |
SendReply(arg, $"{arg.Args[1]} is an invalid collection ID"); | |
return; | |
} | |
SendWorkshopCollectionQuery(collectionId, CollectionAction.RemoveSkin, 0, arg, perm); | |
} | |
#endregion | |
#region Blacklisted Skins | |
[ConsoleCommand("skinbox.addexcluded")] | |
private void consoleAddExcluded(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in one or more skin ID's"); | |
return; | |
} | |
int count = 0; | |
for (int i = 0; i < arg.Args.Length; i++) | |
{ | |
if (!ulong.TryParse(arg.Args[i], out ulong skinId)) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it's not a number"); | |
continue; | |
} | |
else | |
{ | |
Configuration.Blacklist.Add(skinId); | |
count++; | |
} | |
} | |
if (count > 0) | |
{ | |
SaveConfiguration(); | |
SendReply(arg, $"Blacklisted {count} skin ID's"); | |
} | |
else SendReply(arg, "No skin ID's were added to the blacklist"); | |
} | |
[ConsoleCommand("skinbox.removeexcluded")] | |
private void consoleRemoveExcluded(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in one or more skin ID's"); | |
return; | |
} | |
int count = 0; | |
for (int i = 0; i < arg.Args.Length; i++) | |
{ | |
if (!ulong.TryParse(arg.Args[i], out ulong skinId)) | |
{ | |
SendReply(arg, $"Ignored '{arg.Args[i]}' as it's not a number"); | |
continue; | |
} | |
else | |
{ | |
if (Configuration.Blacklist.Contains(skinId)) | |
{ | |
Configuration.Blacklist.Remove(skinId); | |
SendReply(arg, $"The skin ID {skinId} is not on the blacklist"); | |
count++; | |
} | |
} | |
} | |
if (count > 0) | |
{ | |
SaveConfiguration(); | |
SendReply(arg, $"Removed {count} skin ID's from the blacklist"); | |
} | |
else SendReply(arg, "No skin ID's were removed from the blacklist"); | |
} | |
[ConsoleCommand("skinbox.addcollectionexclusion")] | |
private void consoleAddCollectionExclusion(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in a collection ID"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[0], out ulong collectionId)) | |
{ | |
SendReply(arg, $"{arg.Args[0]} is an invalid collection ID"); | |
return; | |
} | |
SendWorkshopCollectionQuery(collectionId, CollectionAction.ExcludeSkin, 0, arg); | |
} | |
[ConsoleCommand("skinbox.removecollectionexclusion")] | |
private void consoleRemoveCollectionExclusion(ConsoleSystem.Arg arg) | |
{ | |
if (arg.Connection != null && !permission.UserHasPermission(arg.Connection.userid.ToString(), Configuration.Permission.Admin)) | |
{ | |
SendReply(arg, "You do not have permission to use this command"); | |
return; | |
} | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Args == null || arg.Args.Length < 1) | |
{ | |
SendReply(arg, "You need to type in a collection ID"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[0], out ulong collectionId)) | |
{ | |
SendReply(arg, $"{arg.Args[0]} is an invalid collection ID"); | |
return; | |
} | |
SendWorkshopCollectionQuery(collectionId, CollectionAction.RemoveExludeSkin, 0, arg); | |
} | |
#endregion | |
[ConsoleCommand("skinbox.open")] | |
private void consoleSkinboxOpen(ConsoleSystem.Arg arg) | |
{ | |
if (arg == null) | |
return; | |
if (!_skinsLoaded) | |
{ | |
SendReply(arg, "SkinBox is still loading skins. Please wait"); | |
return; | |
} | |
if (arg.Connection == null) | |
{ | |
if (arg.Args == null || arg.Args.Length == 0) | |
{ | |
SendReply(arg, "This command requires a Steam ID of the target user"); | |
return; | |
} | |
if (!ulong.TryParse(arg.Args[0], out ulong targetUserID) || !Oxide.Core.ExtensionMethods.IsSteamId(targetUserID)) | |
{ | |
SendReply(arg, "Invalid Steam ID entered"); | |
return; | |
} | |
BasePlayer targetPlayer = BasePlayer.FindByID(targetUserID); | |
if (targetPlayer == null || !targetPlayer.IsConnected) | |
{ | |
SendReply(arg, $"Unable to find a player with the specified Steam ID"); | |
return; | |
} | |
if (targetPlayer.IsDead()) | |
{ | |
SendReply(arg, $"The specified player is currently dead"); | |
return; | |
} | |
if (!targetPlayer.inventory.loot.IsLooting()) | |
CreateSkinBox<LootHandler>(targetPlayer, null); | |
} | |
else if (arg.Connection != null && arg.Connection.player != null) | |
{ | |
BasePlayer player = arg.Player(); | |
cmdSkinBox(player, string.Empty, Array.Empty<string>()); | |
} | |
} | |
#endregion | |
#region API | |
private bool IsSkinBoxPlayer(ulong playerId) => _activeSkinBoxes.ContainsKey(playerId); | |
#endregion | |
#region Config | |
private static ConfigData Configuration; | |
private class ConfigData : BaseConfigData | |
{ | |
[JsonProperty(PropertyName = "Skin Options")] | |
public SkinOptions Skins { get; set; } | |
[JsonProperty(PropertyName = "Cooldown Options")] | |
public CooldownOptions Cooldown { get; set; } | |
[JsonProperty(PropertyName = "Command Options")] | |
public CommandOptions Command { get; set; } | |
[JsonProperty(PropertyName = "Permission Options")] | |
public PermissionOptions Permission { get; set; } | |
[JsonProperty(PropertyName = "Usage Cost Options")] | |
public CostOptions Cost { get; set; } | |
[JsonProperty(PropertyName = "Other Options")] | |
public OtherOptions Other { get; set; } | |
[JsonProperty(PropertyName = "Favourites Options")] | |
public FavouritesOptions Favourites { get; set; } | |
[JsonProperty(PropertyName = "Set Options")] | |
public SetOptions Sets { get; set; } | |
[JsonProperty(PropertyName = "Imported Workshop Skins")] | |
public Hash<string, HashSet<ulong>> SkinList { get; set; } | |
[JsonProperty(PropertyName = "Blacklisted Skin ID's")] | |
public HashSet<ulong> Blacklist { get; set; } | |
[JsonProperty(PropertyName = "Prevent items with the following Skin ID's from being re-skinned")] | |
public HashSet<ulong> ReskinSkinBlocklist { get; set; } = new HashSet<ulong>(); | |
[JsonProperty(PropertyName = "Prevent these items from being re-skinned (shortname)")] | |
public HashSet<string> ReskinItemBlocklist { get; set; } = new HashSet<string>(); | |
public class SkinOptions | |
{ | |
[JsonProperty(PropertyName = "Maximum number of approved skins allowed for each item (-1 is unlimited)")] | |
public int ApprovedLimit { get; set; } | |
[JsonProperty(PropertyName = "Maximum number of pages viewable")] | |
public int MaximumPages { get; set; } | |
[JsonProperty(PropertyName = "Include approved skins")] | |
public bool UseApproved { get; set; } | |
[JsonProperty(PropertyName = "Only use approved skins for these item shortnames (only used when 'Include approved skins' is disabled)")] | |
public HashSet<string> ApprovedItems { get; set; } = new HashSet<string>(); | |
[JsonProperty(PropertyName = "Approved skin timeout (seconds)")] | |
public int ApprovedTimeout { get; set; } | |
[JsonProperty(PropertyName = "Include manually imported workshop skins")] | |
public bool UseWorkshop { get; set; } | |
[JsonProperty(PropertyName = "Remove approved skin ID's from config workshop skin list")] | |
public bool RemoveApproved { get; set; } | |
[JsonProperty(PropertyName = "Include redirected skins")] | |
public bool UseRedirected { get; set; } | |
[JsonProperty(PropertyName = "Show skin ID's in the name for admins")] | |
public bool ShowSkinIDs { get; set; } | |
[JsonProperty(PropertyName = "Skin list order (Config, ConfigReversed, Alphabetical)")] | |
public SortBy Sorting { get; set; } | |
[JsonProperty(PropertyName = "Steam API key for workshop skins (https://steamcommunity.com/dev/apikey)")] | |
public string APIKey { get; set; } | |
} | |
public class CooldownOptions | |
{ | |
[JsonProperty(PropertyName = "Enable cooldowns")] | |
public bool Enabled { get; set; } | |
[JsonProperty(PropertyName = "Cooldown time start's when a item is removed from the box")] | |
public bool ActivateOnTake { get; set; } | |
[JsonProperty(PropertyName = "Length of cooldown time (seconds)")] | |
public int Time { get; set; } | |
} | |
public class PermissionOptions | |
{ | |
[JsonProperty(PropertyName = "Permission required to use SkinBox")] | |
public string Use { get; set; } | |
[JsonProperty(PropertyName = "Permission required to reskin deployed items")] | |
public string UseDeployed { get; set; } | |
[JsonProperty(PropertyName = "Permission required to use admin functions")] | |
public string Admin { get; set; } | |
[JsonProperty(PropertyName = "Permission that bypasses usage costs")] | |
public string NoCost { get; set; } | |
[JsonProperty(PropertyName = "Permission that bypasses usage cooldown")] | |
public string NoCooldown { get; set; } | |
[JsonProperty(PropertyName = "Permission required to skin weapons")] | |
public string Weapon { get; set; } | |
[JsonProperty(PropertyName = "Permission required to skin deployables")] | |
public string Deployable { get; set; } | |
[JsonProperty(PropertyName = "Permission required to skin attire")] | |
public string Attire { get; set; } | |
[JsonProperty(PropertyName = "Permission required to view approved skins")] | |
public string Approved { get; set; } | |
[JsonProperty(PropertyName = "Permission required to use skin sets")] | |
public string Sets { get; set; } | |
[JsonProperty(PropertyName = "Custom permissions per skin")] | |
public Hash<string, List<ulong>> Custom { get; set; } | |
public void RegisterPermissions(Permission permission, Plugin plugin) | |
{ | |
permission.RegisterPermission(Use, plugin); | |
if (!permission.PermissionExists(UseDeployed, plugin)) | |
permission.RegisterPermission(UseDeployed, plugin); | |
if (!permission.PermissionExists(Admin, plugin)) | |
permission.RegisterPermission(Admin, plugin); | |
if (!permission.PermissionExists(NoCost, plugin)) | |
permission.RegisterPermission(NoCost, plugin); | |
if (!permission.PermissionExists(NoCooldown, plugin)) | |
permission.RegisterPermission(NoCooldown, plugin); | |
if (!permission.PermissionExists(Weapon, plugin)) | |
permission.RegisterPermission(Weapon, plugin); | |
if (!permission.PermissionExists(Deployable, plugin)) | |
permission.RegisterPermission(Deployable, plugin); | |
if (!permission.PermissionExists(Attire, plugin)) | |
permission.RegisterPermission(Attire, plugin); | |
if (!permission.PermissionExists(Approved, plugin)) | |
permission.RegisterPermission(Approved, plugin); | |
if (!permission.PermissionExists(Sets, plugin)) | |
permission.RegisterPermission(Sets, plugin); | |
foreach (string perm in Custom.Keys) | |
{ | |
if (!permission.PermissionExists(perm, plugin)) | |
permission.RegisterPermission(perm, plugin); | |
} | |
} | |
public void ReverseCustomSkinPermissions(ref Hash<ulong, string> list) | |
{ | |
foreach (KeyValuePair<string, List<ulong>> kvp in Custom) | |
{ | |
for (int i = 0; i < kvp.Value.Count; i++) | |
{ | |
list[kvp.Value[i]] = kvp.Key; | |
} | |
} | |
} | |
} | |
public class CommandOptions | |
{ | |
[JsonProperty(PropertyName = "Commands to open the SkinBox")] | |
public string[] Commands { get; set; } | |
[JsonProperty(PropertyName = "Commands to open the deployed item SkinBox")] | |
public string[] DeployedCommands { get; set; } | |
[JsonProperty(PropertyName = "Commands to open the skin set box")] | |
public string[] SetCommands { get; set; } | |
[JsonProperty(PropertyName = "Allow skinning via Skinbox spray can")] | |
public bool SprayCan { get; set; } | |
internal void RegisterCommands(Game.Rust.Libraries.Command cmd, Plugin plugin) | |
{ | |
for (int i = 0; i < Commands.Length; i++) | |
cmd.AddChatCommand(Commands[i], plugin, "cmdSkinBox"); | |
for (int i = 0; i < DeployedCommands.Length; i++) | |
cmd.AddChatCommand(DeployedCommands[i], plugin, "cmdDeployedSkinBox"); | |
for (int i = 0; i < SetCommands.Length; i++) | |
cmd.AddChatCommand(SetCommands[i], plugin, "cmdSkinSetBox"); | |
} | |
} | |
public class CostOptions | |
{ | |
[JsonProperty(PropertyName = "Enable usage costs")] | |
public bool Enabled { get; set; } | |
[JsonProperty(PropertyName = "Currency used for usage costs (Scrap, Economics, ServerRewards)")] | |
public CostType Currency { get; set; } | |
[JsonProperty(PropertyName = "Cost to open the SkinBox")] | |
public int Open { get; set; } | |
[JsonProperty(PropertyName = "Cost to skin deployables")] | |
public int Deployable { get; set; } | |
[JsonProperty(PropertyName = "Cost to skin attire")] | |
public int Attire { get; set; } | |
[JsonProperty(PropertyName = "Cost to skin weapons")] | |
public int Weapon { get; set; } | |
} | |
public class OtherOptions | |
{ | |
[JsonProperty(PropertyName = "Allow stacked items")] | |
public bool AllowStacks { get; set; } | |
[JsonProperty(PropertyName = "Auth-level required to view blacklisted skins")] | |
public int BlacklistAuth { get; set; } | |
[JsonProperty(PropertyName = "Spray can max uses")] | |
public int SprayCanMaxUses { get; set; } | |
[JsonProperty(PropertyName = "Only allow skinning of items in areas where the player has building privilege")] | |
public bool RequireBuildingPrivilege { get; set; } | |
[JsonProperty(PropertyName = "Apply skin workshop name to item if available (false keeps the name of the input item)")] | |
public bool ApplyWorkshopName { get; set; } | |
} | |
public class FavouritesOptions | |
{ | |
[JsonProperty(PropertyName = "Enable favourites system")] | |
public bool Enabled { get; set; } | |
[JsonProperty(PropertyName = "Enable purging of favourites data")] | |
public bool Purge { get; set; } | |
[JsonProperty(PropertyName = "Purge user favourites that haven't been online for x amount of days")] | |
public int PurgeDays { get; set; } | |
} | |
public class SetOptions | |
{ | |
[JsonProperty(PropertyName = "Enable set system")] | |
public bool Enabled { get; set; } | |
[JsonProperty(PropertyName = "Number of skin sets allowed")] | |
public int NumberSets { get; set; } | |
} | |
} | |
protected override void LoadConfig() | |
{ | |
base.LoadConfig(); | |
Configuration = ConfigurationData as ConfigData; | |
} | |
protected override void PrepareConfigFile(ref ConfigurationFile configurationFile) => configurationFile = new ConfigurationFile<ConfigData>(Config); | |
protected override T GenerateDefaultConfiguration<T>() | |
{ | |
return new ConfigData | |
{ | |
Skins = new ConfigData.SkinOptions | |
{ | |
APIKey = string.Empty, | |
ApprovedLimit = -1, | |
MaximumPages = 3, | |
UseApproved = true, | |
ApprovedTimeout = 180, | |
RemoveApproved = false, | |
UseRedirected = true, | |
UseWorkshop = true, | |
ShowSkinIDs = true, | |
Sorting = SortBy.Config | |
}, | |
Command = new ConfigData.CommandOptions | |
{ | |
Commands = new [] { "skinbox", "sb" }, | |
DeployedCommands = new[] { "skindeployed", "sd" }, | |
SetCommands = new [] { "skinset", "ss" }, | |
SprayCan = true | |
}, | |
Permission = new ConfigData.PermissionOptions | |
{ | |
Admin = "skinbox.admin", | |
NoCost = "skinbox.ignorecost", | |
NoCooldown = "skinbox.ignorecooldown", | |
Use = "skinbox.use", | |
UseDeployed = "skinbox.use", | |
Approved = "skinbox.use", | |
Attire = "skinbox.use", | |
Deployable = "skinbox.use", | |
Weapon = "skinbox.use", | |
Sets = "skinbox.use", | |
Custom = new Hash<string, List<ulong>> | |
{ | |
["skinbox.example1"] = new List<ulong>() { 9990, 9991, 9992 }, | |
["skinbox.example2"] = new List<ulong>() { 9993, 9994, 9995 }, | |
["skinbox.example3"] = new List<ulong>() { 9996, 9997, 9998 } | |
} | |
}, | |
Cooldown = new ConfigData.CooldownOptions | |
{ | |
Enabled = false, | |
ActivateOnTake = true, | |
Time = 60 | |
}, | |
Cost = new ConfigData.CostOptions | |
{ | |
Enabled = false, | |
Currency = CostType.Scrap, | |
Open = 5, | |
Weapon = 30, | |
Attire = 20, | |
Deployable = 10 | |
}, | |
Other = new ConfigData.OtherOptions | |
{ | |
AllowStacks = false, | |
BlacklistAuth = 2, | |
ApplyWorkshopName = true, | |
RequireBuildingPrivilege = false | |
}, | |
Favourites = new ConfigData.FavouritesOptions | |
{ | |
Enabled = true, | |
Purge = true, | |
PurgeDays = 7 | |
}, | |
Sets = new ConfigData.SetOptions | |
{ | |
Enabled = true, | |
NumberSets = 3 | |
}, | |
SkinList = new Hash<string, HashSet<ulong>>(), | |
Blacklist = new HashSet<ulong>(), | |
} as T; | |
} | |
protected override void OnConfigurationUpdated(VersionNumber oldVersion) | |
{ | |
ConfigData baseConfig = GenerateDefaultConfiguration<ConfigData>(); | |
if (oldVersion < new VersionNumber(2, 0, 0)) | |
ConfigurationData = baseConfig; | |
if (oldVersion < new VersionNumber(2, 0, 10)) | |
{ | |
(ConfigurationData as ConfigData).Skins.Sorting = SortBy.Config; | |
(ConfigurationData as ConfigData).Skins.ShowSkinIDs = true; | |
} | |
if (oldVersion < new VersionNumber(2, 1, 0)) | |
{ | |
(ConfigurationData as ConfigData).Permission.UseDeployed = baseConfig.Permission.UseDeployed; | |
(ConfigurationData as ConfigData).Command.DeployedCommands = baseConfig.Command.DeployedCommands; | |
} | |
if (oldVersion < new VersionNumber(2, 1, 3)) | |
(ConfigurationData as ConfigData).Skins.ApprovedTimeout = 90; | |
if (oldVersion < new VersionNumber(2, 1, 9) && (ConfigurationData as ConfigData).Skins.ApprovedTimeout == 90) | |
(ConfigurationData as ConfigData).Skins.ApprovedTimeout = 180; | |
if (oldVersion < new VersionNumber(2, 1, 14)) | |
{ | |
(ConfigurationData as ConfigData).Favourites = baseConfig.Favourites; | |
} | |
if (oldVersion < new VersionNumber(2, 1, 23)) | |
{ | |
(ConfigurationData as ConfigData).Other.SprayCanMaxUses = 10; | |
} | |
if (oldVersion < new VersionNumber(2, 1, 25)) | |
(ConfigurationData as ConfigData).Other.ApplyWorkshopName = true; | |
if (oldVersion < new VersionNumber(2, 2, 0)) | |
{ | |
(ConfigurationData as ConfigData).Command.SetCommands = baseConfig.Command.SetCommands; | |
(ConfigurationData as ConfigData).Permission.Sets = baseConfig.Permission.Sets; | |
(ConfigurationData as ConfigData).Sets = baseConfig.Sets; | |
} | |
if (oldVersion < new VersionNumber(2, 2, 1)) | |
{ | |
(ConfigurationData as ConfigData).Command.SetCommands = baseConfig.Command.SetCommands; | |
} | |
Configuration = ConfigurationData as ConfigData; | |
} | |
#endregion | |
#region JSON Deserialization | |
public class QueryResponse | |
{ | |
public Response response; | |
} | |
public class Response | |
{ | |
public int total; | |
public PublishedFileDetails[] publishedfiledetails; | |
} | |
public class PublishedFileDetails | |
{ | |
public int result; | |
public string publishedfileid; | |
public string creator; | |
public int creator_appid; | |
public int consumer_appid; | |
public int consumer_shortcutid; | |
public string filename; | |
public string file_size; | |
public string preview_file_size; | |
public string file_url; | |
public string preview_url; | |
public string url; | |
public string hcontent_file; | |
public string hcontent_preview; | |
public string title; | |
public string file_description; | |
public int time_created; | |
public int time_updated; | |
public int visibility; | |
public int flags; | |
public bool workshop_file; | |
public bool workshop_accepted; | |
public bool show_subscribe_all; | |
public int num_comments_public; | |
public bool banned; | |
public string ban_reason; | |
public string banner; | |
public bool can_be_deleted; | |
public string app_name; | |
public int file_type; | |
public bool can_subscribe; | |
public int subscriptions; | |
public int favorited; | |
public int followers; | |
public int lifetime_subscriptions; | |
public int lifetime_favorited; | |
public int lifetime_followers; | |
public string lifetime_playtime; | |
public string lifetime_playtime_sessions; | |
public int views; | |
public int num_children; | |
public int num_reports; | |
public Preview[] previews; | |
public Tag[] tags; | |
public int language; | |
public bool maybe_inappropriate_sex; | |
public bool maybe_inappropriate_violence; | |
public class Tag | |
{ | |
public string tag; | |
public bool adminonly; | |
} | |
} | |
public class PublishedFileQueryResponse | |
{ | |
public FileResponse response { get; set; } | |
} | |
public class FileResponse | |
{ | |
public int result { get; set; } | |
public int resultcount { get; set; } | |
public PublishedFileQueryDetail[] publishedfiledetails { get; set; } | |
} | |
public class PublishedFileQueryDetail | |
{ | |
public string publishedfileid { get; set; } | |
public int result { get; set; } | |
public string creator { get; set; } | |
public int creator_app_id { get; set; } | |
public int consumer_app_id { get; set; } | |
public string filename { get; set; } | |
public int file_size { get; set; } | |
public string preview_url { get; set; } | |
public string hcontent_preview { get; set; } | |
public string title { get; set; } | |
public string description { get; set; } | |
public int time_created { get; set; } | |
public int time_updated { get; set; } | |
public int visibility { get; set; } | |
public int banned { get; set; } | |
public string ban_reason { get; set; } | |
public int subscriptions { get; set; } | |
public int favorited { get; set; } | |
public int lifetime_subscriptions { get; set; } | |
public int lifetime_favorited { get; set; } | |
public int views { get; set; } | |
public Tag[] tags { get; set; } | |
public class Tag | |
{ | |
public string tag { get; set; } | |
} | |
} | |
public class Preview | |
{ | |
public string previewid; | |
public int sortorder; | |
public string url; | |
public int size; | |
public string filename; | |
public int preview_type; | |
public string youtubevideoid; | |
public string external_reference; | |
} | |
public class CollectionQueryResponse | |
{ | |
public CollectionResponse response { get; set; } | |
} | |
public class CollectionResponse | |
{ | |
public int result { get; set; } | |
public int resultcount { get; set; } | |
public CollectionDetails[] collectiondetails { get; set; } | |
} | |
public class CollectionDetails | |
{ | |
public string publishedfileid { get; set; } | |
public int result { get; set; } | |
public CollectionChild[] children { get; set; } | |
} | |
public class CollectionChild | |
{ | |
public string publishedfileid { get; set; } | |
public int sortorder { get; set; } | |
public int filetype { get; set; } | |
} | |
#endregion | |
#region Data Management | |
private static StoredData Data; | |
private class StoredData | |
{ | |
public Hash<ulong, UserFavourites> Users = new Hash<ulong, UserFavourites>(); | |
public Hash<ulong, List<SetItem[]>> Sets = new Hash<ulong, List<SetItem[]>>(); | |
public void SortForPlayer(BasePlayer player, string shortname, ref List<ulong> list) | |
{ | |
if (!Users.TryGetValue(player.userID, out UserFavourites userFavourites)) | |
return; | |
if (!userFavourites.Usage.TryGetValue(shortname, out Hash<ulong, int> usage)) | |
return; | |
int index = 0; | |
foreach(KeyValuePair<ulong, int> kvp in usage.OrderByDescending(x => x.Value)) | |
{ | |
if (list.Contains(kvp.Key)) | |
{ | |
list.Remove(kvp.Key); | |
list.Insert(index, kvp.Key); | |
index++; | |
} | |
} | |
} | |
public bool UserHasFavouritesFor(BasePlayer player, string shortname) | |
{ | |
if (!Users.TryGetValue(player.userID, out UserFavourites userFavourites)) | |
return false; | |
return userFavourites.Usage.ContainsKey(shortname); | |
} | |
public void ClearForPlayer(BasePlayer player, string shortname) | |
{ | |
if (!Users.TryGetValue(player.userID, out UserFavourites userFavourites)) | |
return; | |
userFavourites.Usage.Remove(shortname); | |
} | |
public void OnSkinClaimed(BasePlayer looter, Item item) | |
{ | |
if (!Users.TryGetValue(looter.userID, out UserFavourites userFavourites)) | |
Users[looter.userID] = userFavourites = new UserFavourites(); | |
userFavourites.OnSkinClaimed(item.info.shortname, item.skin); | |
} | |
public void PurgeOldEntries() | |
{ | |
if (!Configuration.Favourites.Purge) | |
return; | |
List<ulong> list = Pool.Get<List<ulong>>(); | |
foreach(KeyValuePair<ulong, UserFavourites> kvp in Users) | |
{ | |
if (kvp.Value.HasExpired(Configuration.Favourites.PurgeDays)) | |
list.Add(kvp.Key); | |
} | |
foreach (ulong userId in list) | |
Users.Remove(userId); | |
Pool.FreeUnmanaged(ref list); | |
} | |
public List<SetItem[]> GetSkinSetsForPlayer(BasePlayer player) | |
{ | |
if (!Sets.TryGetValue(player.userID, out List<SetItem[]> sets)) | |
Sets[player.userID] = sets = new List<SetItem[]>(); | |
return sets; | |
} | |
public class SetItem | |
{ | |
public string Shortname; | |
public ulong SkinId; | |
[JsonIgnore] | |
private ItemDefinition _itemDefinition; | |
[JsonIgnore] | |
public ItemDefinition Definition | |
{ | |
get | |
{ | |
if (!_itemDefinition) | |
_itemDefinition = ItemManager.FindItemDefinition(Shortname); | |
return _itemDefinition; | |
} | |
} | |
} | |
public class UserFavourites | |
{ | |
public Hash<string, Hash<ulong, int>> Usage = new Hash<string, Hash<ulong, int>>(); | |
public double LastOnline = TimeSinceEpoch(); | |
public bool HasExpired(int days) => (TimeSinceEpoch() - LastOnline) > (days * 86400); | |
public void OnSkinClaimed(string shortname, ulong skinId) | |
{ | |
if (skinId == 0) | |
return; | |
if (!Usage.TryGetValue(shortname, out Hash<ulong, int> usage)) | |
Usage[shortname] = usage = new Hash<ulong, int>(); | |
usage[skinId] += 1; | |
} | |
private static readonly DateTime Epoch = new DateTime(1970, 1, 1); | |
private static int TimeSinceEpoch() => (int)DateTime.UtcNow.Subtract(Epoch).TotalSeconds; | |
} | |
private static DynamicConfigFile data; | |
public static void Save() => data.WriteObject(Data); | |
public static void Load() | |
{ | |
data = Interface.Oxide.DataFileSystem.GetFile("SkinBox/user_favourites"); | |
data.Settings.Converters.Add(new SetItemConverter()); | |
Data = data.ReadObject<StoredData>(); | |
if (Data == null) | |
Data = new StoredData(); | |
Data.PurgeOldEntries(); | |
} | |
} | |
#endregion | |
#region Converters | |
public class SetItemConverter : JsonConverter | |
{ | |
public override bool CanConvert(Type objectType) | |
{ | |
return objectType == typeof(Hash<ulong, List<StoredData.SetItem[]>>); | |
} | |
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |
{ | |
Hash<ulong, List<StoredData.SetItem[]>> sets = (Hash<ulong, List<StoredData.SetItem[]>>)value; | |
Dictionary<ulong, List<StoredData.SetItem[]>> filteredSets = sets.ToDictionary( | |
kvp => kvp.Key, | |
kvp => kvp.Value.Select(array => array.Where(item => item != null).ToArray()).ToList() | |
); | |
serializer.Serialize(writer, filteredSets); | |
} | |
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |
{ | |
Dictionary<ulong, List<StoredData.SetItem[]>> sets = serializer.Deserialize<Dictionary<ulong, List<StoredData.SetItem[]>>>(reader); | |
Hash<ulong, List<StoredData.SetItem[]>> results = new Hash<ulong, List<StoredData.SetItem[]>>(); | |
foreach (KeyValuePair<ulong, List<StoredData.SetItem[]>> kvp in sets) | |
{ | |
results[kvp.Key] = new List<StoredData.SetItem[]>(); | |
foreach (var set in kvp.Value) | |
{ | |
StoredData.SetItem[] array = set; | |
if (array.Length != SET_SLOTS) | |
Array.Resize(ref array, SET_SLOTS); | |
results[kvp.Key].Add(array); | |
} | |
} | |
return results; | |
} | |
} | |
#endregion | |
protected override Dictionary<string, string> Messages => new Dictionary<string, string> | |
{ | |
["NoAPIKey"] = "The server owner has not entered a Steam API key in the config. Unable to continue!", | |
["SkinsLoading"] = "SkinBox is still gathering skins. Please try again soon", | |
["NoPermission"] = "You don't have permission to use the SkinBox", | |
["NoPermission.Sets"] = "You don't have permission to use skin sets", | |
["NoBuildingAuth"] = "You must have building privilege to use the SkinBox", | |
["ToNearPlayer"] = "The SkinBox is currently not usable at this place", | |
["CooldownTime"] = "You need to wait {0} seconds to use the SkinBox again", | |
["NotEnoughBalanceOpen"] = "You need at least {0} {1} to open the SkinBox", | |
["NotEnoughBalanceUse"] = "You would need at least {0} {1} to skin {2}", | |
["NotEnoughBalanceTake"] = "{0} was not skinned. You do not have enough {1}", | |
["NotEnoughBalanceSet"] = "You need at least {0} {1} to apply this skin set", | |
["CostToUse2"] = "{0} {3} to skin a deployable\n{1} {3} to skin a weapon\n{2} {3} to skin attire", | |
["TargetSkinNotInList"] = "That skin is not defined in the skin list for that item", | |
["TargetSkinIsCurrentSkin"] = "The target item is already skinned with that skin ID", | |
["ApplyingSkin"] = "Applying skin {0}", | |
["NoItemInHands"] = "You must have a item in your hands to use the quick skin method", | |
["NoSkinsForItem"] = "There are no skins setup for that item", | |
["BlockedItem.Item"] = "That item is not allowed in the skin box", | |
["BlockedItem.Skin"] = "That skin is not allowed in the skin box", | |
["BrokenItem"] = "You can not skin broken items", | |
["HasItem"] = "The skin box already contains an item", | |
["RedirectsDisabled"] = "Redirected skins are disabled on this server", | |
["InsufficientItemPermission"] = "You do not have permission to skin this type of item", | |
["Cost.Scrap"] = "Scrap", | |
["Cost.ServerRewards"] = "RP", | |
["Cost.Economics"] = "Eco", | |
["ReskinError.InvalidResourcePath"] = "Failed to find resource path for deployed item", | |
["ReskinError.TargetNull"] = "The target deployable has been destroyed", | |
["ReskinError.MountBlocked"] = "You can not skin this while a player is mounted", | |
["ReskinError.IOConnected"] = "You can not skin this while it is connected", | |
["ReskinError.NoAuth"] = "You need building auth to reskin deployed items", | |
["ReskinError.NoTarget"] = "Unable to find a valid deployed item", | |
["NoDefinitionForEntity"] = "Failed to find the definition for the target item", | |
["FavouritesEnabled"] = "Favourites system enabled\nYour most used skins will be displayed first", | |
["UI.ClearFavourites"] = "Clear Favourites", | |
["UI.Sets"] = "Sets", | |
["UI.Box"] = "Box", | |
["UI.Title"] = "SkinBox", | |
["UI.Title.Sets"] = "Skin Sets", | |
["UI.Apply"] = "Apply", | |
["UI.Clear"] = "Clear", | |
["Sets.Cost"] = "Cost : {0} {1}", | |
["Sets.NoItems"] = "No items in your inventory are applicable for the chosen set", | |
["Sets.Applied"] = "You have applied skin set {0} to {1} item(s) in your inventory", | |
["Sets.Applied.Cost"] = "You have applied skin set {0} to {1} item(s) in your inventory for a price of {2} {3}", | |
["Sets.Error.InvalidSetID"] = "The set ID you entered is invalid, the range is between 1 and {0}", | |
["Sets.Error.EmptySet"] = "Skin set {0} has no skins in it", | |
["Sets.Help"] = "- Dragged skinned items into the container to add it to the set\n- Right click or hover loot to remove items from the set\n- A set can only contain unique items\n- Applying a set will skin the first matching item in your inventory" | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment