C#:
/* Copyright (C) Whispers88 - All Rights Reserved
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
* Written by Whispers88 rustafarian.server@gmail.com, March 2026
* Version 3.1.3
*
*You may not copy, modify, merge, publish, distribute, sublicense, or sell copies of This Software without the Developer’s consent
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using Facepunch;
using Network;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace Oxide.Plugins {
[Info("Skinner", "Whispers88", "3.1.3")]
[Description("Brings automation and ease to skinning items")]
public class Skinner : CovalencePlugin {
static Skinner skinner;
#region Perms
private const string permdefault = "skinner.default";
private const string permitems = "skinner.items";
private const string permcraft = "skinner.craft";
private const string permskininv = "skinner.skininv";
private const string permskinteam = "skinner.skinteam";
private const string permskinteamblock = "skinner.skinteamblock";
private const string permskincon = "skinner.skincon";
private const string permbypassauth = "skinner.bypassauth";
private const string permimport = "skinner.import";
private const string permskinbase = "skinner.skinbase";
private const string permskinall = "skinner.skinall";
private const string permskinauto = "skinner.skinauto";
private const string permskinautotoggle = "skinner.skinautotoggled";
private const string permskinrequest = "skinner.skinrequest";
private const string permskintry = "skinner.permskintry";
private const string permdev = "skinner.developer";
private List<string> permissions = new List<string>() {
permdefault, permcraft, permitems, permbypassauth, permskincon, permskininv, permimport, permskinbase, permskinall, permskinteam, permskinteamblock, permskinrequest, permskintry, permskinauto, permskinautotoggle, permdev
}
;
#endregion Perms
#region Init
private void OnServerInitialized() {
skinner = this;
_cachedSkinKeys = new List<int>();
_skinNames = new Dictionary<ulong, string>();
_skinableItems = new HashSet<int>();
_cachedSkins2 = new Dictionary<string, Dictionary<int, List<ulong>>>();
_WaitForSecondsMore = CoroutineEx.waitForSeconds(1f);
foreach (string perm in permissions) permission.RegisterPermission(perm, this);
foreach (string perm in config.Cooldowns.Keys) permission.RegisterPermission($"skinner.{perm}", this);
AddCovalenceCommand(config.commandSettings.cmdsskin, "SkinCMD");
AddCovalenceCommand(config.commandSettings.cmdsskincraft, "DefaultSkinsCMD");
AddCovalenceCommand(config.commandSettings.cmdsskinitems, "SkinItemCMD");
AddCovalenceCommand(config.commandSettings.cmdsskininv, "SkinInvCMD");
AddCovalenceCommand(config.commandSettings.cmdsskincon, "SkinConCMD");
AddCovalenceCommand(config.commandSettings.cmdtoggleautoskin, "SkinAutoCMD");
AddCovalenceCommand(config.commandSettings.cmdskinimport, "SkinImportCMD");
AddCovalenceCommand(config.commandSettings.cmdskinremove, "SkinRemoveCMD");
AddCovalenceCommand(config.commandSettings.cmdcollectionimport, "SkinImportCollection");
AddCovalenceCommand(config.commandSettings.cmdskinbase, "SkinBaseCMD");
AddCovalenceCommand(config.commandSettings.cmdskinallitems, "SkinAllItemsCMD");
AddCovalenceCommand(config.commandSettings.cmdskinteam, "SkinTeamCMD");
AddCovalenceCommand(config.commandSettings.cmdskinrequest, "SkinRequestCMD");
List<string> tempList = new List<string>(config.commandSettings.cmdskinset);
tempList.Add("setSelectCMD");
AddCovalenceCommand(tempList.ToArray(), "SetSelectCMD");
RegisterUICommands();
foreach (var skin in _workShopSkins) {
if (!string.IsNullOrEmpty(skin.Value.itemDisplayname) && !string.IsNullOrEmpty(skin.Value.itemShortname)) continue;
if (_WorkshopSkinIDCollectionList.Contains(skin.Key.ToString())) continue;
_WorkshopSkinIDCollectionList.Add(skin.Key.ToString());
}
if (!config.sprayCanOveride) Unsubscribe("OnActiveItemChanged");
if (!config.useOnItemCraft) Unsubscribe("OnItemCraftFinished");
//InitUI
InitUI();
GetSkins();
if (getCollectionscouroutine != null) {
Puts("getcollections already running!!");
}
else {
getCollectionscouroutine = GetCollectionSkinIDS();
ServerMgr.Instance.StartCoroutine(getCollectionscouroutine);
}
if (config.periodicDataSave > 0f) {
ServerMgr.Instance.InvokeRandomized(SaveData, config.periodicDataSave * 60f, config.periodicDataSave * 60f, 1f);
}
}
private void Loaded() {
_defaultSkins = Interface.Oxide.DataFileSystem.GetFile("Skinner/DefaultCraftSkins");
_playerData = Interface.Oxide.DataFileSystem.GetFile("Skinner/PlayerUsageData");
_skinRequestsData = Interface.Oxide.DataFileSystem.GetFile("Skinner/SkinsRequestData");
_workShopSkinsData = Interface.Oxide.DataFileSystem.GetFile("Skinner/ImportedSkins");
LoadData();
}
private void Unload() {
ServerMgr.Instance.CancelInvokeFixedTime(SaveData);
if (getCollectionscouroutine != null) ServerMgr.Instance.StopCoroutine(getCollectionscouroutine);
if (getSteamWorkshopSkinData != null) ServerMgr.Instance.StopCoroutine(getSteamWorkshopSkinData);
if (getSteamWorkshopRequestData != null) ServerMgr.Instance.StopCoroutine(getSteamWorkshopRequestData);
if (notifyDiscordCoroutine != null) ServerMgr.Instance.StopCoroutine(notifyDiscordCoroutine);
foreach (var player in BasePlayer.allPlayerList) {
UI.DestroyUI(player, BSkinPageUI);
UI.DestroyUI(player, BSkinSearchUI);
UI.DestroyUI(player, BSkinSetsSelectUI);
UI.DestroyUI(player, BSkinRequestsUI);
UI.DestroyUI(player, BSkinCloseMenuUI);
UI.DestroyUI(player, BSkinMenuUI);
if (_playerUsageData.TryGetValue(player.userID, out PlayerData playerData)) playerData.UpdateLastOnline();
}
foreach (var player in BasePlayer.allPlayerList) {
if (player.TryGetComponent<BoxController>(out BoxController boxController)) {
UnityEngine.Object.Destroy(boxController);
}
if (player.TryGetComponent<InventoryWatcher>(out InventoryWatcher inventoryWatcher)) {
UnityEngine.Object.Destroy(inventoryWatcher);
}
if (player.TryGetComponent<SpraycanController>(out SpraycanController spraycanController)) {
UnityEngine.Object.Destroy(spraycanController);
}
}
SaveData();
skinner = null;
_WaitForSecondsMore = null;
}
public static Dictionary<ulong, string>? _skinNames;
public static HashSet<int>? _skinableItems;
public static Dictionary<string, Dictionary<int, List<ulong>>>? _cachedSkins2;
private static ulong maskID = (ulong)1 << 63;
private static ulong maskID2 = 53199;
private ulong SetMask(ulong num) {
return num | maskID;
}
private ulong Get2Unmasked(ulong num) {
return num & ~maskID2;
}
public static ulong GetMask(ulong skinID, int itemID, bool redirectSkin = false) {
return redirectSkin ? (ulong)itemID | maskID : skinID;
}
public static bool HasMask(ulong uID) {
return (uID & maskID) == maskID;
}
public static ulong UnsetMask(ulong num) {
return num & ~maskID;
}
private void AddSkin(ulong skinID, int itemID, string displayName, int redirectID = -1, string perm = permdefault) {
if (config.blacklistedskins.Contains(skinID) || config.blacklisteditems.Contains(itemID) || config.blacklisteditems.Contains(redirectID)) return;
if (config.skinImportSettings.removeDLC) {
if (_skinToItemID.ContainsKey(skinID) || _skinToItemID.ContainsKey((ulong)itemID) || _skinToItemID.ContainsKey((ulong)redirectID)) {
perm = permdev;
}
}
if (config.skinImportSettings.devMode) {
perm = permdefault;
}
ulong uID = GetMask(skinID, redirectID, redirectID != -1);
AddCachedSkins(itemID, uID, perm);
_skinNames[uID] = displayName;
}
private void AddCachedSkins(int itemID, ulong skinID, string perm) {
if (!_cachedSkins2.TryGetValue(perm, out var itemDict)) {
itemDict = new Dictionary<int, List<ulong>>();
_cachedSkins2[perm] = itemDict;
}
if (!itemDict.TryGetValue(itemID, out var skinList)) {
skinList = new List<ulong>();
itemDict[itemID] = skinList;
if (perm == permdev) {
skinList.Add(0ul);
}
}
else if (skinList.Contains(skinID)) {
return;
}
skinList.Add(skinID);
}
private void RemoveCachedSkin(ulong skinID) {
ulong uID = GetMask(skinID, 0, false);
foreach (var perm in _cachedSkins2) {
foreach (var item in perm.Value) {
if (item.Value.Contains(skinID)) {
item.Value.Remove(skinID);
return;
}
}
}
}
private Dictionary<ulong, int> _skinToItemID = new Dictionary<ulong, int>();
private Dictionary<int, ulong> _contentToSkinID = new Dictionary<int, ulong>();
private Dictionary<int, int> _contentToItemID = new Dictionary<int, int>();
private Dictionary<int, List<int>> _unlockViaSteamID = new Dictionary<int, List<int>>();
private void GetSkins() {
if ((Steamworks.SteamInventory.Definitions?.Length ?? 0) == 0) {
Puts("Waiting for Steamworks to update skin item definitions");
Steamworks.SteamInventory.OnDefinitionsUpdated += GetSkins;
return;
}
Puts("Steamworks Updated, Updating Skins");
Steamworks.SteamInventory.OnDefinitionsUpdated -= GetSkins;
Dictionary<int, ItemCategory> catDefinitions = Pool.Get<Dictionary<int, ItemCategory>>();
if (!_cachedSkins2.TryGetValue(permdev, out Dictionary<int, List<ulong>>? _cachedSkins)) _cachedSkins = new Dictionary<int, List<ulong>>();
_cachedSkins2[permdev] = _cachedSkins;
//first add skins that are available via steam item packs (so we get the packids)
for (int i = 0; i < ItemSkinDirectory.Instance.skins.Length; i++) {
ItemSkinDirectory.Skin skin = ItemSkinDirectory.Instance.skins[i];
if (skin.invItem.UnlockedViaSteamItem) {
if (!_unlockViaSteamID.TryGetValue(skin.invItem.UnlockedViaSteamItem.id, out List<int> steamIDs)) {
steamIDs = new List<int>();
_unlockViaSteamID[skin.invItem.UnlockedViaSteamItem.id] = steamIDs;
}
steamIDs.Add(skin.id);
}
}
for (int i = 0; i < ItemSkinDirectory.Instance.skins.Length; i++) {
ItemSkinDirectory.Skin skin = ItemSkinDirectory.Instance.skins[i];
_skinToItemID[(ulong)skin.id] = skin.itemid;
ItemSkin itemSkin = skin.invItem as ItemSkin;
if (itemSkin != null) {
_skinToItemID[itemSkin.workshopID] = skin.itemid;
_skinToItemID[(ulong)itemSkin.id] = skin.itemid;
ItemDefinition itemDefinition = ItemManager.FindItemDefinition(skin.itemid);
if (itemSkin.Redirect) {
if (_unlockViaSteamID.TryGetValue(itemSkin.id, out List<int> steamIDs)) {
if (!steamIDs.Contains(itemSkin.Redirect.itemid)) {
steamIDs.Add(itemSkin.Redirect.itemid);
_contentToItemID[itemSkin.Redirect.itemid] = itemSkin.Redirect.itemid;
}
}
_contentToItemID[skin.id] = itemSkin.Redirect.itemid;
AddSkin((ulong)skin.id, itemSkin.Redirect.itemid, skin.invItem?.displayName?.english ?? itemDefinition.displayName.english, itemSkin.Redirect.itemid, permdev);
}
}
}
foreach (ItemDefinition itemDef in ItemManager.GetItemDefinitions()) {
if (!catDefinitions.ContainsKey(itemDef.itemid)) {
catDefinitions.Add(itemDef.itemid, itemDef.category);
}
if (itemDef.steamItem != null) {
_contentToItemID[itemDef.steamItem.id] = itemDef.itemid;
}
if (itemDef.steamDlc != null) {
_contentToItemID[itemDef.steamDlc.dlcAppID] = itemDef.itemid;
}
if (itemDef.isRedirectOf != null) {
AddSkin(0ul, itemDef.isRedirectOf.itemid, string.Empty, itemDef.itemid, permdev);
continue;
}
foreach (var skin in ItemSkinDirectory.ForItem(itemDef)) {
if (skin.id == 0) continue;
ItemSkin itemSkin = skin.invItem as ItemSkin;
if (itemSkin == null) continue;
bool isredirect = itemSkin?.Redirect != null;
if (isredirect) {
if (itemSkin?.itemDefinition == null) {
if (skin.id != 10431) Puts($"Skin {skin.name} - {skin.id} has a redirect but no item definition!");
continue;
}
AddSkin((ulong)skin.id, itemSkin.Redirect.itemid, skin.invItem?.displayName?.english ?? itemDef.displayName.english, itemSkin.itemDefinition.itemid, permdev);
}
else {
AddSkin((ulong)skin.id, itemDef.itemid, skin.invItem?.displayName?.english ?? itemDef.displayName.english, -1, permdev);
}
}
}
foreach (Steamworks.InventoryDef item in Steamworks.SteamInventory.Definitions) {
string shortname = item.GetProperty("itemshortname") == "lr300.item" ? "rifle.lr300" : item.GetProperty("itemshortname");
if (string.IsNullOrEmpty(shortname) || item.Id < 100) continue;
ulong skinid;
if (!ulong.TryParse(item.GetProperty("workshopid"), out skinid)) {
skinid = (ulong)item.Id;
}
if (skinid < 1000000) continue;
ItemDefinition steamitemdef = ItemManager.FindItemDefinition(shortname);
if (steamitemdef == null) continue;
_skinToItemID[skinid] = steamitemdef.itemid;
_contentToSkinID[item.Id] = skinid;
if (_cachedSkins.TryGetValue(steamitemdef.itemid, out List<ulong> cachedskinsList)) {
if (cachedskinsList.Contains((ulong)item.Id)) continue;
}
if (steamitemdef.isRedirectOf != null) AddSkin(skinid, steamitemdef.isRedirectOf.itemid, item.Name, steamitemdef.itemid, permdev);
else AddSkin(skinid, steamitemdef.itemid, item.Name, -1, permdev);
}
_cachedSkinKeys = new List<int>();
int sk = 0;
foreach (var perm in _cachedSkins2) {
foreach (var item in perm.Value) {
if (item.Value.Count <= 1) continue;
sk += item.Value.Count;
if (_cachedSkinKeys.Contains(item.Key)) continue;
if (item.Key == -1366326648) //broken spraycan decal
continue;
var itemDefinition = ItemManager.FindItemDefinition(item.Key);
if (itemDefinition == null || itemDefinition.isRedirectOf) continue;
_cachedSkinKeys.Add(item.Key);
}
}
SaveConfig();
//Re-order to look nice
_cachedSkinKeys.Sort((pair1, pair2) => catDefinitions[pair1].CompareTo(catDefinitions[pair2]));
Pool.FreeUnmanaged(ref catDefinitions);
Puts($"{sk} Workshop skins were indexed.. Checking for imported skins and collections");
Interface.CallHook("OnSkinnerCacheUpdated", _cachedSkins);
UpdateImportedSkins();
}
private void UpdateSkinKeys() {
Dictionary<int, ItemCategory> catDefinitions = Pool.Get<Dictionary<int, ItemCategory>>();
foreach (var def in ItemManager.GetItemDefinitions()) {
if (!catDefinitions.ContainsKey(def.itemid)) {
catDefinitions.Add(def.itemid, def.category);
}
}
bool changed = false;
foreach (var perm in _cachedSkins2) {
foreach (var item in perm.Value) {
if (item.Value.Count <= 1) continue;
if (_cachedSkinKeys.Contains(item.Key)) continue;
if (item.Key == -1366326648) continue;
var itemDefinition = ItemManager.FindItemDefinition(item.Key);
if (itemDefinition == null || itemDefinition.isRedirectOf) continue;
changed = true;
_cachedSkinKeys.Add(item.Key);
}
}
if (!changed) {
Pool.FreeUnmanaged(ref catDefinitions);
return;
}
_cachedSkinKeys.Sort((pair1, pair2) => catDefinitions[pair1].CompareTo(catDefinitions[pair2]));
Pool.FreeUnmanaged(ref catDefinitions);
Puts("Finished Updating Custom Skins");
}
public static List<int>? _cachedSkinKeys;
public static Dictionary<int, List<ulong>>? _cachedSkinDict;
private List<string> _customSkinPermissions = new List<string>();
private void UpdateImportedSkins() {
List<ulong> keys = Pool.Get<List<ulong>>();
keys.AddRange(new List<ulong>(_workShopSkins.Keys));
for (int i = _workShopSkins.Count - 1; i >= 0; i--) {
var whitelistSkin = _workShopSkins[keys[i]];
if (string.IsNullOrEmpty(whitelistSkin.itemDisplayname) || string.IsNullOrEmpty(whitelistSkin.itemShortname)) {
_workShopSkins.Remove(keys[i]);
continue;
}
if (string.IsNullOrEmpty(whitelistSkin.permission)) {
whitelistSkin.permission = permdefault;
_workShopSkins[keys[i]] = whitelistSkin;
}
else if (!whitelistSkin.permission.Contains("skinner.")) {
whitelistSkin.permission = $"skinner.{whitelistSkin.permission}";
_workShopSkins[keys[i]] = whitelistSkin;
}
ItemDefinition itemdef = ItemManager.FindItemDefinition(whitelistSkin.itemShortname);
if (itemdef == null) {
_workShopSkins.Remove(keys[i]);
Puts($"Could not find item definition for {whitelistSkin.itemShortname} {keys[i]}");
continue;
}
if (!_customSkinPermissions.Contains(whitelistSkin.permission)) {
permission.RegisterPermission(whitelistSkin.permission, this);
_customSkinPermissions.Add(whitelistSkin.permission);
}
AddSkin(keys[i], itemdef.itemid, whitelistSkin.itemDisplayname, -1, whitelistSkin.permission);
}
SaveWorkShopSkinsData();
UpdateSkinKeys();
UpdatePlayerSkins();
Pool.FreeUnmanaged(ref keys);
}
#endregion Init
#region Configuration
private Configuration config;
public class Configuration {
[JsonProperty("Skin Request Notification Discord Webhook")]
public string DiscordWebhook = "";
[JsonProperty("Remove player data after inactivity (days)")]
public int removedataTime = 14;
[JsonProperty("Use on itemcraft hook (skin items after crafting - not required when using skinauto)")]
public bool useOnItemCraft = false;
[JsonProperty("Apply workshop names to skins")]
public bool applySkinNames = true;
[JsonProperty("Override spraycan behaviour")]
public bool sprayCanOveride = false;
[JsonProperty("Use spraycan effect when holding spraycan and skinning deployables")]
public bool sprayCanEffect = true;
[JsonProperty("Periodic Data Saving (minutes 0 - on unload only)")]
public float periodicDataSave = 0f;
[JsonProperty("Debug Skin Imports")]
public bool debugImports = false;
[JsonProperty("Blacklisted Skins (skinID)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<ulong> blacklistedskins = new List<ulong>();
[JsonProperty("Blacklisted Items (itemID)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<int> blacklisteditems = new List<int>();
[JsonProperty("Import Skin collections (steam workshop ID)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<ulong> skinCollectionIDs = new List<ulong>();
[JsonProperty("Command based cooldowns ('permission' : 'command' seconds")]
public Dictionary<string, CoolDowns> Cooldowns = new Dictionary<string, CoolDowns>() {
{
"Default30CD", new CoolDowns()
}
}
;
[JsonProperty("Skin Import Settings")]
public SkinImportSettings skinImportSettings = new SkinImportSettings();
[JsonProperty("UI Settings")]
public UISettings uiSettings = new UISettings();
[JsonProperty("Command Settings")]
public CommandSettings commandSettings = new CommandSettings();
[JsonProperty("Imported Skins List", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<ulong, ImportedItem> ImportedSkinList = new Dictionary<ulong, ImportedItem>();
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
public class SkinImportSettings {
[JsonProperty("Dev Mode - unlocks all skins")]
public bool devMode = false;
[JsonProperty("Cache player skins (Better for performance, requires relogging to update skins list")]
public bool cachePSkins = true;
[JsonProperty("Auto Remove DLC and Paid Skins from Imports")]
public bool removeDLC = true;
[JsonProperty("Allow Owned Skins")]
public bool ownedSkins = true;
[JsonProperty("Allows skin sharing for teams")]
public bool skinShareTeam = false;
}
public class UISettings {
[JsonProperty("Disable Skin Sets and Options UI")]
public bool skinMenuButtons = false;
[JsonProperty("Add Search Bar UI")]
public bool searchbar = true;
[JsonProperty("Custom Page Change UI Positon anchor/offset 'min x, min y', 'max x', max y'")]
public string[] uiposition = new[] {
"0.5 0.0", "0.5 0.0", "198 60", "400 97"
}
;
[JsonProperty("Custom Search Menu UI Positon anchor/offset 'min x, min y', 'max x', max y'")]
public string[] uisearchmenuposition = new[] {
"0.5 0.0", "0.5 0.0", "240 635", "572 660"
}
;
[JsonProperty("Custom Set Selection UI Positon anchor/offset 'min x, min y', 'max x', max y'")]
public string[] uisetsposition = new[] {
"0.5 0.0", "0.5 0.0", "250 610", "573 633"
}
;
}
public class CommandSettings {
[JsonProperty("Skin Commands (skin items in you inventory")]
public string[] cmdsskin = new[] {
"skin", "s", "skinbox", "sb"
}
;
[JsonProperty("Skin Items Commands (skin items you have already placed")]
public string[] cmdsskinitems = new[] {
"skinitem", "si", "skindeployed", "sd"
}
;
[JsonProperty("Set default items to be skinned")]
public string[] cmdsskincraft = new[] {
"skincraft", "sc"
}
;
[JsonProperty("Automatically set all items in you inventory to your default skins")]
public string[] cmdsskininv = new[] {
"skininv", "sinv"
}
;
[JsonProperty("Automatically set all items a container to your default skins")]
public string[] cmdsskincon = new[] {
"skincon", "scon"
}
;
[JsonProperty("Automatically skin all deployables in your base")]
public string[] cmdskinbase = new[] {
"skinbase", "skinbuilding"
}
;
[JsonProperty("Automatically skin all items in your base")]
public string[] cmdskinallitems = new[] {
"skinall", "sall"
}
;
[JsonProperty("Automatically skin all items that are moved into you inventory")]
public string[] cmdtoggleautoskin = new[] {
"skinauto", "sauto"
}
;
[JsonProperty("Skin your teams inventories with your skin set")]
public string[] cmdskinteam = new[] {
"skinteam", "stm"
}
;
[JsonProperty("Request workshop skins via workshop ID")]
public string[] cmdskinrequest = new[] {
"skinrequest", "sr"
}
;
[JsonProperty("Approve workshop skin requests")]
public string[] cmdskinrequests = new[] {
"skinrequests", "srs"
}
;
[JsonProperty("Set your selected skin set")]
public string[] cmdskinset = new[] {
"skinset", "ss"
}
;
[JsonProperty("Import Custom Skins")]
public string[] cmdskinimport = new[] {
"skinimport", "sip"
}
;
[JsonProperty("Remove Custom Skins")]
public string[] cmdskinremove = new[] {
"skinremove"
}
;
[JsonProperty("Import Workshop Collection Command")]
public string[] cmdcollectionimport = new[] {
"colimport", "cip"
}
;
}
public struct ImportedItem {
public string itemShortname;
public string itemDisplayname;
public string permission;
}
public struct RequestItem {
public ulong skinID;
public int itemID;
public string itemDisplayname;
}
protected override void LoadDefaultConfig() => config = new Configuration();
protected override void LoadConfig() {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config == null) {
throw new JsonException();
}
var configDict = config.ToDictionary();
Dictionary<string, object> defaultconObjects = new Dictionary<string, object>();
foreach (var obj in Config) {
defaultconObjects.Add(obj.Key, obj.Value);
}
if (configDict.Count != defaultconObjects.Count) {
Puts("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
else {
foreach (var key in configDict.Keys) {
if (defaultconObjects.ContainsKey(key)) continue;
Puts("Configuration appears to be outdated; updating and saving");
SaveConfig();
break;
}
}
}
catch {
Puts($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig() {
Puts($"Configuration changes saved to {Name}.json");
Config.WriteObject(config, true);
}
#endregion Configuration
#region Data
private DynamicConfigFile _defaultSkins;
private DynamicConfigFile _playerData;
private DynamicConfigFile _skinRequestsData;
private DynamicConfigFile _workShopSkinsData;
public Dictionary<ulong, ImportedItem> _workShopSkins = new Dictionary<ulong, ImportedItem>();
private Dictionary<ulong, CoolDowns> _playercooldowns = new Dictionary<ulong, CoolDowns>();
private Dictionary<ulong, int> _playerSelectedSet = new Dictionary<ulong, int>();
private void LoadData() {
List<ulong> skipdata = Pool.Get<List<ulong>>();
try {
_playerUsageData = new Dictionary<ulong, PlayerData>();
long timenow = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
//need to read as dec and parse to ulong
var temp = _playerData.ReadObject<Dictionary<ulong, PlayerDataDec>>();
foreach (var pair in temp) {
//Not logged in for two weeks
if (timenow - pair.Value.lastonline > config.removedataTime * 86400) {
skipdata.Add(pair.Key);
continue;
}
_playerUsageData.Add(pair.Key, new PlayerData() {
lastonline = pair.Value.lastonline
}
);
foreach (var innerDict in pair.Value.skinusage) {
List<ulong> ulongList = new List<ulong>();
foreach (decimal d in innerDict.Value) {
ulong id = decimal.ToUInt64(d);
if (HasMask(id) && config.blacklisteditems.Contains((int)UnsetMask(id))) continue;
ulongList.Add(decimal.ToUInt64(d));
}
_playerUsageData[pair.Key].skinusage.TryAdd(innerDict.Key, ulongList);
}
}
}
catch {
_playerUsageData = new Dictionary<ulong, PlayerData>();
}
try {
_playerDefaultSkins = new Dictionary<ulong, Dictionary<int, ulong>[]>();
//need to read as dec and parse to ulong
var temp = _defaultSkins.ReadObject<Dictionary<ulong, Dictionary<int, decimal>[]>>();
foreach (var outerPair in temp) {
if (skipdata.Contains(outerPair.Key)) continue;
var innerDictArray = new Dictionary<int, ulong>[outerPair.Value.Length];
for (int i = 0; i < outerPair.Value.Length; i++) {
var innerDict = new Dictionary<int, ulong>();
foreach (var innerPair in outerPair.Value[i]) {
if (HasMask((ulong)innerPair.Value) && config.blacklisteditems.Contains((int)UnsetMask((ulong)innerPair.Value))) continue;
innerDict.Add(innerPair.Key, (ulong)innerPair.Value);
}
innerDictArray[i] = innerDict;
}
_playerDefaultSkins.Add(outerPair.Key, innerDictArray);
}
}
catch {
_playerDefaultSkins = new Dictionary<ulong, Dictionary<int, ulong>[]>();
}
try {
_requestsData = _skinRequestsData.ReadObject<List<RequestItem>>();
}
catch {
_requestsData = new List<RequestItem>();
}
try {
_workShopSkins = _workShopSkinsData.ReadObject<Dictionary<ulong, ImportedItem>>();
}
catch {
_workShopSkins = new Dictionary<ulong, ImportedItem>();
}
if (config.ImportedSkinList?.Count > 0) {
Puts("Merging imported skins from config to data file");
foreach (var skin in config.ImportedSkinList) {
if (!_workShopSkins.ContainsKey(skin.Key)) _workShopSkins.Add(skin.Key, skin.Value);
}
config.ImportedSkinList.Clear();
SaveConfig();
}
Pool.FreeUnmanaged(ref skipdata);
}
private void SaveData() {
Puts("DataSaved");
_defaultSkins.WriteObject(_playerDefaultSkins);
_playerData.WriteObject(_playerUsageData);
_skinRequestsData.WriteObject(_requestsData);
//_workShopSkinsData.WriteObject(_workShopSkins);
}
private void SaveWorkShopSkinsData() {
Puts("Saved Workshop Skins");
_workShopSkinsData.WriteObject(_workShopSkins);
}
private Dictionary<ulong, Dictionary<int, ulong>[]> _playerDefaultSkins;
private Dictionary<ulong, PlayerData> _playerUsageData;
private List<RequestItem> _requestsData;
public class PlayerDataDec {
public Dictionary<int, List<decimal>> skinusage = new Dictionary<int, List<decimal>>();
public long lastonline {
get;
set;
}
= 0;
}
public class PlayerData {
public Dictionary<int, List<ulong>> skinusage = new Dictionary<int, List<ulong>>();
public long lastonline {
get;
set;
}
= 0;
public void AddSkinUsage(ulong skinID, int itemID, int rItemID = 0) {
if (skinID == 0 && rItemID == 0) return;
if (!skinusage.TryGetValue(itemID, out var list)) {
list = new List<ulong>(6);
skinusage[itemID] = list;
}
ulong uID = GetMask(skinID, rItemID, rItemID != 0);
for (int i = list.Count - 1; i >= 0; i--) {
if (list[i] != uID) continue;
list.RemoveAt(i);
}
if (list.Count >= 6) {
list.RemoveAt(0);
}
list.Add(uID);
}
public List<ulong>? GetSkinUsage(int itemID) {
if (!skinusage.TryGetValue(itemID, out List<ulong>? usageList)) {
return null;
}
return usageList;
}
public void UpdateLastOnline() {
lastonline = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
}
public class CoolDowns {
public float skin = 30f;
public float skinitem = 30f;
public float skincraft = 30f;
public float skincon = 30f;
public float skininv = 30f;
public float skinteam = 30f;
public float skinbase = 60f;
public float skinall = 60f;
}
#endregion Data
#region Localization
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["NoPerms"]
= "You don't have permissions to use this command", ["NoBuildingAuth"] = "You must have building auth to use this", ["NoObjectsFound"] = "No object found", ["NoSkins"] = "No skins available", ["ImportSkinArgs"] = "Bad args, Required input skinid", ["ImportCollectionArgs"] = "Bad args, Required input collectionID", ["SkinIDError"] = "Cannot parse skinid {0}", ["NoShortname"] = "No item found for shortname : {0}", ["DuplicateSkin"] = "Duplicate Skin ID for : {0} {1}", ["SkinImported2"] = "Skin {0} has been imported and saved", ["SkinRemoved"] = "Skin {0} has been removed and saved", ["SkinRequested"] = "Skin {0} has been requested", ["AlreadyRequesting"] = "Already updating requests data please wait...", ["RequestingData"] = "Updating requests data please wait...", ["CollectionImported2"] = "Steam Skin Collection {0} has been imported and saved", ["CommandCooldown"] = "You can not use this command for another {0}", ["CompletedInvSkin"] = "All items in your inventory have been set to your default skins", ["CompletedConSkin"] = "All items in {0} have been set to your default skins", ["CompletedBuildingSkin"] = "All {0} in your base have been set to your default skins", ["CompletedAllSkin"] = "All {0} items in your base have been set to your default skins", ["SkinSetSelected"] = "Skin set {0} selected", ["SkinSetSelectedArgs"] = "Bad args, Required input set No. 1, 2 or 3", ["AutoSkinEnabled"] = "Auto skins enabled", ["AutoSkinDisabled"] = "Auto skins disabled", ["CompletedTeamSkin"] = "All items in your team have been set skin set {0}", ["NoTeam"] = "You need to be in a team to use this feature", ["TeamSkinBlockEnabled"] = "You enabled team skins", ["TeamSkinBlockDisabled"] = "You disabled team skins"
}
, this);
}
#endregion Localization
#region Commands
private void SkinAutoCMD(IPlayer iplayer, string command, string[] args) {
BasePlayer? player = iplayer.Object as BasePlayer;
if (player == null) return;
if (!HasPerm(player.UserIDString, permskinauto)) {
ChatMessage(iplayer, "NoPerms");
return;
}
if (!player.TryGetComponent<InventoryWatcher>(out InventoryWatcher inventoryWatcher)) {
player.gameObject.AddComponent<InventoryWatcher>();
ChatMessage(iplayer, "AutoSkinEnabled");
permission.GrantUserPermission(player.UserIDString, permskinautotoggle, this);
return;
}
permission.RevokeUserPermission(player.UserIDString, permskinautotoggle);
UnityEngine.Object.Destroy(inventoryWatcher);
ChatMessage(iplayer, "AutoSkinDisabled");
}
private void SkinImportCMD(IPlayer iplayer, string command, string[] args) {
BasePlayer? player = iplayer.Object as BasePlayer;
if (player != null) {
if (!HasPerm(player.UserIDString, permimport)) {
ChatMessage(iplayer, "NoPerms");
return;
}
}
else if (!iplayer.IsServer) return;
if (args.Length < 1) {
ChatMessage(iplayer, "ImportSkinArgs");
return;
}
ulong skinid = 0ul;
foreach (string arg in args) {
if (!ulong.TryParse(arg, out skinid)) continue;
_WorkshopSkinIDCollectionList.Add(skinid.ToString());
}
if (getSteamWorkshopSkinData != null) {
Puts("getSteamWorkshopSkinData already running!!");
}
else {
getSteamWorkshopSkinData = GetSteamWorkshopSkinData();
ServerMgr.Instance.StartCoroutine(getSteamWorkshopSkinData);
}
ChatMessage(iplayer, "SkinImported2", args[0]);
}
private void SkinRemoveCMD(IPlayer iplayer, string command, string[] args) {
BasePlayer? player = iplayer.Object as BasePlayer;
if (player != null) {
if (!HasPerm(player.UserIDString, permimport)) {
ChatMessage(iplayer, "NoPerms");
return;
}
}
else if (!iplayer.IsServer) return;
if (args.Length < 1) {
ChatMessage(iplayer, "ImportSkinArgs");
return;
}
ulong skinid = 0ul;
if (!ulong.TryParse(args[0], out skinid)) {
ChatMessage(iplayer, "ImportSkinArgs", args[0]);
return;
}
_workShopSkins.Remove(skinid);
RemoveSkinFromDefaultSkins(skinid);
RemoveSkinFromUsageHistory(skinid);
RemoveCachedSkin(skinid);
UpdateImportedSkins();
ChatMessage(iplayer, "SkinRemoved", args[0]);
}
private void RemoveSkinFromUsageHistory(ulong skinID) {
foreach (var player in _playerUsageData) {
foreach (var item in player.Value.skinusage) {
for (int i = item.Value.Count - 1; i >= 0; i--) {
if (item.Value[i] == skinID || HasMask(item.Value[i]) && UnsetMask(item.Value[i]) == skinID) {
item.Value.RemoveAt(i);
break;
}
}
}
}
_playerData.WriteObject(_playerUsageData);
}
private void RemoveSkinFromDefaultSkins(ulong skinID) {
foreach (var player in _playerDefaultSkins) {
foreach (var set in player.Value) {
foreach (var item in set) {
if (item.Value == skinID || HasMask(item.Value) && UnsetMask(item.Value) == skinID) {
set[item.Key] = 0ul;
break;
}
}
}
}
_defaultSkins.WriteObject(_playerDefaultSkins);
}
private void SkinImportCollection(IPlayer iplayer, string command, string[] args) {
BasePlayer player = iplayer.Object as BasePlayer;
if (player != null) {
if (!HasPerm(player.UserIDString, permimport)) {
ChatMessage(iplayer, "NoPerms");
return;
}
}
else if (!iplayer.IsServer) return;
if (args.Length < 1) {
ChatMessage(iplayer, "ImportCollectionArgs");
return;
}
ulong collectionid = 0ul;
if (!ulong.TryParse(args[0], out collectionid)) {
ChatMessage(iplayer, "ImportCollectionArgs", args[0]);
return;
}
config.skinCollectionIDs.Add(collectionid);
if (getCollectionscouroutine != null) {
Puts("getcollections already running!!");
}
else {
getCollectionscouroutine = GetCollectionSkinIDS();
ServerMgr.Instance.StartCoroutine(getCollectionscouroutine);
}
ChatMessage(iplayer, "CollectionImported2", args[0]);
}
private void SkinCMD(IPlayer iplayer, string command, string[] args) {
BasePlayer player = iplayer.Object as BasePlayer;
if (player == null) return;
if (!HasPerm(player.UserIDString, permdefault)) {
ChatMessage(iplayer, "NoPerms");
return;
}
if (_viewingcon.TryGetValue(player.userID, out BoxController? boxController)) {
if (boxController != null) UnityEngine.Object.Destroy(boxController);
_viewingcon.Remove(player.userID);
return;
}
//Check for cooldown
float cdtime = 0;
//Find shortest cd perm
foreach (var cdperm in config.Cooldowns) {
if (!HasPerm(player.UserIDString, $"skinner.{cdperm.Key}")) continue;
if (cdtime == 0) cdtime = cdperm.Value.skin;
else if (cdtime > cdperm.Value.skin) cdtime = cdperm.Value.skin;
}
if (cdtime > 0) {
if (!_playercooldowns.TryGetValue(player.userID, out CoolDowns? coolDowns)) _playercooldowns.Add(player.userID, new CoolDowns() {
skin = Time.time
}
);
else {
if (coolDowns.skin + cdtime > Time.time) {
ChatMessage(iplayer, "CommandCooldown", TimeSpan.FromSeconds(coolDowns.skin + cdtime - Time.time).ToString("hh' hrs 'mm' mins 'ss' secs'"));
return;
}
coolDowns.skin = Time.time;
}
}
ItemContainer itemContainer = CreateContainer();
if (itemContainer == null) return;
boxController = player.gameObject.AddComponent<BoxController>();
boxController.inventory = itemContainer;
boxController.StartAwake();
boxController.StartItemSkin();
_vie
// ... [Code preview truncated due to length. Full source in attached file] ...