C#:
using Facepunch;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using UnityEngine;
using Oxide.Game.Rust.Libraries;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine.UI;
namespace Oxide.Plugins;
[Info("Server Rewards", "k1lly0u", "2.0.9")]
class ServerRewards : RustPlugin {
#region Fields
[PluginReference]
private Plugin Economics, HumanNPC, ImageLibrary, Kits, PlayerDLCAPI, PlaytimeTracker;
private List<ItemDefinition> _sortedItemDefinitions;
private readonly StringBuilder _stringBuilder = new StringBuilder();
private static Func<string, string, string> _getMessage;
private const string ADMIN_PERMISSION = "serverrewards.admin";
#endregion
#region Oxide Hooks
private void Loaded() {
LoadData();
CreateCommonFilters();
_getMessage = (key, userId) => lang.GetMessage(key, this, userId);
permission.RegisterPermission(ADMIN_PERMISSION, this);
_products.Data.RegisterPermissions(permission, this);
Command command = Interface.Oxide.GetLibrary<Command>();
command.AddChatCommand(Configuration.Options.StoreCommand, this, ChatOpenMenu);
command.AddChatCommand(Configuration.Options.RPCommand, this, ChatPointsManagement);
command.AddConsoleCommand(Configuration.Options.RPCommand, this, ConsolePointsManagement);
}
protected override void LoadDefaultMessages() => lang.RegisterMessages(_messages, this);
private void OnServerInitialized() {
CreateValidItemList();
UpdateSellPriceList();
RegisterImages();
CreateKitsFunctions();
if (!Configuration.Options.OwnedSkins) Debug.LogWarning("[ServerRewards] WARNING! As of August 7th 2025, granting access to paid skins that users do not own is against Rust's Terms of Service and can result in your server being delisted or worse.\n" + "If you continue to allow users to use paid skins, you do so at your own risk!\n" + "You can prevent users access to skins they do not own by enabling 'Only show/give players skins that they are allowed to use' in the config\n" + "https://facepunch.com/legal/servers");
else if (!PlayerDLCAPI) Debug.LogWarning("[ServerRewards] - PlayerDLCAPI plugin is not loaded, skin ownership checks will not work!");
foreach (BasePlayer player in BasePlayer.activePlayerList) SendPointNotification(player);
}
private void OnPlayerConnected(BasePlayer player) => SendPointNotification(player);
private void OnServerSave() {
_balances.Save();
_cooldowns.Save();
}
private void Unload() {
_balances.Save();
_cooldowns.Save();
UIUser.OnUnload();
}
#endregion
#region Functions
private readonly string[] _ignoreItems = new string[] {
"ammo.snowballgun", "blueprintbase", "rhib", "attackhelicopter", "motorbike", "motorbike_sidecar", "bicycle", "trike", "kayak", "rowboat", "tugboat", "skidoo", "minihelicopter.repair", "scraptransportheli.repair", "mlrs", "snowmobile", "spraycandecal", "vehicle.chassis", "vehicle.module", "water", "water.salt"
}
;
private void CreateValidItemList() {
_sortedItemDefinitions ??= new List<ItemDefinition>(ItemManager.itemList.Count);
foreach (ItemDefinition itemDefinition in ItemManager.itemList) {
if (_ignoreItems.Contains(itemDefinition.shortname)) continue;
_sortedItemDefinitions.Add(itemDefinition);
}
_sortedItemDefinitions.Sort((a, b) => {
if (string.IsNullOrEmpty(a.displayName.english) && string.IsNullOrEmpty(b.displayName.english)) return 0;
if (string.IsNullOrEmpty(a.displayName.english)) return 1;
if (string.IsNullOrEmpty(b.displayName.english)) return -1;
return a.displayName.english.CompareTo(b.displayName.english);
}
);
}
private void SendPointNotification(BasePlayer player) {
if (!player) return;
if (!_balances.Data.TryGetValue(player.userID, out int balance) || balance <= 0) return;
_stringBuilder.Clear();
_stringBuilder.AppendLine(_getMessage("Message.Title", player.UserIDString));
_stringBuilder.AppendLine(string.Format(_getMessage("Message.Notification.Unspent", player.UserIDString), balance));
_stringBuilder.AppendLine(Configuration.Options.NpcOnly ? _getMessage("Message.Notification.Unspent.NPC", player.UserIDString) : string.Format(_getMessage("Message.Notification.Unspent.Command", player.UserIDString), Configuration.Options.StoreCommand));
player.ChatMessage(_stringBuilder.ToString());
}
private static double CurrentTime() => DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
private bool IsAdmin(BasePlayer player) => permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION) || (player.net?.connection != null && player.net.connection.authLevel >= 2);
private bool IsAdminOrConsole(ConsoleSystem.Arg arg) {
if (arg.Connection == null) return true;
BasePlayer player = arg.Player();
if (!player) return false;
return IsAdmin(player);
}
#endregion
#region Chat Commands
private bool OpenBlockedByPlugin(BasePlayer player) {
object success = Interface.Call("canShop", player);
if (success == null) return false;
string message = success as string ?? _getMessage("Message.ShopBlockedByPlugin", player.UserIDString);
player.ChatMessage(message);
return true;
}
private void ChatOpenMenu(BasePlayer player, string command, string[] args) {
if (Configuration.Options.NpcOnly && !IsAdmin(player)) return;
if (OpenBlockedByPlugin(player)) return;
OpenStore(player);
}
#endregion
#region Admin Commands
private void ChatPointsManagement(BasePlayer player, string command, string[] args) {
if (!IsAdmin(player)) return;
if (args.Length >= 2) {
bool isWildcard = args[1] == "*";
IPlayer target = isWildcard ? null : covalence.Players.FindPlayer(args[1]);
ulong targetID = target != null && ulong.TryParse(target.Id, out ulong u) ? u : 0UL;
int amount = args.Length == 3 && int.TryParse(args[2], out int a) ? a : 0;
if (!isWildcard) {
if (target == null) {
player.ChatMessage("Player not found, please specify a valid username or user ID.");
return;
}
if (targetID == 0UL) {
player.ChatMessage("Player was found, but they have a invalid user ID.");
return;
}
}
switch (args[0].ToLower()) {
case "add" : player.ChatMessage(CommandAddPoints(isWildcard, target, targetID, amount));
return;
case "take" : player.ChatMessage(CommandTakePoints(isWildcard, target, targetID, amount));
return;
case "clear" : player.ChatMessage(CommandClearPoints(isWildcard, target, targetID));
return;
case "check" : if (isWildcard) {
player.ChatMessage("You can not check RP using the '*' wildcard, please specify a valid username or user ID.");
return;
}
player.ChatMessage(CommandCheckPoints(target, targetID));
return;
default : break;
}
}
string hex = Configuration.UI.ButtonConfirmText.Hex;
string cmd = Configuration.Options.RPCommand;
player.ChatMessage($"<size=16><color=#{hex}>[{Title}]</color> Points Management</size>");
player.ChatMessage($"<color=#{hex}>/{cmd} add <username|userid|*> <amount></color> - Add the specified amount of RP to a player");
player.ChatMessage($"<color=#{hex}>/{cmd} take <username|userid|*> <amount></color> - Deduct the specified amount of RP from a player");
player.ChatMessage($"<color=#{hex}>/{cmd} clear <username|userid|*></color> - Reset the specified players RP to 0");
player.ChatMessage($"<color=#{hex}>/{cmd} check <username|userid></color> - Show the amount of RP a player has");
player.ChatMessage($"Use the '<color=#{hex}>*</color>' wildcard in place of name or id to apply the command to all players.");
}
private bool ConsolePointsManagement(ConsoleSystem.Arg arg) {
if (!IsAdminOrConsole(arg)) return false;
if (arg.Args is {
Length : >= 2
}
) {
bool isWildcard = arg.Args[1] == "*";
IPlayer target = isWildcard ? null : covalence.Players.FindPlayer(arg.GetString(1));
ulong targetID = target != null && ulong.TryParse(target.Id, out ulong u) ? u : 0UL;
int amount = arg.Args.Length == 3 && int.TryParse(arg.GetString(2), out int a) ? a : 0;
if (!isWildcard) {
if (target == null) {
SendReply(arg, "Player not found, please specify a valid username or user ID.");
return true;
}
if (targetID == 0UL) {
SendReply(arg, "Player was found, but they have a invalid user ID.");
return true;
}
}
switch (arg.GetString(0).ToLower()) {
case "add" : SendReply(arg, CommandAddPoints(isWildcard, target, targetID, amount));
return true;
case "take" : SendReply(arg, CommandTakePoints(isWildcard, target, targetID, amount));
return true;
case "clear" : SendReply(arg, CommandClearPoints(isWildcard, target, targetID));
return true;
case "check" : if (isWildcard) {
SendReply(arg, "You can not check RP using the '*' wildcard, please specify a valid username or user ID.");
return true;
}
SendReply(arg, CommandCheckPoints(target, targetID));
return true;
default : break;
}
}
SendReply(arg, $"{Configuration.Options.RPCommand} add <username|userid|*> <amount> - Add the specified amount of RP to a player");
SendReply(arg, $"{Configuration.Options.RPCommand} take <username|userid|*> <amount> - Deduct the specified amount of RP from a player");
SendReply(arg, $"{Configuration.Options.RPCommand} clear <username|userid|*> - Reset the specified players RP to 0");
SendReply(arg, $"{Configuration.Options.RPCommand} check <username|userid> - Show the amount of RP a player has");
SendReply(arg, "Use the '*' wildcard in place of name or id to apply the command to all players.");
return true;
}
private string CommandAddPoints(bool isWildcard, IPlayer target, ulong targetID, int amount) {
if (amount <= 0) return "Please specify a valid amount to add. Must be greater than 0.";
if (isWildcard) {
List<ulong> userIDs = Pool.Get<List<ulong>>();
userIDs.AddRange(_balances.Data.Keys);
foreach (ulong userID in userIDs) {
_balances.Data[userID] += amount;
SendPointsUpdated(userID);
}
Pool.FreeUnmanaged(ref userIDs);
return $"You have added {amount}RP to every player!";
}
if (!_balances.Data.ContainsKey(targetID)) _balances.Data[targetID] = amount;
else _balances.Data[targetID] += amount;
SendPointsUpdated(targetID);
return $"You have added {amount}RP to {target.Name} ({target.Id})!";
}
private string CommandTakePoints(bool isWildcard, IPlayer target, ulong targetID, int amount) {
if (amount <= 0) return "Please specify a valid amount to add. Must be greater than 0.";
if (isWildcard) {
List<ulong> userIDs = Pool.Get<List<ulong>>();
userIDs.AddRange(_balances.Data.Keys);
foreach (ulong userID in userIDs) {
_balances.Data[userID] = Mathf.Max(_balances.Data[userID] - amount, 0);
SendPointsUpdated(userID);
}
Pool.FreeUnmanaged(ref userIDs);
return $"You have deducted {amount}RP from every player!";
}
if (!_balances.Data.ContainsKey(targetID)) return $"Player {target.Name} ({target.Id}) does not have any RP to deduct from!";
_balances.Data[targetID] = Mathf.Max(_balances.Data[targetID] - amount, 0);
SendPointsUpdated(targetID);
return $"You have deducted {amount}RP from {target.Name} ({target.Id})!";
}
private string CommandClearPoints(bool isWildcard, IPlayer target, ulong targetID) {
if (isWildcard) {
List<ulong> userIDs = Pool.Get<List<ulong>>();
userIDs.AddRange(_balances.Data.Keys);
foreach (ulong userID in userIDs) {
_balances.Data[userID] = 0;
SendPointsUpdated(userID);
}
Pool.FreeUnmanaged(ref userIDs);
return "You have reset every players RP to 0!";
}
if (!_balances.Data.ContainsKey(targetID)) return $"Player {target.Name} ({target.Id}) does not have any RP!";
_balances.Data[targetID] = 0;
SendPointsUpdated(targetID);
return $"You have reset {target.Name}'s ({target.Id}) RP to 0!";
}
private string CommandCheckPoints(IPlayer target, ulong targetID) => !_balances.Data.TryGetValue(targetID, out int balance) ? $"Player {target.Name} ({target.Id}) does not have any RP!" : $"{target.Name} ({target.Id}) has {balance}RP!";
#endregion
#region Image Library
private const string MagnifyIconUrl = "https://files.rusthq.com/images/magnifyingglass.png";
private const string SearchIconIdentifier = "srui.search";
private static string _magnifyIcon;
private static string _dataDirectory;
private void RegisterImages() {
if (!ImageLibrary) {
Debug.LogWarning("[ServerRewards] ImageLibrary plugin not found, custom images will not be loaded...");
return;
}
Dictionary<string, string> loadOrder = new Dictionary<string, string>();
_dataDirectory = Path.Combine($"file://{Interface.Oxide.DataDirectory}", "ServerRewards", "Images");
loadOrder[SearchIconIdentifier] = MagnifyIconUrl;
foreach (Products.Item item in _products.Data.Items) {
if (string.IsNullOrEmpty(item.IconURL)) continue;
string url = item.IconURL;
if (!IsUrl(url)) url = Path.Combine(_dataDirectory, url);
loadOrder[item.IconURL] = url;
}
foreach (Products.Kit kit in _products.Data.Kits) {
if (string.IsNullOrEmpty(kit.IconURL)) continue;
string url = kit.IconURL;
if (!IsUrl(url)) url = Path.Combine(_dataDirectory, url);
loadOrder[kit.IconURL] = url;
}
foreach (Products.Command command in _products.Data.Commands) {
if (string.IsNullOrEmpty(command.IconURL)) continue;
string url = command.IconURL;
if (!IsUrl(url)) url = Path.Combine(_dataDirectory, url);
loadOrder[command.IconURL] = url;
}
ImageLibrary.Call("ImportImageList", Title, loadOrder, 0UL, false, new Action(OnImagesLoaded));
}
private void OnImagesLoaded() => _magnifyIcon = GetImage(SearchIconIdentifier);
private void AddImage(string iconUrl) {
string url = iconUrl;
if (!IsUrl(url)) url = Path.Combine(_dataDirectory, url);
ImageLibrary?.Call("AddImage", url, iconUrl, 0UL);
}
private string GetImage(string name, ulong skinID = 0UL) {
if (!ImageLibrary) return string.Empty;
return ImageLibrary.Call<string>("GetImage", name, skinID);
}
private static bool IsUrl(string input) => Uri.TryCreate(input, UriKind.Absolute, out Uri result) && (result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps);
#endregion
#region Kits
private static Func<string, bool> _isKit;
private static Func<BasePlayer, string, object> _giveKit;
private static Func<string, string> _getKitDescription;
private static Func<string, string> _getKitImage;
private static Func<string, JObject> _getKitData;
private void CreateKitsFunctions() {
_isKit = IsKit;
_giveKit = GiveKit;
_getKitDescription = GetKitDescription;
_getKitImage = GetKitImage;
_getKitData = GetKitObject;
}
private object GiveKit(BasePlayer player, string kit) => Kits?.Call("GiveKit", player, kit);
private void GetAllKits(List<string> list) => Kits?.Call("GetAllKits", list);
private bool IsKit(string name) => Kits && Kits.Call<bool>("IsKit", name);
private string GetKitDescription(string name) => Kits ? Kits.Call<string>("GetKitDescription", name) : string.Empty;
private string GetKitImage(string name) => Kits ? Kits.Call<string>("GetKitImage", name) : string.Empty;
private JObject GetKitObject(string name) => Kits ? Kits.Call<JObject>("GetKitObject", name) : null;
private class KitData {
public ItemData[] MainItems {
get;
set;
}
public ItemData[] WearItems {
get;
set;
}
public ItemData[] BeltItems {
get;
set;
}
public class ItemData {
public string Shortname {
get;
set;
}
public ulong SkinID {
get;
set;
}
public int Amount {
get;
set;
}
public bool IsBlueprint {
get;
set;
}
[JsonIgnore]
private ItemDefinition _itemDefinition;
[JsonIgnore]
public ItemDefinition ItemDefinition {
get {
if (!_itemDefinition) _itemDefinition = ItemManager.FindItemDefinition(Shortname);
return _itemDefinition;
}
}
}
}
#endregion
#region PlayerDLCAPI
private bool IsOwnedOrFreeItem(BasePlayer player, int itemID, ulong skinID) {
if (PlayerDLCAPI != null && Configuration.Options.OwnedSkins) return PlayerDLCAPI.Call<bool>("IsOwnedOrFreeItem", player, itemID, skinID);
return true;
}
private bool CanUseSkin(BasePlayer player, ulong skinID) {
if (skinID == 0UL) return true;
if (PlayerDLCAPI != null && Configuration.Options.OwnedSkins) return PlayerDLCAPI.Call<bool>("IsOwnedOrFreeSkin", player, skinID);
return true;
}
private int GetRedirectedItemIdIfNotOwned(BasePlayer player, int itemId) {
if (PlayerDLCAPI != null && Configuration.Options.OwnedSkins) return PlayerDLCAPI.Call<int>("GetRedirectedItemIdIfNotOwned", player, itemId);
return itemId;
}
#endregion
#region HumanNPC
private void OnUseNPC(BasePlayer npc, BasePlayer player) {
if (!player || !npc) return;
if (!_npcStores.Data.TryGetValue(npc.userID, out NpcStore npcStore)) return;
if (OpenBlockedByPlugin(player)) return;
if (npcStore.Products != null) {
UIUser user = UIUser.Get(player);
user.NpcStore = npcStore;
}
OpenStore(player);
}
[ChatCommand("srnpc")]
private void CommandNpc(BasePlayer player, string command, string[] args) {
if (!IsAdmin(player)) return;
if (!HumanNPC) {
player.ChatMessage("HumanNPC plugin not found, please install it to use this command.");
return;
}
string hex = Configuration.UI.ButtonConfirmText.Hex;
if (args.Length >= 1) {
BasePlayer target = FindPlayer(player);
if (!target) {
player.ChatMessage("Look at the HumanNPC you want to interact with.");
return;
}
if (!HumanNPC.Call<bool>("IsHumanNpc", target.userID.Get())) {
player.ChatMessage("The player you are looking at it not a HumanNPC.");
return;
}
_npcStores.Data.TryGetValue(target.userID, out NpcStore npcStore);
switch (args[0].ToLower()) {
case "add" : if (npcStore != null) {
player.ChatMessage("The NPC you are looking at is already a store NPC.");
return;
}
_npcStores.Data[target.userID] = new NpcStore {
Products = new Products(), Navigation = new StoreNavigation(Configuration.Navigation), CustomStore = false
}
;
_npcStores.Save();
player.ChatMessage($"You have turned the NPC <color=#{hex}>{target.displayName}</color> into a store NPC.");
return;
case "remove" : if (npcStore == null) {
player.ChatMessage("The NPC you are looking at is not a store NPC.");
return;
}
_npcStores.Data.Remove(target.userID);
_npcStores.Save();
player.ChatMessage($"You have removed the store from NPC <color=#{hex}>{target.displayName}</color>.");
return;
case "setname" : if (npcStore == null) {
player.ChatMessage("The NPC you are looking at is not a store NPC.");
return;
}
if (args.Length < 2) {
player.ChatMessage("Please specify a store name");
return;
}
string name = string.Join(" ", args.Skip(1));
if (string.IsNullOrEmpty(name)) {
player.ChatMessage("Store name can not be empty.");
return;
}
npcStore.Name = name;
_npcStores.Save();
player.ChatMessage($"You have set the store name for NPC <color=#{hex}>{target.displayName}</color> to <color=#{hex}>{name}</color>.");
return;
case "nav" : if (npcStore == null) {
player.ChatMessage("The NPC you are looking at is not a store NPC.");
return;
}
if (args.Length < 2) {
player.ChatMessage($"Please specify a navigation type to toggle. Valid types: items, kits, commands, exchange, transfer, sell");
return;
}
if (!Enum.TryParse(args[1].ToLower(), true, out NavigationCategory category)) {
player.ChatMessage($"Invalid navigation type '{args[1]}'. Valid types: items, kits, commands, exchange, transfer, sell");
return;
}
bool result = false;
switch (category) {
case NavigationCategory.Kits : result = npcStore.Navigation.Kits = !npcStore.Navigation.Kits;
break;
case NavigationCategory.Commands : result = npcStore.Navigation.Commands = !npcStore.Navigation.Commands;
break;
case NavigationCategory.Items : result = npcStore.Navigation.Items = !npcStore.Navigation.Items;
break;
case NavigationCategory.Exchange : result = npcStore.Navigation.Exchange = !npcStore.Navigation.Exchange;
break;
case NavigationCategory.Transfer : result = npcStore.Navigation.Transfer = !npcStore.Navigation.Transfer;
break;
case NavigationCategory.Sell : result = npcStore.Navigation.Seller = !npcStore.Navigation.Seller;
break;
default : player.ChatMessage("Invalid navigation type. Valid types: items, kits, commands, exchange, transfer, sell");
break;
}
_npcStores.Save();
player.ChatMessage($"Navigation type <color=#{hex}>{category}</color> for NPC <color=#{hex}>{target.displayName}</color> is now <color=#{hex}>{(result ? "enabled" : "disabled")}</color>.");
return;
case "customstore" : if (npcStore == null) {
player.ChatMessage("The NPC you are looking at is not a store NPC.");
return;
}
npcStore.CustomStore = !npcStore.CustomStore;
_npcStores.Save();
player.ChatMessage($"Custom store mode for NPC <color=#{hex}>{target.displayName}</color> is now <color=#{hex}>{(npcStore.CustomStore ? "enabled" : "disabled")}</color>.");
return;
default : break;
}
}
player.ChatMessage($"<size=16><color=#{hex}>[{Title}]</color> NPC Stores</size>");
player.ChatMessage($"<color=#{hex}>/srnpc add</color> - Turn the NPC you are looking at into a store NPC");
player.ChatMessage($"<color=#{hex}>/srnpc remove</color> - Remove the store from the NPC you are looking at");
player.ChatMessage($"<color=#{hex}>/srnpc setname <name></color> - Sets a store name for the NPC you are looking at. This name is shown on the store page if custom store is enabled");
player.ChatMessage($"<color=#{hex}>/srnpc nav <items|kits|commands|exchange|transfer|sell></color> - Toggles whether the specified navigation type is shown in the NPC store");
player.ChatMessage($"<color=#{hex}>/srnpc customstore</color> - Toggles whether this NPC runs a custom store or just opens the global store");
}
private BasePlayer FindPlayer(BasePlayer player) {
if (Physics.Raycast(player.eyes.HeadRay(), out RaycastHit raycastHit, 10f, 1 << (int)Rust.Layer.Player_Server)) return raycastHit.collider.ToBaseEntity() as BasePlayer;
return null;
}
#endregion
#region UI
private const string UI_MENU = "sr.menu";
private const string UI_TOAST = "sr.toast";
private void OpenStore(BasePlayer player) {
UIUser user = UIUser.Get(player);
Products products = user.NpcStore is {
CustomStore : true
}
? user.NpcStore.Products : _products.Data;
StoreNavigation navigation = user.NpcStore != null ? user.NpcStore.Navigation : Configuration.Navigation;
user.UpdateAvailableCategories(navigation, products, Economics);
if (user.NpcStore != null && user.Category == NavigationCategory.None) {
List<NavigationCategory> categories = user.AvailableCategories;
if (categories.Count == 0) {
user.Player.ChatMessage(user.Translate("Message.Error.NoCategories"));
user.CloseMenu();
return;
}
if (!categories.Contains(NavigationCategory.Items) && !categories.Contains(NavigationCategory.Kits) && !categories.Contains(NavigationCategory.Commands) && !categories.Contains(NavigationCategory.Sell)) {
if (categories.Contains(NavigationCategory.Transfer)) {
user.ExitToGame = true;
CreateTransferMenu(user);
return;
}
if (categories.Contains(NavigationCategory.Exchange)) {
user.ExitToGame = true;
CreateExchangeMenu(user);
return;
}
}
}
CuiElementContainer container = UI.Container(Layer.Overlay, UI_MENU, Configuration.UI.Background, Anchor.FullStretch, Offset.zero);
CreateHeader(container, user);
switch (user.Category) {
case NavigationCategory.Kits : CreateKits(container, user);
break;
case NavigationCategory.Commands : CreateCommands(container, user);
break;
case NavigationCategory.Items : CreateItems(container, user);
break;
case NavigationCategory.Sell : CreateSellItems(container, user);
break;
default : break;
}
CreateFooter(container, user);
user.SendUI(container);
}
private void CreateHeader(CuiElementContainer container, UIUser user) {
string storeName = user.NpcStore is {
CustomStore : true
}
&& !string.IsNullOrEmpty(user.NpcStore.Name) ? user.NpcStore.Name : user.Translate("UI.Title");
const string UI_HEADER = "sr.header";
UI.Panel(container, UI_MENU, Configuration.UI.PanelPrimary, Anchor.TopStretch, new Offset(5f, -35f, -5f, -5f), UI_HEADER);
UI.Text(container, UI_HEADER, storeName, Anchor.FullStretch, new Offset(5f, 0f, -5f, 0f), align : TextAnchor.MiddleLeft, size : 18);
CreateNavigation(container, user, UI_HEADER);
if (user.Category <= NavigationCategory.Items) {
if (!string.IsNullOrEmpty(_magnifyIcon)) UI.PNG(container, UI_HEADER, _magnifyIcon, Anchor.CenterRight, new Offset(-225f, -10f, -205f, 10f));
const string UI_INPUT = "sr.search";
UI.Panel(container, UI_HEADER, Configuration.UI.PanelSecondary, Anchor.CenterRight, new Offset(-200f, -10f, -30f, 10f), UI_INPUT);
UI.Input(container, UI_INPUT, user.SearchFilter, $"{Commands.Search} {(int)ReturnType.Store}", Anchor.FullStretch, new Offset(5f, 0f, -5f, 0f));
}
const string BUTTON_CLOSE = "sr.close";
UI.Panel(container, UI_HEADER, Configuration.UI.ButtonReject, Anchor.CenterRight, new Offset(-25f, -10f, -5f, 10f), BUTTON_CLOSE);
UI.Sprite(container, BUTTON_CLOSE, Sprites.Close, Configuration.UI.ButtonRejectText, Anchor.Center, new Offset(-7f, -7f, 7f, 7f));
UI.Button(container, BUTTON_CLOSE, Commands.Close, Anchor.FullStretch, Offset.zero);
}
#region Navigation
private void CreateNavigation(CuiElementContainer container, UIUser user, string parent) {
List<NavigationCategory> categories = user.AvailableCategories;
const string NAVIGATION = "sr.navigation";
UI.Panel(container, parent, Colors.Clear, Anchor.FullStretch, new Offset(250f, 0f, -250f, 0f), NAVIGATION);
if (user.Category == 0 && categories.Count > 0) user.Category = categories[0];
const float WIDTH = 100f;
const float SPACING = 5f;
float totalWidth = (WIDTH * categories.Count) + (SPACING * (categories.Count - 1));
float left = -(totalWidth * 0.5f);
for (int i = 0; i < categories.Count; i++) {
NavigationCategory category = categories[i];
string buttonName = $"{NAVIGATION}.{category}";
string buttonColor = user.Category == category ? Configuration.UI.ButtonConfirm : Configuration.UI.Button;
string textColor = user.Category == category ? Configuration.UI.ButtonConfirmText : Configuration.UI.ButtonText;
float xMin = left + (i * (WIDTH + SPACING));
UI.Panel(container, NAVIGATION, buttonColor, Anchor.Center, new Offset(xMin, -10f, xMin + WIDTH, 10f), buttonName);
UI.Text(container, buttonName, user.Translate($"UI.Category.{category.ToString()}"), Anchor.FullStretch, Offset.zero, color : textColor);
UI.Button(container, buttonName, $"{Commands.Navigation} {(int)category}", Anchor.FullStretch, Offset.zero);
}
}
public enum NavigationCategory {
None = 0, Kits, Commands, Items, Sell, Transfer, Exchange
}
#endregion
#region Footer
private void CreateFooter(CuiElementContainer container, UIUser user) {
const string FOOTER = "sr.footer";
UI.Panel(container, UI_MENU, Configuration.UI.PanelPrimary, Anchor.BottomStretch, new Offset(5f, 5f, -5f, 35f), FOOTER);
// Admin Toggle
if (IsAdmin(user.Player)) {
const string TOGGLE = "sr.admin.toggle";
string buttonColor = user.AdminMode ? Configuration.UI.ButtonConfirm : Configuration.UI.Button;
string textColor = user.AdminMode ? Configuration.UI.ButtonConfirmText : Configuration.UI.ButtonText;
UI.Panel(container, FOOTER, buttonColor, Anchor.CenterLeft, new Offset(5f, -10f, 135f, 10f), TOGGLE);
UI.Text(container, TOGGLE, user.Translate("UI.AdminMode"), Anchor.FullStretch, Offset.zero, color : textColor);
UI.Button(container, TOGGLE, Commands.AdminToggle, Anchor.FullStretch, Offset.zero);
if (user.AdminMode && user.Category <= NavigationCategory.Items) {
const string ADD_PRODUCT = "sr.admin.addproduct";
UI.Panel(container, FOOTER, Configuration.UI.ButtonConfirm, Anchor.Center, new Offset(-75f, -10f, 75f, 10f), ADD_PRODUCT);
UI.Sprite(container, ADD_PRODUCT, Sprites.Authorize, Configuration.UI.ButtonConfirmText, Anchor.CenterLeft, new Offset(7f, -6f, 19f, 6f));
UI.Text(container, ADD_PRODUCT, user.Translate("UI.AddProduct"), Anchor.FullStretch, new Offset(11f, 0f, 0f, 0f), color : textColor);
UI.Button(container, ADD_PRODUCT, Commands.AddProduct, Anchor.FullStretch, Offset.zero);
}
}
const string BALANCE = "sr.balance";
_balances.Data.TryGetValue(user.UserId, out int balance);
UI.Panel(container, FOOTER, Configuration.UI.ButtonConfirm, Anchor.CenterRight, new Offset(-130f, -10f, -5f, 10f), BALANCE);
UI.Text(container, BALANCE, user.Translate("UI.RP", balance), Anchor.FullStretch, Offset.zero, color : Configuration.UI.ButtonConfirmText);
if (Configuration.UI.ShowPlaytime && PlaytimeTracker != null) {
object t = Interface.CallHook("GetPlayTime", user.Player.UserIDString);
if (t is double time) {
const string PLAYTIME = "sr.playtime";
UI.Panel(container, FOOTER, Configuration.UI.ButtonPurchase, Anchor.CenterRight, new Offset(-280f, -10f, -135f, 10f), PLAYTIME);
UI.Countdown(container, PLAYTIME, user.Translate("UI.Playtime", "%TIME_LEFT%"), (int)time, int.MaxValue, Anchor.FullStretch, Offset.zero, color : Configuration.UI.ButtonPurchaseText);
}
}
}
#endregion
#region Items
private void CreateItems(CuiElementContainer container, UIUser user) {
Products products = user.NpcStore is {
CustomStore : true
}
? user.NpcStore.Products : _products.Data;
const string ITEMS = "sr.items";
UI.Panel(container, UI_MENU, Colors.Clear, Anchor.FullStretch, new Offset(5f, 40f, -5f, -40f), ITEMS);
CreateItemsNavigation(container, ITEMS, user, products);
CreateItemsLayout(container, ITEMS, user, products);
}
private void CreateItemsNavigation(CuiElementContainer container, string parent, UIUser user, Products products) {
const string NAVIGATION = "sr.items.navigation";
UI.Panel(container, parent, Configuration.UI.PanelPrimary, Anchor.LeftStretch, new Offset(0f, 0f, 140f, 0f), NAVIGATION);
const float HEIGHT = 20f;
const float SPACING = 5f;
for (int i = 0; i < products.ItemCategories.Length; i++) {
ItemCategory category = products.ItemCategories[i];
string buttonName = $"{NAVIGATION}.{category}";
string buttonColor = user.ItemCategory == category ? Configuration.UI.ButtonConfirm : Configuration.UI.Button;
string textColor = user.ItemCategory == category ? Configuration.UI.ButtonConfirmText : Configuration.UI.ButtonText;
float yMax = -5f - (i * (HEIGHT + SPACING));
UI.Panel(container, NAVIGATION, buttonColor, Anchor.TopStretch, new Offset(5f, yMax - HEIGHT, -5f, yMax), buttonName);
UI.Text(container, buttonName, user.Translate($"UI.ItemCategory.{category.ToString()}"), Anchor.FullStretch, Offset.zero, color : textColor);
UI.Button(container, buttonName, $"{Commands.ItemCategory} {(int)category}", Anchor.FullStretch, Offset.zero);
}
}
private void CreateItemsLayout(CuiElementContainer container, string parent, UIUser user, Products products) {
const string LAYOUT = "sr.items.layout";
UI.Panel(container, parent, Configuration.UI.PanelPrimary, Anchor.FullStretch, new Offset(145f, 0f, 0f, 0f), LAYOUT);
UI.Text(container, LAYOUT, user.Translate("UI.Items"), Anchor.FullStretch, Offset.zero, color : Colors.BarelyVisible, size : 120);
const string SCROLL = "sr.items.scroll";
CuiScrollbar scrollbar = UI.Scrollbar(Configuration.UI.ScrollbarHandle, Configuration.UI.ScrollbarHighlight, Configuration.UI.ScrollbarPressed, Configuration.UI.Scrollbar);
CuiRectTransformComponent contentRect = UI.ScrollView(container, LAYOUT, Anchor.FullStretch, new Offset(5f, 5f, -5f, -5f), Anchor.FullStretch, Offset.zero, null, scrollbar, SCROLL);
int count = 0;
_itemsGrid ??= new HorizontalGrid(new Vector2(133f, 155.5f), new Vector2(5f, 5f), 8, 630f);
_balances.Data.TryGetValue(user.UserId, out int balance);
List<Products.Item> items = Pool.Get<List<Products.Item>>();
FilterList(products.Items, items, user, _itemProductsContainsFilter);
items.Sort((a, b) => {
ItemDefinition aDefinition = a.ItemDefinition;
ItemDefinition bDefinition = b.ItemDefinition;
if (!aDefinition && !bDefinition) return 0;
if (!aDefinition) return 1;
if (!bDefinition) return -1;
string aName = string.IsNullOrEmpty(a.DisplayName) ? (aDefinition.displayName?.english ?? "") : a.DisplayName;
string bName = string.IsNullOrEmpty(b.DisplayName) ? (bDefinition.displayName?.english ?? "") : b.DisplayName;
return string.Compare(aName, bName, StringComparison.Ordinal);
}
);
for (int index = 0; index < items.Count; index++) {
Products.Item item = items[index];
if (item.Category != user.ItemCategory && user.ItemCategory != ItemCategory.All) continue;
if (!string.IsNullOrEmpty(item.Permission) && !permission.UserHasPermission(user.Player.UserIDString, item.Permission) && !IsAdmin(user.Player)) continue;
ItemDefinition itemDefinition = item.ItemDefinition;
if (!itemDefinition) continue;
bool isOwnedOrFree = IsOwnedOrFreeItem(user.Player, itemDefinition.itemid, item.SkinId);
if (Configuration.Options.HideDlc && !user.AdminMode && !isOwnedOrFree) continue;
string itemName = $"{LAYOUT}.{item.ID}";
UI.Panel(container, SCROLL, Configuration.UI.PanelSecondary, Anchor.TopLeft, _itemsGrid.Get(count), itemName);
CreateItemElement(container, itemName, item, user, balance, isOwnedOrFree);
count++;
}
Pool.FreeUnmanaged(ref items);
_itemsGrid.ResizeToFit(count, contentRect);
}
private void CreateItemElement(CuiElementContainer container, string parent, Products.Item item, UIUser user, int balance, bool isOwnedOrFree) {
bool isDlcRestricted = !item.IgnoreDlcCheck && !isOwnedOrFree;
string iconName = $"{parent}.icon";
UI.Panel(container, parent, Colors.Clear, Anchor.TopCenter, new Offset(-64f, -130.5f, 64f, -2.5f), iconName);
if (!string.IsNullOrEmpty(item.IconURL)) UI.PNG(container, iconName, GetImage(item.IconURL), Anchor.FullStretch, new Offset(16f, 8f, -16f, -24f));
else UI.Icon(container, iconName, item.ItemDefinition.itemid, item.SkinId, Anchor.FullStretch, new Offset(16f, 8f, -16f, -24f));
string name = string.IsNullOrEmpty(item.DisplayName) ? item.ItemDefinition.displayName.english : item.DisplayName;
string titleName = $"{parent}.title";
UI.Panel(container, iconName, Colors.BarelyVisibleDark, Anchor.TopStretch, new Offset(0f, -20f, 0f, 0f), titleName);
UI.Text(container, titleName, name, Anchor.FullStretch, Offset.zero, size : 12);
if (item.IsBp) UI.Icon(container, iconName, Products.Item.BlueprintBase.itemid, 0UL, Anchor.BottomLeft, new Offset(2.5f, 2.5f, 26.5f, 26.5f));
if (item.Amount > 1) UI.Text(container, iconName, $"x{item.Amount}", Anchor.BottomStretch, new Offset(2.5f, 0f, -2.5f, 20f), align : TextAnchor.LowerRight);
if (user.AdminMode) CreateAdminButtons(container, parent, user, ProductType.Item, item.ID);
else CreatePurchaseButton(container, parent, user, ProductType.Item, item.ID, item.Cost, balance, isDlcRestricted);
}
#endregion
#region Kits
private void CreateKits(CuiElementContainer container, UIUser user) {
Products products = user.NpcStore is {
CustomStore : true
}
? user.NpcStore.Products : _products.Data;
const string KITS = "sr.kits";
UI.Panel(container, UI_MENU, Configuration.UI.PanelPrimary, Anchor.FullStretch, new Offset(5f, 40f, -5f, -40f), KITS);
UI.Text(container, KITS, user.Translate("UI.Kits"), Anchor.FullStretch, Offset.zero, color : Colors.BarelyVisible, size : 120);
const string SCROLL = "sr.kits.scroll";
CuiScrollbar scrollbar = UI.Scrollbar(Configuration.UI.ScrollbarHandle, Configuration.UI.ScrollbarHighlight, Configuration.UI.ScrollbarPressed, Configuration.UI.Scrollbar);
CuiRectTransformComponent contentRect = UI.ScrollView(container, KITS, Anchor.FullStretch, new Offset(5f, 5f, -5f, -5f), Anchor.FullStretch, Offset.zero, null, scrollbar, SCROLL);
int count = 0;
_kitsGrid ??= new HorizontalGrid(new Vector2(307.5f, 361.5f), new Vector2(5f, 5f), 4, 630f);
_balances.Data.TryGetValue(user.UserId, out int balance);
List<Products.Kit> kits = Pool.Get<List<Products.Kit>>();
FilterList(products.Kits, kits, user, _kitProductsContainsFilter);
kits.Sort((a, b) => a.DisplayName.CompareTo(b.DisplayName));
for (int index = 0; index < kits.Count; index++) {
Products.Kit kit = kits[index];
if (!IsKit(kit.KitName)) continue;
if (!string.IsNullOrEmpty(kit.Permission) && !permission.UserHasPermission(user.Player.UserIDString, kit.Permission) && !IsAdmin(user.Player)) continue;
string kitName = $"sr.kits.{kit.ID}";
UI.Panel(container, SCROLL, Configuration.UI.PanelSecondary, Anchor.TopLeft, _kitsGrid.Get(count), kitName);
if (CreateKitElement(container, kitName, kit, user, balance)) count++;
}
Pool.FreeUnmanaged(ref kits);
_kitsGrid.ResizeToFit(count, contentRect);
}
private bool CreateKitElement(CuiElementContainer container, string parent, Products.Kit kit, UIUser user, int balance) {
KitData kitData = kit.KitData;
if (kitData == null) return false;
string icon = !string.IsNullOrEmpty(kit.IconURL) ? GetImage(kit.IconURL) : string.Empty;
bool hasIcon = !string.IsNullOrEmpty(icon);
UI.Text(container, parent, kit.DisplayName, Anchor.TopStretch, new Offset(5f, -22.5f, hasIcon ? -55f : -5f, -2.5f), size : 12, align : TextAnchor.MiddleLeft);
if (!string.IsNullOrEmpty(kit.Description)) UI.Text(container, parent, kit.Description, Anchor.TopStretch, new Offset(5f, -55f, hasIcon ? -55f : -5f, -25f), size : 10, align : TextAnchor.UpperLeft);
if (hasIcon) UI.PNG(container, parent, GetImage(kit.IconURL), Anchor.TopRight, new Offset(-52.5f, -52.5f, -2.5f, -2.5f));
_kitsBeltGrid ??= new HorizontalGrid(new Vector2(32, 32), new Vector2(5f, 5f), 8, 0f);
_kitsWearGrid ??= new HorizontalGrid(new Vector2(32, 32), new Vector2(5f, 5f), 8, 0f);
_kitsMainGrid ??= new HorizontalGrid(new Vector2(32, 32), new Vector2(5f, 5f), 6, 0f);
// Belt Layout
string beltName = $"{parent}.belt";
UI.Panel(container, parent, Colors.Clear, Anchor.TopStretch, new Offset(2.5f, -110.75f, -2.5f, -56.25f), beltName);
UI.Panel(container, beltName, Colors.BarelyVisibleDark, Anchor.TopStretch, new Offset(0f, -20f, 0f, 0f));
UI.Text(container, beltName, user.Translate("UI.Kit.Belt"), Anchor.TopStretch, new Offset(5f, -20f, 5f, 0f), size : 12, align : TextAnchor.MiddleLeft);
LayoutKitContainer(container, beltName, user, kitData.BeltItems, 6, _kitsBeltGrid);
// Wear Layout
string wearName = $"{parent}.wear";
UI.Panel(container, parent, Colors.Clear, Anchor.TopStretch, new Offset(2.5f, -167.75f, -2.5f, -113.25f), wearName);
UI.Panel(container, wearName, Colors.BarelyVisibleDark, Anchor.TopStretch, new Offset(0f, -20f, 0f, 0f));
UI.Text(container, wearName, user.Translate("UI.Kit.Wear"), Anchor.TopStretch, new Offset(5f, -20f, 5f, 0f), size : 12, align : TextAnchor.MiddleLeft);
LayoutKitContainer(container, wearName, user, kitData.WearItems, 8, _kitsWearGrid);
// Main Layout
string mainName = $"{parent}.main";
UI.Panel(container, parent, Colors.Clear, Anchor.TopStretch, new Offset(2.5f, -338.25f, -2.5f, -170.25f), mainName);
UI.Panel(container, mainName, Colors.BarelyVisibleDark, Anchor.TopStretch, new Offset(0f, -20f, 0f, 0f));
UI.Text(container, mainName, user.Translate("UI.Kit.Main"), Anchor.TopStretch, new Offset(5f, -20f, 5f, 0f), size : 12, align : TextAnchor.MiddleLeft);
LayoutKitContainer(container, mainName, user, kitData.MainItems, 24, _kitsMainGrid);
if (user.AdminMode) CreateAdminButtons(container, parent, user, ProductType.Kit, kit.ID);
else CreatePurchaseButton(container, parent, user, ProductType.Kit, kit.ID, kit.Cost, balance);
return true;
}
private void LayoutKitContainer(CuiElementContainer container, string parent, UIUser user, KitData.ItemData[] items, int minCount, HorizontalGrid grid) {
string layout = $"{parent}.layout";
UI.Panel(container, parent, Colors.Clear, Anchor.FullStretch, new Offset(2.5f, 0f, -2.5f, -22.5f), layout);
for (int i = 0; i < minCount; i++) {
KitData.ItemData itemData = items != null && i < items.Length ? items[i] : null;
string item = $"{layout}.{i}";
Offset offset = grid.Get(i);
UI.Panel(container, layout, Colors.BarelyVisibleDark, Anchor.TopLeft, offset, item);
if (itemData != null) {
ItemDefinition itemDefinition = itemData.ItemDefinition;
if (!itemDefinition) continue;
int itemId = GetRedirectedItemIdIfNotOwned(user.Player, itemDefinition.itemid);
ulong skinId = CanUseSkin(user.Player, itemData.SkinID) ? itemData.SkinID : 0UL;
UI.Icon(container, item, itemId, skinId, Anchor.FullStretch, Offset.zero);
if (itemData.Amount > 1) UI.Text(container, item, $"x{itemData.Amount}", Anchor.FullStretch, Offset.zero, size : 8, align : TextAnchor.LowerRight);
if (itemData.IsBlueprint) UI.Icon(container, item, Products.Item.BlueprintBase.itemid, 0UL, Anchor.TopLeft, new Offset(0f
// ... [Code preview truncated due to length. Full source in attached file] ...