C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using UnityEngine;
using Steamworks;
using System.Linq;
using System.Collections;
using System.Globalization;
namespace Oxide.Plugins {
[Info("PlayerSkins", "k1lly0u", "2.0.23")]
[Description("Fully Customization Skin Management Plugin - Sell Skinned items or Skins!")]
class PlayerSkins : RustPlugin {
#region Fields
[PluginReference]
Plugin Economics, ImageLibrary, ServerRewards;
private Dictionary<string, Dictionary<ulong, SkinData>> skinData = new Dictionary<string, Dictionary<ulong, SkinData>>();
private Dictionary<ulong, UserData> userData = new Dictionary<ulong, UserData>();
private List<ulong> excludedSkins = new List<ulong>();
private DynamicConfigFile userdata, skindata, excludeddata;
private Dictionary<string, Dictionary<ulong, WorkshopItem>> workshopItems = new Dictionary<string, Dictionary<ulong, WorkshopItem>>();
private Dictionary<string, string> shortnameToDisplayname = new Dictionary<string, string>();
private List<ulong> adminToggle = new List<ulong>();
private List<ulong> ownedToggle = new List<ulong>();
private List<ulong> approvedIds = new List<ulong>();
private bool initialized = false;
private bool workshopInitialized = false;
private bool itemlistInitialized = false;
private JsonSerializerSettings errorHandling = new JsonSerializerSettings {
Error = (se, ev) => {
ev.ErrorContext.Handled = true;
}
}
;
private static Dictionary<Colors, string> uiColor;
private static bool forceHttp = false;
private TokenType purchaseType;
private DisplayMode forcedMode;
public enum TokenType {
None, ServerRewards, Economics
}
#endregion
#region Oxide Hooks
private void Loaded() {
skindata = Interface.Oxide.DataFileSystem.GetFile("PlayerSkins/skinlist");
userdata = Interface.Oxide.DataFileSystem.GetFile("PlayerSkins/userdata");
excludeddata = Interface.Oxide.DataFileSystem.GetFile("PlayerSkins/excludedskins");
shortnameToDisplayname = Interface.Oxide.DataFileSystem.GetFile("PlayerSkins/itemDisplayNames").ReadObject<Dictionary<string, string>>();
permission.RegisterPermission("playerskins.shop", this);
permission.RegisterPermission("playerskins.reskin", this);
permission.RegisterPermission("playerskins.admin", this);
permission.RegisterPermission("playerskins.nocharge", this);
lang.RegisterMessages(Messages, this);
}
private void OnServerInitialized() {
LoadData();
forceHttp = configData.Workshop.ForceHTTP;
purchaseType = ParseType<TokenType>(configData.Purchase.Type);
if (configData.Purchase.Enabled && purchaseType == TokenType.None) {
PrintError("Invalid purchase plugin specified in config.Must be either 'ServerRewards' or 'Economics'!");
return;
}
forcedMode = ParseType<DisplayMode>(configData.Shop.ForcedMode);
foreach (string perm in configData.Shop.Permissions) {
if (!perm.StartsWith("playerskins.")) permission.RegisterPermission($"playerskins.{perm}", this);
else permission.RegisterPermission(perm, this);
}
uiColor = new Dictionary<Colors, string>();
foreach (var color in configData.UI.Colors) uiColor.Add(color.Key, UI.Color(color.Value.Color, color.Value.Alpha));
bool displayNameChanged = false;
foreach (ItemDefinition item in ItemManager.itemList) {
if (!shortnameToDisplayname.ContainsKey(item.shortname)) {
shortnameToDisplayname.Add(item.shortname, item.displayName.english);
displayNameChanged = true;
}
string workshopName = item.displayName.english.ToLower().Replace("skin", "").Replace(" ", "").Replace("-", "");
if (!workshopNameToShortname.ContainsKey(workshopName)) workshopNameToShortname.Add(workshopName, item.shortname);
if (!workshopNameToShortname.ContainsKey(item.shortname)) workshopNameToShortname.Add(item.shortname, item.shortname);
}
if (displayNameChanged) Interface.Oxide.DataFileSystem.WriteObject("PlayerSkins/itemDisplayNames", shortnameToDisplayname);
if (string.IsNullOrEmpty(configData.Workshop.SteamAPIKey)) {
PrintError("You must enter a Steam API key in your config in order to retrieve approved skin icons and/or access workshop items.Unable to continue...");
return;
}
if ((Steamworks.SteamInventory.Definitions?.Length ?? 0) == 0) {
PrintWarning("Waiting for Steamworks to update item definitions....");
Steamworks.SteamInventory.OnDefinitionsUpdated += StartApprovedRequest;
}
else StartApprovedRequest();
}
private void OnServerSave() => SaveUserData();
private void Unload() {
foreach (BasePlayer player in BasePlayer.activePlayerList) DestroyUI(player);
initialized = false;
}
private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem) {
if (player == null) return;
CuiHelper.DestroyUi(player, UI_RESKIN);
}
private void OnUseNPC(BasePlayer npc, BasePlayer player) {
if (player == null || npc == null) return;
if (!userData.ContainsKey(player.userID)) userData.Add(player.userID, new UserData());
if (configData.Shop.NPCs.Contains(npc.UserIDString)) OpenSkinMenu(player, "", 0, 0);
else if (configData.Reskin.NPCs.Contains(npc.UserIDString) && !configData.Shop.GiveItemOnPurchase) CreateReskinMenu(player);
}
private void OnItemCraftFinished(ItemCraftTask task, Item item) {
BasePlayer player = task?.owner;
if (player == null || item == null) return;
if (item.skin != 0) return;
UserData data;
if (!userData.TryGetValue(player.userID, out data)) return;
if (data.defaultSkins.ContainsKey(item.info.shortname)) ChangeItemSkin(item, data.defaultSkins[item.info.shortname]);
}
#endregion
#region Functions
private T ParseType<T>(string type) {
try {
return (T)Enum.Parse(typeof(T), type, true);
}
catch {
return default(T);
}
}
private bool HasPermission(BasePlayer player, string perm) {
if (!perm.StartsWith("playerskins.")) perm = $"playerskins.{perm}";
return permission.UserHasPermission(player.UserIDString, perm);
}
private void BroadcastAnnouncement() {
if (!configData.Announcements.Enabled && configData.Announcements.Interval > 0) return;
foreach (BasePlayer player in BasePlayer.activePlayerList) {
if (configData.Shop.DisableCommand) SendReply(player, msg("Help.Shop.NPC", player.userID));
else SendReply(player, msg("Help.Shop.Command", player.userID));
if (configData.Reskin.DisableCommand) SendReply(player, msg("Help.Reskin.NPC", player.userID));
else SendReply(player, msg("Help.Reskin.Command", player.userID));
}
timer.In(configData.Announcements.Interval * 60, BroadcastAnnouncement);
}
private void ChangeItemSkin(BasePlayer player, ulong targetSkin) {
Item item = player.GetActiveItem();
if (item == null) {
item = player.inventory.containerBelt.GetSlot(0);
if (item == null) return;
}
ChangeItemSkin(item, targetSkin);
int slot = item.position;
item.SetParent(null);
item.MarkDirty();
timer.Once(0.15f, () => {
if (item == null) return;
item.SetParent(player.inventory.containerBelt);
item.position = slot;
item.MarkDirty();
}
);
}
private void ChangeItemSkin(Item item, ulong targetSkin) {
item.skin = targetSkin;
item.MarkDirty();
BaseEntity heldEntity = item.GetHeldEntity();
if (heldEntity != null) {
heldEntity.skinID = targetSkin;
heldEntity.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
}
}
#endregion
#region Skin List Initialization
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"
}
, {
"burlapgloves", "burlap.gloves"
}
, {
"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"
}
, {
"burlap.gloves", "burlap.gloves.new"
}
, {
"leather.gloves", "burlap.gloves"
}
, {
"python", "pistol.python"
}
, {
"m39", "rifle.m39"
}
, {
"woodendoubledoor", "door.double.hinged.wood"
}
}
;
private string[] blockedItems = new string[] {
"sign.", "jackolantern.", "xmas", "boots.frog", "skull_fire_pit", "scarecrow", "snowman"
}
;
#endregion
#region Approved Skins
private void StartApprovedRequest() {
Steamworks.SteamInventory.OnDefinitionsUpdated -= StartApprovedRequest;
if (configData.Workshop.ApprovedDisabled && !configData.Workshop.Enabled) {
PrintError("You have approved skins and workshop skins disabled.This leaves no skins to be shown in the skin shop!");
return;
}
if (configData.Workshop.ApprovedDisabled && configData.Workshop.Enabled) {
itemlistInitialized = initialized = true;
GetWorkshopSkins();
return;
}
PrintWarning("Retrieving approved skin lists...");
itemlistInitialized = true;
GetApprovedItemSkins(BuildApprovedItemList(), 0);
}
private void GetApprovedItemSkins(List<ulong> itemsToDownload, int page) {
if (itemsToDownload.Count < 1) {
ValidateDataShortnames();
initialized = true;
Puts("Approved skins loaded");
if (configData.Workshop.Enabled) GetWorkshopSkins();
return;
}
int totalPages = Mathf.CeilToInt((float)itemsToDownload.Count / 100f) - 1;
string details = BuildDetailsString(itemsToDownload, page);
QueueFileQueryRequest(details, (PublishedFileQueryDetail[] items) => {
ServerMgr.Instance.StartCoroutine(ProcessApprovedBlock(itemsToDownload, items, page, totalPages));
}
);
}
private IEnumerator ProcessApprovedBlock(List<ulong> itemsToDownload, PublishedFileQueryDetail[] items, int page, int totalPages) {
PrintWarning($"Processing approved skins; Page {page + 1}/{totalPages + 1}");
Dictionary<string, Dictionary<ulong, string>> loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
foreach (PublishedFileQueryDetail item in items) {
if (!IsValid(item)) continue;
foreach (PublishedFileQueryDetail.Tag tag in item.tags) {
if (string.IsNullOrEmpty(tag.tag)) continue;
ulong workshopid = Convert.ToUInt64(item.publishedfileid);
string adjTag = tag.tag.ToLower().Replace("skin", "").Replace(" ", "").Replace("-", "").Replace(".item", "");
if (workshopNameToShortname.ContainsKey(adjTag)) {
string shortname = workshopNameToShortname[adjTag];
if (!skinData.ContainsKey(shortname)) skinData.Add(shortname, new Dictionary<ulong, SkinData>());
if (!skinData[shortname].ContainsKey(workshopid)) skinData[shortname].Add(workshopid, new SkinData());
if (!workshopItems.ContainsKey(shortname)) workshopItems.Add(shortname, new Dictionary<ulong, WorkshopItem>());
if (!workshopItems[shortname].ContainsKey(workshopid)) {
WorkshopItem workshopItem = new WorkshopItem(item);
if (workshopItem == null) continue;
workshopItems[shortname].Add(workshopid, workshopItem);
}
if (!HasImage(shortname, workshopid)) {
if (!loadOrder.ContainsKey(shortname)) loadOrder.Add(shortname, new Dictionary<ulong, string>());
if (!loadOrder[shortname].ContainsKey(workshopid)) {
loadOrder[shortname].Add(workshopid, workshopItems[shortname][workshopid].imageUrl);
}
}
}
}
}
if (loadOrder.Count > 0) {
loadOrderCount++;
Puts($"Creating load order for approved skins (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} approved skin icons (Order #{loadOrderCount})", loadOrder);
}
yield return CoroutineEx.waitForEndOfFrame;
yield return CoroutineEx.waitForEndOfFrame;
if (page < totalPages) GetApprovedItemSkins(itemsToDownload, page + 1);
else {
itemsToDownload.Clear();
ValidateDataShortnames();
initialized = true;
Puts("Approved skins loaded");
if (configData.Workshop.Enabled) GetWorkshopSkins();
}
}
private List<ulong> BuildApprovedItemList() {
List<ulong> list = new List<ulong>();
foreach (InventoryDef item in Steamworks.SteamInventory.Definitions) {
string shortname = item.GetProperty("itemshortname");
ulong workshopid;
if (item == null || string.IsNullOrEmpty(shortname)) continue;
if (workshopNameToShortname.ContainsKey(shortname)) shortname = workshopNameToShortname[shortname];
if (item.Id < 100 || blockedItems.Any(x => shortname.StartsWith(x))) continue;
if (!ulong.TryParse(item.GetProperty("workshopid"), out workshopid)) continue;
approvedIds.Add(workshopid);
if (excludedSkins.Contains(workshopid)) continue;
if (!skinData.ContainsKey(shortname)) skinData.Add(shortname, new Dictionary<ulong, SkinData>());
if (!skinData[shortname].ContainsKey(workshopid)) skinData[shortname].Add(workshopid, new SkinData());
if (HasImage(shortname, workshopid)) {
if (!workshopItems.ContainsKey(shortname)) workshopItems.Add(shortname, new Dictionary<ulong, WorkshopItem>());
if (!workshopItems[shortname].ContainsKey(workshopid)) {
WorkshopItem workshopItem = new WorkshopItem(item, GetImageURL(shortname, workshopid));
if (workshopItem == null) continue;
workshopItems[shortname].Add(workshopid, workshopItem);
}
continue;
}
list.Add(workshopid);
}
return list;
}
#endregion
#region Workshop Items
private const string publishedFileQuery = "https://api.steampowered.com/IPublishedFileService/QueryFiles/v1/?key={0}&page={1}&numperpage=100&appid=252490&requiredtags=version3%2Cskin&match_all_tags=1&return_tags=1&return_previews=1";
private const string publishedFileDetails = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/";
private int workshopPage = 0;
private int totalPages = -1;
private int totalCount = 0;
private int loadOrderCount = 0;
private void GetWorkshopSkins() {
if (ImageLibrary == null || string.IsNullOrEmpty(configData.Workshop.SteamAPIKey)) return;
loadOrderCount = 0;
PrintWarning("Querying Steam for workshop items.This process can take a while!\nWorkshop skins will be unavailable in the skin shop and reskin menu until this process has completed.Once the workshop data has been loaded any registered workshop skins will appear.");
WorkshopQuery(string.Format(publishedFileQuery, configData.Workshop.SteamAPIKey, workshopPage));
}
private void WorkshopQuery(string queryStr) {
webrequest.Enqueue(queryStr, null, (code, response) => {
try {
QueryResponse query = JsonConvert.DeserializeObject<QueryResponse>(response, errorHandling);
if (query == null || query.response == null || query.response.publishedfiledetails.Length == 0) {
if (code != 200) PrintError($"There was a error querying Steam for workshop data : Code ({code})");
workshopPage++;
if (workshopPage < totalPages) WorkshopQuery(string.Format(publishedFileQuery, configData.Workshop.SteamAPIKey, workshopPage));
else InitializeWorkshop();
return;
}
else {
if (totalPages == -1) totalPages = Mathf.CeilToInt((float)query.response.total / 100f);
ServerMgr.Instance.StartCoroutine(ProcessWorkshopResponse(query.response.publishedfiledetails));
}
}
catch (Exception ex) {
if (code != 200) PrintError($"There was a error querying Steam for workshop data : Code ({code})\n{ex.Message}");
workshopPage++;
if (workshopPage < totalPages) WorkshopQuery(string.Format(publishedFileQuery, configData.Workshop.SteamAPIKey, workshopPage));
else {
List<ulong> invalidSkins = FindMissingSkins();
if (invalidSkins.Count == 0) InitializeWorkshop();
else {
PrintWarning("Querying Steam for missing item icons...");
FindSkinsWithNoData(invalidSkins, 0);
}
}
}
}
, this);
}
private IEnumerator ProcessWorkshopResponse(PublishedFileDetails[] response) {
Dictionary<string, Dictionary<ulong, string>> loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
foreach (PublishedFileDetails item in response) {
if (!IsValid(item)) continue;
foreach (PublishedFileDetails.Tag tag in item.tags) {
if (string.IsNullOrEmpty(tag.tag)) continue;
ulong workshopId = Convert.ToUInt64(item.publishedfileid);
if (approvedIds.Contains(workshopId)) continue;
string adjTag = tag.tag.ToLower().Replace("skin", "").Replace(" ", "").Replace("-", "").Replace(".item", "");
if (workshopNameToShortname.ContainsKey(adjTag)) {
string shortname = workshopNameToShortname[adjTag];
if (!workshopItems.ContainsKey(shortname)) workshopItems.Add(shortname, new Dictionary<ulong, WorkshopItem>());
if (!workshopItems[shortname].ContainsKey(workshopId)) {
WorkshopItem workshopItem = new WorkshopItem(item);
if (workshopItem == null) continue;
workshopItems[shortname].Add(workshopId, workshopItem);
totalCount++;
}
if (skinData.ContainsKey(shortname)) {
if (skinData[shortname].ContainsKey(workshopId)) {
if (!HasImage(shortname, workshopId)) {
Puts("needs download " + shortname + " " + workshopId);
if (!loadOrder.ContainsKey(shortname)) loadOrder.Add(shortname, new Dictionary<ulong, string>());
if (!loadOrder[shortname].ContainsKey(workshopId)) {
loadOrder[shortname].Add(workshopId, workshopItems[shortname][workshopId].imageUrl);
}
}
}
}
}
}
}
if (loadOrder.Count > 0) {
Puts("create load order " + loadOrder.Count);
loadOrderCount++;
Puts($"Creating a load order for workshop skins (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} workshop skin icons (Order #{loadOrderCount})", loadOrder);
}
workshopPage++;
yield return CoroutineEx.waitForEndOfFrame;
yield return CoroutineEx.waitForEndOfFrame;
if (workshopPage < totalPages) {
WorkshopQuery(string.Format(publishedFileQuery, configData.Workshop.SteamAPIKey, workshopPage));
}
else {
List<ulong> invalidSkins = FindMissingSkins();
if (invalidSkins.Count == 0) InitializeWorkshop();
else {
PrintWarning("Querying Steam for missing item icons...");
FindSkinsWithNoData(invalidSkins, 0);
}
}
}
#endregion
#region Find Missing Skins
private List<ulong> FindMissingSkins() {
List<ulong> invalidSkins = new List<ulong>();
for (int i = skinData.Count - 1; i >= 0; i--) {
KeyValuePair<string, Dictionary<ulong, SkinData>> skinInfo = skinData.ElementAt(i);
for (int y = skinInfo.Value.Count - 1; y >= 0; y--) {
KeyValuePair<ulong, SkinData> skin = skinInfo.Value.ElementAt(y);
if (excludedSkins.Contains(skin.Key)) continue;
if (!workshopItems.ContainsKey(skinInfo.Key) || !workshopItems[skinInfo.Key].ContainsKey(skin.Key) || workshopItems[skinInfo.Key][skin.Key] == null) {
invalidSkins.Add(skin.Key);
}
}
}
return invalidSkins;
}
private void FindSkinsWithNoData(List<ulong> invalidSkins, int page) {
int totalPages = Mathf.CeilToInt((float)invalidSkins.Count / 100f) - 1;
string details = BuildDetailsString(invalidSkins, page);
QueueFileQueryRequest(details, (PublishedFileQueryDetail[] items) => {
ServerMgr.Instance.StartCoroutine(ProcessMissingBlock(invalidSkins, items, page, totalPages));
}
);
}
private IEnumerator ProcessMissingBlock(List<ulong> invalidSkins, PublishedFileQueryDetail[] items, int page, int totalPages) {
Dictionary<string, Dictionary<ulong, string>> loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
foreach (PublishedFileQueryDetail item in items) {
if (!IsValid(item)) continue;
foreach (PublishedFileQueryDetail.Tag tag in item.tags) {
if (string.IsNullOrEmpty(tag.tag)) continue;
ulong workshopId = Convert.ToUInt64(item.publishedfileid);
string adjTag = tag.tag.ToLower().Replace("skin", "").Replace(" ", "").Replace("-", "").Replace(".item", "");
if (workshopNameToShortname.ContainsKey(adjTag)) {
string shortname = workshopNameToShortname[adjTag];
if (!workshopItems.ContainsKey(shortname)) workshopItems.Add(shortname, new Dictionary<ulong, WorkshopItem>());
if (!workshopItems[shortname].ContainsKey(workshopId)) {
WorkshopItem workshopItem = new WorkshopItem(item);
if (workshopItem == null) continue;
workshopItems[shortname].Add(workshopId, workshopItem);
totalCount++;
}
if (skinData.ContainsKey(shortname)) {
if (skinData[shortname].ContainsKey(workshopId)) {
if (!HasImage(shortname, workshopId)) {
if (!loadOrder.ContainsKey(shortname)) loadOrder.Add(shortname, new Dictionary<ulong, string>());
if (!loadOrder[shortname].ContainsKey(workshopId)) {
loadOrder[shortname].Add(workshopId, workshopItems[shortname][workshopId].imageUrl);
}
}
}
}
}
}
}
if (loadOrder.Count > 0) {
loadOrderCount++;
Puts($"Creating a load order for missing skin icons (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} missing workshop skin icons (Order #{loadOrderCount})", loadOrder);
}
yield return CoroutineEx.waitForEndOfFrame;
yield return CoroutineEx.waitForEndOfFrame;
if (page < totalPages) FindSkinsWithNoData(invalidSkins, page + 1);
else {
invalidSkins.Clear();
InitializeWorkshop();
}
}
private void InitializeWorkshop() {
foreach (KeyValuePair<string, Dictionary<ulong, WorkshopItem>> kvp in workshopItems) Interface.Oxide.DataFileSystem.WriteObject<Dictionary<ulong, WorkshopItem>>($"PlayerSkins/WorkshopData/{kvp.Key}", kvp.Value);
Puts("Saved available workshop skin lists to \"/oxide/data/PlayerSkins/WorkshopData/\"");
PrintWarning($"Workshop skin list retrieval completed.{totalCount} workshop skins are now available!");
workshopInitialized = true;
}
#endregion
#region API Helpers
private bool ContainsKeyword(string title) {
foreach (string keyword in configData.Workshop.Filter) {
if (title.ToLower().Contains(keyword.ToLower())) return true;
}
return false;
}
private bool IsValid(PublishedFileDetails item) {
if (ContainsKeyword(item.title)) return false;
if (string.IsNullOrEmpty(item.preview_url)) return false;
if (item.tags == null) return false;
return true;
}
private bool IsValid(PublishedFileQueryDetail item) {
if (ContainsKeyword(item.title)) return false;
if (string.IsNullOrEmpty(item.preview_url)) return false;
if (item.tags == null) return false;
return true;
}
private string BuildDetailsString(List<ulong> list, int page) {
int start = page * 100;
int end = start + 100 > list.Count ? list.Count : start + 100;
string details = string.Format("?key={0}&itemcount={1}", configData.Workshop.SteamAPIKey, end - start);
for (int i = start; i < end; i++) details += string.Format("&publishedfileids[{0}]={1}", i - start, list[i]);
return details;
}
private void QueueFileQueryRequest(string details, Action<PublishedFileQueryDetail[]> callback) {
webrequest.Enqueue(publishedFileDetails, details, (code, response) => {
try {
PublishedFileQueryResponse query = JsonConvert.DeserializeObject<PublishedFileQueryResponse>(response, errorHandling);
if (query == null || query.response == null || query.response.publishedfiledetails.Length == 0) {
if (code != 200) PrintError($"There was a error querying Steam for workshop item data : Code ({code})");
return;
}
else {
if (query?.response?.publishedfiledetails?.Length > 0) callback.Invoke(query.response.publishedfiledetails);
}
}
catch {
}
}
, this, Core.Libraries.RequestMethod.POST);
}
private void ValidateDataShortnames() {
bool flag = false;
for (int i = skinData.Count - 1; i >= 0; i--) {
string shortname = skinData.ElementAt(i).Key;
if (!shortnameToDisplayname.ContainsKey(shortname)) {
skinData.Remove(shortname);
Puts($"Removing invalid item shortname from data file : {shortname}");
flag = true;
}
}
if (flag) SaveSkinData();
}
#endregion
#region Image Library
private string GetImage(string shortname, ulong skin = 0) => (string)ImageLibrary.Call("GetImage", shortname, skin);
private string GetImageURL(string shortname, ulong skin = 0) => (string)ImageLibrary.Call("GetImageURL", shortname, skin);
private bool AddImage(string url, string shortname, ulong skin = 0) => (bool)ImageLibrary.Call("AddImage", url, shortname, skin);
private bool HasImage(string shortname, ulong skin = 0) => (bool)ImageLibrary.Call("HasImage", shortname, skin);
private bool IsReady() => (bool)ImageLibrary.Call("IsReady");
#endregion
#region Chat Commands
[ChatCommand("skin")]
private void cmdSkin(BasePlayer player, string command, string[] args) {
if (!userData.ContainsKey(player.userID)) userData.Add(player.userID, new UserData());
if (args.Length == 0) {
if (!HasPermission(player, "reskin")) {
SendReply(player, "You do not have permission to use this command");
return;
}
if (configData.Shop.GiveItemOnPurchase) return;
if (configData.Reskin.DisableCommand) {
SendReply(player, "You can only access the re-skin menu via a re-skin NPC");
return;
}
CreateReskinMenu(player);
}
else {
if (args[0].ToLower() != "shop") {
SendReply(player, "/skin - Open the re-skin menu");
SendReply(player, "/skin shop - Open the skin shop");
return;
}
if (!HasPermission(player, "shop")) {
SendReply(player, "You do not have permission to use this command");
return;
}
if (configData.Shop.DisableCommand) {
SendReply(player, "You can only access the skin shop menu via a skin shop NPC");
return;
}
OpenSkinMenu(player, string.Empty, 0, 0);
}
}
#endregion
#region Console Commands
[ConsoleCommand("playerskins.skins")]
private void ccmdSkinManager(ConsoleSystem.Arg arg) {
if (arg.Connection != null) {
if (arg.Connection.authLevel < 2) {
SendReply(arg, "You do not have permission to use this command");
return;
}
}
if (arg.Args == null || arg.Args.Length < 3) {
SendReply(arg, "playerskins.skins import <item shortname> <skin ID> - Import the specified workshop skin");
SendReply(arg, "playerskins.skins import random <amount> - Import the specified amount of random skins for random items");
SendReply(arg, "playerskins.skins import random <item shortname> <amount> - Import the specified amount of random skins for the specified item");
SendReply(arg, "playerskins.skins remove <item shortname> <skin ID> - Remove the specified skin from the skin shop");
return;
}
if (!configData.Workshop.Enabled) {
SendReply(arg, "You have workshop disabled in your config.The playerskins.skins commands are unavailable when workshop is disabled");
return;
}
if (!workshopInitialized) {
SendReply(arg, "Workshop initialization has not yet finished.The playerskins.skins commands are unavailable until that process is complete");
return;
}
switch (arg.GetString(0).ToLower()) {
case "import" : if (arg.Args[1].ToLower() == "random") {
if (arg.Args.Length == 4) {
string shortname = arg.GetString(2).ToLower();
if (!ItemManager.itemList.Select(x => x.shortname).Contains(shortname) || !workshopItems.ContainsKey(shortname)) {
SendReply(arg, "The item shortname you have entered either does not have any skins, or is invalid");
return;
}
int amount;
if (!int.TryParse(arg.GetString(3), out amount)) {
SendReply(arg, "You must enter an amount of random skins to import");
return;
}
Dictionary<string, Dictionary<ulong, string>> loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
int skinCount = 0;
int loadOrderCount = 0;
int actualCount = 0;
List<KeyValuePair<ulong, WorkshopItem>> newList = new List<KeyValuePair<ulong, WorkshopItem>>(workshopItems[shortname].ToList());
for (int i = 0; i < amount; i++) {
if (newList.Count == 0) break;
var skinInfo = newList.GetRandom();
newList.Remove(skinInfo);
if (skinInfo.Value == null || (skinData.ContainsKey(shortname) && skinData[shortname].ContainsKey(skinInfo.Key)) || string.IsNullOrEmpty(skinInfo.Value.imageUrl)) {
i--;
continue;
}
actualCount++;
if (!skinData.ContainsKey(shortname)) skinData.Add(shortname, new Dictionary<ulong, SkinData>());
skinData[shortname].Add(skinInfo.Key, new SkinData());
if (!HasImage(shortname, skinInfo.Key)) {
if (!loadOrder.ContainsKey(shortname)) loadOrder.Add(shortname, new Dictionary<ulong, string>());
if (!loadOrder[shortname].ContainsKey(skinInfo.Key)) {
loadOrder[shortname].Add(skinInfo.Key, skinInfo.Value.imageUrl);
skinCount++;
}
}
if (skinCount >= 500) {
loadOrderCount++;
Puts($"Creating a skin load order (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} skin icons (Order #{loadOrderCount})", loadOrder);
loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
skinCount = 0;
}
}
if (skinCount > 0) {
loadOrderCount++;
Puts($"Creating a skin load order (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} skin icons (Order #{loadOrderCount})", loadOrder);
}
SendReply(arg, $"Importing {actualCount} random {shortname} workshop skins to ImageLibrary");
SaveSkinData();
}
else {
int amount;
if (!int.TryParse(arg.GetString(2), out amount)) {
SendReply(arg, "You must enter an amount of random skins to import");
return;
}
Dictionary<string, Dictionary<ulong, string>> loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
int skinCount = 0;
int loadOrderCount = 0;
Dictionary<string, Dictionary<ulong, WorkshopItem>> newList = new Dictionary<string, Dictionary<ulong, WorkshopItem>>(workshopItems);
for (int i = 0; i < amount; i++) {
if (newList.Count == 0) break;
string shortname = newList.Keys.ToArray().GetRandom();
var skinInfo = newList[shortname].ToArray().GetRandom();
if (skinInfo.Value == null || (skinData.ContainsKey(shortname) && skinData[shortname].ContainsKey(skinInfo.Key)) || string.IsNullOrEmpty(skinInfo.Value.imageUrl)) {
i--;
continue;
}
if (!skinData.ContainsKey(shortname)) skinData.Add(shortname, new Dictionary<ulong, SkinData>());
skinData[shortname].Add(skinInfo.Key, new SkinData());
if (!HasImage(shortname, skinInfo.Key)) {
if (!loadOrder.ContainsKey(shortname)) loadOrder.Add(shortname, new Dictionary<ulong, string>());
if (!loadOrder[shortname].ContainsKey(skinInfo.Key)) {
loadOrder[shortname].Add(skinInfo.Key, skinInfo.Value.imageUrl);
skinCount++;
}
}
if (skinCount >= 500) {
loadOrderCount++;
Puts($"Creating a skin load order (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} skin icons (Order #{loadOrderCount})", loadOrder);
loadOrder = new Dictionary<string, Dictionary<ulong, string>>();
skinCount = 0;
}
}
if (skinCount > 0) {
loadOrderCount++;
Puts($"Creating a skin load order (Order #{loadOrderCount})");
ImageLibrary.Call("ImportItemList", $"{Title} skin icons (Order #{loadOrderCount})", loadOrder);
}
SendReply(arg, $"Importing {amount} random workshop skins to ImageLibrary");
SaveSkinData();
}
}
else {
string shortname = arg.GetString(1).ToLower();
if (!ItemManager.itemList.Select(x => x.shortname).Contains(shortname) || !workshopItems.ContainsKey(shortname)) {
SendReply(arg, "The item shortname you have entered either does not have any skins, or is invalid");
return;
}
ulong skinId = 0U;
if (!ulong.TryParse(arg.GetString(2), out skinId) || !workshopItems[shortname].ContainsKey(skinId)) {
SendReply(arg, "The skin ID you have entered is either invalid or there is no skin with that ID");
return;
}
WorkshopItem workshopItem = workshopItems[shortname][skinId];
if (workshopItem == null || string.IsNullOrEmpty(workshopItem.imageUrl)) {
SendReply(arg, "The specified workshop skin can not be imported");
return;
}
if (!skinData.ContainsKey(shortname)) skinData.Add(shortname, new Dictionary<ulong, SkinData>());
if (!skinData[shortname].ContainsKey(skinId)) skinData[shortname].Add(skinId, new SkinData());
if (!HasImage(shortname, skinId)) {
Dictionary<string, Dictionary<ulong, string>> loadOrder = new Dictionary<string, Dictionary<ulong, string>> {
[shortname]
= new Dictionary<ulong, string> {
[skinId]
= workshopItem.imageUrl
}
}
;
ImageLibrary.Call("ImportItemList", $"{Title} - {shortname} {skinId}", loadOrder);
}
SendReply(arg, $"Importing skin ID {skinId} for {shortname} to ImageLibrary!");
SaveSkinData();
}
return;
case "remove" : {
string shortname = arg.GetString(1).ToLower();
if (!ItemManager.itemList.Select(x => x.shortname).Contains(shortname) || !skinData.ContainsKey(shortname)) {
SendReply(arg, "The item shortname you have entered either does not have any available skins, or is invalid");
return;
}
ulong skinId = 0U;
if (!ulong.TryParse(arg.GetString(2), out skinId) || !skinData[shortname].ContainsKey(skinId)) {
SendReply(arg, "The skin ID you have entered is either invalid or has not been added to the list of available skins");
return;
}
skinData[shortname][skinId].isDisabled = true;
ImageLibrary.Call("RemoveImage", shortname, skinId);
SendReply(arg, $"You have removed skin ID {skinId} for {shortname}!");
SaveSkinData();
}
return;
default : SendReply(arg, "Invalid syntax!");
break;
}
}
[ConsoleCommand("playerskins.setprice")]
private void ccmdSetSkinPrice(ConsoleSystem.Arg arg) {
if (arg.Connection != null) {
if (arg.Connection.authLevel < 2) {
SendReply(arg, "You do not have permission to use this command");
return;
}
}
if (arg.Args == null || arg.Args.Length != 2) {
SendReply(arg, "playerskins.setprice <item shortname> <amount> - Set the price for all skins for the specified item");
SendReply(arg, "playerskins.setprice all <amount> - Set the price for all skins for all items");
return;
}
int amount = 0;
if (!int.TryParse(arg.GetString(1), out amount)) {
SendReply(arg, "You must enter a number to set the price");
return;
}
string shortname = arg.GetString(0);
if (shortname.ToLower() == "all") {
foreach (var item in skinData.Values) {
foreach (var skin in item.Values) skin.cost = amount;
}
SendReply(arg, $"You have set all skin costs to {amount}");
}
else {
Dictionary<ulong, SkinData> data;
if (!skinData.TryGetValue(shortname, out data)) {
SendReply(arg, $"Either an invalid shortname was entered, or there are no skins for the specified item : {shortname}");
return;
}
foreach (var skin in data.Values) skin.cost = amount;
SendReply(arg, $"You have set all {shortname} skin costs to {amount}");
}
SaveSkinData();
}
#endregion
#region UI Helper
private const string UI_MAIN = "PSMainPanel";
private const string UI_SELECT = "PSSelectionPanel";
private const string UI_POPUP = "PSPopupPanel";
private const s
// ... [Code preview truncated due to length. Full source in attached file] ...