C#:
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
using Oxide.Core;
using System.Linq;
using Newtonsoft.Json;
using System;
using System.Text.RegularExpressions;
namespace Oxide.Plugins
{
[Info("MBKits", "Barry_Allenn", "1.0.0")]
[Description("MBKits")]
public class MBKits : RustPlugin
{
[PluginReference] private Plugin ImageLibrary, MBMainMenu;
private ImageUI imageUI = new ImageUI();
private Dictionary<string, KitData> kits = new();
private Dictionary<string, string> kitContainerNames = new();
private Dictionary<ulong, List<ChatErrorEntry>> playerErrors = new();
private string kitsFile => $"{Name}/kits";
private Dictionary<ulong, Dictionary<string, double>> kitCooldowns = new();
private string cooldownFile => $"{Name}/cooldowns";
private class KitData
{
public string Name;
public string ImageUrl = "https://i.postimg.cc/tJrNqhNX/42808.png";
public string Permission = "";
public string CooldownText = "1h"; // Строка вида "1h30m" вместо числа
public List<ItemData> Items = new();
}
private class ItemData
{
public string ShortName;
public int Amount;
public ulong SkinID = 0; // ← используем только это
public int AmmoAmount;
public string Container; // "main", "belt", "wear"
}
private class ChatErrorEntry
{
public string Text;
public string ImageKey;
public DateTime Timestamp;
}
private double GetCooldownSeconds(string text)
{
if (TryParseTime(text.ToLower(), out var ts))
return ts.TotalSeconds;
return 0;
}
private static bool TryParseTime(string input, out TimeSpan result)
{
int d = 0, h = 0, m = 0, s = 0;
Match matchD = Regex.Match(input, @"(\d+?)d");
Match matchH = Regex.Match(input, @"(\d+?)h");
Match matchM = Regex.Match(input, @"(\d+?)m");
Match matchS = Regex.Match(input, @"(\d+?)s");
if (matchD.Success) d = int.Parse(matchD.Groups[1].Value);
if (matchH.Success) h = int.Parse(matchH.Groups[1].Value);
if (matchM.Success) m = int.Parse(matchM.Groups[1].Value);
if (matchS.Success) s = int.Parse(matchS.Groups[1].Value);
if (!matchD.Success && !matchH.Success && !matchM.Success && !matchS.Success)
{
result = TimeSpan.Zero;
return false;
}
result = new TimeSpan(d, h, m, s);
return true;
}
private void LoadKits()
{
kits = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, KitData>>(kitsFile) ?? new();
}
private void SaveKits()
{
Interface.Oxide.DataFileSystem.WriteObject(kitsFile, kits);
}
private void LoadCooldowns()
{
kitCooldowns = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, Dictionary<string, double>>>(cooldownFile) ?? new();
}
private void SaveCooldowns()
{
Interface.Oxide.DataFileSystem.WriteObject(cooldownFile, kitCooldowns);
}
void Init()
{
LoadKits();
LoadCooldowns();
permission.RegisterPermission("mbkits.admin", this); // ← один общий
}
private void OnServerInitialized()
{
imageUI = new ImageUI();
imageUI.DownloadImages();
ImageLibrary.Call("AddImage", "https://i.postimg.cc/8cN7k8kD/Group-4.png", "error_icon_invalid_kits");
ImageLibrary.Call("AddImage", "https://i.postimg.cc/GpRw5bXc/Group-1-3.png", "success_icon_kits");
foreach (var kit in kits.Values)
{
if (!string.IsNullOrEmpty(kit.ImageUrl) &&
(kit.ImageUrl.StartsWith("http://") || kit.ImageUrl.StartsWith("https://")))
{
ImageLibrary?.Call("AddImage", kit.ImageUrl, kit.ImageUrl);
}
// Регистрируем permission, если нужно
if (!string.IsNullOrEmpty(kit.Permission) && !permission.PermissionExists(kit.Permission))
{
permission.RegisterPermission(kit.Permission, this);
}
}
}
void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(player, "MBKits_UI");
CuiHelper.DestroyUi(player, "MBKits_Inv_UI");
}
imageUI?.UnloadImages();
}
private void OnPlayerConnected(BasePlayer player)
{
ShowIntroImage(player); // ← Добавлено
}
private void ShowIntroImage(BasePlayer player)
{
CuiHelper.DestroyUi(player, "MBKits_IntroUI");
var container = new CuiElementContainer();
string imageId = imageUI?.GetImage("MBWelcome_Four_Os");
if (string.IsNullOrEmpty(imageId))
{
PrintWarning("[MBKits] Intro image not loaded");
return;
}
container.Add(new CuiElement
{
Name = "MBKits_IntroUI",
Parent = "Overlay",
Components =
{
new CuiRawImageComponent
{
Png = imageId,
Color = "1 1 1 0", // полная непрозрачность картинки
Sprite = "assets/content/textures/generic/fulltransparent.tga"
},
new CuiRectTransformComponent
{
AnchorMin = "0 0",
AnchorMax = "1 0.5"
}
}
});
CuiHelper.AddUi(player, container);
timer.Once(0.5f, () =>
{
CuiHelper.DestroyUi(player, "MBKits_IntroUI");
});
}
#region MBKitsUI
[HookMethod("OpenMBKitsForPlayer")]
private void ShowKitsUI(BasePlayer player)
{
CuiHelper.DestroyUi(player, "MBKits_UI");
var container = new CuiElementContainer();
string root = container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, "Overlay", "MBKits_UI");
string backgroundPanel = container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0.7", Material = "assets/content/ui/uibackgroundblur.mat" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, root);
string imageId = imageUI?.GetImage("MBWelcome_Four_Os");
if (!string.IsNullOrEmpty(imageId))
{
container.Add(new CuiElement
{
Name = CuiHelper.GetGuid(),
Parent = root,
Components =
{
new CuiRawImageComponent { Png = imageId },
new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-558 -314", OffsetMax = "558 315" }
}
});
}
// Добавляем кнопку закрытия UI
container.Add(new CuiButton
{
Button = { Color = "0 0 0 0", Close = "MBKits_UI" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "", FontSize = 0 }
}, root);
string centerPanel = container.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0" },
RectTransform = {
AnchorMin = "0.5 0.5",
AnchorMax = "0.5 0.5",
OffsetMin = "-438 -265", // ширина 800, высота 600
OffsetMax = "440 265"
},
CursorEnabled = true
}, root);
container.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0.341 0.86", AnchorMax = "0.75 0.955" },
Text = { Text = Msg("MB_Kits_Player", player.userID.ToString()), Color = "1 1 1 0.9", Align = TextAnchor.MiddleLeft, FontSize = 30 }
}, centerPanel);
container.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0.341 0.8", AnchorMax = "0.9 0.868" },
Text = { Text = Msg("MB_Kits_Player_2", player.userID.ToString()), Color = "1 1 1 0.9", Align = TextAnchor.MiddleLeft, FontSize = 21, Font = "permanentmarker.ttf" }
}, centerPanel);
container.Add(new CuiLabel
{
Text = { Text = Msg("MB_Close_Kits", player.userID.ToString()), FontSize = 13, Color = "1 1 1 0.4", Align = TextAnchor.MiddleCenter },
RectTransform = { AnchorMin = "0.2 -0.06", AnchorMax = "0.8 -0.02" }
}, centerPanel);
container.Add(new CuiButton
{
Button = {
Command = "mbkits.back", // кастомная команда
Color = "0.2 0.5 0.8 0"
},
RectTransform = { AnchorMin = "0.865 0.04", AnchorMax = "0.947 0.12" },
Text = {
Text = Msg("MB_Back", player.UserIDString),
FontSize = 15,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
}
}, centerPanel);
// Прокрутка
float blockHeight = 242f;
float blockMargin = 8f;
float verticalShift = 3f;
var kitList = kits.Values.ToList();
int total = kitList.Count;
int rowCount = Mathf.CeilToInt(total / 3f);
float totalHeight = rowCount * (blockHeight + blockMargin);
float minVisibleHeight = 300f;
float scrollContentHeight = Mathf.Max(totalHeight, minVisibleHeight);
container.Add(new CuiElement
{
Name = "MBMenuScrollableKits",
Parent = centerPanel,
Components =
{
new CuiScrollViewComponent
{
Vertical = true,
Horizontal = false,
Inertia = true,
Elasticity = 0.2f,
DecelerationRate = 0.2f,
ScrollSensitivity = 30f,
ContentTransform = new CuiRectTransform
{
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = $"0 -{scrollContentHeight}",
OffsetMax = "0 0"
},
VerticalScrollbar = new CuiScrollbar
{
Size = 4f,
AutoHide = false,
HandleColor = "0.3 0.6 0.9 0.7",
TrackColor = "0.1 0.1 0.1 0.6"
}
},
new CuiRectTransformComponent
{
AnchorMin = "0.34 0.285",
AnchorMax = "0.96 0.75"
},
new CuiNeedsCursorComponent()
}
});
container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform =
{
AnchorMin = "0 1",
AnchorMax = "1 1",
OffsetMin = $"0 -{scrollContentHeight}",
OffsetMax = "0 0"
}
}, "MBMenuScrollableKits", "MBMenuKitsContent");
// Отображаем наборы
for (int i = 0; i < total; i++)
{
var kit = kitList[i];
int row = i / 3;
int col = i % 3;
float topOffset = -(row * (blockHeight + blockMargin)) - verticalShift;
float bottomOffset = topOffset - blockHeight;
float left = col switch
{
0 => 0f,
1 => 0.333f,
2 => 0.666f,
_ => 0f
};
float right = col == 2 ? left + 0.31f : left + 0.32f;
string bgImg = imageUI.GetImage("MBKitsBG_Default");
string containerName = $"KitContainer_{i}";
kitContainerNames[kit.Name.ToLower()] = containerName;
container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform =
{
AnchorMin = $"{left} 1",
AnchorMax = $"{right} 1",
OffsetMin = $"0 {bottomOffset}",
OffsetMax = $"0 {topOffset}"
}
}, "MBMenuKitsContent", containerName);
// Фон
if (!string.IsNullOrEmpty(bgImg))
{
container.Add(new CuiElement
{
Name = $"KitBG_{i}",
Parent = containerName,
Components =
{
new CuiRawImageComponent
{
Png = bgImg,
Sprite = "assets/content/textures/generic/fulltransparent.tga"
},
new CuiRectTransformComponent
{
AnchorMin = "0 0",
AnchorMax = "1 1"
}
}
});
}
string kitImg = (string)ImageLibrary.Call("GetImage", kit.ImageUrl);
if (!string.IsNullOrEmpty(kitImg))
{
container.Add(new CuiElement
{
Parent = containerName,
Components =
{
new CuiRawImageComponent
{
Png = kitImg,
Sprite = "assets/content/textures/generic/fulltransparent.tga"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.2",
AnchorMax = "0.9 0.8"
}
}
});
}
// Название набора
container.Add(new CuiLabel
{
Text =
{
Text = kit.Name.ToUpper(),
FontSize = 17,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 0.9"
},
RectTransform = { AnchorMin = "0.05 0.85", AnchorMax = "0.95 0.98" }
}, containerName);
bool hasPermission = string.IsNullOrEmpty(kit.Permission) || permission.UserHasPermission(player.UserIDString, kit.Permission);
double cooldownUntil = 0;
bool isOnCooldown = kitCooldowns.TryGetValue(player.userID, out var cooldowns) &&
cooldowns.TryGetValue(kit.Name.ToLower(), out cooldownUntil) &&
cooldownUntil > Facepunch.Math.Epoch.Current;
if (!hasPermission)
{
// Нет прав — серая неактивная кнопка
container.Add(new CuiButton
{
Button = { Color = "0.3 0.3 0.3 0" }, // тёмно-серая
RectTransform = { AnchorMin = "0.05 0.04", AnchorMax = "0.72 0.18" },
Text = {
Text = Msg("MB_NoAccess", player.UserIDString),
FontSize = 14,
Align = TextAnchor.MiddleCenter,
Color = "0.8 0.8 0.8 1" // светло-серый текст
}
}, containerName);
}
else if (isOnCooldown)
{
double timeLeft = cooldownUntil - Facepunch.Math.Epoch.Current;
if (timeLeft < 0) timeLeft = 0;
TimeSpan ts = TimeSpan.FromSeconds(timeLeft);
string cooldownText = string.Format("{0:D2}h {1:D2}m", ts.Hours, ts.Minutes);
// Кулдаун — жёлтая неактивная кнопка
container.Add(new CuiButton
{
Button = { Color = "0.8 0.6 0.1 0" }, // жёлтый фон
RectTransform = { AnchorMin = "0.05 0.04", AnchorMax = "0.72 0.18" },
Text = {
Text = cooldownText,
FontSize = 14,
Align = TextAnchor.MiddleCenter,
Color = "1 0.4 0.4 1"
}
}, containerName);
}
else
{
// Кнопка "ПОЛУЧИТЬ"
container.Add(new CuiButton
{
Button =
{
Color = "0.2 0.6 0.2 0",
Command = $"mbkits.givekit {kit.Name}"
},
RectTransform = { AnchorMin = "0.05 0.04", AnchorMax = "0.72 0.18" },
Text =
{
Text = Msg("MB_Claim", player.UserIDString),
FontSize = 14,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
}
}, containerName, $"KitButton_{kit.Name}");
}
// Кнопка "ПРОСМОТР"
container.Add(new CuiButton
{
Button =
{
Color = "0.2 0.2 0.8 0",
Command = $"mbkits.viewkit {kit.Name}"
},
RectTransform = { AnchorMin = "0.75 0.04", AnchorMax = "0.948 0.18" },
}, containerName);
}
CuiHelper.AddUi(player, container);
}
#endregion
#region MBKitsInvUI
private void ShowKitsInvUI(BasePlayer player, KitData kit)
{
CuiHelper.DestroyUi(player, "MBKits_Inv_UI");
CuiHelper.DestroyUi(player, "MBKits_UI");
var container = new CuiElementContainer();
string root = container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, "Overlay", "MBKits_Inv_UI");
string backgroundPanel = container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0.7", Material = "assets/content/ui/uibackgroundblur.mat" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, root);
// Картинка фону
string imageId = imageUI?.GetImage("MBKits_inv");
if (!string.IsNullOrEmpty(imageId))
{
container.Add(new CuiElement
{
Name = CuiHelper.GetGuid(),
Parent = root,
Components =
{
new CuiRawImageComponent { Png = imageId },
new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-558 -314", OffsetMax = "558 315" }
}
});
}
// Закриття
container.Add(new CuiButton
{
Button = { Color = "0 0 0 0", Close = "MBKits_Inv_UI" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "", FontSize = 0 }
}, root);
string centerPanel = container.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0" },
RectTransform = {
AnchorMin = "0.5 0.5",
AnchorMax = "0.5 0.5",
OffsetMin = "-438 -265",
OffsetMax = "440 265"
},
CursorEnabled = true
}, root);
container.Add(new CuiLabel
{
Text = { Text = Msg("MB_Close_Kits", player.userID.ToString()), FontSize = 13, Color = "1 1 1 0.4", Align = TextAnchor.MiddleCenter },
RectTransform = { AnchorMin = "0.2 -0.06", AnchorMax = "0.8 -0.02" }
}, centerPanel);
container.Add(new CuiButton
{
Button = {
Command = "mbkits_inv.back", // кастомная команда
Color = "0.2 0.5 0.8 0"
},
RectTransform = { AnchorMin = "0.865 0.04", AnchorMax = "0.947 0.12" },
Text = {
Text = Msg("MB_Back", player.UserIDString),
FontSize = 15,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
}
}, centerPanel);
// Название
container.Add(new CuiLabel
{
Text = {
Text = $"{kit.Name.ToUpper()}",
FontSize = 15,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 0.9"
},
RectTransform = { AnchorMin = "0.411 0.8411", AnchorMax = "0.5345 0.897" }
}, centerPanel);
// Получаем локализованные строки через Msg
string textPrefix = Msg("CooldownPrefix", player.UserIDString);
string textPrefix_2 = Msg("CooldownPrefix_2", player.UserIDString);
string textH = Msg("TimeHours", player.UserIDString);
string textM = Msg("TimeMinutes", player.UserIDString);
string textS = Msg("TimeSeconds", player.UserIDString);
// Форматирование: всегда H M S, даже если 0
string FormatCooldown(TimeSpan time)
{
return $"{(int)time.TotalHours}{textH} {(int)time.Minutes}{textM} {(int)time.Seconds}{textS}";
}
// Проверка кулдауна
double cooldownUntil = 0;
bool isOnCooldown = kitCooldowns.TryGetValue(player.userID, out var cooldowns) &&
cooldowns.TryGetValue(kit.Name.ToLower(), out cooldownUntil) &&
cooldownUntil > Facepunch.Math.Epoch.Current;
string cooldownText;
if (isOnCooldown)
{
double remaining = cooldownUntil - Facepunch.Math.Epoch.Current;
if (remaining < 0) remaining = 0;
TimeSpan remainingTime = TimeSpan.FromSeconds(remaining);
cooldownText = $"{textPrefix}: {FormatCooldown(remainingTime)}";
}
else
{
TimeSpan fullTime = TimeSpan.FromSeconds(GetCooldownSeconds(kit.CooldownText));
cooldownText = $"{textPrefix_2}: {FormatCooldown(fullTime)}";
}
// Вывод в UI
container.Add(new CuiLabel
{
Text = {
Text = cooldownText,
FontSize = 13,
Align = TextAnchor.MiddleLeft,
Color = isOnCooldown ? "1 0.4 0.4 1" : "1 1 1 0.7"
},
RectTransform = { AnchorMin = "0.547 0.8411", AnchorMax = "0.792 0.897" }
}, centerPanel);
// Слоты main — 6x4
var main = kit.Items.Where(i => i.Container == "main").ToList();
for (int i = 0; i < 24; i++)
{
int row = i / 6;
int col = i % 6;
float xMin = 0.412f + col * 0.0645f;
float yMax = 0.827f - row * 0.1075f;
float xMax = xMin + 0.0585f;
float yMin = yMax - 0.0955f;
AddItemSlot(container, centerPanel, $"Main_{i}", xMin, yMin, xMax, yMax, i < main.Count ? main[i] : null);
}
// Слоты wear — 7 ячеек
var wear = kit.Items.Where(i => i.Container == "wear").ToList();
for (int i = 0; i < 7; i++)
{
float xMin = 0.38f + i * 0.0645f;
float yMin = 0.298f;
float xMax = xMin + 0.0585f;
float yMax = yMin + 0.0955f;
AddItemSlot(container, centerPanel, $"Wear_{i}", xMin, yMin, xMax, yMax, i < wear.Count ? wear[i] : null);
}
// Слоты belt — 6 ячеек
var belt = kit.Items.Where(i => i.Container == "belt").ToList();
for (int i = 0; i < 6; i++)
{
float xMin = 0.412f + i * 0.0645f;
float yMin = 0.187f;
float xMax = xMin + 0.0585f;
float yMax = yMin + 0.0955f;
AddItemSlot(container, centerPanel, $"Belt_{i}", xMin, yMin, xMax, yMax, i < belt.Count ? belt[i] : null);
}
CuiHelper.AddUi(player, container);
}
#endregion
private void AddErrorMessage(BasePlayer player, string text, string imageKey)
{
ulong id = player.userID;
if (!playerErrors.ContainsKey(id))
playerErrors[id] = new List<ChatErrorEntry>();
var entry = new ChatErrorEntry
{
Text = text,
ImageKey = imageKey,
Timestamp = DateTime.UtcNow
};
playerErrors[id].Add(entry);
if (playerErrors[id].Count > 5)
playerErrors[id].RemoveAt(0);
RedrawErrorUI(player);
timer.Once(3f, () =>
{
if (playerErrors.TryGetValue(id, out var list) && list.Contains(entry))
{
list.Remove(entry);
RedrawErrorUI(player);
}
});
}
private void RedrawErrorUI(BasePlayer player)
{
CuiHelper.DestroyUi(player, "MB_ErrorChatUI");
if (!playerErrors.TryGetValue(player.userID, out var messages) || messages.Count == 0)
return;
var container = new CuiElementContainer();
string parent = container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "0 0", OffsetMin = "5 96", OffsetMax = "200 500" }
}, "Overlay", "MB_ErrorChatUI");
float blockHeight = 50f;
float spacing = 5f;
float yStart = 0f;
for (int i = 0; i < messages.Count; i++)
{
var error = messages[messages.Count - 1 - i];
float offsetY = yStart + i * (blockHeight + spacing);
string panel = container.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = {
AnchorMin = "0 0",
AnchorMax = "1 0",
OffsetMin = $"0 {offsetY}",
OffsetMax = $"0 {offsetY + blockHeight}"
}
}, parent);
container.Add(new CuiElement
{
Parent = panel,
Components =
{
new CuiRawImageComponent
{
Png = (string)ImageLibrary?.Call("GetImage", error.ImageKey),
Color = "1 1 1 1"
},
new CuiRectTransformComponent
{
AnchorMin = "0 0",
AnchorMax = "1 1",
}
}
});
string titleKey = error.ImageKey switch
{
"success_icon_kits" => "SuccessTitle",
"error_icon_invalid_kits" => "ErrorTitle",
"error_icon_expired" => "ErrorTitle",
_ => "ErrorTitle"
};
string title = Msg(titleKey, player.UserIDString);
container.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0.31 0.56", AnchorMax = "1 0.9" },
Text = {
Text = title,
Align = TextAnchor.MiddleLeft,
FontSize = 12,
Color = "1 1 1 0.9"
}
}, panel);
container.Add(new CuiLabel
{
RectTransform = { AnchorMin = "0.31 0", AnchorMax = "0.98 0.54" },
Text = {
Text = error.Text,
Align = TextAnchor.UpperLeft,
FontSize = 9,
Color = "1 1 1 0.8"
}
}, panel);
}
CuiHelper.AddUi(player, container);
}
#region Command
// [ChatCommand("kit")]
// private void CmdKits(BasePlayer player, string command, string[] args)
// {
// ShowKitsUI(player);
// }
[ConsoleCommand("mbkits.viewkit")]
private void CmdViewKit(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !arg.HasArgs()) return;
string kitName = arg.Args[0].ToLower();
if (!kits.TryGetValue(kitName, out var kit)) return;
ShowKitsInvUI(player, kit);
}
[ConsoleCommand("mbkits.givekit")]
private void CmdGiveKit(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !arg.HasArgs()) return;
string kitName = arg.Args[0].ToLower();
if (!kits.TryGetValue(kitName, out var kit))
return;
if (!string.IsNullOrEmpty(kit.Permission) && !permission.UserHasPermission(player.UserIDString, kit.Permission))
return;
// --- Проверка отката ---
double now = Facepunch.Math.Epoch.Current;
double cooldownUntil = 0;
if (kitCooldowns.TryGetValue(player.userID, out var cooldowns))
{
cooldowns.TryGetValue(kitName, out cooldownUntil);
}
if (cooldownUntil > now)
{
double remaining = cooldownUntil - now;
TimeSpan remainingTime = TimeSpan.FromSeconds(remaining);
AddErrorMessage(player, string.Format(Msg("CooldownPrefix", player.UserIDString) + ": {0:D2}м {1:D2}с", remainingTime.Minutes, remainingTime.Seconds), "error_icon_expired");
return;
}
// ✅ Проверка на заполненность до выдачи!
if (!CanFitAllItems(player, kit))
{
AddErrorMessage(player, Msg("KitInventoryFull", player.UserIDString), "error_icon_invalid_kits");
return;
}
// --- Выдача набора ---
GiveKitToPlayer(player, kit);
AddErrorMessage(player, Msg("KitSuccess", player.UserIDString), "success_icon_kits");
// 🔊 Звук
EffectNetwork.Send(new Effect("assets/prefabs/misc/xmas/presents/effects/unwrap.prefab", player, 0, Vector3.up, Vector3.zero)
{
scale = UnityEngine.Random.Range(0f, 1f)
});
// --- Запись нового времени ---
if (!kitCooldowns.TryGetValue(player.userID, out cooldowns))
{
cooldowns = new Dictionary<string, double>();
kitCooldowns[player.userID] = cooldowns;
}
double cooldownSec = GetCooldownSeconds(kit.CooldownText);
cooldowns[kitName.ToLower()] = now + cooldownSec;
SaveCooldowns();
UpdateKitButton(player, kitName);
}
private void UpdateKitButton(BasePlayer player, string kitName)
{
var kit = kits[kitName];
string containerName = kitContainerNames[kitName.ToLower()];
CuiHelper.DestroyUi(player, $"KitButton_{kitName}");
var container = new CuiElementContainer();
double now = Facepunch.Math.Epoch.Current;
double cooldownUntil = 0;
bool isOnCooldown = kitCooldowns.TryGetValue(player.userID, out var cooldowns) &&
cooldowns.TryGetValue(kitName.ToLower(), out cooldownUntil) &&
cooldownUntil > now;
if (isOnCooldown)
{
double remaining = cooldownUntil - now;
if (remaining < 1) remaining = 1; // минимум 1 секунда, чтобы избежать 00:00
TimeSpan ts = TimeSpan.FromSeconds(remaining);
string cooldownText = FormatShortTime(ts, player); // Используем форматированный вывод
container.Add(new CuiButton
{
Button = { Color = "0.8 0.6 0.1 0" },
RectTransform = { AnchorMin = "0.05 0.04", AnchorMax = "0.72 0.18" },
Text = {
Text = cooldownText,
FontSize = 14,
Align = TextAnchor.MiddleCenter,
Color = "1 0.4 0.4 1"
}
}, containerName, $"KitButton_{kitName}");
}
CuiHelper.AddUi(player, container);
}
[ConsoleCommand("mbkits.back")]
private void CmdKitsBack(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player == null) return;
CuiHelper.DestroyUi(player, "MBKits_UI");
MBMainMenu?.Call("ShowMainMenu", player);
}
[ConsoleCommand("mbkits_inv.back")]
private void CmdKitsBackInv(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player == null) return;
CuiHelper.DestroyUi(player, "MBKits_Inv_UI");
ShowKitsUI(player);
}
[ChatCommand("kit")]
private void CmdKit(BasePlayer player, string command, string[] args)
{
if (args.Length == 0)
{
ShowKitsUI(player);
return;
}
switch (args[0].ToLower())
{
case "add":
{
if (!permission.UserHasPermission(player.UserIDString, "mbkits.admin"))
{
player.ChatMessage(Msg("NoAdminPermission", player.UserIDString));
return;
}
if (args.Length < 2)
{
player.ChatMessage(Msg("KitAddUsage", player.UserIDString));
return;
}
string kitName = args[1].ToLower();
string permName = args.Length >= 3 ? args[2].ToLower() : $"mbkits.{kitName}";
if (!string.IsNullOrEmpty(permName) && !permission.PermissionExists(permName))
permission.RegisterPermission(permName, this);
var kit = new KitData
{
Name = kitName,
Permission = permName,
CooldownText = "1h"
};
foreach (var item in player.inventory.containerMain.itemList)
{
if (item == null) continue;
kit.Items.Add(CreateItemData(item, "main"));
}
foreach (var item in player.inventory.containerBelt.itemList)
{
if (item == null) continue;
kit.Items.Add(CreateItemData(item, "belt"));
}
foreach (var item in player.inventory.containerWear.itemList)
{
if (item == null) continue;
kit.Items.Add(CreateItemData(item, "wear"));
}
kits[kitName] = kit;
SaveKits();
player.ChatMessage(string.Format(Msg("KitAdded", player.UserIDString), kitName, permName));
break;
}
case "remove":
{
if (!permission.UserHasPermission(player.UserIDString, "mbkits.admin"))
{
player.ChatMessage(Msg("NoAdminPermission", player.UserIDString));
return;
}
if (args.Length < 2)
{
player.ChatMessage(Msg("KitRemoveUsage", player.UserIDString));
return;
}
string kitName = args[1].ToLower();
if (!kits.Remove(kitName))
{
player.ChatMessage(string.Format(Msg("KitNotFound", player.UserIDString), kitName));
return;
}
int count = 0;
foreach (var entry in kitCooldowns)
{
if (entry.Value.Remove(kitName))
count++;
}
SaveKits();
SaveCooldowns();
player.ChatMessage(string.Format(Msg("KitRemoved", player.UserIDString), kitName, count));
break;
}
default:
{
ShowKitsUI(player);
break;
}
}
}
#endregion
private ItemData CreateItemData(Item item, string container)
{
return new ItemData
{
ShortName = item.info.shortname,
Amount = item.amount,
SkinID = item.skin, // сохраняем оригинальный SkinID
AmmoAmount = item.contents?.itemList?.FirstOrDefault()?.amount ?? 0,
Container = container
};
}
private string FormatShortTime(TimeSpan time, BasePlayer player)
{
string result = "";
if (time.Days > 0) result += $"{time.Days}{Msg("TimeDays", player.UserIDString)} ";
if (time.Hours > 0) result += $"{time.Hours}{Msg("TimeHours", player.UserIDString)} ";
if (time.Minutes > 0) result += $"{time.Minutes}{Msg("TimeMinutes", player.UserIDString)} ";
if (time.Seconds > 0) result += $"{time.Seconds}{Msg("TimeSeconds", player.UserIDString)}";
return result.Trim();
}
private bool CanFitAllItems(BasePlayer player, KitData kit)
{
int totalAvailable = player.inventory.containerMain.capacity - player.inventory.containerMain.itemList.Count
+ player.inventory.containerBelt.capacity - player.inventory.containerBelt.itemList.Count
+ player.inventory.containerWear.capacity - player.inventory.containerWear.itemList.Count;
int totalItems = kit.Items.Count;
return totalAvailable >= totalItems;
}
private void GiveKitToPlayer(BasePlayer player, KitData kit)
{
foreach (var entry in kit.Items)
{
var definition = ItemManager.FindItemDefinition(entry.ShortName);
if (definition == null) continue;
// Используем только SkinID
var item = ItemManager.Create(definition, entry.Amount, entry.SkinID);
if (item == null) continue;
// Если это оружие с патронами
if (item.contents != null && entry.AmmoAmount > 0)
{
var ammoDef = ItemManager.FindItemDefinition("ammo.pistol");
if (ammoDef != null)
{
var ammo = ItemManager.Create(ammoDef, entry.AmmoAmount);
ammo?.MoveToContainer(item.contents);
}
}
// Куда положить предмет
ItemContainer primary = entry.Container switch
{
"belt" => player.inventory.containerBelt,
"wear" => player.inventory.containerWear,
_ => player.inventory.containerMain
};
// Альтернативы, если не влезло
ItemContainer[] fallback =
{
player.inventory.containerMain,
player.inventory.containerBelt,
player.inventory.containerWear
};
// Пытаемся положить в основную цель
if (item.MoveToContainer(primary)) continue;
// Пытаемся в альтернативные
bool moved = false;
foreach (var alt in fallback)
{
if (alt == primary) continue;
if (item.MoveToContainer(alt))
{
moved = true;
break;
}
}
}
}
private void AddItemSlot(CuiElementContainer container, string parent, string name, float xMin, float yMin, float xMax, float yMax, ItemData item)
{
container.Add(new CuiPanel
{
RectTransform = { AnchorMin = $"{xMin} {yMin}", AnchorMax = $"{xMax} {yMax}" },
Image = { Color = "0 0 0 0" }
}, parent, name);
if (item == null) return;
int itemId = ItemManager.FindItemDefinition(item.ShortName)?.itemid ?? 0;
ulong skinId = item.SkinID;
container.Add(new CuiElement
{
Parent = name,
Components =
{
new CuiImageComponent { ItemId = itemId, SkinId = skinId },
new CuiRectTransformComponent
{
AnchorMin = "0 0",
AnchorMax = "1 1",
OffsetMin = "4 4",
OffsetMax = "-4 -4"
}
}
});
container.Add(new CuiLabel
{
Text = {
Text = $"x{item.Amount}",
FontSize = 10,
Align = TextAnchor.LowerRight,
Color = "1 1 1 0.7"
},
RectTransform = { AnchorMin = "0 0.05", AnchorMax = "0.9 1" }
}, name);
}
#region ImageUI
private class ImageUI
{
private const string PathFolder = "MBSystem/MBKits/Images/";
private readonly Dictionary<string, ImageData> _images = new()
{
{ "MBWelcome_Four_Os", new ImageData() },
{ "MBKitsBG_Default", new ImageData() },
{ "MBKits_inv", new ImageData() },
};
private enum ImageStatus { NotLoaded, Loaded, Failed }
private class ImageData
{
public ImageStatus Status = ImageStatus.NotLoaded;
public string Id { get; set; }
}
public string GetImage(string name)
{
if (_images.TryGetValue(name, out var image) && image.Status == ImageStatus.Loaded)
return image.Id;
return null;
}
public void DownloadImages()
{
string fullPath = Interface.Oxide.DataDirectory + System.IO.Path.DirectorySeparatorChar + PathFolder;
if (!System.IO.Directory.Exists(fullPath))
{
System.IO.Directory.CreateDirectory(fullPath);
Interface.Oxide.LogInfo($"[MBKits] Создана папка: {fullPath}");
}
var nextImage = _images.FirstOrDefault(img => img.Value.Status == ImageStatus.NotLoaded);
if (!string.IsNullOrEmpty(nextImage.Key))
ServerMgr.Instance.StartCoroutine(LoadImage(nextImage));
else
Interface.Oxide.LogInfo("[MBKits] Все изображения загружены!");
}
private IEnumerator LoadImage(KeyValuePair<string, ImageData> image)
{
string filePath = "file://" + Interface.Oxide.DataDirectory + System.IO.Path.DirectorySeparatorChar + PathFolder + image.Key + ".png";
using (var www = UnityEngine.Networking.UnityWebRequestTexture.GetTexture(filePath))
{
yield return www.SendWebRequest();
if (www.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
{
// ... [Code preview truncated due to length. Full source in attached file] ...