· 2 years ago · Mar 17, 2023, 01:40 PM
1using Newtonsoft.Json;
2using Newtonsoft.Json.Linq;
3using Oxide.Core;
4using Oxide.Core.Configuration;
5using Oxide.Core.Libraries;
6using Oxide.Game.Rust.Cui;
7using Oxide.Core.Plugins;
8using Oxide.Plugins;
9using System;
10using System.Collections.Generic;
11using System.Globalization;
12using System.Linq;
13using UnityEngine;
14
15namespace Oxide.Plugins
16{
17 [Info("Kits", "k1lly0u", "4.4.0"), Description("Create kits containing items that players can redeem")]
18 class Kits : RustPlugin
19 {
20 #region Fields
21 [PluginReference]
22 private Plugin CopyPaste, ImageLibrary, ServerRewards, Economics;
23
24 private DateTime _deprecatedHookTime = new DateTime(2021, 12, 31);
25
26 private Hash<ulong, KitData.Kit> _kitCreators = new Hash<ulong, KitData.Kit>();
27
28 private const string ADMIN_PERMISSION = "kits.admin";
29
30 private const string BLUEPRINT_BASE = "blueprintbase";
31 #endregion
32
33 #region Oxide Hooks
34 private void Loaded()
35 {
36 LoadData();
37
38 permission.RegisterPermission(ADMIN_PERMISSION, this);
39 kitData.RegisterPermissions(permission, this);
40
41 _costType = ParseType<CostType>(Configuration.Currency);
42
43 cmd.AddChatCommand(Configuration.Command, this, cmdKit);
44 cmd.AddConsoleCommand(Configuration.Command, this, "ccmdKit");
45 }
46
47 protected override void LoadDefaultMessages() => lang.RegisterMessages(Messages, this);
48
49 private void OnServerInitialized()
50 {
51 LastWipeTime = SaveRestore.SaveCreatedTime.Subtract(Epoch).TotalSeconds;
52
53 kitData.RegisterImages(ImageLibrary);
54
55 CheckForShortnameUpdates();
56
57 if (Configuration.AutoKits.Count == 0)
58 Unsubscribe(nameof(OnPlayerRespawned));
59 }
60
61 private void OnNewSave(string filename)
62 {
63 if (Configuration.WipeData)
64 playerData.Wipe();
65 }
66
67 private void OnServerSave() => SavePlayerData();
68
69 private void OnPlayerRespawned(BasePlayer player)
70 {
71 if (player == null)
72 return;
73
74 if ((Interface.Oxide.CallDeprecatedHook("canRedeemKit", "CanRedeemKit", _deprecatedHookTime, player) ?? Interface.Oxide.CallHook("CanRedeemKit", player)) != null)
75 return;
76
77 if (Configuration.AllowAutoToggle && !playerData[player.userID].ClaimAutoKits)
78 {
79 player.ChatMessage(Message("Error.AutoKitDisabled", player.userID));
80 return;
81 }
82
83 for (int i = 0; i < Configuration.AutoKits.Count; i++)
84 {
85 KitData.Kit kit;
86 if (!kitData.Find(Configuration.AutoKits[i], out kit))
87 continue;
88
89 object success = CanClaimKit(player, kit, true);
90 if (success != null)
91 continue;
92
93 player.inventory.Strip();
94
95 success = GiveKit(player, kit);
96 if (success is string)
97 continue;
98
99 OnKitReceived(player, kit);
100 return;
101 }
102 }
103
104 private void Unload()
105 {
106 if (!Interface.Oxide.IsShuttingDown)
107 SavePlayerData();
108
109 foreach (BasePlayer player in BasePlayer.activePlayerList)
110 {
111 CuiHelper.DestroyUi(player, UI_MENU);
112 CuiHelper.DestroyUi(player, UI_POPUP);
113 }
114
115 Configuration = null;
116 }
117 #endregion
118
119 #region Kit Claiming
120 private bool TryClaimKit(BasePlayer player, string name, bool usingUI)
121 {
122 if (string.IsNullOrEmpty(name))
123 {
124 if (usingUI)
125 CreateMenuPopup(player, Message("Error.EmptyKitName", player.userID));
126 else player.ChatMessage(Message("Error.EmptyKitName", player.userID));
127 return false;
128 }
129
130 KitData.Kit kit;
131 if (!kitData.Find(name, out kit))
132 {
133 if (usingUI)
134 CreateMenuPopup(player, Message("Error.InvalidKitName", player.userID));
135 else player.ChatMessage(Message("Error.InvalidKitName", player.userID));
136 return false;
137 }
138
139 object success = CanClaimKit(player, kit) ?? GiveKit(player, kit);
140 if (success is string)
141 {
142 if (usingUI)
143 CreateMenuPopup(player, (string)success);
144 else player.ChatMessage((string)success);
145 return false;
146 }
147
148 OnKitReceived(player, kit);
149 return true;
150 }
151
152 private object CanClaimKit(BasePlayer player, KitData.Kit kit, bool ignoreAuthCost = false)
153 {
154 object success = Interface.Oxide.CallDeprecatedHook("canRedeemKit", "CanRedeemKit", _deprecatedHookTime, player) ?? Interface.Oxide.CallHook("CanRedeemKit", player);
155 if (success != null)
156 {
157 if (success is string)
158 return (string)success;
159 return Message("Error.CantClaimNow", player.userID);
160 }
161
162 if (!ignoreAuthCost && kit.RequiredAuth > 0 && player.net.connection.authLevel < kit.RequiredAuth)
163 return Message("Error.CanClaim.Auth", player.userID);
164
165 if (Configuration.AdminIgnoreRestrictions && IsAdmin(player))
166 {
167 if (!kit.HasSpaceForItems(player))
168 return Message("Error.CanClaim.InventorySpace", player.userID);
169
170 return null;
171 }
172
173 if (!string.IsNullOrEmpty(kit.RequiredPermission) && !permission.UserHasPermission(player.UserIDString, kit.RequiredPermission))
174 return Message("Error.CanClaim.Permission", player.userID);
175
176 int wipeCooldownTime;
177 if (Configuration.WipeCooldowns.TryGetValue(kit.Name, out wipeCooldownTime))
178 {
179 if (kitData.IsOnWipeCooldown(wipeCooldownTime, out wipeCooldownTime))
180 return string.Format(Message("Error.CanClaim.WipeCooldown", player.userID), FormatTime(wipeCooldownTime));
181 }
182
183 PlayerData.PlayerUsageData playerUsageData;
184 if (playerData.Find(player.userID, out playerUsageData))
185 {
186 if (kit.Cooldown > 0)
187 {
188 double cooldownRemaining = playerUsageData.GetCooldownRemaining(kit.Name);
189 if (cooldownRemaining > 0)
190 return string.Format(Message("Error.CanClaim.Cooldown", player.userID), FormatTime(cooldownRemaining));
191 }
192
193 if (kit.MaximumUses > 0)
194 {
195 int currentUses = playerUsageData.GetKitUses(kit.Name);
196 if (currentUses >= kit.MaximumUses)
197 return Message("Error.CanClaim.MaxUses", player.userID);
198 }
199 }
200
201 if (!kit.HasSpaceForItems(player))
202 return Message("Error.CanClaim.InventorySpace", player.userID);
203
204 if (!ignoreAuthCost && kit.Cost > 0)
205 {
206 if (!ChargePlayer(player, kit.Cost))
207 return string.Format(Message("Error.CanClaim.InsufficientFunds", player.userID), kit.Cost, Message($"Cost.{_costType}", player.userID));
208 }
209
210 return null;
211 }
212
213 private object GiveKit(BasePlayer player, KitData.Kit kit)
214 {
215 if (!string.IsNullOrEmpty(kit.CopyPasteFile))
216 {
217 object success = CopyPaste?.CallHook("TryPasteFromSteamId", player.userID, kit.CopyPasteFile, Configuration.CopyPasteParams, null);
218 if (success != null)
219 return success;
220 }
221
222 kit.GiveItemsTo(player);
223
224 return true;
225 }
226
227 private void OnKitReceived(BasePlayer player, KitData.Kit kit)
228 {
229 playerData[player.userID].OnKitClaimed(kit);
230
231 Interface.CallHook("OnKitRedeemed", player, kit.Name);
232
233 if (Configuration.LogKitsGiven)
234 LogToFile("Kits_Received", $"{player.displayName} ({player.userID}) - Received {kit.Name}", this);
235 }
236 #endregion
237
238 #region Purchase Costs
239 private CostType _costType;
240
241 private enum CostType { Scrap, ServerRewards, Economics }
242
243 private const int SCRAP_ITEM_ID = -932201673;
244
245 private bool ChargePlayer(BasePlayer player, int amount)
246 {
247 if (amount == 0)
248 return true;
249
250 switch (_costType)
251 {
252 case CostType.Scrap:
253 if (amount <= player.inventory.GetAmount(SCRAP_ITEM_ID))
254 {
255 player.inventory.Take(null, SCRAP_ITEM_ID, amount);
256 return true;
257 }
258 return false;
259 case CostType.ServerRewards:
260 {
261 if ((ServerRewards?.Call<int>("CheckPoints", player.UserIDString) ?? 0) < amount)
262 return false;
263
264 return (bool)ServerRewards?.Call("TakePoints", player.userID, amount);
265 }
266 case CostType.Economics:
267 return (bool)Economics?.Call("Withdraw", player.UserIDString, (double)amount);
268 }
269 return false;
270 }
271 #endregion
272
273 #region Helpers
274 private T ParseType<T>(string type)
275 {
276 try
277 {
278 return (T)Enum.Parse(typeof(T), type, true);
279 }
280 catch
281 {
282 return default(T);
283 }
284 }
285
286 private string FormatTime(double time)
287 {
288 TimeSpan dateDifference = TimeSpan.FromSeconds(time);
289 int days = dateDifference.Days;
290 int hours = dateDifference.Hours;
291 int mins = dateDifference.Minutes;
292 int secs = dateDifference.Seconds;
293
294 if (days > 0)
295 return string.Format("~{0:00}d:{1:00}h", days, hours);
296 else if (hours > 0)
297 return string.Format("~{0:00}h:{1:00}m", hours, mins, secs);
298 else if (mins > 0)
299 return string.Format("{0:00}m:{1:00}s", mins, secs);
300 else return string.Format("{0}s", secs);
301 }
302
303 private void GetUserValidKits(BasePlayer player, List<KitData.Kit> list, ulong npcId = 0UL)
304 {
305 bool isAdmin = IsAdmin(player);
306 bool viewPermissionKits = Configuration.ShowPermissionKits;
307
308 if (npcId != 0UL)
309 {
310 ConfigData.NPCKit npcKit;
311 if (Configuration.NPCKitMenu.TryGetValue(npcId, out npcKit))
312 {
313 npcKit.Kits.ForEach((string kitName) =>
314 {
315 KitData.Kit kit;
316 if (kitData.Find(kitName, out kit))
317 {
318 if (!viewPermissionKits && !string.IsNullOrEmpty(kit.RequiredPermission) && !permission.UserHasPermission(player.UserIDString, kit.RequiredPermission) && !isAdmin)
319 return;
320
321 if (player.net.connection.authLevel < kit.RequiredAuth)
322 return;
323
324 list.Add(kit);
325 }
326 });
327 }
328 }
329 else
330 {
331 kitData.ForEach((KitData.Kit kit) =>
332 {
333 if (kit.IsHidden && !isAdmin)
334 return;
335
336 if (!viewPermissionKits && !string.IsNullOrEmpty(kit.RequiredPermission) && !permission.UserHasPermission(player.UserIDString, kit.RequiredPermission) && !isAdmin)
337 return;
338
339 if (player.net.connection.authLevel < kit.RequiredAuth)
340 return;
341
342 list.Add(kit);
343 });
344 }
345 }
346
347 private bool IsAdmin(BasePlayer player) => permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION);
348
349 private BasePlayer FindPlayer(string partialNameOrID) => BasePlayer.allPlayerList.FirstOrDefault<BasePlayer>((BasePlayer x) => x.UserIDString.Equals(partialNameOrID) ||
350 x.displayName.Equals(partialNameOrID, StringComparison.OrdinalIgnoreCase) ||
351 x.displayName.Contains(partialNameOrID, CompareOptions.OrdinalIgnoreCase));
352
353 private BasePlayer RaycastPlayer(BasePlayer player)
354 {
355 RaycastHit raycastHit;
356 if (!Physics.Raycast(new Ray(player.eyes.position, Quaternion.Euler(player.serverInput.current.aimAngles) * Vector3.forward), out raycastHit, 5f))
357 return null;
358
359 return raycastHit.collider.GetComponentInParent<BasePlayer>();
360 }
361
362 private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0);
363
364 private static double LastWipeTime;
365
366 private static double CurrentTime => DateTime.UtcNow.Subtract(Epoch).TotalSeconds;
367 #endregion
368
369 #region ImageLibrary
370 private void RegisterImage(string name, string url) => ImageLibrary?.Call("AddImage", url, name.Replace(" ", ""), 0UL, null);
371
372 private string GetImage(string name, ulong skinId = 0UL) => ImageLibrary?.Call<string>("GetImage", name.Replace(" ", ""), skinId, false);
373 #endregion
374
375 #region HumanNPC
376 private void OnUseNPC(BasePlayer npcPlayer, BasePlayer player)
377 {
378 if (Configuration.NPCKitMenu.ContainsKey(npcPlayer.userID))
379 OpenKitGrid(player, 0, npcPlayer.userID);
380 }
381 #endregion
382
383 #region Deprecated API
384 [HookMethod("isKit")]
385 public bool isKit(string name) => IsKit(name);
386
387 [HookMethod("GetAllKits")]
388 public string[] GetAllKits() => kitData.Keys.ToArray();
389
390 [HookMethod("KitImage")]
391 public string KitImage(string name) => GetKitImage(name);
392
393 [HookMethod("KitDescription")]
394 public string KitDescription(string name) => GetKitDescription(name);
395
396 [HookMethod("KitMax")]
397 public int KitMax(string name) => GetKitMaxUses(name);
398
399 [HookMethod("KitCooldown")]
400 public double KitCooldown(string name) => (double)GetKitCooldown(name);
401
402 [HookMethod("PlayerKitMax")]
403 public int PlayerKitMax(ulong playerId, string name) => GetPlayerKitUses(playerId, name);
404
405 [HookMethod("PlayerKitCooldown")]
406 public double PlayerKitCooldown(ulong playerId, string name) => GetPlayerKitCooldown(playerId, name);
407
408 [HookMethod("GetKitContents")]
409 public string[] GetKitContents(string name)
410 {
411 KitData.Kit kit;
412 if (kitData.Find(name, out kit))
413 {
414 List<string> items = Facepunch.Pool.GetList<string>();
415 for (int i1 = 0; i1 < kit.BeltItems.Length; i1++)
416 {
417 ItemData itemData = kit.BeltItems[i1];
418 string itemstring = $"{itemData.ItemID}_{itemData.Amount}";
419
420 for (int i2 = 0; i2 < itemData.Contents?.Length; i2++)
421 itemstring = itemstring + $"_{itemData.Contents[i2].ItemID}";
422
423 items.Add(itemstring);
424 }
425
426 for (int i1 = 0; i1 < kit.WearItems.Length; i1++)
427 {
428 ItemData itemData = kit.WearItems[i1];
429 string itemstring = $"{itemData.ItemID}_{itemData.Amount}";
430
431 for (int i2 = 0; i2 < itemData.Contents?.Length; i2++)
432 itemstring = itemstring + $"_{itemData.Contents[i2].ItemID}";
433
434 items.Add(itemstring);
435 }
436
437 for (int i1 = 0; i1 < kit.MainItems.Length; i1++)
438 {
439 ItemData itemData = kit.MainItems[i1];
440 string itemstring = $"{itemData.ItemID}_{itemData.Amount}";
441
442 for (int i2 = 0; i2 < itemData.Contents?.Length; i2++)
443 itemstring = itemstring + $"_{itemData.Contents[i2].ItemID}";
444
445 items.Add(itemstring);
446 }
447
448 string[] array = items.ToArray();
449 Facepunch.Pool.FreeList(ref items);
450
451 return array;
452 }
453
454 return null;
455 }
456
457 [HookMethod("GetKitInfo")]
458 public object GetKitInfo(string name)
459 {
460 KitData.Kit kit;
461 if (kitData.Find(name, out kit))
462 {
463 JObject obj = new JObject
464 {
465 ["name"] = kit.Name,
466 ["permission"] = kit.RequiredPermission,
467 ["max"] = kit.MaximumUses,
468 ["image"] = kit.KitImage,
469 ["hide"] = kit.IsHidden,
470 ["description"] = kit.Description,
471 ["cooldown"] = kit.Cooldown,
472 ["building"] = kit.CopyPasteFile,
473 ["authlevel"] = kit.RequiredAuth
474 };
475
476 JArray array = new JArray();
477 GetItemObject_Old(ref array, kit.BeltItems, "belt");
478 GetItemObject_Old(ref array, kit.MainItems, "main");
479 GetItemObject_Old(ref array, kit.WearItems, "wear");
480
481 obj["items"] = array;
482 return obj;
483 }
484
485 return null;
486 }
487
488 private void GetItemObject_Old(ref JArray array, ItemData[] items, string container)
489 {
490 for (int i = 0; i < items.Length; i++)
491 {
492 ItemData itemData = items[i];
493 JObject item = new JObject
494 {
495 ["amount"] = itemData.Amount,
496 ["container"] = container,
497 ["itemid"] = itemData.ItemID,
498 ["skinid"] = itemData.Skin,
499 ["weapon"] = !string.IsNullOrEmpty(itemData.Ammotype),
500 ["blueprint"] = itemData.BlueprintItemID
501 };
502
503 item["mods"] = new JArray();
504 for (int i1 = 0; i1 < itemData.Contents?.Length; i1++)
505 (item["mods"] as JArray).Add(itemData.Contents[i1].ItemID);
506
507 array.Add(item);
508 }
509 }
510 #endregion
511
512 #region API
513 [HookMethod("GiveKit")]
514 public object GiveKit(BasePlayer player, string name)
515 {
516 if (player == null)
517 return null;
518
519 if (string.IsNullOrEmpty(name))
520 return Message("Error.EmptyKitName", player.userID);
521
522 KitData.Kit kit;
523 if (!kitData.Find(name, out kit))
524 return Message("Error.InvalidKitName", player.userID);
525
526 return GiveKit(player, kit);
527 }
528
529 [HookMethod("IsKit")]
530 public bool IsKit(string name) => !string.IsNullOrEmpty(name) ? kitData.Exists(name) : false;
531
532 [HookMethod("GetKitNames")]
533 public void GetKitNames(List<string> list) => list.AddRange(kitData.Keys);
534
535 [HookMethod("GetKitImage")]
536 public string GetKitImage(string name) => kitData[name]?.KitImage ?? string.Empty;
537
538 [HookMethod("GetKitDescription")]
539 public string GetKitDescription(string name) => kitData[name]?.Description ?? string.Empty;
540
541 [HookMethod("GetKitMaxUses")]
542 public int GetKitMaxUses(string name) => kitData[name]?.MaximumUses ?? 0;
543
544 [HookMethod("GetKitCooldown")]
545 public int GetKitCooldown(string name) => kitData[name]?.Cooldown ?? 0;
546
547 [HookMethod("GetPlayerKitUses")]
548 public int GetPlayerKitUses(ulong playerId, string name) => playerData.Exists(playerId) ? playerData[playerId].GetKitUses(name) : 0;
549
550 [HookMethod("SetPlayerKitUses")]
551 public void SetPlayerKitUses(ulong playerId, string name, int amount)
552 {
553 if (playerData.Exists(playerId))
554 playerData[playerId].SetKitUses(name, amount);
555 }
556
557 [HookMethod("GetPlayerKitCooldown")]
558 public double GetPlayerKitCooldown(ulong playerId, string name) => playerData.Exists(playerId) ? playerData[playerId].GetCooldownRemaining(name) : 0;
559
560 [HookMethod("SetPlayerKitCooldown")]
561 public void SetPlayerCooldown(ulong playerId, string name, double seconds)
562 {
563 if (playerData.Exists(playerId))
564 playerData[playerId].SetCooldownRemaining(name, seconds);
565 }
566
567 [HookMethod("GetKitObject")]
568 public JObject GetKitObject(string name)
569 {
570 KitData.Kit kit;
571 if (!kitData.Find(name, out kit))
572 return null;
573
574 return kit.ToJObject;
575 }
576
577 [HookMethod("CreateKitItems")]
578 public IEnumerable<Item> CreateKitItems(string name)
579 {
580 KitData.Kit kit;
581 if (!kitData.Find(name, out kit))
582 yield break;
583 foreach (var item in kit.CreateItems())
584 yield return item;
585 }
586 #endregion
587
588 #region UI
589 private const string UI_MENU = "kits.menu";
590 private const string UI_POPUP = "kits.popup";
591
592 private const string DEFAULT_ICON = "kits.defaultkiticon";
593 private const string MAGNIFY_ICON = "kits.magnifyicon";
594
595 #region Kit Grid View
596 private void OpenKitGrid(BasePlayer player, int page = 0, ulong npcId = 0UL)
597 {
598 CuiElementContainer container = UI.Container(UI_MENU, "0 0 0 0.9", new UI4(0.2f, 0.15f, 0.8f, 0.85f), true, "Hud");
599
600 UI.Panel(container, UI_MENU, Configuration.Menu.Panel.Get, new UI4(0.005f, 0.93f, 0.995f, 0.99f));
601
602 UI.Label(container, UI_MENU, Message("UI.Title", player.userID), 20, new UI4(0.015f, 0.93f, 0.99f, 0.99f), TextAnchor.MiddleLeft);
603
604 UI.Button(container, UI_MENU, Configuration.Menu.Color3.Get, "<b>×</b>", 20, new UI4(0.9575f, 0.9375f, 0.99f, 0.9825f), "kits.close");
605
606 if (IsAdmin(player) && npcId == 0UL)
607 UI.Button(container, UI_MENU, Configuration.Menu.Color2.Get, Message("UI.CreateNew", player.userID), 14, new UI4(0.85f, 0.9375f, 0.9525f, 0.9825f), "kits.create");
608
609 CreateGridView(player, container, page, npcId);
610
611 CuiHelper.DestroyUi(player, UI_MENU);
612 CuiHelper.AddUi(player, container);
613 }
614
615 private void CreateGridView(BasePlayer player, CuiElementContainer container, int page = 0, ulong npcId = 0UL)
616 {
617 List<KitData.Kit> list = Facepunch.Pool.GetList<KitData.Kit>();
618
619 GetUserValidKits(player, list, npcId);
620
621 if (list.Count == 0)
622 {
623 UI.Label(container, UI_MENU, Message("UI.NoKitsAvailable", player.userID), 14, new UI4(0.015f, 0.88f, 0.99f, 0.92f), TextAnchor.MiddleLeft);
624 return;
625 }
626
627 PlayerData.PlayerUsageData playerUsageData = playerData[player.userID];
628
629 int max = Mathf.Min(list.Count, (page + 1) * 8);
630 int count = 0;
631 for (int i = page * 8; i < max; i++)
632 {
633 CreateKitEntry(player, playerUsageData, container, list[i], count, page, npcId);
634 count += 1;
635 }
636
637 if (page > 0)
638 UI.Button(container, UI_MENU, Configuration.Menu.Color1.Get, "◀\n\n◀\n\n◀", 16, new UI4(0.005f, 0.35f, 0.03f, 0.58f), $"kits.gridview page {page - 1} {npcId}");
639 if (max < list.Count)
640 UI.Button(container, UI_MENU, Configuration.Menu.Color1.Get, "▶\n\n▶\n\n▶", 16, new UI4(0.97f, 0.35f, 0.995f, 0.58f), $"kits.gridview page {page + 1} {npcId}");
641
642 Facepunch.Pool.FreeList(ref list);
643 }
644
645 private void CreateKitEntry(BasePlayer player, PlayerData.PlayerUsageData playerUsageData, CuiElementContainer container, KitData.Kit kit, int index, int page, ulong npcId)
646 {
647 UI4 position = KitAlign.Get(index);
648
649 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(position.xMin, position.yMax, position.xMax, position.yMax + 0.04f));
650 UI.Label(container, UI_MENU, kit.Name, 14, new UI4(position.xMin, position.yMax, position.xMax, position.yMax + 0.04f));
651
652 UI.Panel(container, UI_MENU, Configuration.Menu.Panel.Get, position);
653
654 string imageId = string.IsNullOrEmpty(kit.KitImage) ? GetImage(DEFAULT_ICON) : GetImage(kit.Name);
655 UI.Image(container, UI_MENU, imageId, new UI4(position.xMin + 0.005f, position.yMax - 0.3f, position.xMax - 0.005f, position.yMax - 0.0075f));
656
657 UI.Button(container, UI_MENU, "0 0 0 0", string.Empty, 0, new UI4(position.xMin + 0.005f, position.yMax - 0.3f, position.xMax - 0.005f, position.yMax - 0.0075f), $"kits.gridview inspect {CommandSafe(kit.Name)} {page} {npcId}");
658
659 string buttonText;
660 string buttonCommand = string.Empty;
661 string buttonColor;
662
663 double cooldown = playerUsageData.GetCooldownRemaining(kit.Name);
664 int currentUses = playerUsageData.GetKitUses(kit.Name);
665
666 if (Configuration.AdminIgnoreRestrictions && IsAdmin(player))
667 {
668 buttonText = Message("UI.Redeem", player.userID);
669 buttonColor = Configuration.Menu.Color2.Get;
670 buttonCommand = $"kits.gridview redeem {CommandSafe(kit.Name)} {page} {npcId}";
671 }
672 else
673 {
674 if (!string.IsNullOrEmpty(kit.RequiredPermission) && !permission.UserHasPermission(player.UserIDString, kit.RequiredPermission))
675 {
676 buttonText = Message("UI.NeedsPermission", player.userID);
677 buttonColor = Configuration.Menu.Disabled.Get;
678 }
679 else if (kit.Cooldown > 0 && cooldown > 0)
680 {
681 UI.Label(container, UI_MENU, string.Format(Message("UI.Cooldown", player.userID), FormatTime(cooldown)), 12,
682 new UI4(position.xMin + 0.005f, position.yMin + 0.0475f, position.xMax - 0.005f, position.yMax - 0.3f), TextAnchor.MiddleLeft);
683
684 buttonText = Message("UI.OnCooldown", player.userID);
685 buttonColor = Configuration.Menu.Disabled.Get;
686 }
687 else if (kit.MaximumUses > 0 && currentUses >= kit.MaximumUses)
688 {
689 buttonText = Message("UI.MaximumUses", player.userID);
690 buttonColor = Configuration.Menu.Disabled.Get;
691 }
692 else if (kit.Cost > 0)
693 {
694 UI.Label(container, UI_MENU, string.Format(Message("UI.Cost", player.userID), kit.Cost, Message($"Cost.{_costType}", player.userID)), 12,
695 new UI4(position.xMin + 0.005f, position.yMin + 0.0475f, position.xMax - 0.005f, position.yMax - 0.3f), TextAnchor.MiddleLeft);
696
697 buttonText = Message("UI.Purchase", player.userID);
698 buttonColor = Configuration.Menu.Color2.Get;
699 buttonCommand = $"kits.gridview redeem {CommandSafe(kit.Name)} {page} {npcId}";
700 }
701 else
702 {
703 buttonText = Message("UI.Redeem", player.userID);
704 buttonColor = Configuration.Menu.Color2.Get;
705 buttonCommand = $"kits.gridview redeem {CommandSafe(kit.Name)} {page} {npcId}";
706 }
707 }
708
709 UI.Button(container, UI_MENU, buttonColor, buttonText, 14,
710 new UI4(position.xMin + 0.038f, position.yMin + 0.0075f, position.xMax - 0.005f, position.yMin + 0.0475f), buttonCommand);
711
712 UI.Button(container, UI_MENU, ICON_BACKGROUND_COLOR, GetImage(MAGNIFY_ICON),
713 new UI4(position.xMin + 0.005f, position.yMin + 0.0075f, position.xMin + 0.033f, position.yMin + 0.0475f), $"kits.gridview inspect {CommandSafe(kit.Name)} {page} {npcId}");
714 }
715 #endregion
716
717 #region Kit View
718 private void OpenKitView(BasePlayer player, string name, int page, ulong npcId)
719 {
720 KitData.Kit kit;
721 if (!kitData.Find(name, out kit))
722 {
723 OpenKitGrid(player);
724 return;
725 }
726
727 CuiElementContainer container = UI.Container(UI_MENU, "0 0 0 0.9", new UI4(0.2f, 0.15f, 0.8f, 0.85f), true, "Hud");
728
729 UI.Panel(container, UI_MENU, Configuration.Menu.Panel.Get, new UI4(0.005f, 0.93f, 0.995f, 0.99f));
730
731 UI.Label(container, UI_MENU, $"{Message("UI.Title", player.userID)} - {name}", 20, new UI4(0.015f, 0.93f, 0.99f, 0.99f), TextAnchor.MiddleLeft);
732
733 UI.Button(container, UI_MENU, Configuration.Menu.Color3.Get, "<b>×</b>", 20, new UI4(0.9575f, 0.9375f, 0.99f, 0.9825f), $"kits.gridview page 0 {npcId}");
734
735 bool isAdmin;
736 if (isAdmin = IsAdmin(player))
737 {
738 UI.Button(container, UI_MENU, Configuration.Menu.Color2.Get, Message("UI.EditKit", player.userID), 14, new UI4(0.7525f, 0.9375f, 0.845f, 0.9825f), $"kits.edit {CommandSafe(name)}");
739 UI.Button(container, UI_MENU, Configuration.Menu.Color2.Get, Message("UI.CreateNew", player.userID), 14, new UI4(0.85f, 0.9375f, 0.9525f, 0.9825f), "kits.create");
740 }
741
742 PlayerData.PlayerUsageData playerUsageData = playerData[player.userID];
743
744 int i = -1;
745
746 if (!string.IsNullOrEmpty(kit.KitImage))
747 {
748 UI.Image(container, UI_MENU, GetImage(kit.Name), new UI4(0.15f, 0.62f, 0.35f, 0.92f));
749 i = 6;
750 }
751
752 AddTitleSperator(container, i += 1, Message("UI.Details", player.userID));
753 AddLabelField(container, i += 1, Message("UI.Name", player.userID), kit.Name);
754
755 if (!string.IsNullOrEmpty(kit.Description))
756 {
757 int descriptionSlots = Mathf.Min(Mathf.CeilToInt(((float)kit.Description.Length / 38f) / 1.25f), 4);
758 AddLabelField(container, i += 1, Message("UI.Description", player.userID), kit.Description, descriptionSlots - 1);
759 i += descriptionSlots - 1;
760 }
761
762 string buttonText = string.Empty;
763 string buttonCommand = string.Empty;
764 string buttonColor = string.Empty;
765
766 if (kit.Cooldown != 0 || kit.MaximumUses != 0 || kit.Cost != 0)
767 {
768 AddTitleSperator(container, i += 1, Message("UI.Usage", player.userID));
769
770 if (kit.MaximumUses != 0)
771 {
772 int playerUses = playerUsageData.GetKitUses(kit.Name);
773
774 AddLabelField(container, i += 1, Message("UI.MaxUses", player.userID), kit.MaximumUses.ToString());
775 AddLabelField(container, i += 1, Message("UI.YourUses", player.userID), playerUses.ToString());
776
777 if (playerUses >= kit.MaximumUses)
778 {
779 buttonText = Message("UI.MaximumUses", player.userID);
780 buttonColor = Configuration.Menu.Disabled.Get;
781 }
782 }
783 if (kit.Cooldown != 0)
784 {
785 double cooldownRemaining = playerUsageData.GetCooldownRemaining(kit.Name);
786
787 AddLabelField(container, i += 1, Message("UI.CooldownTime", player.userID), FormatTime(kit.Cooldown));
788 AddLabelField(container, i += 1, Message("UI.CooldownRemaining", player.userID), cooldownRemaining == 0 ? Message("UI.None", player.userID) :
789 FormatTime(cooldownRemaining));
790
791 if (string.IsNullOrEmpty(buttonText) && cooldownRemaining > 0)
792 {
793 buttonText = Message("UI.OnCooldown", player.userID);
794 buttonColor = Configuration.Menu.Disabled.Get;
795 }
796 }
797 if (kit.Cost != 0)
798 {
799 AddLabelField(container, i += 1, Message("UI.PurchaseCost", player.userID), $"{kit.Cost} {(Message($"Cost.{_costType}", player.userID))}");
800
801 if (string.IsNullOrEmpty(buttonText))
802 {
803 buttonText = Message("UI.Purchase", player.userID);
804 buttonColor = Configuration.Menu.Color2.Get;
805 buttonCommand = $"kits.gridview redeem {CommandSafe(kit.Name)} {page} {npcId}";
806 }
807 }
808 }
809
810 if (!string.IsNullOrEmpty(kit.RequiredPermission) && !permission.UserHasPermission(player.UserIDString, kit.RequiredPermission))
811 {
812 buttonText = Message("UI.NeedsPermission", player.userID);
813 buttonColor = Configuration.Menu.Disabled.Get;
814 }
815
816 if (i <= 16 && !string.IsNullOrEmpty(kit.CopyPasteFile))
817 {
818 AddTitleSperator(container, i += 1, Message("UI.CopyPaste", player.userID));
819 AddLabelField(container, i += 1, Message("UI.FileName", player.userID), kit.CopyPasteFile);
820 }
821
822 if ((Configuration.AdminIgnoreRestrictions && isAdmin) || string.IsNullOrEmpty(buttonText))
823 {
824 buttonText = Message("UI.Redeem", player.userID);
825 buttonCommand = $"kits.gridview redeem {CommandSafe(kit.Name)} {page} {npcId}";
826 buttonColor = Configuration.Menu.Color2.Get;
827 }
828
829 CreateKitLayout(player, container, kit);
830
831 UI.Button(container, UI_MENU, buttonColor, buttonText, 14, new UI4(0.005f, 0.005f, 0.495f, 0.045f), buttonCommand);
832
833 CuiHelper.DestroyUi(player, UI_MENU);
834 CuiHelper.AddUi(player, container);
835 }
836 #endregion
837
838 #region Kit Layout
839 private const string ICON_BACKGROUND_COLOR = "1 1 1 0.15";
840
841 private void CreateKitLayout(BasePlayer player, CuiElementContainer container, KitData.Kit kit)
842 {
843 UI.Panel(container, UI_MENU, Configuration.Menu.Color1.Get, new UI4(0.505f, 0.88f, 0.995f, 0.92f));
844 UI.Label(container, UI_MENU, Message("UI.KitItems", player.userID), 14, new UI4(0.51f, 0.88f, 0.995f, 0.92f), TextAnchor.MiddleLeft);
845
846 // Main Items
847 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(0.505f, 0.835f, 0.995f, 0.875f));
848 UI.Label(container, UI_MENU, Message("UI.MainItems", player.userID), 14, new UI4(0.51f, 0.835f, 0.995f, 0.875f), TextAnchor.MiddleLeft);
849 CreateInventoryItems(container, MainAlign, kit.MainItems, 24);
850
851 // Wear Items
852 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(0.505f, 0.365f, 0.995f, 0.405f));
853 UI.Label(container, UI_MENU, Message("UI.WearItems", player.userID), 14, new UI4(0.51f, 0.365f, 0.995f, 0.405f), TextAnchor.MiddleLeft);
854 CreateInventoryItems(container, WearAlign, kit.WearItems, 7);
855
856 // Belt Items
857 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(0.505f, 0.21f, 0.995f, 0.25f));
858 UI.Label(container, UI_MENU, Message("UI.BeltItems", player.userID), 14, new UI4(0.51f, 0.21f, 0.995f, 0.25f), TextAnchor.MiddleLeft);
859 CreateInventoryItems(container, BeltAlign, kit.BeltItems, 6);
860 }
861
862 #region Item Layout Helpers
863 private void CreateInventoryItems(CuiElementContainer container, GridAlignment alignment, ItemData[] items, int capacity)
864 {
865 for (int i = 0; i < capacity; i++)
866 UI.Panel(container, UI_MENU, ICON_BACKGROUND_COLOR, alignment.Get(i));
867
868 for (int i = 0; i < items.Length; i++)
869 {
870 ItemData itemData = items[i];
871 if (itemData.Position > capacity - 1)
872 continue;
873
874 UI4 position = alignment.Get(itemData.Position);
875
876 UI.Image(container, UI_MENU, itemData.ItemID, itemData.Skin /*GetImage(itemData.Shortname, itemData.Skin)*/, position);
877
878 if (itemData.IsBlueprint && !string.IsNullOrEmpty(itemData.BlueprintShortname))
879 UI.Image(container, UI_MENU, itemData.BlueprintItemID, 0UL /*GetImage(itemData.BlueprintShortname, 0UL)*/, position);
880
881 if (itemData.Amount > 1)
882 UI.Label(container, UI_MENU, $"x{itemData.Amount}", 10, position, TextAnchor.LowerRight);
883 }
884 }
885 #endregion
886 #endregion
887
888 #region Kit Editor
889 private void OpenKitsEditor(BasePlayer player, bool overwrite = false)
890 {
891 KitData.Kit kit;
892 if (!_kitCreators.TryGetValue(player.userID, out kit))
893 return;
894
895 CuiElementContainer container = UI.Container(UI_MENU, "0 0 0 0.9", new UI4(0.2f, 0.15f, 0.8f, 0.85f), true, "Hud");
896
897 UI.Panel(container, UI_MENU, Configuration.Menu.Panel.Get, new UI4(0.005f, 0.93f, 0.995f, 0.99f));
898
899 UI.Label(container, UI_MENU, Message("UI.Title.Editor", player.userID), 20, new UI4(0.015f, 0.93f, 0.99f, 0.99f), TextAnchor.MiddleLeft);
900
901 UI.Button(container, UI_MENU, Configuration.Menu.Color3.Get, "<b>×</b>", 20, new UI4(0.9575f, 0.9375f, 0.99f, 0.9825f), "kits.close");
902
903 // Kit Options
904 AddTitleSperator(container, 0, Message("UI.Details", player.userID));
905 AddInputField(container, 1, Message("UI.Name", player.userID), "name", kit.Name);
906 AddInputField(container, 2, Message("UI.Description", player.userID), "description", kit.Description, 3);
907 AddInputField(container, 6, Message("UI.IconURL", player.userID), "image", kit.KitImage);
908
909 AddTitleSperator(container, 7, Message("UI.UsageAuthority", player.userID));
910 AddInputField(container, 8, Message("UI.Permission", player.userID), "permission", kit.RequiredPermission);
911 AddInputField(container, 9, Message("UI.AuthLevel", player.userID), "authLevel", kit.RequiredAuth);
912 AddToggleField(container, 10, Message("UI.IsHidden", player.userID), "isHidden", kit.IsHidden);
913
914 AddTitleSperator(container, 11, Message("UI.Usage", player.userID));
915 AddInputField(container, 12, Message("UI.MaxUses", player.userID), "maximumUses", kit.MaximumUses);
916 AddInputField(container, 13, Message("UI.CooldownSeconds", player.userID), "cooldown", kit.Cooldown);
917 AddInputField(container, 14, Message("UI.PurchaseCost", player.userID), "cost", kit.Cost);
918
919 AddTitleSperator(container, 15, Message("UI.CopyPaste", player.userID));
920 AddInputField(container, 16, Message("UI.FileName", player.userID), "copyPaste", kit.CopyPasteFile);
921
922 // Kit Items
923 CreateKitLayout(player, container, kit);
924
925 // Kit Saving
926 UI.Button(container, UI_MENU, Configuration.Menu.Color2.Get, Message("UI.SaveKit", player.userID), 14, new UI4(0.005f, 0.005f, 0.2475f, 0.045f), $"kits.savekit {overwrite}");
927 UI.Toggle(container, UI_MENU, ICON_BACKGROUND_COLOR, 14, new UI4(0.2525f, 0.005f, 0.2825f, 0.045f), $"kits.toggleoverwrite {overwrite}", overwrite);
928 UI.Label(container, UI_MENU, Message("UI.Overwrite", player.userID), 14, new UI4(0.2875f, 0.005f, 0.495f, 0.045f), TextAnchor.MiddleLeft);
929
930 // Item Management
931 UI.Button(container, UI_MENU, Configuration.Menu.Color3.Get, Message("UI.ClearItems", player.userID), 14, new UI4(0.505f, 0.005f, 0.7475f, 0.045f), $"kits.clearitems {overwrite}");
932 UI.Button(container, UI_MENU, Configuration.Menu.Color2.Get, Message("UI.CopyInv", player.userID), 14, new UI4(0.7525f, 0.005f, 0.995f, 0.045f), $"kits.copyinv {overwrite}");
933
934 CuiHelper.DestroyUi(player, UI_MENU);
935 CuiHelper.AddUi(player, container);
936 }
937
938 #region Editor Helpers
939 private const float EDITOR_ELEMENT_HEIGHT = 0.04f;
940
941 private void AddInputField(CuiElementContainer container, int index, string title, string fieldName, object currentValue, int additionalHeight = 0)
942 {
943 float yMin = GetVerticalPos(index, 0.88f);
944 float yMax = yMin + EDITOR_ELEMENT_HEIGHT;
945
946 if (additionalHeight != 0)
947 yMin = GetVerticalPos(index + additionalHeight, 0.88f);
948
949 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(0.005f, yMin, 0.175f, yMax));
950 UI.Label(container, UI_MENU, title, 12, new UI4(0.01f, yMin, 0.175f, yMax - 0.0075f), TextAnchor.UpperLeft);
951
952 UI.Panel(container, UI_MENU, ICON_BACKGROUND_COLOR, new UI4(0.175f, yMin, 0.495f, yMax));
953
954 string label = GetInputLabel(currentValue);
955 if (!string.IsNullOrEmpty(label))
956 {
957 UI.Label(container, UI_MENU, label, 12, new UI4(0.18f, yMin, 0.47f, yMax - 0.0075f), TextAnchor.UpperLeft);
958 UI.Button(container, UI_MENU, Configuration.Menu.Color3.Get, "X", 14, new UI4(0.47f, yMax - EDITOR_ELEMENT_HEIGHT, 0.495f, yMax), $"kits.clear {fieldName}");
959 }
960 else UI.Input(container, UI_MENU, string.Empty, 12, $"kits.creator {fieldName}", new UI4(0.18f, yMin, 0.495f, yMax - 0.0075f), TextAnchor.UpperLeft);
961 }
962
963 private void AddTitleSperator(CuiElementContainer container, int index, string title)
964 {
965 float yMin = GetVerticalPos(index, 0.88f);
966 float yMax = yMin + EDITOR_ELEMENT_HEIGHT;
967
968 UI.Panel(container, UI_MENU, Configuration.Menu.Color1.Get, new UI4(0.005f, yMin, 0.495f, yMax));
969 UI.Label(container, UI_MENU, title, 14, new UI4(0.01f, yMin, 0.495f, yMax), TextAnchor.MiddleLeft);
970 }
971
972 private void AddLabelField(CuiElementContainer container, int index, string title, string value, int additionalHeight = 0)
973 {
974 float yMin = GetVerticalPos(index, 0.88f);
975 float yMax = yMin + EDITOR_ELEMENT_HEIGHT;
976
977 if (additionalHeight != 0)
978 yMin = GetVerticalPos(index + additionalHeight, 0.88f);
979
980 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(0.005f, yMin, 0.175f, yMax));
981 UI.Label(container, UI_MENU, title, 12, new UI4(0.01f, yMin, 0.175f, yMax - 0.0075f), TextAnchor.UpperLeft);
982
983 UI.Panel(container, UI_MENU, ICON_BACKGROUND_COLOR, new UI4(0.175f, yMin, 0.495f, yMax));
984 UI.Label(container, UI_MENU, value, 12, new UI4(0.18f, yMin, 0.495f, yMax - 0.0075f), TextAnchor.UpperLeft);
985 }
986
987 private void AddToggleField(CuiElementContainer container, int index, string title, string fieldName, bool currentValue)
988 {
989 float yMin = GetVerticalPos(index, 0.88f);
990 float yMax = yMin + EDITOR_ELEMENT_HEIGHT;
991
992 UI.Panel(container, UI_MENU, Configuration.Menu.Color4.Get, new UI4(0.005f, yMin, 0.175f, yMax));
993 UI.Label(container, UI_MENU, title, 14, new UI4(0.01f, yMin, 0.175f, yMax), TextAnchor.MiddleLeft);
994 UI.Toggle(container, UI_MENU, ICON_BACKGROUND_COLOR, 14, new UI4(0.175f, yMin, 0.205f, yMax), $"kits.creator {fieldName} {!currentValue}", currentValue);
995 }
996
997 private string GetInputLabel(object obj)
998 {
999 if (obj is string)
1000 return string.IsNullOrEmpty(obj as string) ? null : obj.ToString();
1001 else if (obj is int)
1002 return (int)obj <= 0 ? null : obj.ToString();
1003 else if (obj is float)
1004 return (float)obj <= 0 ? null : obj.ToString();
1005 return null;
1006 }
1007
1008 private float GetVerticalPos(int i, float start = 0.9f) => start - (i * (EDITOR_ELEMENT_HEIGHT + 0.005f));
1009 #endregion
1010 #endregion
1011
1012 #region Popup Messages
1013 private void CreateMenuPopup(BasePlayer player, string text, float duration = 5f)
1014 {
1015 CuiElementContainer container = UI.Container(UI_POPUP, Configuration.Menu.Color4.Get, new UI4(0.2f, 0.11f, 0.8f, 0.15f));
1016 UI.Label(container, UI_POPUP, text, 14, UI4.Full);
1017
1018 CuiHelper.DestroyUi(player, UI_POPUP);
1019 CuiHelper.AddUi(player, container);
1020
1021 player.Invoke(() => CuiHelper.DestroyUi(player, UI_POPUP), duration);
1022 }
1023 #endregion
1024
1025 #region UI Grid Helper
1026 private readonly GridAlignment KitAlign = new GridAlignment(4, 0.04f, 0.2f, 0.04f, 0.87f, 0.39f, 0.06f);
1027
1028 private readonly GridAlignment MainAlign = new GridAlignment(6, 0.545f, 0.065f, 0.0035f, 0.8275f, 0.1f, 0.005f);
1029 private readonly GridAlignment WearAlign = new GridAlignment(7, 0.51f, 0.065f, 0.0035f, 0.3575f, 0.1f, 0.005f);
1030 private readonly GridAlignment BeltAlign = new GridAlignment(6, 0.545f, 0.065f, 0.0035f, 0.2025f, 0.1f, 0.005f);
1031
1032 private class GridAlignment
1033 {
1034 internal int Columns { get; set; }
1035 internal float XOffset { get; set; }
1036 internal float Width { get; set; }
1037 internal float XSpacing { get; set; }
1038 internal float YOffset { get; set; }
1039 internal float Height { get; set; }
1040 internal float YSpacing { get; set; }
1041
1042 internal GridAlignment(int columns, float xOffset, float width, float xSpacing, float yOffset, float height, float ySpacing)
1043 {
1044 Columns = columns;
1045 XOffset = xOffset;
1046 Width = width;
1047 XSpacing = xSpacing;
1048 YOffset = yOffset;
1049 Height = height;
1050 YSpacing = ySpacing;
1051 }
1052
1053 internal UI4 Get(int index)
1054 {
1055 int rowNumber = index == 0 ? 0 : Mathf.FloorToInt(index / Columns);
1056 int columnNumber = index - (rowNumber * Columns);
1057
1058 float offsetX = XOffset + (Width * columnNumber) + (XSpacing * columnNumber);
1059
1060 float offsetY = (YOffset - (rowNumber * Height) - (YSpacing * rowNumber));
1061
1062 return new UI4(offsetX, offsetY - Height, offsetX + Width, offsetY);
1063 }
1064 }
1065 #endregion
1066 #endregion
1067
1068 #region UI Commands
1069 #region View Commands
1070 [ConsoleCommand("kits.close")]
1071 private void ccmdKitsClose(ConsoleSystem.Arg arg)
1072 {
1073 BasePlayer player = arg.Connection.player as BasePlayer;
1074 if (player == null)
1075 return;
1076
1077 _kitCreators.Remove(player.userID);
1078
1079 CuiHelper.DestroyUi(player, UI_MENU);
1080 CuiHelper.DestroyUi(player, UI_POPUP);
1081 }
1082
1083 [ConsoleCommand("kits.gridview")]
1084 private void ccmdKitsGridView(ConsoleSystem.Arg arg)
1085 {
1086 BasePlayer player = arg.Connection.player as BasePlayer;
1087 if (player == null)
1088 return;
1089
1090 switch (arg.GetString(0).ToLower())
1091 {
1092 case "page":
1093 OpenKitGrid(player, arg.GetInt(1), arg.GetULong(2));
1094 return;
1095 case "inspect":
1096 OpenKitView(player, CommandSafe(arg.GetString(1), true), arg.GetInt(2), arg.GetULong(3));
1097 return;
1098 case "redeem":
1099 {
1100 string kit = CommandSafe(arg.GetString(1), true);
1101 if (TryClaimKit(player, kit, true))
1102 {
1103 CuiHelper.DestroyUi(player, UI_MENU);
1104 CuiHelper.DestroyUi(player, UI_POPUP);
1105 player.ChatMessage(string.Format(Message("Notification.KitReceived", player.userID), kit));
1106 }
1107 else OpenKitGrid(player, arg.GetInt(2), arg.GetULong(3));
1108 }
1109 return;
1110 default:
1111 break;
1112 }
1113 }
1114 #endregion
1115
1116 #region Editor Commands
1117 [ConsoleCommand("kits.create")]
1118 private void ccmdCreateKit(ConsoleSystem.Arg arg)
1119 {
1120 BasePlayer player = arg.Player();
1121 if (player == null)
1122 return;
1123
1124 if (IsAdmin(player))
1125 {
1126 _kitCreators[player.userID] = new KitData.Kit();
1127 OpenKitsEditor(player);
1128 }
1129 }
1130
1131 [ConsoleCommand("kits.edit")]
1132 private void ccmdEditKit(ConsoleSystem.Arg arg)
1133 {
1134 BasePlayer player = arg.Player();
1135 if (player == null)
1136 return;
1137
1138 if (IsAdmin(player))
1139 {
1140 string name = CommandSafe(arg.GetString(0), true);
1141
1142 KitData.Kit editKit;
1143 if (!kitData.Find(name, out editKit))
1144 {
1145 player.ChatMessage(string.Format(Message("Chat.Error.DoesntExist", player.userID), name));
1146 return;
1147 }
1148
1149 _kitCreators[player.userID] = KitData.Kit.CloneOf(editKit);
1150 OpenKitsEditor(player);
1151 }
1152 }
1153
1154 [ConsoleCommand("kits.savekit")]
1155 private void ccmdSaveKit(ConsoleSystem.Arg arg)
1156 {
1157 BasePlayer player = arg.Player();
1158 if (player == null)
1159 return;
1160
1161 KitData.Kit kit;
1162 if (!_kitCreators.TryGetValue(player.userID, out kit))
1163 return;
1164
1165 if (string.IsNullOrEmpty(kit.Name))
1166 {
1167 CreateMenuPopup(player, Message("SaveKit.Error.NoName", player.userID));
1168 return;
1169 }
1170
1171 if (kit.ItemCount == 0 && string.IsNullOrEmpty(kit.CopyPasteFile))
1172 {
1173 CreateMenuPopup(player, Message("SaveKit.Error.NoContents", player.userID));
1174 return;
1175 }
1176
1177 if (kitData.Exists(kit.Name) && !arg.GetBool(0))
1178 {
1179 CreateMenuPopup(player, Message("SaveKit.Error.Exists", player.userID));
1180 return;
1181 }
1182
1183 kitData[kit.Name] = kit;
1184 SaveKitData();
1185
1186 _kitCreators.Remove(player.userID);
1187
1188 if (!string.IsNullOrEmpty(kit.RequiredPermission) && !permission.PermissionExists(kit.RequiredPermission))
1189 permission.RegisterPermission(kit.RequiredPermission, this);
1190
1191 if (!string.IsNullOrEmpty(kit.KitImage))
1192 RegisterImage(kit.Name, kit.KitImage);
1193
1194 OpenKitView(player, kit.Name, 0, 0UL);
1195 CreateMenuPopup(player, string.Format(Message("SaveKit.Success", player.userID), kit.Name));
1196 }
1197
1198 [ConsoleCommand("kits.toggleoverwrite")]
1199 private void ccmdToggleOverwrite(ConsoleSystem.Arg arg)
1200 {
1201 BasePlayer player = arg.Player();
1202 if (player == null)
1203 return;
1204
1205 KitData.Kit kit;
1206 if (!_kitCreators.TryGetValue(player.userID, out kit))
1207 return;
1208
1209 OpenKitsEditor(player, !arg.GetBool(0));
1210 }
1211
1212 [ConsoleCommand("kits.clearitems")]
1213 private void ccmdClearItems(ConsoleSystem.Arg arg)
1214 {
1215 BasePlayer player = arg.Player();
1216 if (player == null)
1217 return;
1218
1219 KitData.Kit kit;
1220 if (!_kitCreators.TryGetValue(player.userID, out kit))
1221 return;
1222
1223 kit.ClearItems();
1224
1225 OpenKitsEditor(player);
1226 }
1227
1228 [ConsoleCommand("kits.copyinv")]
1229 private void ccmdCopyInv(ConsoleSystem.Arg arg)
1230 {
1231 BasePlayer player = arg.Player();
1232 if (player == null)
1233 return;
1234
1235 KitData.Kit kit;
1236 if (!_kitCreators.TryGetValue(player.userID, out kit))
1237 return;
1238
1239 kit.CopyItemsFrom(player);
1240
1241 OpenKitsEditor(player);
1242 }
1243
1244 [ConsoleCommand("kits.clear")]
1245 private void ccmdClearField(ConsoleSystem.Arg arg)
1246 {
1247 BasePlayer player = arg.Player();
1248 if (player == null)
1249 return;
1250
1251 KitData.Kit kit;
1252 if (!_kitCreators.TryGetValue(player.userID, out kit))
1253 return;
1254
1255 string fieldName = arg.GetString(0);
1256
1257 switch (fieldName)
1258 {
1259 case "name":
1260 kit.Name = string.Empty;
1261 break;
1262 case "description":
1263 kit.Description = string.Empty;
1264 break;
1265 case "copyPaste":
1266 kit.CopyPasteFile = string.Empty;
1267 break;
1268 case "permission":
1269 kit.RequiredPermission = string.Empty;
1270 break;
1271 case "image":
1272 kit.KitImage = string.Empty;
1273 break;
1274 case "cost":
1275 kit.Cost = 0;
1276 break;
1277 case "cooldown":
1278 kit.Cooldown = 0;
1279 break;
1280 case "maximumUses":
1281 kit.MaximumUses = 0;
1282 break;
1283 case "authLevel":
1284 kit.RequiredAuth = 0;
1285 break;
1286
1287 default:
1288 break;
1289 }
1290
1291 OpenKitsEditor(player);
1292 }
1293
1294 [ConsoleCommand("kits.creator")]
1295 private void ccmdSetField(ConsoleSystem.Arg arg)
1296 {
1297 BasePlayer player = arg.Player();
1298 if (player == null)
1299 return;
1300
1301 KitData.Kit kit;
1302 if (!_kitCreators.TryGetValue(player.userID, out kit))
1303 return;
1304
1305 if (arg.HasArgs(2))
1306 {
1307 SetParameter(player, kit, arg.GetString(0), string.Join(" ", arg.Args.Skip(1)));
1308 OpenKitsEditor(player);
1309 }
1310 }
1311
1312 private void SetParameter(BasePlayer player, KitData.Kit kit, string fieldName, object value)
1313 {
1314 if (value == null)
1315 return;
1316
1317 switch (fieldName)
1318 {
1319 case "name":
1320 kit.Name = (string)value;
1321 break;
1322 case "description":
1323 kit.Description = (string)value;
1324 break;
1325 case "copyPaste":
1326 kit.CopyPasteFile = (string)value;
1327 break;
1328 case "permission":
1329 if (!((string)value).StartsWith("kits."))
1330 {
1331 CreateMenuPopup(player, Message("EditKit.PermissionPrefix", player.userID));
1332 return;
1333 }
1334 kit.RequiredPermission = (string)value;
1335 break;
1336 case "image":
1337 kit.KitImage = (string)value;
1338 break;
1339 case "cost":
1340 {
1341 int intValue;
1342 if (!TryConvertValue<int>(value, out intValue))
1343 CreateMenuPopup(player, Message("EditKit.Number", player.userID));
1344 else kit.Cost = intValue;
1345 }
1346 break;
1347 case "cooldown":
1348 {
1349 int intValue;
1350 if (!TryConvertValue<int>(value, out intValue))
1351 CreateMenuPopup(player, Message("EditKit.Number", player.userID));
1352 else kit.Cooldown = intValue;
1353 }
1354 break;
1355 case "maximumUses":
1356 {
1357 int intValue;
1358 if (!TryConvertValue<int>(value, out intValue))
1359 CreateMenuPopup(player, Message("EditKit.Number", player.userID));
1360 else kit.MaximumUses = intValue;
1361 }
1362 break;
1363 case "authLevel":
1364 {
1365 int intValue;
1366 if (!TryConvertValue<int>(value, out intValue))
1367 CreateMenuPopup(player, Message("EditKit.Number", player.userID));
1368 else kit.RequiredAuth = Mathf.Clamp(intValue, 0, 2);
1369 }
1370 break;
1371 case "isHidden":
1372 {
1373 bool boolValue;
1374 if (!TryConvertValue<bool>(value, out boolValue))
1375 CreateMenuPopup(player, Message("EditKit.Bool", player.userID));
1376 else kit.IsHidden = boolValue;
1377 }
1378 break;
1379 default:
1380 return;
1381 }
1382 }
1383
1384 private bool TryConvertValue<T>(object value, out T result)
1385 {
1386 try
1387 {
1388 result = (T)Convert.ChangeType(value, typeof(T));
1389 return true;
1390 }
1391 catch
1392 {
1393 result = default(T);
1394 return false;
1395 }
1396 }
1397 #endregion
1398 #endregion
1399
1400 #region Command Helpers
1401 private static string CommandSafe(string text, bool unpack = false) => unpack ? text.Replace("▊▊", " ") : text.Replace(" ", "▊▊");
1402 #endregion
1403
1404 #region UI Helper
1405 public static class UI
1406 {
1407 public static CuiElementContainer Container(string panel, string color, UI4 dimensions, bool blur = true, string parent = "Overlay")
1408 {
1409 CuiElementContainer container = new CuiElementContainer()
1410 {
1411 {
1412 new CuiPanel
1413 {
1414 Image = { Color = color, Material = blur ? "assets/content/ui/uibackgroundblur-ingamemenu.mat" : string.Empty },
1415 RectTransform = { AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() },
1416 CursorEnabled = true
1417 },
1418 new CuiElement().Parent = parent,
1419 panel
1420 }
1421 };
1422 return container;
1423 }
1424
1425 public static CuiElementContainer Popup(string panel, string text, int size, UI4 dimensions, TextAnchor align = TextAnchor.MiddleCenter, string parent = "Overlay")
1426 {
1427 CuiElementContainer container = UI.Container(panel, "0 0 0 0", dimensions);
1428
1429 UI.Label(container, panel, text, size, UI4.Full, align);
1430
1431 return container;
1432 }
1433
1434 public static void Panel(CuiElementContainer container, string panel, string color, UI4 dimensions)
1435 {
1436 container.Add(new CuiPanel
1437 {
1438 Image = { Color = color },
1439 RectTransform = { AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() }
1440 },
1441 panel);
1442 }
1443
1444 public static void Label(CuiElementContainer container, string panel, string text, int size, UI4 dimensions, TextAnchor align = TextAnchor.MiddleCenter)
1445 {
1446 container.Add(new CuiLabel
1447 {
1448 Text = { FontSize = size, Align = align, Text = text },
1449 RectTransform = { AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() }
1450 },
1451 panel);
1452 }
1453
1454 public static void Button(CuiElementContainer container, string panel, string color, string text, int size, UI4 dimensions, string command, TextAnchor align = TextAnchor.MiddleCenter)
1455 {
1456 container.Add(new CuiButton
1457 {
1458 Button = { Color = color, Command = command, FadeIn = 0f },
1459 RectTransform = { AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() },
1460 Text = { Text = text, FontSize = size, Align = align }
1461 },
1462 panel);
1463 }
1464
1465 public static void Button(CuiElementContainer container, string panel, string color, string png, UI4 dimensions, string command)
1466 {
1467 UI.Panel(container, panel, color, dimensions);
1468 UI.Image(container, panel, png, dimensions);
1469 UI.Button(container, panel, "0 0 0 0", string.Empty, 0, dimensions, command);
1470 }
1471
1472 public static void Input(CuiElementContainer container, string panel, string text, int size, string command, UI4 dimensions, TextAnchor anchor = TextAnchor.MiddleLeft)
1473 {
1474 container.Add(new CuiElement
1475 {
1476 Name = CuiHelper.GetGuid(),
1477 Parent = panel,
1478 Components =
1479 {
1480 new CuiInputFieldComponent
1481 {
1482 Align = anchor,
1483 CharsLimit = 300,
1484 Command = command + text,
1485 FontSize = size,
1486 IsPassword = false,
1487 Text = text,
1488 NeedsKeyboard = true
1489 },
1490 new CuiRectTransformComponent {AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() }
1491 }
1492 });
1493 }
1494
1495 public static void Image(CuiElementContainer container, string panel, string png, UI4 dimensions)
1496 {
1497 container.Add(new CuiElement
1498 {
1499 Name = CuiHelper.GetGuid(),
1500 Parent = panel,
1501 Components =
1502 {
1503 new CuiRawImageComponent {Png = png },
1504 new CuiRectTransformComponent { AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() }
1505 }
1506 });
1507 }
1508
1509 public static void Image(CuiElementContainer container, string panel, int itemId, ulong skinId, UI4 dimensions)
1510 {
1511 container.Add(new CuiElement
1512 {
1513 Name = CuiHelper.GetGuid(),
1514 Parent = panel,
1515 Components =
1516 {
1517 new CuiImageComponent { ItemId = itemId, SkinId = skinId },
1518 new CuiRectTransformComponent { AnchorMin = dimensions.GetMin(), AnchorMax = dimensions.GetMax() }
1519 }
1520 });
1521 }
1522
1523 public static void Toggle(CuiElementContainer container, string panel, string boxColor, int fontSize, UI4 dimensions, string command, bool isOn)
1524 {
1525 UI.Panel(container, panel, boxColor, dimensions);
1526
1527 if (isOn)
1528 UI.Label(container, panel, "✔", fontSize, dimensions);
1529
1530 UI.Button(container, panel, "0 0 0 0", string.Empty, 0, dimensions, command);
1531 }
1532
1533 public static string Color(string hexColor, float alpha)
1534 {
1535 if (hexColor.StartsWith("#"))
1536 hexColor = hexColor.TrimStart('#');
1537
1538 int red = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
1539 int green = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
1540 int blue = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
1541
1542 return $"{(double)red / 255} {(double)green / 255} {(double)blue / 255} {alpha}";
1543 }
1544 }
1545
1546 public class UI4
1547 {
1548 public float xMin, yMin, xMax, yMax;
1549
1550 public UI4(float xMin, float yMin, float xMax, float yMax)
1551 {
1552 this.xMin = xMin;
1553 this.yMin = yMin;
1554 this.xMax = xMax;
1555 this.yMax = yMax;
1556 }
1557
1558 public string GetMin() => $"{xMin} {yMin}";
1559
1560 public string GetMax() => $"{xMax} {yMax}";
1561
1562 private static UI4 _full;
1563
1564 public static UI4 Full
1565 {
1566 get
1567 {
1568 if (_full == null)
1569 _full = new UI4(0, 0, 1, 1);
1570 return _full;
1571 }
1572 }
1573 }
1574 #endregion
1575
1576 #region Chat Commands
1577 private void cmdKit(BasePlayer player, string command, string[] args)
1578 {
1579 if (args.Length == 0)
1580 {
1581 if (Configuration.UseUI)
1582 OpenKitGrid(player);
1583 else ReplyHelp(player);
1584
1585 return;
1586 }
1587
1588 bool isAdmin = IsAdmin(player);
1589
1590 switch (args[0].ToLower())
1591 {
1592 case "help":
1593 ReplyHelp(player);
1594 return;
1595
1596 case "list":
1597 if (isAdmin)
1598 player.ChatMessage(string.Format(Message("Chat.KitList", player.userID), kitData.Keys.ToSentence()));
1599 else
1600 {
1601 List<KitData.Kit> kits = Facepunch.Pool.GetList<KitData.Kit>();
1602 GetUserValidKits(player, kits);
1603
1604 player.ChatMessage(string.Format(Message("Chat.KitList", player.userID), kits.Select((KitData.Kit kit) => kit.Name).ToSentence()));
1605 Facepunch.Pool.FreeList(ref kits);
1606 }
1607 return;
1608
1609 case "add":
1610 case "new":
1611 if (!isAdmin)
1612 {
1613 player.ChatMessage(Message("Chat.Error.NotAdmin", player.userID));
1614 return;
1615 }
1616
1617 _kitCreators[player.userID] = new KitData.Kit();
1618 OpenKitsEditor(player);
1619
1620 return;
1621
1622 case "edit":
1623 if (!isAdmin)
1624 {
1625 player.ChatMessage(Message("Chat.Error.NotAdmin", player.userID));
1626 return;
1627 }
1628
1629 if (args.Length != 2)
1630 {
1631 player.ChatMessage(Message("Chat.Error.NoKit", player.userID));
1632 return;
1633 }
1634
1635 KitData.Kit editKit;
1636 if (!kitData.Find(args[1], out editKit))
1637 {
1638 player.ChatMessage(string.Format(Message("Chat.Error.DoesntExist", player.userID), args[1]));
1639 return;
1640 }
1641
1642 _kitCreators[player.userID] = KitData.Kit.CloneOf(editKit);
1643 OpenKitsEditor(player);
1644
1645 return;
1646
1647 case "remove":
1648 case "delete":
1649 if (!isAdmin)
1650 {
1651 player.ChatMessage(Message("Chat.Error.NotAdmin", player.userID));
1652 return;
1653 }
1654
1655 if (args.Length != 2)
1656 {
1657 player.ChatMessage(Message("Chat.Error.NoKit", player.userID));
1658 return;
1659 }
1660
1661 KitData.Kit deleteKit;
1662 if (!kitData.Find(args[1], out deleteKit))
1663 {
1664 player.ChatMessage(string.Format(Message("Chat.Error.DoesntExist", player.userID), args[1]));
1665 return;
1666 }
1667
1668 kitData.Remove(deleteKit);
1669 SaveKitData();
1670 player.ChatMessage(string.Format(Message("Chat.KitDeleted", player.userID), args[1]));
1671
1672 return;
1673
1674 case "give":
1675 if (!isAdmin)
1676 {
1677 player.ChatMessage(Message("Chat.Error.NotAdmin", player.userID));
1678 return;
1679 }
1680
1681 if (args.Length != 3)
1682 {
1683 player.ChatMessage(Message("Chat.Error.GiveArgs", player.userID));
1684 return;
1685 }
1686
1687 BasePlayer target = FindPlayer(args[1]);
1688 if (target == null)
1689 {
1690 player.ChatMessage(Message("Chat.Error.NoPlayer", player.userID));
1691 return;
1692 }
1693
1694 KitData.Kit giveKit;
1695 if (!kitData.Find(args[2], out giveKit))
1696 {
1697 player.ChatMessage(Message("Chat.Error.DoesntExist", player.userID));
1698 return;
1699 }
1700
1701 GiveKit(target, giveKit);
1702 player.ChatMessage(string.Format(Message("Chat.KitGiven", player.userID), target.displayName, args[2]));
1703 return;
1704
1705 case "givenpc":
1706 if (!isAdmin)
1707 {
1708 player.ChatMessage(Message("Chat.Error.NotAdmin", player.userID));
1709 return;
1710 }
1711
1712 if (args.Length != 2)
1713 {
1714 player.ChatMessage(Message("Chat.Error.NPCGiveArgs", player.userID));
1715 return;
1716 }
1717
1718 KitData.Kit npcGiveKit;
1719 if (!kitData.Find(args[1], out npcGiveKit))
1720 {
1721 player.ChatMessage(Message("Chat.Error.DoesntExist", player.userID));
1722 return;
1723 }
1724
1725 BasePlayer npc = RaycastPlayer(player);
1726 if (npc == null)
1727 {
1728 player.ChatMessage(Message("Chat.Error.NoNPCTarget", player.userID));
1729 return;
1730 }
1731
1732 npc.inventory.Strip();
1733 GiveKit(npc, npcGiveKit);
1734
1735 player.ChatMessage(string.Format(Message("Chat.KitGiven", player.userID), npc.displayName, args[1]));
1736 return;
1737
1738 case "reset":
1739 if (!isAdmin)
1740 {
1741 player.ChatMessage(Message("Chat.Error.NotAdmin", player.userID));
1742 return;
1743 }
1744
1745 playerData.Wipe();
1746 SavePlayerData();
1747 player.ChatMessage(Message("Chat.ResetPlayers", player.userID));
1748
1749 return;
1750
1751 case "autokit":
1752 if (Configuration.AllowAutoToggle)
1753 {
1754 bool v = playerData[player.userID].ClaimAutoKits = !playerData[player.userID].ClaimAutoKits;
1755 player.ChatMessage(string.Format(Message("Chat.AutoKit.Toggle", player.userID), Message($"Chat.AutoKit.{v}", player.userID)));
1756 }
1757 return;
1758
1759 default:
1760 if (!kitData.Exists(args[0]))
1761 {
1762 player.ChatMessage(string.Format(Message("Chat.Error.DoesntExist", player.userID), args[0]));
1763 return;
1764 }
1765
1766 if (TryClaimKit(player, args[0], false))
1767 player.ChatMessage(string.Format(Message("Notification.KitReceived", player.userID), args[0]));
1768
1769 break;
1770 }
1771 }
1772
1773 private void ccmdKit(ConsoleSystem.Arg arg)
1774 {
1775 BasePlayer player = arg.Connection?.player as BasePlayer;
1776 if (player != null && !IsAdmin(player))
1777 return;
1778
1779 if (arg.Args == null || arg.Args.Length == 0)
1780 {
1781 SendReply(arg, "kit list - List all kits");
1782 SendReply(arg, "kit delete <kitname> - Delete the specified kit");
1783 SendReply(arg, "kit give <playername> <kitname> - Give the specified kit to the specified playuer");
1784 SendReply(arg, "kit reset - Reset player usage data");
1785 return;
1786 }
1787
1788 switch (arg.Args[0].ToLower())
1789 {
1790 case "list":
1791 SendReply(arg, string.Format("Kit List: {0}", kitData.Keys.ToSentence()));
1792 return;
1793
1794 case "remove":
1795 case "delete":
1796 if (arg.Args.Length != 2)
1797 {
1798 SendReply(arg, "You must specify a kit name");
1799 return;
1800 }
1801
1802 KitData.Kit deleteKit;
1803 if (!kitData.Find(arg.Args[1], out deleteKit))
1804 {
1805 SendReply(arg, string.Format("The kit {0} does not exist", arg.Args[1]));
1806 return;
1807 }
1808
1809 kitData.Remove(deleteKit);
1810 SaveKitData();
1811 SendReply(arg, string.Format("You have deleted the kit {0}", arg.Args[1]));
1812
1813 return;
1814
1815 case "give":
1816 if (arg.Args.Length != 3)
1817 {
1818 SendReply(arg, "You must specify target player and a kit name");
1819 return;
1820 }
1821
1822 BasePlayer target = FindPlayer(arg.Args[1]);
1823 if (target == null)
1824 {
1825 SendReply(arg, "Failed to find a player with the specified name or ID");
1826 return;
1827 }
1828
1829 KitData.Kit giveKit;
1830 if (!kitData.Find(arg.Args[2], out giveKit))
1831 {
1832 SendReply(arg, "The kit {0} does not exist");
1833 return;
1834 }
1835
1836 GiveKit(target, giveKit);
1837 SendReply(arg, string.Format("You have given {0} the kit {1}", target.displayName, arg.Args[2]));
1838 return;
1839
1840 case "reset":
1841 playerData.Wipe();
1842 SavePlayerData();
1843 SendReply(arg, "You have wiped player usage data");
1844 return;
1845
1846 default:
1847 SendReply(arg, "Invalid syntax");
1848 break;
1849 }
1850 }
1851
1852 private void ReplyHelp(BasePlayer player)
1853 {
1854 player.ChatMessage(string.Format(Message("Chat.Help.Title", player.userID), Version));
1855 player.ChatMessage(Message("Chat.Help.1", player.userID));
1856 player.ChatMessage(Message("Chat.Help.2", player.userID));
1857
1858 if (Configuration.AllowAutoToggle)
1859 player.ChatMessage(Message("Chat.Help.9", player.userID));
1860
1861 if (IsAdmin(player))
1862 {
1863 player.ChatMessage(Message("Chat.Help.3", player.userID));
1864 player.ChatMessage(Message("Chat.Help.4", player.userID));
1865 player.ChatMessage(Message("Chat.Help.5", player.userID));
1866 player.ChatMessage(Message("Chat.Help.6", player.userID));
1867 player.ChatMessage(Message("Chat.Help.7", player.userID));
1868 player.ChatMessage(Message("Chat.Help.8", player.userID));
1869 player.ChatMessage(Message("Chat.Help.10", player.userID));
1870 }
1871 }
1872 #endregion
1873
1874 #region Old Data Conversion
1875 [ConsoleCommand("kits.convertolddata")]
1876 private void ccmdConvertKitsData(ConsoleSystem.Arg arg)
1877 {
1878 BasePlayer player = arg?.Connection?.player as BasePlayer;
1879 if (player != null)
1880 return;
1881
1882 ConvertOldKitData();
1883 }
1884
1885 [ConsoleCommand("kits.convertoldplayerdata")]
1886 private void ccmdConvertPlayerData(ConsoleSystem.Arg arg)
1887 {
1888 BasePlayer player = arg?.Connection?.player as BasePlayer;
1889 if (player != null)
1890 return;
1891
1892 ConvertOldPlayerData();
1893 }
1894
1895 private void ConvertOldPlayerData()
1896 {
1897 try
1898 {
1899 Dictionary<ulong, Dictionary<string, OldKitData>> oldPlayerData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, Dictionary<string, OldKitData>>>("Kits_Data");
1900
1901 if (oldPlayerData != null)
1902 {
1903 int success = 0;
1904
1905 foreach (KeyValuePair<ulong, Dictionary<string, OldKitData>> oldPlayer in oldPlayerData)
1906 {
1907 PlayerData.PlayerUsageData playerUsageData = playerData[oldPlayer.Key];
1908
1909 foreach(KeyValuePair<string, OldKitData> oldUsageData in oldPlayer.Value)
1910 {
1911 if (kitData.Exists(oldUsageData.Key))
1912 playerUsageData.InsertOldData(oldUsageData.Key, oldUsageData.Value.max, oldUsageData.Value.cooldown);
1913 }
1914
1915 success++;
1916 }
1917
1918 if (success > 0)
1919 SavePlayerData();
1920
1921 Debug.Log($"Successfully converted {success} / {oldPlayerData.Count} player's data");
1922 }
1923 }
1924 catch { }
1925 }
1926
1927 private void ConvertOldKitData()
1928 {
1929 try
1930 {
1931 DynamicConfigFile kits = Interface.Oxide.DataFileSystem.GetFile("Kits");
1932 kits.Settings.NullValueHandling = NullValueHandling.Ignore;
1933 OldStoredData oldStoredData = kits.ReadObject<OldStoredData>();
1934
1935 int success = 0;
1936
1937 foreach (OldKit oldKit in oldStoredData.Kits.Values)
1938 {
1939 Debug.Log($"Converting Kit {oldKit.name} with {oldKit.items.Count} items");
1940 KitData.Kit kit = new KitData.Kit()
1941 {
1942 Name = oldKit.name,
1943 Description = oldKit.description ?? string.Empty,
1944 Cooldown = Convert.ToInt32(oldKit.cooldown),
1945 CopyPasteFile = oldKit.building ?? string.Empty,
1946 Cost = 0,
1947 IsHidden = oldKit.hide,
1948 KitImage = oldKit.image ?? string.Empty,
1949 MaximumUses = oldKit.max,
1950 RequiredAuth = oldKit.authlevel,
1951 RequiredPermission = oldKit.permission ?? string.Empty
1952 };
1953
1954 TryConvertItems(ref kit, oldKit.items);
1955
1956 kitData[oldKit.name] = kit;
1957
1958 success++;
1959 }
1960
1961 if (success > 0)
1962 SaveKitData();
1963
1964 Debug.Log($"Successfully converted {success} / {oldStoredData.Kits.Count} kits");
1965 }
1966 catch { }
1967 }
1968
1969 private void TryConvertItems(ref KitData.Kit kit, List<OldKitItem> items)
1970 {
1971 List<ItemData> wear = Facepunch.Pool.GetList<ItemData>();
1972 List<ItemData> belt = Facepunch.Pool.GetList<ItemData>();
1973 List<ItemData> main = Facepunch.Pool.GetList<ItemData>();
1974
1975 ConvertItems(ref wear, items.Where((OldKitItem oldKitItem) => oldKitItem.container == "wear"));
1976 ConvertItems(ref belt, items.Where((OldKitItem oldKitItem) => oldKitItem.container == "belt"));
1977 ConvertItems(ref main, items.Where((OldKitItem oldKitItem) => oldKitItem.container == "main"));
1978
1979 kit.WearItems = wear.ToArray();
1980 kit.BeltItems = belt.ToArray();
1981 kit.MainItems = main.ToArray();
1982
1983 Facepunch.Pool.FreeList(ref wear);
1984 Facepunch.Pool.FreeList(ref belt);
1985 Facepunch.Pool.FreeList(ref main);
1986 }
1987
1988 private ItemDefinition FindItemDefinition(int itemID)
1989 {
1990 ItemDefinition itemDefinition;
1991
1992 string shortname;
1993 if (_itemIdShortnameConversions.TryGetValue(itemID, out shortname))
1994 itemDefinition = ItemManager.FindItemDefinition(shortname);
1995 else itemDefinition = ItemManager.FindItemDefinition(itemID);
1996
1997 return itemDefinition;
1998 }
1999
2000 private void ConvertItems(ref List<ItemData> list, IEnumerable<OldKitItem> items)
2001 {
2002 int position = 0;
2003 foreach (OldKitItem oldKitItem in items)
2004 {
2005 ItemDefinition itemDefinition = FindItemDefinition(oldKitItem.itemid);
2006
2007 if (itemDefinition == null)
2008 {
2009 Debug.Log($"Failed to find ItemDefinition for item ID {oldKitItem.itemid}");
2010 continue;
2011 }
2012
2013 ItemData itemData = new ItemData()
2014 {
2015 Shortname = itemDefinition.shortname,
2016 Amount = oldKitItem.amount,
2017 Skin = oldKitItem.skinid,
2018 Position = position
2019 };
2020
2021 if (itemDefinition.condition.enabled)
2022 itemData.Condition = itemData.MaxCondition = itemDefinition.condition.max;
2023
2024 if (itemData.IsBlueprint)
2025 {
2026 itemDefinition = FindItemDefinition(oldKitItem.blueprintTarget);
2027 if (itemDefinition == null)
2028 {
2029 Debug.Log($"Failed to find ItemDefinition for blueprint target {oldKitItem.blueprintTarget}");
2030 continue;
2031 }
2032
2033 itemData.BlueprintShortname = itemDefinition.shortname;
2034 }
2035
2036 if (oldKitItem.mods?.Count > 0)
2037 {
2038 List<ItemData> contents = Facepunch.Pool.GetList<ItemData>();
2039
2040 oldKitItem.mods.ForEach((int itemId) =>
2041 {
2042 itemDefinition = FindItemDefinition(itemId);
2043 if (itemDefinition != null)
2044 {
2045 contents.Add(new ItemData
2046 {
2047 Shortname = itemDefinition.shortname,
2048 Amount = 1
2049 });
2050 }
2051 });
2052
2053 itemData.Contents = contents.ToArray();
2054
2055 Facepunch.Pool.FreeList(ref contents);
2056 }
2057
2058 list.Add(itemData);
2059 position++;
2060 }
2061 }
2062
2063 private class OldStoredData
2064 {
2065 public Dictionary<string, OldKit> Kits = new Dictionary<string, OldKit>();
2066 }
2067
2068 private class OldKitData
2069 {
2070 public int max;
2071 public double cooldown;
2072 }
2073
2074 private class OldKitItem
2075 {
2076 public int itemid;
2077 public string container;
2078 public int amount;
2079 public ulong skinid;
2080 public bool weapon;
2081 public int blueprintTarget;
2082 public List<int> mods = new List<int>();
2083 }
2084
2085 private class OldKit
2086 {
2087 public string name;
2088 public string description;
2089 public int max;
2090 public double cooldown;
2091 public int authlevel;
2092 public bool hide;
2093 public bool npconly;
2094 public string permission;
2095 public string image;
2096 public string building;
2097 public List<OldKitItem> items = new List<OldKitItem>();
2098 }
2099
2100 private readonly Dictionary<int, string> _itemIdShortnameConversions = new Dictionary<int, string>
2101 {
2102 [-1461508848] = "rifle.ak",
2103 [2115555558] = "ammo.handmade.shell",
2104 [-533875561] = "ammo.pistol",
2105 [1621541165] = "ammo.pistol.fire",
2106 [-422893115] = "ammo.pistol.hv",
2107 [815896488] = "ammo.rifle",
2108 [805088543] = "ammo.rifle.explosive",
2109 [449771810] = "ammo.rifle.incendiary",
2110 [1152393492] = "ammo.rifle.hv",
2111 [1578894260] = "ammo.rocket.basic",
2112 [1436532208] = "ammo.rocket.fire",
2113 [542276424] = "ammo.rocket.hv",
2114 [1594947829] = "ammo.rocket.smoke",
2115 [-1035059994] = "ammo.shotgun",
2116 [1818890814] = "ammo.shotgun.fire",
2117 [1819281075] = "ammo.shotgun.slug",
2118 [1685058759] = "antiradpills",
2119 [93029210] = "apple",
2120 [-1565095136] = "apple.spoiled",
2121 [-1775362679] = "arrow.bone",
2122 [-1775249157] = "arrow.fire",
2123 [-1280058093] = "arrow.hv",
2124 [-420273765] = "arrow.wooden",
2125 [563023711] = "autoturret",
2126 [790921853] = "axe.salvaged",
2127 [-337261910] = "bandage",
2128 [498312426] = "barricade.concrete",
2129 [504904386] = "barricade.metal",
2130 [-1221200300] = "barricade.sandbags",
2131 [510887968] = "barricade.stone",
2132 [-814689390] = "barricade.wood",
2133 [1024486167] = "barricade.woodwire",
2134 [2021568998] = "battery.small",
2135 [97329] = "bbq",
2136 [1046072789] = "trap.bear",
2137 [97409] = "bed",
2138 [-1480119738] = "tool.binoculars",
2139 [1611480185] = "black.raspberries",
2140 [-1386464949] = "bleach",
2141 [93832698] = "blood",
2142 [-1063412582] = "blueberries",
2143 [-1887162396] = "blueprintbase",
2144 [-55660037] = "rifle.bolt",
2145 [919780768] = "bone.club",
2146 [-365801095] = "bone.fragments",
2147 [68998734] = "botabag",
2148 [-853695669] = "bow.hunting",
2149 [271534758] = "box.wooden.large",
2150 [-770311783] = "box.wooden",
2151 [-1192532973] = "bucket.water",
2152 [-307490664] = "building.planner",
2153 [707427396] = "burlap.shirt",
2154 [707432758] = "burlap.shoes",
2155 [-2079677721] = "cactusflesh",
2156 [-1342405573] = "tool.camera",
2157 [-139769801] = "campfire",
2158 [-1043746011] = "can.beans",
2159 [2080339268] = "can.beans.empty",
2160 [-171664558] = "can.tuna",
2161 [1050986417] = "can.tuna.empty",
2162 [-1693683664] = "candycaneclub",
2163 [523409530] = "candycane",
2164 [1300054961] = "cctv.camera",
2165 [-2095387015] = "ceilinglight",
2166 [1428021640] = "chainsaw",
2167 [94623429] = "chair",
2168 [1436001773] = "charcoal",
2169 [1711323399] = "chicken.burned",
2170 [1734319168] = "chicken.cooked",
2171 [-1658459025] = "chicken.raw",
2172 [-726947205] = "chicken.spoiled",
2173 [-341443994] = "chocholate",
2174 [1540879296] = "xmasdoorwreath",
2175 [94756378] = "cloth",
2176 [3059095] = "coal",
2177 [3059624] = "corn",
2178 [2045107609] = "clone.corn",
2179 [583366917] = "seed.corn",
2180 [2123300234] = "crossbow",
2181 [1983936587] = "crude.oil",
2182 [1257201758] = "cupboard.tool",
2183 [-1144743963] = "diving.fins",
2184 [-1144542967] = "diving.mask",
2185 [-1144334585] = "diving.tank",
2186 [1066729526] = "diving.wetsuit",
2187 [-1598790097] = "door.double.hinged.metal",
2188 [-933236257] = "door.double.hinged.toptier",
2189 [-1575287163] = "door.double.hinged.wood",
2190 [-2104481870] = "door.hinged.metal",
2191 [-1571725662] = "door.hinged.toptier",
2192 [1456441506] = "door.hinged.wood",
2193 [1200628767] = "door.key",
2194 [-778796102] = "door.closer",
2195 [1526866730] = "xmas.door.garland",
2196 [1925723260] = "dropbox",
2197 [1891056868] = "ducttape",
2198 [1295154089] = "explosive.satchel",
2199 [498591726] = "explosive.timed",
2200 [1755466030] = "explosives",
2201 [726730162] = "facialhair.style01",
2202 [-1034048911] = "fat.animal",
2203 [252529905] = "femalearmpithair.style01",
2204 [471582113] = "femaleeyebrow.style01",
2205 [-1138648591] = "femalepubichair.style01",
2206 [305916740] = "female_hairstyle_01",
2207 [305916742] = "female_hairstyle_03",
2208 [305916744] = "female_hairstyle_05",
2209 [1908328648] = "fireplace.stone",
2210 [-2078972355] = "fish.cooked",
2211 [-533484654] = "fish.raw",
2212 [1571660245] = "fishingrod.handmade",
2213 [1045869440] = "flamethrower",
2214 [1985408483] = "flameturret",
2215 [97513422] = "flare",
2216 [1496470781] = "flashlight.held",
2217 [1229879204] = "weapon.mod.flashlight",
2218 [-1722829188] = "floor.grill",
2219 [1849912854] = "floor.ladder.hatch",
2220 [-1266285051] = "fridge",
2221 [-1749787215] = "boots.frog",
2222 [28178745] = "lowgradefuel",
2223 [-505639592] = "furnace",
2224 [1598149413] = "furnace.large",
2225 [-1779401418] = "gates.external.high.stone",
2226 [-57285700] = "gates.external.high.wood",
2227 [98228420] = "gears",
2228 [1422845239] = "geiger.counter",
2229 [277631078] = "generator.wind.scrap",
2230 [115739308] = "burlap.gloves",
2231 [-522149009] = "gloweyes",
2232 [3175989] = "glue",
2233 [718197703] = "granolabar",
2234 [384204160] = "grenade.beancan",
2235 [-1308622549] = "grenade.f1",
2236 [-217113639] = "fun.guitar",
2237 [-1580059655] = "gunpowder",
2238 [-1832205789] = "male_hairstyle_01",
2239 [305916741] = "female_hairstyle_02",
2240 [936777834] = "attire.hide.helterneck",
2241 [-1224598842] = "hammer",
2242 [-1976561211] = "hammer.salvaged",
2243 [-1406876421] = "hat.beenie",
2244 [-1397343301] = "hat.boonie",
2245 [1260209393] = "bucket.helmet",
2246 [-1035315940] = "burlap.headwrap",
2247 [-1381682752] = "hat.candle",
2248 [696727039] = "hat.cap",
2249 [-2128719593] = "coffeecan.helmet",
2250 [-1178289187] = "deer.skull.mask",
2251 [1351172108] = "heavy.plate.helmet",
2252 [-450738836] = "hat.miner",
2253 [-966287254] = "attire.reindeer.headband",
2254 [340009023] = "riot.helmet",
2255 [124310981] = "hat.wolf",
2256 [1501403549] = "wood.armor.helmet",
2257 [698310895] = "hatchet",
2258 [523855532] = "hazmatsuit",
2259 [2045246801] = "clone.hemp",
2260 [583506109] = "seed.hemp",
2261 [-148163128] = "attire.hide.boots",
2262 [-132588262] = "attire.hide.skirt",
2263 [-1666761111] = "attire.hide.vest",
2264 [-465236267] = "weapon.mod.holosight",
2265 [-1211618504] = "hoodie",
2266 [2133577942] = "hq.metal.ore",
2267 [-1014825244] = "humanmeat.burned",
2268 [-991829475] = "humanmeat.cooked",
2269 [-642008142] = "humanmeat.raw",
2270 [661790782] = "humanmeat.spoiled",
2271 [-1440143841] = "icepick.salvaged",
2272 [569119686] = "bone.armor.suit",
2273 [1404466285] = "heavy.plate.jacket",
2274 [-1616887133] = "jacket.snow",
2275 [-1167640370] = "jacket",
2276 [-1284735799] = "jackolantern.angry",
2277 [-1278649848] = "jackolantern.happy",
2278 [776005741] = "knife.bone",
2279 [108061910] = "ladder.wooden.wall",
2280 [255101535] = "trap.landmine",
2281 [-51678842] = "lantern",
2282 [-789202811] = "largemedkit",
2283 [516382256] = "weapon.mod.lasersight",
2284 [50834473] = "leather",
2285 [-975723312] = "lock.code",
2286 [1908195100] = "lock.key",
2287 [-1097452776] = "locker",
2288 [146685185] = "longsword",
2289 [-1716193401] = "rifle.lr300",
2290 [193190034] = "lmg.m249",
2291 [371156815] = "pistol.m92",
2292 [3343606] = "mace",
2293 [825308669] = "machete",
2294 [830965940] = "mailbox",
2295 [1662628660] = "male.facialhair.style02",
2296 [1662628661] = "male.facialhair.style03",
2297 [1662628662] = "male.facialhair.style04",
2298 [-1832205788] = "male_hairstyle_02",
2299 [-1832205786] = "male_hairstyle_04",
2300 [1625090418] = "malearmpithair.style01",
2301 [-1269800768] = "maleeyebrow.style01",
2302 [429648208] = "malepubichair.style01",
2303 [-1832205787] = "male_hairstyle_03",
2304 [-1832205785] = "male_hairstyle_05",
2305 [107868] = "map",
2306 [997973965] = "mask.balaclava",
2307 [-46188931] = "mask.bandana",
2308 [-46848560] = "metal.facemask",
2309 [-2066726403] = "bearmeat.burned",
2310 [-2043730634] = "bearmeat.cooked",
2311 [1325935999] = "bearmeat",
2312 [-225234813] = "deermeat.burned",
2313 [-202239044] = "deermeat.cooked",
2314 [-322501005] = "deermeat.raw",
2315 [-1851058636] = "horsemeat.burned",
2316 [-1828062867] = "horsemeat.cooked",
2317 [-1966381470] = "horsemeat.raw",
2318 [968732481] = "meat.pork.burned",
2319 [991728250] = "meat.pork.cooked",
2320 [-253819519] = "meat.boar",
2321 [-1714986849] = "wolfmeat.burned",
2322 [-1691991080] = "wolfmeat.cooked",
2323 [179448791] = "wolfmeat.raw",
2324 [431617507] = "wolfmeat.spoiled",
2325 [688032252] = "metal.fragments",
2326 [-1059362949] = "metal.ore",
2327 [1265861812] = "metal.plate.torso",
2328 [374890416] = "metal.refined",
2329 [1567404401] = "metalblade",
2330 [-1057402571] = "metalpipe",
2331 [-758925787] = "mining.pumpjack",
2332 [-1411620422] = "mining.quarry",
2333 [88869913] = "fish.minnows",
2334 [-2094080303] = "smg.mp5",
2335 [843418712] = "mushroom",
2336 [-1569356508] = "weapon.mod.muzzleboost",
2337 [-1569280852] = "weapon.mod.muzzlebrake",
2338 [449769971] = "pistol.nailgun",
2339 [590532217] = "ammo.nailgun.nails",
2340 [3387378] = "note",
2341 [1767561705] = "burlap.trousers",
2342 [106433500] = "pants",
2343 [-1334615971] = "heavy.plate.pants",
2344 [-135651869] = "attire.hide.pants",
2345 [-1595790889] = "roadsign.kilt",
2346 [-459156023] = "pants.shorts",
2347 [106434956] = "paper",
2348 [-578028723] = "pickaxe",
2349 [-586116979] = "jar.pickle",
2350 [-1379225193] = "pistol.eoka",
2351 [-930579334] = "pistol.revolver",
2352 [548699316] = "pistol.semiauto",
2353 [142147109] = "planter.large",
2354 [148953073] = "planter.small",
2355 [102672084] = "attire.hide.poncho",
2356 [640562379] = "pookie.bear",
2357 [-1732316031] = "xmas.present.large",
2358 [-2130280721] = "xmas.present.medium",
2359 [-1725510067] = "xmas.present.small",
2360 [1974032895] = "propanetank",
2361 [-225085592] = "pumpkin",
2362 [509654999] = "clone.pumpkin",
2363 [466113771] = "seed.pumpkin",
2364 [2033918259] = "pistol.python",
2365 [2069925558] = "target.reactive",
2366 [-1026117678] = "box.repair.bench",
2367 [1987447227] = "research.table",
2368 [540154065] = "researchpaper",
2369 [1939428458] = "riflebody",
2370 [-288010497] = "roadsign.jacket",
2371 [-847065290] = "roadsigns",
2372 [3506021] = "rock",
2373 [649603450] = "rocket.launcher",
2374 [3506418] = "rope",
2375 [569935070] = "rug.bear",
2376 [113284] = "rug",
2377 [1916127949] = "water.salt",
2378 [-1775234707] = "salvaged.cleaver",
2379 [-388967316] = "salvaged.sword",
2380 [2007564590] = "santahat",
2381 [-1705696613] = "scarecrow",
2382 [670655301] = "hazmatsuit_scientist",
2383 [1148128486] = "hazmatsuit_scientist_peacekeeper",
2384 [-141135377] = "weapon.mod.small.scope",
2385 [109266897] = "scrap",
2386 [-527558546] = "searchlight",
2387 [-1745053053] = "rifle.semiauto",
2388 [1223860752] = "semibody",
2389 [-419069863] = "sewingkit",
2390 [-1617374968] = "sheetmetal",
2391 [2057749608] = "shelves",
2392 [24576628] = "shirt.collared",
2393 [-1659202509] = "shirt.tanktop",
2394 [2107229499] = "shoes.boots",
2395 [191795897] = "shotgun.double",
2396 [-1009492144] = "shotgun.pump",
2397 [2077983581] = "shotgun.waterpipe",
2398 [378365037] = "guntrap",
2399 [-529054135] = "shutter.metal.embrasure.a",
2400 [-529054134] = "shutter.metal.embrasure.b",
2401 [486166145] = "shutter.wood.a",
2402 [1628490888] = "sign.hanging.banner.large",
2403 [1498516223] = "sign.hanging",
2404 [-632459882] = "sign.hanging.ornate",
2405 [-626812403] = "sign.pictureframe.landscape",
2406 [385802761] = "sign.pictureframe.portrait",
2407 [2117976603] = "sign.pictureframe.tall",
2408 [1338515426] = "sign.pictureframe.xl",
2409 [-1455694274] = "sign.pictureframe.xxl",
2410 [1579245182] = "sign.pole.banner.large",
2411 [-587434450] = "sign.post.double",
2412 [-163742043] = "sign.post.single",
2413 [-1224714193] = "sign.post.town",
2414 [644359987] = "sign.post.town.roof",
2415 [-1962514734] = "sign.wooden.huge",
2416 [-705305612] = "sign.wooden.large",
2417 [-357728804] = "sign.wooden.medium",
2418 [-698499648] = "sign.wooden.small",
2419 [1213686767] = "weapon.mod.silencer",
2420 [386382445] = "weapon.mod.simplesight",
2421 [1859976884] = "skull_fire_pit",
2422 [960793436] = "skull.human",
2423 [1001265731] = "skull.wolf",
2424 [1253290621] = "sleepingbag",
2425 [470729623] = "small.oil.refinery",
2426 [1051155022] = "stash.small",
2427 [865679437] = "fish.troutsmall",
2428 [927253046] = "smallwaterbottle",
2429 [109552593] = "smg.2",
2430 [-2092529553] = "smgbody",
2431 [691633666] = "snowball",
2432 [-2055888649] = "snowman",
2433 [621575320] = "shotgun.spas12",
2434 [-2118132208] = "spear.stone",
2435 [-1127699509] = "spear.wooden",
2436 [-685265909] = "spikes.floor",
2437 [552706886] = "spinner.wheel",
2438 [1835797460] = "metalspring",
2439 [-892259869] = "sticks",
2440 [-1623330855] = "stocking.large",
2441 [-1616524891] = "stocking.small",
2442 [789892804] = "stone.pickaxe",
2443 [-1289478934] = "stonehatchet",
2444 [-892070738] = "stones",
2445 [-891243783] = "sulfur",
2446 [889398893] = "sulfur.ore",
2447 [-1625468793] = "supply.signal",
2448 [1293049486] = "surveycharge",
2449 [1369769822] = "fishtrap.small",
2450 [586484018] = "syringe.medical",
2451 [110115790] = "table",
2452 [1490499512] = "targeting.computer",
2453 [3552619] = "tarp",
2454 [1471284746] = "techparts",
2455 [456448245] = "smg.thompson",
2456 [110547964] = "torch",
2457 [1588977225] = "xmas.decoration.baubels",
2458 [918540912] = "xmas.decoration.candycanes",
2459 [-471874147] = "xmas.decoration.gingerbreadmen",
2460 [205978836] = "xmas.decoration.lights",
2461 [-1044400758] = "xmas.decoration.pinecone",
2462 [-2073307447] = "xmas.decoration.star",
2463 [435230680] = "xmas.decoration.tinsel",
2464 [-864578046] = "tshirt",
2465 [1660607208] = "tshirt.long",
2466 [260214178] = "tunalight",
2467 [-1847536522] = "vending.machine",
2468 [-496055048] = "wall.external.high.stone",
2469 [-1792066367] = "wall.external.high",
2470 [562888306] = "wall.frame.cell.gate",
2471 [-427925529] = "wall.frame.cell",
2472 [995306285] = "wall.frame.fence.gate",
2473 [-378017204] = "wall.frame.fence",
2474 [447918618] = "wall.frame.garagedoor",
2475 [313836902] = "wall.frame.netting",
2476 [1175970190] = "wall.frame.shopfront",
2477 [525244071] = "wall.frame.shopfront.metal",
2478 [-1021702157] = "wall.window.bars.metal",
2479 [-402507101] = "wall.window.bars.toptier",
2480 [-1556671423] = "wall.window.bars.wood",
2481 [61936445] = "wall.window.glass.reinforced",
2482 [112903447] = "water",
2483 [1817873886] = "water.catcher.large",
2484 [1824679850] = "water.catcher.small",
2485 [-1628526499] = "water.barrel",
2486 [547302405] = "waterjug",
2487 [1840561315] = "water.purifier",
2488 [-460592212] = "xmas.window.garland",
2489 [3655341] = "wood",
2490 [1554697726] = "wood.armor.jacket",
2491 [-1883959124] = "wood.armor.pants",
2492 [-481416622] = "workbench1",
2493 [-481416621] = "workbench2",
2494 [-481416620] = "workbench3",
2495 [-1151126752] = "xmas.lightstring",
2496 [-1926458555] = "xmas.tree"
2497 };
2498
2499
2500 #endregion
2501
2502 #region Item Shortname Updates
2503 private void CheckForShortnameUpdates()
2504 {
2505 bool hasChanges = false;
2506
2507 kitData.ForEach((KitData.Kit kit) =>
2508 {
2509 Action<ItemData[]> action = ((ItemData[] items) =>
2510 {
2511 for (int i = 0; i < items.Length; i++)
2512 {
2513 ItemData kitItem = items[i];
2514
2515 string replacement;
2516 if (_itemShortnameReplacements.TryGetValue(kitItem.Shortname, out replacement))
2517 {
2518 kitItem.Shortname = replacement;
2519 hasChanges = true;
2520 }
2521 }
2522 });
2523
2524 action(kit.BeltItems);
2525 action(kit.WearItems);
2526 action(kit.MainItems);
2527 });
2528
2529 if (hasChanges)
2530 SaveKitData();
2531 }
2532
2533 private readonly Dictionary<string, string> _itemShortnameReplacements = new Dictionary<string, string>
2534 {
2535 ["chocholate"] = "chocolate"
2536 };
2537 #endregion
2538
2539 #region Config
2540 private static ConfigData Configuration;
2541
2542 private class ConfigData
2543 {
2544 [JsonProperty(PropertyName = "Kit chat command")]
2545 public string Command { get; set; }
2546
2547 [JsonProperty(PropertyName = "Currency used for purchase costs (Scrap, Economics, ServerRewards)")]
2548 public string Currency { get; set; }
2549
2550 [JsonProperty(PropertyName = "Log kits given")]
2551 public bool LogKitsGiven { get; set; }
2552
2553 [JsonProperty(PropertyName = "Wipe player data when the server is wiped")]
2554 public bool WipeData { get; set; }
2555
2556 [JsonProperty(PropertyName = "Use the Kits UI menu")]
2557 public bool UseUI { get; set; }
2558
2559 [JsonProperty(PropertyName = "Allow players to toggle auto-kits on spawn")]
2560 public bool AllowAutoToggle { get; set; }
2561
2562 [JsonProperty(PropertyName = "Show kits with permissions assigned to players without the permission")]
2563 public bool ShowPermissionKits { get; set; }
2564
2565 [JsonProperty(PropertyName = "Players with the admin permission ignore usage restrictions")]
2566 public bool AdminIgnoreRestrictions { get; set; }
2567
2568 [JsonProperty(PropertyName = "Autokits ordered by priority")]
2569 public List<string> AutoKits { get; set; }
2570
2571 [JsonProperty(PropertyName = "Post wipe cooldowns (kit name | seconds)")]
2572 public Hash<string, int> WipeCooldowns { get; set; }
2573
2574 [JsonProperty(PropertyName = "Parameters used when pasting a building via CopyPaste")]
2575 public string[] CopyPasteParams { get; set; }
2576
2577 [JsonProperty(PropertyName = "UI Options")]
2578 public MenuOptions Menu { get; set; }
2579
2580 [JsonProperty(PropertyName = "Kit menu items when opened via HumanNPC (NPC user ID | Items)")]
2581 public Hash<ulong, NPCKit> NPCKitMenu { get; set; }
2582
2583 public class MenuOptions
2584 {
2585 [JsonProperty(PropertyName = "Panel Color")]
2586 public UIColor Panel { get; set; }
2587
2588 [JsonProperty(PropertyName = "Disabled Color")]
2589 public UIColor Disabled { get; set; }
2590
2591 [JsonProperty(PropertyName = "Color 1")]
2592 public UIColor Color1 { get; set; }
2593
2594 [JsonProperty(PropertyName = "Color 2")]
2595 public UIColor Color2 { get; set; }
2596
2597 [JsonProperty(PropertyName = "Color 3")]
2598 public UIColor Color3 { get; set; }
2599
2600 [JsonProperty(PropertyName = "Color 4")]
2601 public UIColor Color4 { get; set; }
2602
2603 [JsonProperty(PropertyName = "Default kit image URL")]
2604 public string DefaultKitURL { get; set; }
2605
2606 [JsonProperty(PropertyName = "View kit icon URL")]
2607 public string MagnifyIconURL { get; set; }
2608 }
2609
2610 public class UIColor
2611 {
2612 public string Hex { get; set; }
2613 public float Alpha { get; set; }
2614
2615 [JsonIgnore]
2616 private string _color;
2617
2618 [JsonIgnore]
2619 public string Get
2620 {
2621 get
2622 {
2623 if (string.IsNullOrEmpty(_color))
2624 _color = UI.Color(Hex, Alpha);
2625 return _color;
2626 }
2627 }
2628 }
2629
2630 public class NPCKit
2631 {
2632 [JsonProperty(PropertyName = "The list of kits that can be claimed from this NPC")]
2633 public List<string> Kits { get; set; }
2634
2635 [JsonProperty(PropertyName = "The NPC's response to opening their kit menu")]
2636 public string Description { get; set; }
2637 }
2638
2639 public VersionNumber Version { get; set; }
2640 }
2641
2642 protected override void LoadConfig()
2643 {
2644 base.LoadConfig();
2645 Configuration = Config.ReadObject<ConfigData>();
2646
2647 if (Configuration.Version < Version)
2648 UpdateConfigValues();
2649
2650 Config.WriteObject(Configuration, true);
2651 }
2652
2653 protected override void LoadDefaultConfig() => Configuration = GetBaseConfig();
2654
2655 private ConfigData GetBaseConfig()
2656 {
2657 return new ConfigData
2658 {
2659 Command = "kit",
2660 Currency = "Scrap",
2661 LogKitsGiven = false,
2662 WipeData = false,
2663 ShowPermissionKits = false,
2664 UseUI = true,
2665 AllowAutoToggle = false,
2666 AutoKits = new List<string>
2667 {
2668 "ExampleKitName",
2669 "OtherKitName"
2670 },
2671 WipeCooldowns = new Hash<string, int>
2672 {
2673 ["ExampleKitName"] = 3600,
2674 ["OtherKitName"] = 600
2675 },
2676 CopyPasteParams = new string[] { "deployables", "true", "inventories", "true" },
2677 Menu = new ConfigData.MenuOptions
2678 {
2679 Panel = new ConfigData.UIColor { Hex = "#232323", Alpha = 1f },
2680 Disabled = new ConfigData.UIColor { Hex = "#3e3e42", Alpha = 1f },
2681 Color1 = new ConfigData.UIColor { Hex = "#007acc", Alpha = 1f },
2682 Color2 = new ConfigData.UIColor { Hex = "#6a8b38", Alpha = 1f },
2683 Color3 = new ConfigData.UIColor { Hex = "#d85540", Alpha = 1f },
2684 Color4 = new ConfigData.UIColor { Hex = "#d08822", Alpha = 1f },
2685 DefaultKitURL = "https://chaoscode.io/oxide/Images/kiticon.png",
2686 MagnifyIconURL = "https://chaoscode.io/oxide/Images/magnifyingglass.png"
2687 },
2688 NPCKitMenu = new Hash<ulong, ConfigData.NPCKit>
2689 {
2690 [0UL] = new ConfigData.NPCKit
2691 {
2692 Kits = new List<string>
2693 {
2694 "ExampleKitName",
2695 "OtherKitName"
2696 },
2697 Description = "Welcome to this server! Here are some free kits you can claim"
2698 },
2699 [1111UL] = new ConfigData.NPCKit
2700 {
2701 Kits = new List<string>
2702 {
2703 "ExampleKitName",
2704 "OtherKitName"
2705 },
2706 Description = "Welcome to this server! Here are some free kits you can claim"
2707 },
2708 },
2709 Version = Version
2710 };
2711 }
2712
2713 protected override void SaveConfig() => Config.WriteObject(Configuration, true);
2714
2715 private void UpdateConfigValues()
2716 {
2717 PrintWarning("Config update detected! Updating config values...");
2718
2719 ConfigData baseConfig = GetBaseConfig();
2720
2721 if (Configuration.Version < new VersionNumber(4, 0, 0))
2722 Configuration = baseConfig;
2723
2724 if (Configuration.Version < new VersionNumber(4, 0, 1))
2725 {
2726 Configuration.UseUI = true;
2727 Configuration.NPCKitMenu = baseConfig.NPCKitMenu;
2728 }
2729
2730 if (Configuration.Version < new VersionNumber(4, 0, 12))
2731 Configuration.Command = baseConfig.Command;
2732
2733 Configuration.Version = Version;
2734 PrintWarning("Config update completed!");
2735 }
2736
2737 #endregion
2738
2739 #region Data Management
2740 private KitData kitData;
2741 private PlayerData playerData;
2742
2743 private DynamicConfigFile kitdata;
2744 private DynamicConfigFile playerdata;
2745
2746 private void SaveKitData() => kitdata.WriteObject(kitData);
2747
2748 private void SavePlayerData() => playerdata.WriteObject(playerData);
2749
2750 private void LoadData()
2751 {
2752 kitdata = Interface.Oxide.DataFileSystem.GetFile("Kits/kits_data");
2753 playerdata = Interface.Oxide.DataFileSystem.GetFile("Kits/player_data");
2754
2755 kitData = kitdata.ReadObject<KitData>();
2756 playerData = playerdata.ReadObject<PlayerData>();
2757
2758 if (!kitData?.IsValid ?? true)
2759 kitData = new KitData();
2760
2761 if (!playerData?.IsValid ?? true)
2762 playerData = new PlayerData();
2763 }
2764
2765 private class KitData
2766 {
2767 [JsonProperty]
2768 private Dictionary<string, Kit> _kits = new Dictionary<string, Kit>(StringComparer.OrdinalIgnoreCase);
2769
2770 internal Kit this[string key]
2771 {
2772 get
2773 {
2774 Kit tValue;
2775 if (_kits.TryGetValue(key, out tValue))
2776 return tValue;
2777
2778 return null;
2779 }
2780 set
2781 {
2782 if (value == null)
2783 {
2784 _kits.Remove(key);
2785 return;
2786 }
2787 _kits[key] = value;
2788 }
2789 }
2790
2791 internal int Count => _kits.Count;
2792
2793 internal bool Find(string name, out Kit kit) => _kits.TryGetValue(name, out kit);
2794
2795 internal bool Exists(string name) => _kits.ContainsKey(name);
2796
2797 internal ICollection<string> Keys => _kits.Keys;
2798
2799 internal ICollection<Kit> Values => _kits.Values;
2800
2801 internal void ForEach(Action<Kit> action)
2802 {
2803 foreach(Kit kit in Values)
2804 {
2805 action.Invoke(kit);
2806 }
2807 }
2808
2809 internal void RegisterPermissions(Permission permission, Plugin plugin)
2810 {
2811 foreach(Kit kit in _kits.Values)
2812 {
2813 if (!string.IsNullOrEmpty(kit.RequiredPermission))
2814 {
2815 if(!permission.PermissionExists(kit.RequiredPermission, plugin))
2816 permission.RegisterPermission(kit.RequiredPermission, plugin);
2817 }
2818 }
2819 }
2820
2821 internal void RegisterImages(Plugin plugin)
2822 {
2823 Dictionary<string, string> loadOrder = new Dictionary<string, string>
2824 {
2825 [DEFAULT_ICON] = Configuration.Menu.DefaultKitURL,
2826 [MAGNIFY_ICON] = Configuration.Menu.MagnifyIconURL
2827 };
2828
2829 foreach (Kit kit in _kits.Values)
2830 {
2831 if (!string.IsNullOrEmpty(kit.KitImage))
2832 loadOrder.Add(kit.Name.Replace(" ", ""), kit.KitImage);
2833 }
2834
2835 plugin?.CallHook("ImportImageList", "Kits", loadOrder, 0UL, true, null);
2836 }
2837
2838 internal bool IsOnWipeCooldown(int seconds, out int remaining)
2839 {
2840 double currentTime = CurrentTime;
2841 double nextUseTime = LastWipeTime + seconds;
2842
2843 if (currentTime < nextUseTime)
2844 {
2845 remaining = Mathf.RoundToInt((float)nextUseTime - (float)currentTime);
2846 return true;
2847 }
2848
2849 remaining = 0;
2850 return false;
2851 }
2852
2853 internal void Remove(Kit kit) => _kits.Remove(kit.Name);
2854
2855 internal bool IsValid => _kits != null;
2856
2857 public class Kit
2858 {
2859 public string Name { get; set; } = string.Empty;
2860 public string Description { get; set; } = string.Empty;
2861 public string RequiredPermission { get; set; } = string.Empty;
2862
2863 public int MaximumUses { get; set; }
2864 public int RequiredAuth { get; set; }
2865 public int Cooldown { get; set; }
2866 public int Cost { get; set; }
2867
2868 public bool IsHidden { get; set; }
2869
2870 public string CopyPasteFile { get; set; } = string.Empty;
2871 public string KitImage { get; set; } = string.Empty;
2872
2873 public ItemData[] MainItems { get; set; } = new ItemData[0];
2874 public ItemData[] WearItems { get; set; } = new ItemData[0];
2875 public ItemData[] BeltItems { get; set; } = new ItemData[0];
2876
2877 [JsonIgnore]
2878 internal int ItemCount => MainItems.Length + WearItems.Length + BeltItems.Length;
2879
2880 [JsonIgnore]
2881 private JObject _jObject;
2882
2883 [JsonIgnore]
2884 internal JObject ToJObject
2885 {
2886 get
2887 {
2888 if (_jObject == null)
2889 {
2890 _jObject = new JObject
2891 {
2892 ["Name"] = Name,
2893 ["Description"] = Description,
2894 ["RequiredPermission"] = RequiredPermission,
2895 ["MaximumUses"] = MaximumUses,
2896 ["RequiredAuth"] = RequiredAuth,
2897 ["Cost"] = Cost,
2898 ["IsHidden"] = IsHidden,
2899 ["CopyPasteFile"] = CopyPasteFile,
2900 ["KitImage"] = KitImage,
2901 ["MainItems"] = new JArray(),
2902 ["WearItems"] = new JArray(),
2903 ["BeltItems"] = new JArray()
2904 };
2905
2906 for (int i = 0; i < MainItems.Length; i++)
2907 (_jObject["MainItems"] as JArray).Add(MainItems[i].ToJObject);
2908
2909 for (int i = 0; i < WearItems.Length; i++)
2910 (_jObject["WearItems"] as JArray).Add(WearItems[i].ToJObject);
2911
2912 for (int i = 0; i < BeltItems.Length; i++)
2913 (_jObject["BeltItems"] as JArray).Add(BeltItems[i].ToJObject);
2914 }
2915
2916 return _jObject;
2917 }
2918 }
2919
2920 internal static Kit CloneOf(Kit other)
2921 {
2922 Kit kit = new Kit();
2923
2924 kit.Name = other.Name;
2925 kit.Description = other.Description;
2926 kit.RequiredPermission = other.RequiredPermission;
2927
2928 kit.MaximumUses = other.MaximumUses;
2929 kit.RequiredAuth = other.RequiredAuth;
2930 kit.Cooldown = other.Cooldown;
2931 kit.Cost = other.Cost;
2932
2933 kit.IsHidden = other.IsHidden;
2934
2935 kit.CopyPasteFile = other.CopyPasteFile;
2936 kit.KitImage = other.KitImage;
2937
2938 ItemData[] array = kit.MainItems;
2939 Array.Resize(ref array, other.MainItems.Length);
2940 Array.Copy(other.MainItems, array, other.MainItems.Length);
2941 kit.MainItems = array;
2942
2943 array = kit.WearItems;
2944 Array.Resize(ref array, other.WearItems.Length);
2945 Array.Copy(other.WearItems, array, other.WearItems.Length);
2946 kit.WearItems = array;
2947
2948 array = kit.BeltItems;
2949 Array.Resize(ref array, other.BeltItems.Length);
2950 Array.Copy(other.BeltItems, array, other.BeltItems.Length);
2951 kit.BeltItems = array;
2952
2953 return kit;
2954 }
2955
2956 internal bool HasSpaceForItems(BasePlayer player)
2957 {
2958 int wearSpacesFree = 7 - player.inventory.containerWear.itemList.Count;
2959 int mainSpacesFree = 24 - player.inventory.containerMain.itemList.Count;
2960 int beltSpacesFree = 6 - player.inventory.containerBelt.itemList.Count;
2961
2962 return (wearSpacesFree >= WearItems.Length &&
2963 beltSpacesFree >= BeltItems.Length &&
2964 mainSpacesFree >= MainItems.Length) || ItemCount <= mainSpacesFree + beltSpacesFree;
2965 }
2966
2967 internal void GiveItemsTo(BasePlayer player)
2968 {
2969 List<ItemData> list = Facepunch.Pool.GetList<ItemData>();
2970
2971 GiveItems(MainItems, player.inventory.containerMain, ref list);
2972 GiveItems(WearItems, player.inventory.containerWear, ref list, true);
2973 GiveItems(BeltItems, player.inventory.containerBelt, ref list);
2974
2975 for (int i = 0; i < list.Count; i++)
2976 {
2977 Item item = CreateItem(list[i]);
2978
2979 if (!MoveToIdealContainer(player.inventory, item) && !item.MoveToContainer(player.inventory.containerMain, -1, true) && !item.MoveToContainer(player.inventory.containerBelt, -1, true))
2980 item.Drop(player.GetDropPosition(), player.GetDropVelocity());
2981 }
2982
2983 Facepunch.Pool.FreeList(ref list);
2984 }
2985
2986 private void GiveItems(ItemData[] items, ItemContainer container, ref List<ItemData> leftOverItems, bool isWearContainer = false)
2987 {
2988 for (int i = 0; i < items.Length; i++)
2989 {
2990 ItemData itemData = items[i];
2991 if (itemData.Amount < 1)
2992 continue;
2993
2994 if (container.GetSlot(itemData.Position) != null)
2995 leftOverItems.Add(itemData);
2996 else
2997 {
2998 Item item = CreateItem(itemData);
2999 if (!isWearContainer || (isWearContainer && item.info.isWearable && CanWearItem(container, item)))
3000 {
3001 item.position = itemData.Position;
3002 item.SetParent(container);
3003 }
3004 else
3005 {
3006 leftOverItems.Add(itemData);
3007 item.Remove(0f);
3008 }
3009 }
3010 }
3011 }
3012
3013 internal IEnumerable<Item> CreateItems()
3014 {
3015 for (int i = 0; i < MainItems.Length; i++)
3016 {
3017 ItemData itemData = MainItems[i];
3018 if (itemData.Amount > 0)
3019 yield return CreateItem(itemData);
3020 }
3021
3022 for (int i = 0; i < WearItems.Length; i++)
3023 {
3024 ItemData itemData = WearItems[i];
3025 if (itemData.Amount > 0)
3026 yield return CreateItem(itemData);
3027 }
3028
3029 for (int i = 0; i < BeltItems.Length; i++)
3030 {
3031 ItemData itemData = BeltItems[i];
3032 if (itemData.Amount > 0)
3033 yield return CreateItem(itemData);
3034 }
3035 }
3036
3037 private bool MoveToIdealContainer(PlayerInventory playerInventory, Item item)
3038 {
3039 if (item.info.isWearable && CanWearItem(playerInventory.containerWear, item))
3040 return item.MoveToContainer(playerInventory.containerWear, -1, false);
3041
3042 if (item.info.stackable > 1)
3043 {
3044 if (playerInventory.containerBelt != null && playerInventory.containerBelt.FindItemByItemID(item.info.itemid) != null)
3045 return item.MoveToContainer(playerInventory.containerBelt, -1, true);
3046
3047
3048 if (playerInventory.containerMain != null && playerInventory.containerMain.FindItemByItemID(item.info.itemid) != null)
3049 return item.MoveToContainer(playerInventory.containerMain, -1, true);
3050
3051 }
3052 if (item.info.HasFlag(ItemDefinition.Flag.NotStraightToBelt) || !item.info.isUsable)
3053 return item.MoveToContainer(playerInventory.containerMain, -1, true);
3054
3055 return item.MoveToContainer(playerInventory.containerBelt, -1, false);
3056 }
3057
3058 private bool CanWearItem(ItemContainer containerWear, Item item)
3059 {
3060 ItemModWearable itemModWearable = item.info.GetComponent<ItemModWearable>();
3061 if (itemModWearable == null)
3062 return false;
3063
3064 for (int i = 0; i < containerWear.itemList.Count; i++)
3065 {
3066 Item otherItem = containerWear.itemList[i];
3067 if (otherItem != null)
3068 {
3069 ItemModWearable otherModWearable = otherItem.info.GetComponent<ItemModWearable>();
3070 if (otherModWearable != null && !itemModWearable.CanExistWith(otherModWearable))
3071 return false;
3072 }
3073 }
3074
3075 return true;
3076 }
3077
3078 internal void CopyItemsFrom(BasePlayer player)
3079 {
3080 ItemData[] array = MainItems;
3081 CopyItems(ref array, player.inventory.containerMain, 24);
3082 MainItems = array;
3083
3084 array = WearItems;
3085 CopyItems(ref array, player.inventory.containerWear, 7);
3086 WearItems = array;
3087
3088 array = BeltItems;
3089 CopyItems(ref array, player.inventory.containerBelt, 6);
3090 BeltItems = array;
3091 }
3092
3093 private void CopyItems(ref ItemData[] array, ItemContainer container, int limit)
3094 {
3095 limit = Mathf.Min(container.itemList.Count, limit);
3096
3097 Array.Resize(ref array, limit);
3098
3099 for (int i = 0; i < limit; i++)
3100 array[i] = new ItemData(container.itemList[i]);
3101 }
3102
3103 internal void ClearItems()
3104 {
3105 ItemData[] array = MainItems;
3106 Array.Resize(ref array, 0);
3107 MainItems = array;
3108
3109 array = WearItems;
3110 Array.Resize(ref array, 0);
3111 WearItems = array;
3112
3113 array = BeltItems;
3114 Array.Resize(ref array, 0);
3115 BeltItems = array;
3116 }
3117 }
3118 }
3119
3120 private class PlayerData
3121 {
3122 [JsonProperty]
3123 private Dictionary<ulong, PlayerUsageData> _players = new Dictionary<ulong, PlayerUsageData>();
3124
3125 internal bool Find(ulong playerId, out PlayerUsageData playerUsageData) => _players.TryGetValue(playerId, out playerUsageData);
3126
3127 internal bool Exists(ulong playerId) => _players.ContainsKey(playerId);
3128
3129 internal PlayerUsageData this[ulong key]
3130 {
3131 get
3132 {
3133 PlayerUsageData tValue;
3134 if (_players.TryGetValue(key, out tValue))
3135 return tValue;
3136
3137 tValue = (PlayerUsageData)Activator.CreateInstance(typeof(PlayerUsageData));
3138 _players.Add(key, tValue);
3139 return tValue;
3140 }
3141 set
3142 {
3143 if (value == null)
3144 {
3145 _players.Remove(key);
3146 return;
3147 }
3148 _players[key] = value;
3149 }
3150 }
3151
3152 internal void OnKitClaimed(BasePlayer player, KitData.Kit kit)
3153 {
3154 if (kit.MaximumUses == 0 && kit.Cooldown == 0)
3155 return;
3156
3157 PlayerUsageData playerUsageData;
3158 if (!_players.TryGetValue(player.userID, out playerUsageData))
3159 playerUsageData = _players[player.userID] = new PlayerUsageData();
3160
3161 playerUsageData.OnKitClaimed(kit);
3162 }
3163
3164 internal void Wipe() => _players.Clear();
3165
3166 internal bool IsValid => _players != null;
3167
3168 public class PlayerUsageData
3169 {
3170 [JsonProperty]
3171 private Hash<string, KitUsageData> _usageData = new Hash<string, KitUsageData>();
3172
3173 public bool ClaimAutoKits { get; set; } = true;
3174
3175 internal double GetCooldownRemaining(string name)
3176 {
3177 KitUsageData kitUsageData;
3178 if (!_usageData.TryGetValue(name, out kitUsageData))
3179 return 0;
3180
3181 double currentTime = CurrentTime;
3182
3183 return currentTime > kitUsageData.NextUseTime ? 0 : kitUsageData.NextUseTime - CurrentTime;
3184 }
3185
3186 internal void SetCooldownRemaining(string name, double seconds)
3187 {
3188 KitUsageData kitUsageData;
3189 if (!_usageData.TryGetValue(name, out kitUsageData))
3190 return;
3191
3192 kitUsageData.NextUseTime = CurrentTime + seconds;
3193 }
3194
3195 internal int GetKitUses(string name)
3196 {
3197 KitUsageData kitUsageData;
3198 if (!_usageData.TryGetValue(name, out kitUsageData))
3199 return 0;
3200
3201 return kitUsageData.TotalUses;
3202 }
3203
3204 internal void SetKitUses(string name, int amount)
3205 {
3206 KitUsageData kitUsageData;
3207 if (!_usageData.TryGetValue(name, out kitUsageData))
3208 return;
3209
3210 kitUsageData.TotalUses = amount;
3211 }
3212
3213 internal void OnKitClaimed(KitData.Kit kit)
3214 {
3215 KitUsageData kitUsageData;
3216 if (!_usageData.TryGetValue(kit.Name, out kitUsageData))
3217 kitUsageData = _usageData[kit.Name] = new KitUsageData();
3218
3219 kitUsageData.OnKitClaimed(kit.Cooldown);
3220 }
3221
3222 internal void InsertOldData(string name, int totalUses, double nextUse)
3223 {
3224 KitUsageData kitUsageData;
3225 if (!_usageData.TryGetValue(name, out kitUsageData))
3226 kitUsageData = _usageData[name] = new KitUsageData();
3227
3228 kitUsageData.NextUseTime = nextUse;
3229 kitUsageData.TotalUses = totalUses;
3230 }
3231
3232 public class KitUsageData
3233 {
3234 public int TotalUses { get; set; }
3235
3236 public double NextUseTime { get; set; }
3237
3238 internal void OnKitClaimed(int cooldownSeconds)
3239 {
3240 TotalUses += 1;
3241 NextUseTime = CurrentTime + cooldownSeconds;
3242 }
3243 }
3244 }
3245 }
3246 #endregion
3247
3248 #region Serialized Items
3249 private static Item CreateItem(ItemData itemData)
3250 {
3251 Item item = ItemManager.CreateByItemID(itemData.ItemID, itemData.Amount, itemData.Skin);
3252 item.condition = itemData.Condition;
3253 item.maxCondition = itemData.MaxCondition;
3254
3255 if (!string.IsNullOrEmpty(itemData.DisplayName))
3256 item.name = itemData.DisplayName;
3257
3258 if (!string.IsNullOrEmpty(itemData.Text))
3259 item.text = itemData.Text;
3260
3261 if (itemData.Frequency > 0)
3262 {
3263 ItemModRFListener rfListener = item.info.GetComponentInChildren<ItemModRFListener>();
3264 if (rfListener != null)
3265 (BaseNetworkable.serverEntities.Find(item.instanceData.subEntity) as PagerEntity)?.ChangeFrequency(itemData.Frequency);
3266 }
3267
3268 if (itemData.BlueprintItemID != 0)
3269 {
3270 if (item.instanceData == null)
3271 item.instanceData = new ProtoBuf.Item.InstanceData();
3272
3273 item.instanceData.ShouldPool = false;
3274
3275 item.instanceData.blueprintAmount = 1;
3276 item.instanceData.blueprintTarget = itemData.BlueprintItemID;
3277
3278 item.MarkDirty();
3279 }
3280
3281 FlameThrower flameThrower = item.GetHeldEntity() as FlameThrower;
3282 if (flameThrower != null)
3283 flameThrower.ammo = itemData.Ammo;
3284
3285 if (itemData.Contents != null)
3286 {
3287 foreach (ItemData contentData in itemData.Contents)
3288 {
3289 Item newContent = CreateItem(contentData);
3290 if (newContent != null)
3291 {
3292 if (!newContent.MoveToContainer(item.contents))
3293 newContent.Remove(0f);
3294 }
3295 }
3296 }
3297
3298 BaseProjectile weapon = item.GetHeldEntity() as BaseProjectile;
3299 if (weapon != null)
3300 {
3301 weapon.DelayedModsChanged();
3302
3303 if (!string.IsNullOrEmpty(itemData.Ammotype))
3304 weapon.primaryMagazine.ammoType = ItemManager.FindItemDefinition(itemData.Ammotype);
3305 weapon.primaryMagazine.contents = itemData.Ammo;
3306 }
3307
3308
3309 item.MarkDirty();
3310
3311 return item;
3312 }
3313
3314 public class ItemData
3315 {
3316 public string Shortname { get; set; }
3317
3318 public string DisplayName { get; set; }
3319
3320 public ulong Skin { get; set; }
3321
3322 public int Amount { get; set; }
3323
3324 public float Condition { get; set; }
3325
3326 public float MaxCondition { get; set; }
3327
3328 public int Ammo { get; set; }
3329
3330 public string Ammotype { get; set; }
3331
3332 public int Position { get; set; }
3333
3334 public int Frequency { get; set; }
3335
3336 public string BlueprintShortname { get; set; }
3337
3338 public string Text { get; set; }
3339
3340 public ItemData[] Contents { get; set; }
3341
3342
3343 [JsonIgnore]
3344 private int _itemId = 0;
3345
3346 [JsonIgnore]
3347 private int _blueprintItemId = 0;
3348
3349 [JsonIgnore]
3350 private JObject _jObject;
3351
3352
3353 [JsonIgnore]
3354 internal int ItemID
3355 {
3356 get
3357 {
3358 if (_itemId == 0)
3359 _itemId = ItemManager.itemDictionaryByName[Shortname].itemid;
3360 return _itemId;
3361 }
3362 }
3363
3364 [JsonIgnore]
3365 internal bool IsBlueprint => Shortname.Equals(BLUEPRINT_BASE);
3366
3367 [JsonIgnore]
3368 internal int BlueprintItemID
3369 {
3370 get
3371 {
3372 if (_blueprintItemId == 0 && !string.IsNullOrEmpty(BlueprintShortname))
3373 _blueprintItemId = ItemManager.itemDictionaryByName[BlueprintShortname].itemid;
3374 return _blueprintItemId;
3375 }
3376 }
3377
3378 [JsonIgnore]
3379 internal JObject ToJObject
3380 {
3381 get
3382 {
3383 if (_jObject == null)
3384 {
3385 _jObject = new JObject
3386 {
3387 ["Shortname"] = Shortname,
3388 ["DisplayName"] = DisplayName,
3389 ["SkinID"] = Skin,
3390 ["Amount"] = Amount,
3391 ["Condition"] = Condition,
3392 ["MaxCondition"] = MaxCondition,
3393 ["IsBlueprint"] = BlueprintItemID != 0,
3394 ["Ammo"] = Ammo,
3395 ["AmmoType"] = Ammotype,
3396 ["Text"] = Text,
3397 ["Contents"] = new JArray()
3398 };
3399
3400 for (int i = 0; i < Contents?.Length; i++)
3401 (_jObject["Contents"] as JArray).Add(Contents[i].ToJObject);
3402 }
3403
3404 return _jObject;
3405 }
3406 }
3407
3408 internal ItemData() { }
3409
3410 internal ItemData(Item item)
3411 {
3412 Shortname = item.info.shortname;
3413 Amount = item.amount;
3414 DisplayName = item.name;
3415 Text = item.text;
3416
3417 BaseEntity heldEntity = item.GetHeldEntity();
3418 if (heldEntity)
3419 {
3420 Ammotype = heldEntity is BaseProjectile ? (heldEntity as BaseProjectile).primaryMagazine.ammoType.shortname : null;
3421 Ammo = heldEntity is BaseProjectile ? (heldEntity as BaseProjectile).primaryMagazine.contents :
3422 heldEntity is FlameThrower ? (heldEntity as FlameThrower).ammo : 0;
3423 }
3424
3425 Position = item.position;
3426 Skin = item.skin;
3427
3428 Condition = item.condition;
3429 MaxCondition = item.maxCondition;
3430
3431 Frequency = ItemModAssociatedEntity<PagerEntity>.GetAssociatedEntity(item)?.GetFrequency() ?? -1;
3432
3433 if (item.instanceData != null && item.instanceData.blueprintTarget != 0)
3434 BlueprintShortname = ItemManager.FindItemDefinition(item.instanceData.blueprintTarget).shortname;
3435
3436 Contents = item.contents?.itemList.Select(item1 => new ItemData(item1)).ToArray();
3437 }
3438
3439 public class InstanceData
3440 {
3441 public int DataInt { get; set; }
3442
3443 public int BlueprintTarget { get; set; }
3444
3445 public int BlueprintAmount { get; set; }
3446
3447 public uint SubEntityNetID { get; set; }
3448
3449 internal InstanceData() { }
3450
3451 internal InstanceData(Item item)
3452 {
3453 if (item.instanceData == null)
3454 return;
3455
3456 DataInt = item.instanceData.dataInt;
3457 BlueprintAmount = item.instanceData.blueprintAmount;
3458 BlueprintTarget = item.instanceData.blueprintTarget;
3459 }
3460
3461 internal void Restore(Item item)
3462 {
3463 if (item.instanceData == null)
3464 item.instanceData = new ProtoBuf.Item.InstanceData();
3465
3466 item.instanceData.ShouldPool = false;
3467
3468 item.instanceData.blueprintAmount = BlueprintAmount;
3469 item.instanceData.blueprintTarget = BlueprintTarget;
3470 item.instanceData.dataInt = DataInt;
3471
3472 item.MarkDirty();
3473 }
3474
3475 internal bool IsValid => DataInt != 0 || BlueprintAmount != 0 || BlueprintTarget != 0;
3476 }
3477 }
3478 #endregion
3479
3480 #region Localization
3481 private string Message(string key, ulong playerId = 0U) => lang.GetMessage(key, this, playerId != 0U ? playerId.ToString() : null);
3482
3483 private readonly Dictionary<string, string> Messages = new Dictionary<string, string>
3484 {
3485 ["Error.EmptyKitName"] = "No kit name was specified",
3486 ["Error.InvalidKitName"] = "No kit exists with the specified name",
3487 ["Error.CantClaimNow"] = "Another plugin is preventing you from receiving a kit",
3488 ["Error.CanClaim.Auth"] = "You do not have the required auth level to access this kit",
3489 ["Error.CanClaim.Permission"] = "You do not have the required permission to access this kit",
3490 ["Error.CanClaim.Cooldown"] = "You have a cooldown of {0} remaining before you can claim this kit",
3491 ["Error.CanClaim.MaxUses"] = "You have already reached the maximum number of uses allowed for this kit",
3492 ["Error.CanClaim.WipeCooldown"] = "This kit has a post-wipe cooldown of {0} remaining",
3493 ["Error.CanClaim.InventorySpace"] = "You do not have enough space in your inventory to claim this kit",
3494 ["Error.CanClaim.InsufficientFunds"] = "You need {0} {1} to claim this kit",
3495 ["Error.AutoKitDisabled"] = "Skipped giving auto-kit as you have it disabled",
3496
3497
3498 ["Cost.Scrap"] = "Scrap",
3499 ["Cost.ServerRewards"] = "RP",
3500 ["Cost.Economics"] = "Coins",
3501
3502 ["Notification.KitReceived"] = "You have received the kit: <color=#ce422b>{0}</color>",
3503
3504 ["Chat.Help.Title"] = "<size=18><color=#ce422b>Kits </color></size><size=14>v{0}</size>",
3505 ["Chat.Help.1"] = "<color=#ce422b>/kit</color> - Open the Kit menu",
3506 ["Chat.Help.9"] = "<color=#ce422b>/kit autokit</color> - Toggle auto-kits on/off",
3507 ["Chat.Help.2"] = "<color=#ce422b>/kit <name></color> - Claim the specified kit",
3508 ["Chat.Help.3"] = "<color=#ce422b>/kit new</color> - Create a new kit",
3509 ["Chat.Help.4"] = "<color=#ce422b>/kit edit <name></color> - Edit the specified kit",
3510 ["Chat.Help.5"] = "<color=#ce422b>/kit delete <name></color> - Delete the specified kit",
3511 ["Chat.Help.6"] = "<color=#ce422b>/kit list</color> - List all kits",
3512 ["Chat.Help.7"] = "<color=#ce422b>/kit give <player name or ID> <kit name></color> - Give the target player the specified kit",
3513 ["Chat.Help.8"] = "<color=#ce422b>/kit givenpc <kit name></color> - Give the NPC you are looking at the specified kit",
3514 ["Chat.Help.10"] = "<color=#ce422b>/kit reset</color> - Wipe's all player usage data",
3515
3516 ["Chat.Error.NotAdmin"] = "You must either be a admin, or have the admin permission to use that command",
3517 ["Chat.Error.NoKit"] = "You must specify a kit name",
3518 ["Chat.Error.DoesntExist"] = "The kit <color=#ce422b>{0}</color> does not exist",
3519 ["Chat.Error.GiveArgs"] = "You must specify target player and a kit name",
3520 ["Chat.Error.NPCGiveArgs"] = "You must specify a kit name",
3521 ["Chat.Error.NoNPCTarget"] = "Failed to find the target player",
3522 ["Chat.Error.NoPlayer"] = "Failed to find a player with the specified name or ID",
3523 ["Chat.KitList"] = "<color=#ce422b>Kit List:</color> {0}",
3524 ["Chat.KitDeleted"] = "You have deleted the kit <color=#ce422b>{0}</color>",
3525 ["Chat.KitGiven"] = "You have given <color=#ce422b>{0}</color> the kit <color=#ce422b>{1}</color>",
3526 ["Chat.AutoKit.Toggle"] = "Auto-kits have been <color=#ce422b>{0}</color>",
3527 ["Chat.ResetPlayers"] = "You have wiped player usage data",
3528 ["Chat.AutoKit.True"] = "enabled",
3529 ["Chat.AutoKit.False"] = "disabled",
3530
3531 ["UI.Title"] = "Kits",
3532 ["UI.Title.Editor"] = "Kit Editor",
3533 ["UI.OnCooldown"] = "On Cooldown",
3534 ["UI.Cooldown"] = "Cooldown : {0}",
3535 ["UI.MaximumUses"] = "At Redeem Limit",
3536 ["UI.Purchase"] = "Purchase",
3537 ["UI.Cost"] = "Cost : {0} {1}",
3538 ["UI.Redeem"] = "Redeem",
3539 ["UI.Details"] = "Kit Details",
3540 ["UI.Name"] = "Name",
3541 ["UI.Description"] = "Description",
3542 ["UI.Usage"] = "Usage Details",
3543 ["UI.MaxUses"] = "Maximum Uses",
3544 ["UI.YourUses"] = "Your Uses",
3545 ["UI.CooldownTime"] = "Cooldown Time",
3546 ["UI.CooldownRemaining"] = "Remaining Cooldown",
3547 ["UI.None"] = "None",
3548 ["UI.PurchaseCost"] = "Purchase Cost",
3549 ["UI.CopyPaste"] = "CopyPaste Support",
3550 ["UI.FileName"] = "File Name",
3551 ["UI.KitItems"] = "Kit Items",
3552 ["UI.MainItems"] = "Main Items",
3553 ["UI.WearItems"] = "Wear Items",
3554 ["UI.BeltItems"] = "Belt Items",
3555 ["UI.IconURL"] = "Icon URL",
3556 ["UI.UsageAuthority"] = "Usage Authority",
3557 ["UI.Permission"] = "Permission",
3558 ["UI.AuthLevel"] = "Auth Level",
3559 ["UI.IsHidden"] = "Is Hidden",
3560 ["UI.CooldownSeconds"] = "Cooldown (seconds)",
3561 ["UI.SaveKit"] = "Save Kit",
3562 ["UI.Overwrite"] = "Overwrite Existing",
3563 ["UI.ItemManagement"] = "Item Management",
3564 ["UI.ClearItems"] = "Clear Items",
3565 ["UI.CopyInv"] = "Copy From Inventory",
3566 ["UI.CreateNew"] = "Create New",
3567 ["UI.EditKit"] = "Edit Kit",
3568 ["UI.NeedsPermission"] = "VIP Kit",
3569 ["UI.NoKitsAvailable"] = "There are currently no kits available",
3570
3571 ["SaveKit.Error.NoName"] = "You must enter a kit name",
3572 ["SaveKit.Error.NoContents"] = "A kit must contain atleast 1 item, or a CopyPaste file reference",
3573 ["SaveKit.Error.Exists"] = "A kit with that name already exists. If you want to overwrite it check the 'Overwrite' toggle",
3574 ["SaveKit.Success"] = "You have saved the kit {0}",
3575
3576 ["EditKit.PermissionPrefix"] = "Permissions must start with the 'kits.' prefix",
3577 ["EditKit.Number"] = "You must enter a number",
3578 ["EditKit.Bool"] = "You must enter true or false",
3579 };
3580 #endregion
3581 }
3582}
3583