C#:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Core;
using UnityEngine;
using Newtonsoft.Json;
using Instancing;
using UnityEngine.PlayerLoop;
using Facepunch.Math;
using UnityEngine.UI;
using Facepunch.Nexus;
namespace Oxide.Plugins
{
[Info("JStacks", "cr1sTioJ_ST1_(FIX)", "1.2.3")]
[Description("Plugin allows you to change stack sizes of items")]
class JStacks : RustPlugin
{
#region Global Variables
public static JStacks Instance;
private string InitialLayer = "Ui_Stacks";
private string PermissionUse = "jstacks.use";
private ConfigData _config;
private Dictionary<string, int> _defaultStackSizes = new Dictionary<string, int>();
private Dictionary<string, int> _customStackSizes = new Dictionary<string, int>();
private Dictionary<string, List<string>> _categoryItemsList = new Dictionary<string, List<string>>();
private Dictionary<ulong, Timer> _playersNotifyTimers = new Dictionary<ulong, Timer>();
private Dictionary<ulong, ChoiceOption> _playersChoiceOptions = new Dictionary<ulong, ChoiceOption>();
private Dictionary<ulong, List<string>> _playersSelectedItems = new Dictionary<ulong, List<string>>();
private Dictionary<ulong, int> _playersAmounts = new Dictionary<ulong, int>();
private Dictionary<string, string> _namesWithSeveralWords = new Dictionary<string, string>();
private enum ChoiceOption
{
Single,
Multiple
}
#endregion
#region Config
private class ConfigData
{
[JsonProperty("Default icons for categories (in-game only)")]
public Dictionary<string, string> categoryIcons;
[JsonProperty("Can admins use command")]
public bool canAdminUse;
[JsonProperty("Command to open the menu")]
public string menuCommand;
[JsonProperty("Command to set stack size of an item in your hands")]
public string setHandStackCommand;
}
protected override void LoadDefaultConfig()
{
Config.WriteObject(new ConfigData()
{
categoryIcons = new Dictionary<string, string>()
{
["Weapon"] = "assets/icons/weapon.png",
["Construction"] = "assets/icons/construction.png",
["Items"] = "assets/icons/extinguish.png",
["Resources"] = "assets/icons/community_servers.png",
["Attire"] = "assets/icons/community_servers.png",
["Tool"] = "assets/icons/tools.png",
["Medical"] = "assets/icons/medical.png",
["Food"] = "assets/icons/food_cooked.png",
["Ammunition"] = "assets/icons/ammunition.png",
["Traps"] = "assets/icons/traps.png",
["Component"] = "assets/icons/open.png",
["Electrical"] = "assets/icons/electric.png",
["Fun"] = "assets/icons/fun_alt.png",
["Misc"] = "assets/icons/menu_dots.png"
},
canAdminUse = true,
menuCommand = "stacks",
setHandStackCommand = "sethandstack"
}, true);
}
#endregion
#region Commands
[ConsoleCommand("jstacks.section")]
private void CmdChangeSection(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!CanUse(player)) return;
var categoryName = arg.GetString(0, "");
UpdateLayers(player, categoryName);
}
[ConsoleCommand("jstack.changer.call")]
private void CmdChangerCall(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!CanUse(player)) return;
var itemName = arg.FullString;
ChangerCall(player, itemName);
}
[ConsoleCommand("jstacks.setstack")]
private void CmdSetStack(ConsoleSystem.Arg arg)
{
var player = arg.Player();
var itemName = arg.GetString(0, "");
var stack = arg.GetInt(1, 0);
if (_namesWithSeveralWords.ContainsKey(itemName))
{
itemName = _namesWithSeveralWords[itemName];
stack = arg.GetInt(arg.Args.Length - 1, 0);
}
var itemDef = ItemManager.FindItemDefinition(itemName);
if (itemDef != null)
{
if (stack == itemDef.stackable)
{
CuiHelper.DestroyUi(player, InitialLayer + $".Item.Changer");
return;
}
}
if (player == null)
{
if (SetStack(itemName, stack))
arg.Reply = string.Format(GetMsg("STACK_CHANGED"), itemName, stack);
else
arg.Reply = string.Format(GetMsg("STACK_NOT_CHANGED"), itemName);
return;
}
if (CanUse(player))
{
if (SetStack(itemName, stack))
Notify(player, string.Format(GetMsg("STACK_CHANGED", player), itemName, stack), "green");
else
Notify(player, string.Format(GetMsg("STACK_NOT_CHANGED", player), itemName), "red");
if (itemDef != null)
{
FillMenuWithItems(player, itemDef.category.ToString());
}
}
}
[ConsoleCommand("jstacks.request.restore")]
private void CmdRestoreRequestStacks(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!CanUse(player)) return;
ShowConfirmMenu(player, GetMsg("RESTORE_MAIN_QUESTION", player), "jstacks.restore");
}
[ConsoleCommand("jstacks.restore")]
private void CmdRestoreStacks(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null)
{
RestoreStackSizes();
arg.Reply = GetMsg("RESTORED");
return;
}
if (CanUse(player))
{
RestoreStackSizes();
InitializeLayers(player);
Notify(player, GetMsg("RESTORED", player));
}
}
[ConsoleCommand("jstacks.categorychanger.call")]
private void CmdCategoryChangerCall(ConsoleSystem.Arg arg)
{
var player = arg.Player();
var categoryName = arg.GetString(0, "");
if (player == null) return;
if (!CanUse(player)) return;
if (string.IsNullOrEmpty(categoryName)) return;
CategoryChangerCall(player, categoryName);
}
[ConsoleCommand("jstacks.category.changestack")]
private void CmdCategoryChangeStack(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!CanUse(player)) return;
string categoryName = arg.GetString(0, "");
if (string.IsNullOrEmpty(categoryName)) return;
int stackSize = 0;
if (_playersAmounts.ContainsKey(player.userID))
{
stackSize = _playersAmounts[player.userID];
}
if (stackSize <= 0)
{
Notify(player, GetMsg("ERROR_WRONG_STACK_SIZE", player),"red");
return;
}
var categoryItems = _categoryItemsList[categoryName];
foreach (var item in categoryItems)
{
SetStack(item, stackSize);
}
SaveCustomStackSizes();
UpdateLayers(player, categoryName);
Notify(player, string.Format(GetMsg("CATEGORY_STACK_SIZE_CHANGED", player), categoryName, stackSize), "green");
}
[ConsoleCommand("jstacks.multiple.toggle")]
private void CmdMultipleToggle(ConsoleSystem.Arg arg)
{
var player = arg.Player();
var categoryName = arg.GetString(0, "");
if (player == null) return;
if (!CanUse(player)) return;
if (string.IsNullOrEmpty(categoryName)) return;
_playersChoiceOptions[player.userID] = _playersChoiceOptions[player.userID] == ChoiceOption.Single ? ChoiceOption.Multiple : ChoiceOption.Single;
UpdateLayers(player, categoryName);
UpdateMultipleButton(player);
}
[ConsoleCommand("jstacks.setforselected")]
private void cmdSetForSelected(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!CanUse(player)) return;
var stack = 0;
if (_playersAmounts.ContainsKey(player.userID))
{
stack = _playersAmounts[player.userID];
}
if (stack <= 0)
{
Notify(player, GetMsg("ERROR_WRONG_STACK_SIZE", player), "red");
return;
}
if (!_playersSelectedItems.ContainsKey(player.userID))
{
Notify(player, GetMsg("ERROR_NO_ITEMS_SELECTED", player), "red");
return;
}
if (_playersSelectedItems[player.userID].Count == 0)
{
Notify(player, GetMsg("ERROR_NO_ITEMS_SELECTED", player), "red");
return;
}
foreach (var item in _playersSelectedItems[player.userID])
{
SetStack(item, stack);
}
Notify(player, GetMsg("MULTIPLE_CHANGE_SUCCESS", player).Replace("{0}", _playersSelectedItems[player.userID].Count.ToString()), "green");
_playersChoiceOptions[player.userID] = ChoiceOption.Single;
var category = _categoryItemsList.FirstOrDefault(
x => x.Value.Contains(_playersSelectedItems[player.userID].First())
).Key;
_playersSelectedItems[player.userID].Clear();
UpdateLayers(player, category);
UpdateMultipleButton(player);
SaveCustomStackSizes();
}
[ConsoleCommand("jstacks.changeamount")]
private void CmdChangeAmount(ConsoleSystem.Arg arg)
{
var player = arg.Player();
var amount = arg.GetInt(0, 0);
if (player == null) return;
if (!CanUse(player)) return;
_playersAmounts[player.userID] = amount;
}
private void SetHandStack(BasePlayer player, string command, string[] args)
{
if (!CanUse(player))
{
SendReply(player, GetMsg("ERROR_NO_PERMISSION", player));
return;
}
if (args == null || args.Length < 1)
{
SendReply(player, GetMsg("ERROR_NO_NEW_STACK_SIZE", player));
return;
}
int stack = 0;
if (!int.TryParse(args[0], out stack))
{
SendReply(player, GetMsg("ERROR_NO_NEW_STACK_SIZE", player));
return;
}
var item = player.GetActiveItem();
if (item == null)
{
SendReply(player, GetMsg("NO_ITEM_IN_HANDS", player));
return;
}
if (SetStack(item.info.shortname, stack))
{
SendReply(player, string.Format(GetMsg("STACK_CHANGED", player), item.info.shortname, stack));
}
else
{
SendReply(player, string.Format(GetMsg("STACK_NOT_CHANGED", player), item.info.shortname));
}
}
#endregion
#region Methods
private bool CanUse(BasePlayer player)
{
return permission.UserHasPermission(player.UserIDString, PermissionUse)
|| (_config.canAdminUse && player.IsAdmin);
}
private void SaveCustomStackSizes()
{
Interface.Oxide.DataFileSystem.WriteObject($"{this.Title}/CustomStackSizes", _customStackSizes);
}
private void LoadCustomStackSizes()
{
var storedData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, int>>($"{this.Title}/CustomStackSizes");
if (storedData != null)
{
_customStackSizes = storedData;
foreach (var item in _customStackSizes)
{
if (!ItemManager.itemDictionaryByName.ContainsKey(item.Key)) continue;
ItemManager.itemDictionaryByName[item.Key].stackable = item.Value;
}
}
}
private void SaveDefaultStackSizes()
{
Interface.Oxide.DataFileSystem.WriteObject($"{this.Title}/DefaultStackSizes", _defaultStackSizes);
}
private void LoadDefaultStackSizes()
{
var storedData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, int>>($"{this.Title}/DefaultStackSizes");
if (storedData != null && storedData.Count > 0)
{
_defaultStackSizes = storedData;
return;
}
foreach (var item in ItemManager.itemList)
{
_defaultStackSizes[item.shortname] = item.stackable;
SaveDefaultStackSizes();
}
}
private void FillCategoryItemsList()
{
var processedItems = new HashSet<string>();
foreach (var item in ItemManager.itemList)
{
// Handle names with spaces (for multi-word item names)
if (item.shortname.Contains(" "))
{
string firstWord = item.shortname.Split(' ')[0];
if (!_namesWithSeveralWords.ContainsKey(firstWord))
{
_namesWithSeveralWords[firstWord] = item.shortname;
}
}
// Handle category items list
string category = item.category.ToString();
if (!_categoryItemsList.ContainsKey(category))
{
_categoryItemsList[category] = new List<string>();
}
// Only add each shortname once per category
if (!processedItems.Contains($"{category}_{item.shortname}"))
{
_categoryItemsList[category].Add(item.shortname);
processedItems.Add($"{category}_{item.shortname}");
}
}
}
private void RestoreStackSizes()
{
foreach (var item in ItemManager.itemList)
{
if (_defaultStackSizes.ContainsKey(item.shortname))
{
int defaultSize = _defaultStackSizes[item.shortname];
item.stackable = defaultSize;
}
else
{
_defaultStackSizes[item.shortname] = item.stackable;
}
}
_customStackSizes.Clear();
Log("log", $"Stack sizes restored");
}
private bool SetStack(string itemName, int stack)
{
if (stack <= 0) return false;
var itemDef = ItemManager.FindItemDefinition(itemName);
if (itemDef == null) return false;
itemDef.stackable = stack;
_customStackSizes[itemDef.shortname] = stack;
Log("log", $"{itemName} stack size changed to {stack}");
return true;
}
private void Log(string filename, string text)
{
DateTime currentTime = DateTime.Now;
string formattedTime = currentTime.ToString("dd.MM.yyyy HH:mm:ss");
LogToFile(filename, $"[{formattedTime}] {text}", this);
}
public static string HexToColor(string hex, double alpha)
{
if (hex.Contains(" ")) return hex;
hex = hex.Replace("#", "");
float r = (float)System.Convert.ToInt32(hex.Substring(0, 2), 16) / 255f;
float g = (float)System.Convert.ToInt32(hex.Substring(2, 2), 16) / 255f;
float b = (float)System.Convert.ToInt32(hex.Substring(4, 2), 16) / 255f;
return $"{r} {g} {b} {alpha}";
}
#endregion
#region Hooks
private void OnServerInitialized(bool initial)
{
Instance = this;
permission.RegisterPermission(PermissionUse, this);
_config = Config.ReadObject<ConfigData>();
LoadMessages();
FillCategoryItemsList();
LoadDefaultStackSizes();
LoadCustomStackSizes();
cmd.AddChatCommand(_config.menuCommand, this, OpenMenu);
cmd.AddChatCommand(_config.setHandStackCommand, this, SetHandStack);
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(player, InitialLayer);
}
SaveDefaultStackSizes();
SaveCustomStackSizes();
}
#endregion
#region Cui
private void OpenMenu(BasePlayer player, string command, string[] args)
{
if (!CanUse(player))
{
SendReply(player, GetMsg("ERROR_NO_PERMISSION", player));
return;
}
InitializeLayers(player);
}
private void InitializeLayers(BasePlayer player)
{
_playersChoiceOptions[player.userID] = ChoiceOption.Single;
var elements = new CuiElementContainer();
elements.Add(new CuiElement()
{
Parent = "Overlay",
Name = InitialLayer,
DestroyUi = InitialLayer,
Components =
{
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
new CuiImageComponent
{
Color = "0.113725491 0.1254902 0.121568628 0.8",
Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat",
Sprite = "assets/content/ui/ui.background.transparent.radial.psd"
},
new CuiNeedsCursorComponent()
}
});
elements.Add(new CuiElement()
{
Parent = InitialLayer,
Components =
{
new CuiImageComponent
{
Color = "0.143725491 0.1554902 0.121568628 0.85"
},
new CuiRectTransformComponent
{
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0"
}
}
});
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-250 -248", OffsetMax = "390 248" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.2", Sprite = "Assets/Content/UI/UI.Background.Tile.psd", ImageType = Image.Type.Tiled }
}, InitialLayer, InitialLayer + ".Body");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 5", OffsetMax = "0 -40" },
Image = { Color = "0 0 0 0" }
}, InitialLayer + ".Body", InitialLayer + ".Container");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 1", OffsetMin = "-175 0", OffsetMax = "-3 0" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.2", Sprite = "Assets/Content/UI/UI.Background.Tile.psd", ImageType = Image.Type.Tiled }
}, InitialLayer + ".Body", InitialLayer + ".NavBar");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "1 1", AnchorMax = "1 1", OffsetMin = "-30 -30", OffsetMax = "-10 -10" },
Image = { Color = "0.7 0.2219512 0.14682925 1", Sprite = "Assets/Content/UI/UI.Background.Tile.psd", ImageType = Image.Type.Tiled }
}, InitialLayer + ".Body", InitialLayer + ".CloseButton");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "5 5", OffsetMax = "-5 -5" },
Image = { Color = "1 0.49271604 0.413 1", Sprite = "assets/icons/close.png"}
}, InitialLayer + ".CloseButton");
elements.Add(new CuiButton()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Button = { Close = InitialLayer, Color = "0 0 0 0"},
}, InitialLayer + ".CloseButton");
for (int i = 0, y = 0; i < _config.categoryIcons.Count; i++, y -= 35)
{
var categoryName = _config.categoryIcons.ElementAt(i).Key;
var categoryIcon = _config.categoryIcons.ElementAt(i).Value;
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 1", AnchorMax = "1 1", OffsetMin = $"3 {y - 35}", OffsetMax = $"-3 {y - 3}" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.1",}
}, InitialLayer + ".NavBar", InitialLayer + $".Nav_{categoryName}");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "7 7", OffsetMax = "25 25" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.3686275", Sprite = categoryIcon }
}, InitialLayer + $".Nav_{categoryName}", InitialLayer + $".Nav.Icon_{categoryName}");
elements.Add(new CuiLabel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "32 5", OffsetMax = "-5 -5" },
Text = { Text = categoryName.ToUpper(), Color = "0.968627453 0.921568632 0.882352948 0.3686275", FontSize = 14, Align = TextAnchor.MiddleLeft }
}, InitialLayer + $".Nav_{categoryName}", InitialLayer + $".Nav.Text_{categoryName}");
elements.Add(new CuiButton()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Button = { Command = $"jstacks.section {categoryName}", Color = "0 0 0 0"},
}, InitialLayer + $".Nav_{categoryName}");
}
elements.Add(new CuiElement()
{
Name = InitialLayer + ".ItemList",
Parent = InitialLayer + ".Container",
DestroyUi = InitialLayer + ".ItemList",
Components =
{
new CuiScrollViewComponent
{
ContentTransform = new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Vertical = true,
Elasticity = 0f,
Inertia = true,
DecelerationRate = 5f,
ScrollSensitivity = 35f,
MovementType = UnityEngine.UI.ScrollRect.MovementType.Elastic,
Horizontal = false,
HorizontalScrollbar = null
},
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" }
}
});
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0", OffsetMin = "10 5", OffsetMax = "-10 25" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.1" }
}, InitialLayer + ".Body", InitialLayer + $".InfoPanel");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "3 3", OffsetMax = "17 17" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.5686275", Sprite = "assets/icons/info.png"}
}, InitialLayer + $".InfoPanel");
elements.Add(new CuiLabel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "20 3", OffsetMax = "-5 -3" },
Text = { Text = GetMsg("TEXT_INFO_PANEL", player).ToUpper(), Color = "0.968627453 0.921568632 0.882352948 0.5686275", FontSize = 9, Align = TextAnchor.MiddleLeft }
}, InitialLayer + $".InfoPanel");
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = "10 -30", OffsetMax = "140 -10" },
Image = { Color = "0.7 0.2219512 0.14682925 1", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat", Sprite = "Assets/Content/UI/UI.Background.Tile.psd", ImageType = Image.Type.Tiled }
}, InitialLayer + $".Body", InitialLayer + $".RestoreButton");
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 1", OffsetMin = "3 3", OffsetMax = "18 -3" },
Image = { Color = "1 0.49271604 0.413 1", Sprite = "assets/icons/refresh.png" }
}, InitialLayer + $".RestoreButton");
elements.Add(new CuiLabel{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "24 0", OffsetMax = "0 0" },
Text = { Text = GetMsg("RESTORE_TEXT", player).ToUpper(), Color = "0.97199994 0.8308799 0.80870396 1", FontSize = 10, Align = TextAnchor.MiddleLeft }
}, InitialLayer + $".RestoreButton");
elements.Add(new CuiButton()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Button = { Command = $"jstacks.request.restore", Color = "0 0 0 0" },
}, InitialLayer + $".RestoreButton");
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = "150 -30", OffsetMax = "280 -10" },
Image = { Color = "0.4509804 0.5529412 0.27058825 1", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat" }
}, InitialLayer + $".Body", InitialLayer + $".CategoryChanger");
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 1", OffsetMin = "3 3", OffsetMax = "18 -3" },
Image = { Color = "0.6164634 0.8113208 0.27171594 1", Sprite = "assets/icons/pet_move.png" }
}, InitialLayer + $".CategoryChanger");
elements.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "24 0", OffsetMax = "0 0" },
Text = { Text = GetMsg("CATEGORY_CHANGER_TEXT", player).ToUpper(), Color = "0.7348033 0.8490566 0.53266287 1", FontSize = 10, Align = TextAnchor.MiddleLeft }
}, InitialLayer + $".CategoryChanger");
elements.Add(new CuiButton()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Button = { Color = "0 0 0 0" },
}, InitialLayer + $".CategoryChanger", InitialLayer + $".CategoryChanger.Button");
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = "290 -30", OffsetMax = "420 -10" },
Image = { Color = "0.11574403 0.3310115 0.4811321 0.5", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat" }
}, InitialLayer + $".Body", InitialLayer + $".MultiChoice");
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 1", OffsetMin = "3 3", OffsetMax = "18 -3" },
Image = { Color = "0 0.58914757 1 1", Sprite = "assets/icons/check.png" }
}, InitialLayer + $".MultiChoice", InitialLayer + $".MultiChoice.Icon");
elements.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "24 0", OffsetMax = "0 0" },
Text = { Text = GetMsg("MULTIPLE_CHOICE_TITLE", player).ToUpper(), Color = "0.5030705 0.73362494 0.8962264 1", FontSize = 10, Align = TextAnchor.MiddleLeft }
}, InitialLayer + $".MultiChoice", InitialLayer + $".MultiChoice.Title");
elements.Add(new CuiButton()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Button = { Color = "0 0 0 0", Command = "jstacks.multiple.toggle" },
}, InitialLayer + $".MultiChoice", InitialLayer + $".MultiChoice.Button");
CuiHelper.AddUi(player, elements);
UpdateLayers(player);
}
private void UpdateLayers(BasePlayer player, string categoryName = "")
{
CuiHelper.DestroyUi(player, InitialLayer + ".CategoryChangerBackground");
if (_playersSelectedItems.ContainsKey(player.userID))
{
_playersSelectedItems[player.userID].Clear();
}
if (categoryName == "")
{
categoryName = _config.categoryIcons.ElementAt(0).Key;
}
var categoryIcon = _config.categoryIcons[categoryName];
var elements = new CuiElementContainer();
elements.Add(new CuiElement()
{
Name = InitialLayer + ".ChosenNav",
Parent = InitialLayer + $".Nav_{categoryName}",
DestroyUi = InitialLayer + ".ChosenNav",
Components =
{
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "-7 -3", OffsetMax = "7 3" },
new CuiImageComponent { Color = "0.11574403 0.3310115 0.4811321 1", Material = "assets/content/ui/uibackgroundblur.mat"}
}
});
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "5 5", OffsetMax = "31 31" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.5686275", Sprite = categoryIcon }
}, InitialLayer + ".ChosenNav", InitialLayer + $".ChosenNav.Icon");
elements.Add(new CuiLabel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "36 5", OffsetMax = "-5 -5" },
Text = { Text = categoryName.ToUpper(), Color = "0.968627453 0.921568632 0.882352948 0.6686275", FontSize = 16, Align = TextAnchor.MiddleLeft }
}, InitialLayer + $".ChosenNav");
elements.Add(new CuiElement()
{
Name = InitialLayer + $".CategoryChanger.Button",
Update = true,
Components =
{
new CuiButtonComponent{ Command = "jstacks.categorychanger.call " + categoryName },
}
});
elements.Add(new CuiElement()
{
Name = InitialLayer + $".MultiChoice.Button",
Update = true,
Components =
{
new CuiButtonComponent{ Command = "jstacks.multiple.toggle " + categoryName },
}
});
CuiHelper.AddUi(player, elements);
UpdateMultipleButton(player);
FillMenuWithItems(player, categoryName);
}
private void FillMenuWithItems(BasePlayer player, string categoryName)
{
var elements = new CuiElementContainer();
// Берём только реальные видимые предметы категории
var visibleItems = _categoryItemsList[categoryName]
.Select(shortname => ItemManager.FindItemDefinition(shortname))
.Where(itemDef => itemDef != null && !itemDef.hidden)
.ToList();
// Настройки сетки
const int columns = 10; // сколько предметов в строке
const float itemSize = 54f; // размер плитки предмета
const float spacing = 60f; // расстояние между плитками
const float startX = 8f;
const float startY = 8f;
int rows = Mathf.CeilToInt(visibleItems.Count / (float)columns);
// Минимальная высота, чтобы маленькие категории не ломали окно.
// Если предметов много — высота увеличивается, и появляется нормальный скролл.
float contentHeight = Mathf.Max(420f, rows * spacing + 20f);
elements.Add(new CuiElement()
{
Name = InitialLayer + ".ItemList",
Parent = InitialLayer + ".Container",
DestroyUi = InitialLayer + ".ItemList",
Components =
{
new CuiScrollViewComponent
{
ContentTransform = new CuiRectTransformComponent
{
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = $"0 -{contentHeight}",
OffsetMax = "0 0"
},
Vertical = true,
Horizontal = false,
Elasticity = 0f,
Inertia = true,
DecelerationRate = 0.1f,
ScrollSensitivity = 35f,
MovementType = UnityEngine.UI.ScrollRect.MovementType.Elastic,
HorizontalScrollbar = null
},
new CuiRectTransformComponent
{
AnchorMin = "0 0",
AnchorMax = "1 1",
OffsetMin = "0 30",
OffsetMax = "0 0"
}
}
});
// Прозрачный фон внутри списка
elements.Add(new CuiPanel()
{
Image = { Color = "0 0 0 0" },
RectTransform =
{
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = $"0 -{contentHeight}",
OffsetMax = "0 0"
}
}, InitialLayer + ".ItemList");
for (int i = 0; i < visibleItems.Count; i++)
{
var itemDef = visibleItems[i];
int row = i / columns;
int col = i % columns;
float x = startX + col * spacing;
float y = startY + row * spacing;
string itemPanelName = InitialLayer + $".Item_{itemDef.shortname}";
elements.Add(new CuiPanel()
{
Image = { Color = "0.968627453 0.921568632 0.882352948 0.1" },
RectTransform =
{
AnchorMin = "0 1",
AnchorMax = "0 1",
OffsetMin = $"{x} {-y - itemSize}",
OffsetMax = $"{x + itemSize} {-y}"
},
}, InitialLayer + ".ItemList", itemPanelName);
elements.Add(new CuiPanel()
{
Image = { ItemId = itemDef.itemid },
RectTransform =
{
AnchorMin = "0 0",
AnchorMax = "1 1",
OffsetMin = "5 5",
OffsetMax = "-5 -5"
},
}, itemPanelName);
elements.Add(new CuiButton()
{
RectTransform =
{
AnchorMin = "0 0",
AnchorMax = "1 1",
OffsetMin = "0 0",
OffsetMax = "0 0"
},
Button =
{
Command = $"jstack.changer.call {itemDef.shortname}",
Color = "0 0 0 0"
},
}, itemPanelName);
elements.Add(new CuiLabel()
{
RectTransform =
{
AnchorMin = "0 0",
AnchorMax = "1 0",
OffsetMin = "0 0",
OffsetMax = "-3 15"
},
Text =
{
Text = "x" + itemDef.stackable,
Color = "0.968627453 0.921568632 0.882352948 0.6686275",
FontSize = 9,
Align = TextAnchor.MiddleRight,
Font = "RobotoCondensed-Bold.ttf"
}
}, itemPanelName);
int defaultSize;
if (_defaultStackSizes.ContainsKey(itemDef.shortname))
{
defaultSize = _defaultStackSizes[itemDef.shortname];
}
else
{
defaultSize = itemDef.stackable;
_defaultStackSizes[itemDef.shortname] = defaultSize;
}
elements.Add(new CuiLabel()
{
RectTransform =
{
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = "0 -15",
OffsetMax = "-3 0"
},
Text =
{
Text = "def." + defaultSize,
Color = "0.968627453 0.921568632 0.882352948 0.6686275",
FontSize = 9,
Align = TextAnchor.MiddleRight,
Font = "RobotoCondensed-Bold.ttf"
}
}, itemPanelName);
}
CuiHelper.AddUi(player, elements);
}
private void ChangerCall(BasePlayer player, string itemName)
{
var elements = new CuiElementContainer();
var itemDef = ItemManager.FindItemDefinition(itemName);
var choiceOption = _playersChoiceOptions[player.userID];
if (choiceOption == ChoiceOption.Single)
{
elements.Add(new CuiPanel
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Image = { Color = "0 0 0 0.8" }
}, InitialLayer + $".Item_{itemName}", InitialLayer + $".Item.Changer", InitialLayer + $".Item.Changer");
elements.Add(new CuiElement()
{
Name = InitialLayer + $".Item.Changer.Input",
Parent = InitialLayer + $".Item.Changer",
Components =
{
new CuiInputFieldComponent
{
Text = itemDef.stackable.ToString(),
Color = "1 1 1 1",
Align = TextAnchor.MiddleCenter,
FontSize = 12,
NeedsKeyboard = true,
Command = $"jstacks.setstack {itemDef.shortname}",
Autofocus = true
},
new CuiRectTransformComponent {AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0"}
}
});
}
else
{
if (!_playersSelectedItems.ContainsKey(player.userID))
{
_playersSelectedItems[player.userID] = new List<string>();
}
bool removed = false;
if (_playersSelectedItems[player.userID].Contains(itemDef.shortname))
{
_playersSelectedItems[player.userID].Remove(itemDef.shortname);
removed = true;
}
else
{
_playersSelectedItems[player.userID].Add(itemDef.shortname);
}
elements.Add(new CuiElement()
{
Name = InitialLayer + ".MultiChoice.InputField.Title",
Update = true,
Components =
{
new CuiTextComponent { Text = string.Format(GetMsg("MULTIPLE_CHOICE_INPUT_TITLE", player), _playersSelectedItems[player.userID].Count) },
}
});
var color = removed ? "0.043725491 0.0554902 0.021568628 0.42" : HexToColor("#708A43", 0.8);
elements.Add(new CuiElement()
{
Name = InitialLayer + $".Item_{itemDef.shortname}",
Update = true,
Components =
{
new CuiImageComponent { Color = color},
}
});
}
CuiHelper.AddUi(player, elements);
}
private void Notify(BasePlayer player, string message, string color = "green")
{
color = color.ToLower();
var colors = new Dictionary<string, string>()
{
["red"] = "0.7 0.2219512 0.14682925 1",
["green"] = "0.4509804 0.5529412 0.27058825 1",
["blue"] = "0.1647 0.2941 0.4157 0.8"
};
var iconColors = new Dictionary<string, string>()
{
["red"] = "1 0.49271604 0.413 1",
["green"] = "0.6164634 0.8113208 0.27171594 1",
["blue"] = "0 0.58914757 1 1"
};
var textColors = new Dictionary<string, string>()
{
["red"] = "0.97199994 0.8308799 0.80870396 1",
["green"] = "0.7348033 0.8490566 0.53266287 1",
["blue"] = "0.5030705 0.73362494 0.8962264 1"
};
if (!colors.ContainsKey(color)) return;
int width = (int)(message.Count() * 6 + 22);
var elements = new CuiElementContainer();
elements.Add(new CuiElement()
{
Parent = InitialLayer + ".Body",
Name = InitialLayer + ".Toast",
DestroyUi = InitialLayer + ".Toast",
Components =
{
new CuiRectTransformComponent { AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = $"0 5", OffsetMax = $"{width} 30" },
new CuiImageComponent { Color = colors[color], Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat", FadeIn = 0.5f}
}
});
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "3 3", OffsetMax = "22 22" },
Image = { Color = iconColors[color], Sprite = "assets/icons/info.png"}
}, InitialLayer + ".Toast");
elements.Add(new CuiLabel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "25 3", OffsetMax = "-3 -3" },
Text = { Text = message, Color = textColors[color], FontSize = 12, Align = TextAnchor.MiddleLeft}
}, InitialLayer + ".Toast");
CuiHelper.AddUi(player, elements);
if (_playersNotifyTimers.ContainsKey(player.userID))
{
_playersNotifyTimers[player.userID].Destroy();
_playersNotifyTimers.Remove(player.userID);
}
_playersNotifyTimers[player.userID] = (
timer.Once(5f, () =>
{
CuiHelper.DestroyUi(player, InitialLayer + ".Toast");
})
);
}
private void ShowConfirmMenu(BasePlayer player, string message, string commandOnYes)
{
var elements = new CuiElementContainer();
elements.Add(new CuiElement()
{
Name = InitialLayer + ".ConfirmBackGround",
Parent = InitialLayer,
DestroyUi = InitialLayer + ".ConfirmBackGround",
Components =
{
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "1 1" },
new CuiImageComponent { Color = "0 0 0 1" }
}
});
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-250 -150", OffsetMax = "250 150" },
Image = { Color = "0.968627453 0.921568632 0.882352948 0.2", Sprite = "Assets/Content/UI/UI.Background.Tile.psd", ImageType = Image.Type.Tiled }
}, InitialLayer + ".ConfirmBackGround", InitialLayer + ".Confirm");
elements.Add(new CuiPanel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "0.5 0", OffsetMin = "0 -35", OffsetMax = "0 -3" },
Image = { Color = "0.4509804 0.5529412 0.27058825 1", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat" }
}, InitialLayer + ".Confirm", InitialLayer + ".Confirm.Button_yes");
elements.Add(new CuiLabel()
{
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" },
Text = { Text = message, Color = "0.968627453 0.921568632 0.882352948 0.6686275", FontSize = 24, Align = TextAnchor.MiddleCenter}
}, InitialLayer + ".Confirm");
elements.Add(new CuiButton()
{
Button = { Close = InitialLayer + ".ConfirmBackGround", Command = commandOnYes, Color = "0.4509804 0.5529412 0.27058825 1"},
RectTransform = { AnchorMin = "0 0", AnchorMax = "0.5 0", OffsetMin = "0 -35", OffsetMax = "0 -3" },
Text = { Text = GetMsg("CONFIRM_TEXT", player) , FontSize = 16, Align = TextAnchor.MiddleCenter}
}, InitialLayer + ".Confirm");
elements.Add(new CuiButton()
{
Button = { Close = InitialLayer + ".ConfirmBackGround", Color = "0.7 0.2219512 0.14682925 1"},
RectTransform = { AnchorMin = "0.5 0", AnchorMax = "1 0", OffsetMin = "3 -35", OffsetMax = "0 -3" },
Text = { Text = GetMsg("DECLINE_TEXT", player), Color = "0.968627453 0.921568632 0.882352948 0.6686275", FontSize = 16, Align = TextAnch
// ... [Code preview truncated due to length. Full source in attached file] ...