· 5 years ago · Nov 10, 2020, 01:28 PM
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Text.RegularExpressions;
6using Facepunch;
7using Network;
8using Newtonsoft.Json;
9using Oxide.Core;
10using Oxide.Core.Configuration;
11using Oxide.Core.Libraries.Covalence;
12using Oxide.Core.Plugins;
13using Oxide.Game.Rust.Cui;
14using Rust;
15using Rust.Workshop;
16using UnityEngine;
17using Random = UnityEngine.Random;
18
19// TODO: Sort ladder by WLR
20
21namespace Oxide.Plugins
22{
23 [Info("Duelist", "nivex/MON@H", "1.2.531")]
24 [Description("1v1 and team deathmatch event.")]
25 public class Duelist : RustPlugin
26 {
27 public enum Team
28 {
29 Good = 0,
30 Evil = 1,
31 None = 2
32 }
33
34 private static readonly string hewwPrefab = "assets/prefabs/building/wall.external.high.wood/wall.external.high.wood.prefab";
35 private static readonly string heswPrefab = "assets/prefabs/building/wall.external.high.stone/wall.external.high.stone.prefab";
36 private static Duelist ins;
37 private static bool init; // are we initialized properly? if not disable certain functionality
38 private readonly bool debugMode = false;
39
40 private static readonly List<string> readyUiList = new List<string>();
41 private static readonly List<string> spectators = new List<string>();
42 private static readonly List<Rematch> rematches = new List<Rematch>();
43 private static readonly Dictionary<string, AttackerInfo> tdmAttackers = new Dictionary<string, AttackerInfo>();
44 private static readonly Dictionary<string, string> tdmKits = new Dictionary<string, string>();
45 private static readonly HashSet<GoodVersusEvilMatch> tdmMatches = new HashSet<GoodVersusEvilMatch>();
46 private static readonly List<DuelingZone> duelingZones = new List<DuelingZone>(); // where all the fun is at
47 private static StoredData duelsData = new StoredData();
48 private static readonly Dictionary<string, string> dataDuelists = new Dictionary<string, string>(); // active duelers
49 private static readonly Dictionary<string, long> dataImmunity = new Dictionary<string, long>(); // players immune to damage
50 private static readonly Dictionary<string, Vector3> dataImmunitySpawns = new Dictionary<string, Vector3>(); // players spawn points
51 private readonly int blockedMask = LayerMask.GetMask("Player (Server)", "Prevent Building", "Construction", "Deployed", "Trigger"); // layers we won't be setting a zone within 50 meters of
52 private readonly int constructionMask = LayerMask.GetMask("Construction", "Deployed");
53
54 private static bool matchUpdateRequired;
55 private readonly int groundMask = LayerMask.GetMask("Terrain", "World", "Default"); // used to find dueling zone/set custom zone and create spawn points
56 private readonly int wallMask = LayerMask.GetMask("Terrain", "World", "Default", "Construction", "Deployed");
57 private readonly int waterMask = LayerMask.GetMask("Water"); // used to count water colliders when finding a random dueling zone on the map
58 private readonly int worldMask = LayerMask.GetMask("World");
59 private Timer announceTimer;
60
61 private readonly SortedDictionary<string, string> boneTags = new SortedDictionary<string, string>
62 {
63 ["r_"] = "Right ",
64 ["l_"] = "Left ",
65 [".prefab"] = string.Empty,
66 ["1"] = string.Empty,
67 ["2"] = string.Empty,
68 ["3"] = string.Empty,
69 ["4"] = string.Empty,
70 ["END"] = string.Empty,
71 ["_"] = " ",
72 ["."] = " "
73 };
74
75 private static readonly Dictionary<string, string> announcements = new Dictionary<string, string>(); // users id and announcement
76 private readonly Dictionary<string, long> dataDeath = new Dictionary<string, long>(); // users id and timestamp of when they're to be executed
77 private readonly Dictionary<string, string> dataRequests = new Dictionary<string, string>(); // users requesting a duel and to whom
78 private readonly Dictionary<string, bool> deployables = new Dictionary<string, bool>();
79 private readonly Dictionary<ulong, List<BaseEntity>> duelEntities = new Dictionary<ulong, List<BaseEntity>>();
80 private DynamicConfigFile duelsFile;
81 private Timer eventTimer; // timer to check for immunity and auto death time of duelers
82 private Timer matchTimer; // timer to check for updates to the match ui
83 private readonly SpawnFilter filter = new SpawnFilter(); // RandomDropPosition()
84
85 [PluginReference] private Plugin Kits, ZoneManager, Economics, ServerRewards, LustyMap, Backpacks, PermaMap;
86
87 private readonly Dictionary<Vector3, float> managedZones = new Dictionary<Vector3, float>(); // blocked zones from zonemanager plugin
88 private List<Vector3> monuments = new List<Vector3>(); // positions of monuments on the server
89 private readonly Dictionary<string, string> prefabs = new Dictionary<string, string>();
90 private bool resetDuelists; // if wipe is detected then assign awards and wipe VictoriesSeed / LossesSeed
91 private readonly Dictionary<string, List<ulong>> skinsCache = new Dictionary<string, List<ulong>>(); // used to randomize custom kit skins which skin id values are 0
92 private readonly Dictionary<string, string> tdmRequests = new Dictionary<string, string>(); // users requesting a deathmatch and to whom
93 private readonly Dictionary<string, List<ulong>> workshopskinsCache = new Dictionary<string, List<ulong>>();
94 private readonly List<string> dcsBlock = new List<string>(); // users blocked from 1v1 for 60 seconds after suiciding or disconnecting
95 private readonly Dictionary<string, string> playerZones = new Dictionary<string, string>(); // id, set zone name
96
97 public class StoredData
98 {
99 public List<string> Allowed = new List<string>(); // list of users that allow duel requests
100 public Dictionary<string, List<string>> AutoGeneratedSpawns = new Dictionary<string, List<string>>();
101 public Dictionary<string, string> Bans = new Dictionary<string, string>(); // users banned from dueling
102 public Dictionary<string, BetInfo> Bets = new Dictionary<string, BetInfo>(); // active bets users have placed
103
104 public Dictionary<string, List<string>> BlockedUsers = new Dictionary<string, List<string>>(); // users and the list of players they blocked from requesting duels with
105 public List<string> Chat = new List<string>(); // user ids of those who opted out of seeing duel death messages
106 public List<string> ChatEx = new List<string>(); // user ids of those who opted to see duel death messages when the config blocks them for all players
107 public Dictionary<string, List<BetInfo>> ClaimBets = new Dictionary<string, List<BetInfo>>(); // active bets users need to claim after winning a bet
108 public Dictionary<string, string> CustomKits = new Dictionary<string, string>(); // userid and custom kit
109 public bool DuelsEnabled; // enable/disable dueling for all players (not admins)
110 public Dictionary<string, string> Homes = new Dictionary<string, string>(); // user id and location of where they teleported from
111 public Dictionary<string, string> Kits = new Dictionary<string, string>(); // userid and kit. give kit when they wake up inside of the dueling zone
112
113 public Dictionary<string, int> Losses = new Dictionary<string, int>(); // user id / losses for lifetime
114 public Dictionary<string, int> LossesSeed = new Dictionary<string, int>(); // user id / losses for seed
115 public SortedDictionary<long, string> Queued = new SortedDictionary<long, string>(); // queued duelers sorted by timestamp and user id. first come first serve
116 public List<string> Restricted = new List<string>(); // list of users blocked from requesting a duel for 60 seconds
117 public List<string> Spawns = new List<string>(); // custom spawn points
118 public List<string> AutoReady = new List<string>();
119
120 public Dictionary<string, int> MatchVictories = new Dictionary<string, int>(); // player name & total wins
121 public Dictionary<string, int> MatchVictoriesSeed = new Dictionary<string, int>(); // player name & wins for current seed
122 public Dictionary<string, int> MatchLosses = new Dictionary<string, int>(); // player name & total losses
123 public Dictionary<string, int> MatchLossesSeed = new Dictionary<string, int>(); // player name & losses for current seed
124 public Dictionary<string, int> MatchDeaths = new Dictionary<string, int>(); // player name & total deaths
125 public Dictionary<string, int> MatchDeathsSeed = new Dictionary<string, int>(); // player name & deaths for the seed
126 public Dictionary<string, int> MatchKills = new Dictionary<string, int>(); // player name & total kills
127 public Dictionary<string, int> MatchKillsSeed = new Dictionary<string, int>(); // player name & kills for current seed
128 public Dictionary<string, Dictionary<string, int>> MatchSizesVictories = new Dictionary<string, Dictionary<string, int>>(); // size, id, wins
129 public Dictionary<string, Dictionary<string, int>> MatchSizesVictoriesSeed = new Dictionary<string, Dictionary<string, int>>(); // size, id, wins seed
130 public Dictionary<string, Dictionary<string, int>> MatchSizesLosses = new Dictionary<string, Dictionary<string, int>>(); // size, id, losses
131 public Dictionary<string, Dictionary<string, int>> MatchSizesLossesSeed = new Dictionary<string, Dictionary<string, int>>(); // size, id, losses seed
132
133 public int TotalDuels; // the total amount of duels ever played on the server
134 public Dictionary<string, int> Victories = new Dictionary<string, int>(); // user id / wins for lifetime
135 public Dictionary<string, int> VictoriesSeed = new Dictionary<string, int>(); // user id / wins for seed
136 public List<string> ZoneIds = new List<string>(); // the locations of each dueling zone
137 public Dictionary<string, string> DuelZones = new Dictionary<string, string>(); // location, name
138 public Dictionary<ulong, List<SaveItem>> Items = new Dictionary<ulong, List<SaveItem>>(); // Items from players inventory
139 }
140
141 public class SaveItem
142 {
143 public string ShortName { get; set; }
144 public int Amount { get; set; }
145 public int Blueprint { get; set; }
146 public ulong SkinID { get; set; }
147 public string Container { get; set; }
148 public float Condition { get; set; }
149 public int Change { get; set; }
150 public int DataInt { get; set; }
151 public string Name { get; set; }
152 public string Text { get; set; }
153 public Weapon Weapon { get; set; }
154 public List<ItemContent> Content { get; set; }
155 }
156
157 public class Weapon
158 {
159 public string ammoType { get; set; }
160 public int ammoAmount { get; set; }
161 }
162
163 public class ItemContent
164 {
165 public string ShortName { get; set; }
166 public float Condition { get; set; }
167 public int Amount { get; set; }
168 }
169
170 private const int targetLayer = ~(Layers.Mask.Invisible | Layers.Mask.Trigger | Layers.Mask.Prevent_Movement | Layers.Mask.Prevent_Building); // credits ZoneManager
171
172 class Tracker : FacepunchBehaviour
173 {
174 public BasePlayer player;
175
176 void Awake()
177 {
178 player = GetComponent<BasePlayer>();
179 InvokeRepeating(new Action(Track), 0f, 0.5f);
180 }
181
182 void Track()
183 {
184 if (player == null || player.transform == null)
185 {
186 GameObject.Destroy(this);
187 return;
188 }
189
190 if (dataDuelists.ContainsKey(player.UserIDString) || tdmMatches.Any(team => team.GetTeam(player) != Team.None))
191 {
192 if (!DuelTerritory(player.transform.position))
193 {
194 player.inventory.Strip();
195 GameObject.Destroy(this);
196 }
197 }
198 }
199
200 void OnDestroy()
201 {
202 CancelInvoke(new Action(Track));
203 GameObject.Destroy(this);
204 }
205 }
206
207 public class PlayerComparer : IEqualityComparer<BasePlayer>
208 {
209 public bool Equals(BasePlayer player, BasePlayer target)
210 {
211 return player != null && target != null && player.UserIDString == target.UserIDString;
212 }
213
214 public int GetHashCode(BasePlayer player)
215 {
216 return player?.UserIDString.GetHashCode() ?? 0;
217 }
218 }
219
220 public class Rematch
221 {
222 public List<BasePlayer> Duelists { get; } = new List<BasePlayer>();
223 public List<BasePlayer> Ready { get; } = new List<BasePlayer>();
224 private List<BasePlayer> Evil { get; } = new List<BasePlayer>();
225 private List<BasePlayer> Good { get; } = new List<BasePlayer>();
226 public GoodVersusEvilMatch match { get; set; }
227 private Timer _notify { get; set; }
228
229 public List<BasePlayer> Players
230 {
231 get
232 {
233 Duelists.RemoveAll(player => !player || !player.IsConnected);
234 Good.RemoveAll(player => !player || !player.IsConnected);
235 Evil.RemoveAll(player => !player || !player.IsConnected);
236 Ready.RemoveAll(player => !player || !player.IsConnected);
237
238 return match == null ? Duelists : Good.Union(Evil, new PlayerComparer()).ToList();
239 }
240 }
241
242 public bool HasPlayer(BasePlayer player)
243 {
244 return Players.Contains(player);
245 }
246
247 public bool AddRange(List<BasePlayer> players, Team team)
248 {
249 foreach (var player in players)
250 {
251 if (!player || !player.IsConnected || InEvent(player) || Good.Contains(player) || Evil.Contains(player))
252 break;
253
254 if (team == Team.Evil)
255 Evil.Add(player);
256 else
257 Good.Add(player);
258 }
259
260 return (team == Team.Evil ? Evil.Count : Good.Count) == players.Count;
261 }
262
263 public bool IsReady(BasePlayer player)
264 {
265 if (InEvent(player) || !ins.IsNewman(player) || duelsData.Bans.ContainsKey(player.UserIDString))
266 return false;
267
268 return true;
269 }
270
271 public bool IsReady()
272 {
273 if (Players.Any(player => !IsReady(player)))
274 {
275 Reset("RematchFailed2");
276 return false;
277 }
278
279 return Ready.Count == (match == null ? 2 : match.TeamSize * 2);
280 }
281
282 private void Reset(string key)
283 {
284 tdmMatches.Remove(match);
285 MessageAll(key);
286 Duelists.Clear();
287 Good.Clear();
288 Evil.Clear();
289 Ready.Clear();
290 _notify?.Destroy();
291 rematches.Remove(this);
292 matchUpdateRequired = true;
293 }
294
295 public void MessageAll(string key, params object[] args)
296 {
297 foreach (var player in Players)
298 {
299 player.ChatMessage(ins.msg(key, player.UserIDString, args ?? new string[0]));
300 }
301 }
302
303 public void Notify()
304 {
305 MessageAll("RematchNotify", 60f, match == null ? szDuelChatCommand : szMatchChatCommand);
306
307 foreach (var player in Players)
308 if (duelsData.AutoReady.Contains(player.UserIDString))
309 Ready.Add(player);
310
311 if (IsReady())
312 {
313 Start();
314 rematches.Remove(this);
315 }
316 else if (match == null || !match.IsPublic)
317 _notify = ins.timer.Once(60f, () => Cancel());
318 }
319
320 private void Cancel()
321 {
322 if (match != null && match.IsPublic)
323 return;
324
325 if (rematches.Contains(this))
326 {
327 if (sendHomeSpectatorWhenRematchTimesOut)
328 {
329 foreach (var player in Players)
330 {
331 if (IsSpectator(player))
332 {
333 EndSpectate(player);
334 ins.SendHome(player);
335 }
336 }
337 }
338
339 if (match != null)
340 {
341 match.Reuse();
342 }
343
344 tdmMatches.Remove(match);
345 Reset("RematchTimedOut");
346 }
347 }
348
349 public void Start()
350 {
351 if (match == null)
352 {
353 var player = Ready[0];
354 var target = Ready[1];
355
356 if (!ins.SelectZone(player, target))
357 {
358 player.ChatMessage(ins.msg("AllZonesFull", player.UserIDString, duelingZones.Count, playersPerZone));
359 target.ChatMessage(ins.msg("AllZonesFull", target.UserIDString, duelingZones.Count, playersPerZone));
360 }
361 }
362 else
363 {
364 match.Reuse();
365 tdmMatches.Add(match);
366
367 if (!AddMatchPlayers(Good, Team.Good) || !AddMatchPlayers(Evil, Team.Evil))
368 {
369 Reset("RematchFailed");
370 match.Reuse();
371 }
372 }
373
374 _notify?.Destroy();
375 rematches.Remove(this);
376 }
377
378 private bool AddMatchPlayers(List<BasePlayer> players, Team team)
379 {
380 foreach (var player in players)
381 if (!match.AddMatchPlayer(player, team))
382 return false;
383
384 return true;
385 }
386 }
387
388 public class AttackerInfo
389 {
390 public string AttackerName { get; set; } = "";
391 public string AttackerId { get; set; } = "";
392 public string BoneName { get; set; } = "";
393 public string Distance { get; set; } = "";
394 public string Weapon { get; set; } = "";
395 }
396
397 public class GoodVersusEvilMatch
398 {
399 private readonly HashSet<ulong> _banned = new HashSet<ulong>();
400 private readonly HashSet<BasePlayer> _evil = new HashSet<BasePlayer>();
401 private readonly HashSet<ulong> _evilKIA = new HashSet<ulong>();
402 private readonly List<BasePlayer> _evilRematch = new List<BasePlayer>();
403 private readonly HashSet<BasePlayer> _good = new HashSet<BasePlayer>();
404 private readonly HashSet<ulong> _goodKIA = new HashSet<ulong>();
405 private readonly List<BasePlayer> _goodRematch = new List<BasePlayer>();
406 private string _goodHostName { get; set; } = "";
407 private string _evilHostName { get; set; } = "";
408 private string _goodHostId { get; set; } = "";
409 private string _evilHostId { get; set; } = "";
410 private string _goodCode { get; set; } = "";
411 private string _evilCode { get; set; } = "";
412 private int _teamSize { get; set; } = 2;
413 private bool _started { get; set; }
414 private bool _ended { get; set; }
415 private string _kit { get; set; } = "";
416 private DuelingZone _zone { get; set; }
417 private Timer _queueTimer { get; set; }
418 private bool _enteredQueue { get; set; }
419 private bool _public { get; set; }
420 private bool _canRematch { get; set; } = true;
421
422 public bool CanRematch
423 {
424 get
425 {
426 return _canRematch;
427 }
428 set
429 {
430 _canRematch = value;
431 }
432 }
433
434 public string Id
435 {
436 get
437 {
438 return _goodHostId + _evilHostId;
439 }
440 }
441
442 public string Versus
443 {
444 get
445 {
446 return string.Format("{0} / {1} {2}v{2}", _goodHostName, _evilHostName, _teamSize);
447 }
448 }
449
450 public bool IsPublic
451 {
452 get
453 {
454 return _public;
455 }
456 set
457 {
458 _public = value;
459 matchUpdateRequired = true;
460 MessageAll(_public ? "MatchPublic" : "MatchPrivate");
461 }
462 }
463
464 public int TeamSize
465 {
466 get
467 {
468 return _teamSize;
469 }
470 set
471 {
472 if (IsStarted)
473 return;
474
475 _teamSize = value;
476 matchUpdateRequired = true;
477 MessageAll("MatchSizeChanged", _teamSize);
478 }
479 }
480
481 public DuelingZone Zone
482 {
483 get
484 {
485 return _zone;
486 }
487 }
488
489 public bool EitherEmpty
490 {
491 get
492 {
493 return _good.Count == 0 || _evil.Count == 0;
494 }
495 }
496
497 public bool IsStarted
498 {
499 get
500 {
501 return _started;
502 }
503 set
504 {
505 _started = value;
506 matchUpdateRequired = true;
507 }
508 }
509
510 public bool IsOver
511 {
512 get
513 {
514 return _ended;
515 }
516 set
517 {
518 _ended = value;
519 matchUpdateRequired = true;
520 }
521 }
522
523 public string Kit
524 {
525 get
526 {
527 return _kit;
528 }
529 set
530 {
531 _kit = value;
532
533 if (!EitherEmpty)
534 {
535 _good.RemoveWhere(target => !target || !target.IsConnected);
536 _evil.RemoveWhere(target => !target || !target.IsConnected);
537
538 foreach (var player in _good.Union(_evil, new PlayerComparer()))
539 duelsData.Kits[player.UserIDString] = _kit;
540
541 MessageAll("MatchKitSet", _kit);
542 }
543 }
544 }
545
546 public void Reuse()
547 {
548 if (_zone != null)
549 _zone.IsLocked = false;
550
551 _evilRematch.Clear();
552 _goodRematch.Clear();
553 _good.Clear();
554 _evil.Clear();
555 _goodKIA.Clear();
556 _evilKIA.Clear();
557 _started = false;
558 _ended = false;
559 _enteredQueue = false;
560 _kit = ins.GetRandomKit();
561 _goodHostId = BasePlayer.activePlayerList.FirstOrDefault(x => x.displayName == _goodHostName)?.UserIDString ?? _goodHostId;
562 _evilHostId = BasePlayer.activePlayerList.FirstOrDefault(x => x.displayName == _evilHostName)?.UserIDString ?? _evilHostId;
563 matchUpdateRequired = true;
564 }
565
566 public void Setup(BasePlayer player, BasePlayer target)
567 {
568 tdmMatches.Add(this);
569 _goodHostName = player.displayName;
570 _goodHostId = player.UserIDString;
571 _evilHostName = target.displayName;
572 _evilHostId = target.UserIDString;
573 _goodCode = Random.Range(10000, 99999).ToString();
574 _evilCode = Random.Range(10000, 99999).ToString();
575
576 if (_teamSize < minDeathmatchSize)
577 _teamSize = minDeathmatchSize;
578
579 AddMatchPlayer(player, Team.Good);
580 AddMatchPlayer(target, Team.Evil);
581
582 if (tdmKits.ContainsKey(player.UserIDString))
583 {
584 Kit = tdmKits[player.UserIDString];
585 tdmKits.Remove(player.UserIDString);
586 }
587 else if (tdmKits.ContainsKey(target.UserIDString))
588 {
589 Kit = tdmKits[target.UserIDString];
590 tdmKits.Remove(target.UserIDString);
591 }
592 else
593 Kit = ins.GetRandomKit();
594
595 if (TeamSize > 1)
596 {
597 player.ChatMessage(ins.msg("MatchOpened", player.UserIDString, szMatchChatCommand, _goodCode));
598 target.ChatMessage(ins.msg("MatchOpened", target.UserIDString, szMatchChatCommand, _evilCode));
599 }
600
601 matchUpdateRequired = true;
602 }
603
604 public bool IsFull()
605 {
606 return _good.Count == _teamSize && _evil.Count == _teamSize;
607 }
608
609 public bool IsFull(Team team)
610 {
611 return team == Team.Good ? _good.Count == _teamSize : _evil.Count == _teamSize;
612 }
613
614 public void MessageAll(string key, params object[] args)
615 {
616 Message(Team.Good, key, args);
617 Message(Team.Evil, key, args);
618 }
619
620 public void Message(Team team, string key, params object[] args)
621 {
622 foreach (var player in team == Team.Evil ? _evil : _good)
623 {
624 if (!player || !player.IsConnected) continue;
625 player.ChatMessage(ins.msg(key, player.UserIDString, args ?? new string[0]));
626 }
627 }
628
629 public Team GetTeam(BasePlayer player)
630 {
631 return _good.Contains(player) ? Team.Good : _evil.Contains(player) ? Team.Evil : Team.None;
632 }
633
634 public bool IsHost(BasePlayer player)
635 {
636 return player.UserIDString == _goodHostId || player.UserIDString == _evilHostId;
637 }
638
639 public void SetCode(BasePlayer player, string code)
640 {
641 if (GetTeam(player) == Team.Evil)
642 _evilCode = code;
643 else if (GetTeam(player) == Team.Good)
644 _goodCode = code;
645 }
646
647 public string Code(Team team)
648 {
649 return team == Team.Good ? _goodCode : _evilCode;
650 }
651
652 public bool AlliedTo(BasePlayer player, Team team)
653 {
654 return ins.IsAllied(player.UserIDString, team == Team.Good ? _goodHostId : _evilHostId);
655 }
656
657 public bool IsBanned(ulong targetId)
658 {
659 return _banned.Contains(targetId);
660 }
661
662 public bool Ban(BasePlayer target)
663 {
664 if (target.UserIDString == _goodHostId || target.UserIDString == _evilHostId || IsBanned(target.userID))
665 return false;
666
667 _banned.Add(target.userID);
668 RemoveMatchPlayer(target);
669 return true;
670 }
671
672 public bool Equals(GoodVersusEvilMatch match)
673 {
674 return match._good.Equals(_good) && match._evil.Equals(_evil);
675 }
676
677 public string GetNames(Team team)
678 {
679 return string.Join(", ", team == Team.Good ? _good.Select(player => player.displayName).ToArray() : _evil.Select(player => player.displayName).ToArray());
680 }
681
682 public void GiveShirt(BasePlayer player)
683 {
684 Item item = ItemManager.CreateByName(teamShirt, 1, GetTeam(player) == Team.Evil ? teamEvilShirt : teamGoodShirt);
685
686 if (item == null)
687 return;
688
689 if (item.info.category != ItemCategory.Attire)
690 {
691 item.Remove(0.01f);
692 return;
693 }
694
695 foreach (Item wear in player.inventory.containerWear.itemList)
696 {
697 if (wear.info.shortname.Contains("shirt"))
698 {
699 wear.RemoveFromContainer();
700 wear.Remove(0.01f);
701 break;
702 }
703 }
704
705 item.MoveToContainer(player.inventory.containerWear, -1, false);
706
707 if (!player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked))
708 player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, true);
709 }
710
711 public bool AddMatchPlayer(BasePlayer player, Team team)
712 {
713 if (_started)
714 {
715 player.ChatMessage(ins.msg("MatchStartedAlready", player.UserIDString));
716 return false;
717 }
718
719 _good.RemoveWhere(target => !target || !target.IsConnected);
720 _evil.RemoveWhere(target => !target || !target.IsConnected);
721
722 if (_banned.Contains(player.userID))
723 return false;
724
725 if (!ins.IsNewman(player))
726 {
727 player.ChatMessage(ins.msg("MustBeNaked", player.UserIDString));
728 return false;
729 }
730
731 switch (team)
732 {
733 case Team.Good:
734 if (_good.Count == _teamSize)
735 {
736 player.ChatMessage(ins.msg("MatchTeamFull", player.UserIDString, _teamSize));
737 return false;
738 }
739
740 _good.Add(player);
741 MessageAll("MatchJoinedTeam", player.displayName, _goodHostName, _good.Count, _teamSize, _evilHostName, _evil.Count);
742 break;
743 case Team.Evil:
744 if (_evil.Count == _teamSize)
745 {
746 player.ChatMessage(ins.msg("MatchTeamFull", player.UserIDString, _teamSize));
747 return false;
748 }
749
750 _evil.Add(player);
751 MessageAll("MatchJoinedTeam", player.displayName, _evilHostName, _evil.Count, _teamSize, _goodHostName, _good.Count);
752 break;
753 }
754
755 if (_good.Count == _teamSize && _evil.Count == _teamSize)
756 Queue();
757
758 return true;
759 }
760
761 public bool RemoveMatchPlayer(BasePlayer player)
762 {
763 if (player == null)
764 return false;
765
766 if (player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked))
767 player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, false);
768
769 ins.Metabolize(player, false);
770 ins.Track(player, false);
771 ins.RemoveEntities(player.userID);
772 Interface.Oxide.CallHook("DisableBypass", player.userID);
773
774 if (DuelTerritory(player.transform.position))
775 {
776 if (sendDefeatedHome)
777 ins.SendHome(player);
778 else
779 StartSpectate(player);
780 }
781
782 if (IsOver)
783 {
784 _good.Remove(player);
785 _evil.Remove(player);
786 return true;
787 }
788
789 if (_good.Remove(player))
790 {
791 if (_good.Count == 0)
792 {
793 if (_started)
794 {
795 _goodKIA.Add(player.userID);
796 _goodRematch.Add(player);
797 }
798 else
799 MessageAll("MatchNoPlayersLeft");
800
801 EndMatch(Team.Evil);
802 return true;
803 }
804 if (_started)
805 {
806 _goodKIA.Add(player.userID);
807 _goodRematch.Add(player);
808 }
809
810 if (player.UserIDString == _goodHostId)
811 AssignGoodHostId();
812
813 return true;
814 }
815
816 if (_evil.Remove(player))
817 {
818 if (_evil.Count == 0)
819 {
820 if (_started)
821 {
822 _evilKIA.Add(player.userID);
823 _evilRematch.Add(player);
824 }
825 else
826 MessageAll("MatchNoPlayersLeft");
827
828 EndMatch(Team.Good);
829 return true;
830 }
831 if (_started)
832 {
833 _evilKIA.Add(player.userID);
834 _evilRematch.Add(player);
835 }
836
837 if (player.UserIDString == _evilHostId)
838 AssignEvilHostId();
839
840 return true;
841 }
842
843 return false;
844 }
845
846 private void AssignGoodHostId()
847 {
848 _good.RemoveWhere(player => !player || !player.IsConnected);
849
850 if (_good.Count > 0)
851 {
852 _goodHostId = _good.First().UserIDString;
853 matchUpdateRequired = true;
854 }
855 else
856 EndMatch(Team.Evil);
857 }
858
859 private void AssignEvilHostId()
860 {
861 _evil.RemoveWhere(player => !player || !player.IsConnected);
862
863 if (_evil.Count > 0)
864 {
865 _evilHostId = _evil.First().UserIDString;
866 matchUpdateRequired = true;
867 }
868 else
869 EndMatch(Team.Good);
870 }
871
872 private void Finalize(Team team)
873 {
874 Interface.CallHook("OnDuelistFinalized", team == Team.Good ? _goodKIA : _evilKIA);
875
876 switch (team)
877 {
878 case Team.Evil:
879 {
880 foreach (ulong playerId in _goodKIA)
881 {
882 UpdateMatchStats(playerId.ToString(), false, true, false, false);
883 UpdateMatchSizeStats(playerId.ToString(), true, false, _teamSize);
884 }
885
886 foreach (ulong playerId in _evilKIA)
887 {
888 AwardPlayer(playerId, teamEconomicsMoney, teamServerRewardsPoints);
889 UpdateMatchStats(playerId.ToString(), true, false, false, false);
890 UpdateMatchSizeStats(playerId.ToString(), false, true, _teamSize);
891 }
892
893 break;
894 }
895 case Team.Good:
896 {
897 foreach (ulong playerId in _evilKIA)
898 {
899 UpdateMatchStats(playerId.ToString(), false, true, false, false);
900 UpdateMatchSizeStats(playerId.ToString(), true, false, _teamSize);
901 }
902
903 foreach (ulong playerId in _goodKIA)
904 {
905 AwardPlayer(playerId, teamEconomicsMoney, teamServerRewardsPoints);
906 UpdateMatchStats(playerId.ToString(), true, false, false, false);
907 UpdateMatchSizeStats(playerId.ToString(), false, true, _teamSize);
908 }
909
910 break;
911 }
912 }
913
914 _goodKIA.Clear();
915 _evilKIA.Clear();
916 }
917
918 private bool SetupRematch()
919 {
920 if (!CanRematch || _goodRematch.Any(player => !player || !player.IsConnected) || _evilRematch.Any(player => !player || !player.IsConnected))
921 return false;
922
923 var rematch = new Rematch();
924
925 if (rematch.AddRange(_evilRematch, Team.Evil))
926 {
927 if (rematch.AddRange(_goodRematch, Team.Good))
928 {
929 rematch.match = this;
930 rematches.Add(rematch);
931 rematch.Notify();
932 return true;
933 }
934 }
935
936 return false;
937 }
938
939 private void EndMatch(Team team)
940 {
941 if (!_ended && _started)
942 {
943 Finalize(team);
944 ins.Puts(ins.msg("MatchDefeat", null, team == Team.Evil ? _evilHostName : _goodHostName, team == Team.Evil ? _goodHostName : _evilHostName, _teamSize));
945 IsOver = true;
946 IsStarted = false;
947
948 foreach (var player in _evil.ToList())
949 {
950 RemoveMatchPlayer(player);
951
952 if (player != null && player.IsConnected && !_evilRematch.Contains(player))
953 _evilRematch.Add(player);
954 }
955
956 foreach (var player in _good.ToList())
957 {
958 RemoveMatchPlayer(player);
959
960 if (player != null && player.IsConnected && !_goodRematch.Contains(player))
961 _goodRematch.Add(player);
962 }
963
964 foreach (var target in BasePlayer.activePlayerList.Where(p => p?.displayName != null))
965 {
966 if (guiAnnounceUITime > 0f && (_goodKIA.Contains(target.userID) || _evilKIA.Contains(target.userID)))
967 CreateAnnouncementUI(target, ins.msg("MatchDefeat", target.UserIDString, team == Team.Evil ? _evilHostName : _goodHostName, team == Team.Evil ? _goodHostName : _evilHostName, _teamSize));
968
969 if (duelsData.Chat.Contains(target.UserIDString) && !_goodKIA.Contains(target.userID) && !_evilKIA.Contains(target.userID))
970 continue;
971
972 target.ChatMessage(ins.msg("MatchDefeat", target.UserIDString, team == Team.Evil ? _evilHostName : _goodHostName, team == Team.Evil ? _goodHostName : _evilHostName, _teamSize));
973 }
974
975 if (!SetupRematch())
976 {
977 foreach (var player in _evilRematch.Union(_goodRematch, new PlayerComparer()))
978 {
979 if (!player || !player.IsConnected) continue;
980 player.ChatMessage(ins.msg("RematchFailed2", player.UserIDString));
981 }
982
983 Reuse();
984 }
985 }
986
987 End();
988 }
989
990 public void End()
991 {
992 if (_zone != null)
993 _zone.IsLocked = false;
994
995 _queueTimer?.Destroy();
996 _good.RemoveWhere(player => !player);
997 _evil.RemoveWhere(player => !player);
998
999 foreach (var player in _good.Union(_evil, new PlayerComparer()))
1000 {
1001 if (player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked))
1002 player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, false);
1003
1004 if (IsStarted || IsOver)
1005 {
1006 if (DuelTerritory(player.transform.position))
1007 {
1008 player.inventory.Strip();
1009 ins.SendHome(player);
1010 }
1011
1012 ins.Metabolize(player, false);
1013 ins.Track(player, false);
1014 }
1015 }
1016
1017 _good.Clear();
1018 _evil.Clear();
1019 tdmMatches.Remove(this);
1020 matchUpdateRequired = true;
1021
1022 if (dataDuelists.Count == 0 && tdmMatches.Count == 0)
1023 ins.Unsubscribe(nameof(OnPlayerHealthChange));
1024 }
1025
1026 private void Queue()
1027 {
1028 DuelingZone zone = null;
1029
1030 foreach (var player in _good.Union(_evil, new PlayerComparer()))
1031 {
1032 if (!ins.IsNewman(player))
1033 {
1034 player.ChatMessage(ins.msg("MustBeNaked", player.UserIDString));
1035 MessageAll("MatchIsNotNaked", player.displayName);
1036 _queueTimer = ins.timer.Once(30f, () => Queue());
1037 return;
1038 }
1039
1040 if (zone == null)
1041 zone = ins.GetPlayerZone(player, TeamSize);
1042 }
1043
1044 var zones = duelingZones.Where(x => x.TotalPlayers == 0 && !x.IsLocked && x.Spawns.Count >= (requireTeamSize ? TeamSize * 2 : 2)).ToList();
1045
1046 if (zones == null || zones.Count == 0)
1047 {
1048 if (!_enteredQueue)
1049 {
1050 MessageAll("MatchQueued");
1051 _enteredQueue = true;
1052 }
1053
1054 _queueTimer = ins.timer.Once(2f, () => Queue());
1055 return;
1056 }
1057
1058 _zone = zone ?? LastZone ?? zones.GetRandom();
1059 _queueTimer?.Destroy();
1060 Start();
1061 }
1062
1063 public DuelingZone LastZone
1064 {
1065 get
1066 {
1067 DuelingZone zone = null;
1068
1069 if (_good.Any(player => DuelTerritory(player.transform.position)))
1070 zone = GetDuelZone(_good.First(player => DuelTerritory(player.transform.position)).transform.position);
1071
1072 if (_evil.Any(player => DuelTerritory(player.transform.position)))
1073 zone = GetDuelZone(_evil.First(player => DuelTerritory(player.transform.position)).transform.position);
1074
1075 return zone == null || zone.TotalPlayers > 0 || zone.IsLocked ? null : zone;
1076 }
1077 }
1078
1079 private void Start()
1080 {
1081 ins.SubscribeHooks(true);
1082
1083 var goodSpawn = _zone.Spawns.GetRandom();
1084 var evilSpawn = goodSpawn;
1085 float dist = -100f;
1086
1087 foreach (var spawn in _zone.Spawns) // get the furthest spawn point away from the good team and assign it to the evil team
1088 {
1089 float distance = Vector3.Distance(spawn, goodSpawn);
1090
1091 if (distance > dist)
1092 {
1093 dist = distance;
1094 evilSpawn = spawn;
1095 }
1096 }
1097
1098 Message(Team.Good, "MatchStarted", GetNames(Team.Evil));
1099 Message(Team.Evil, "MatchStarted", GetNames(Team.Good));
1100 _zone.IsLocked = true;
1101 IsStarted = true;
1102
1103 Spawn(_good, goodSpawn);
1104 Spawn(_evil, evilSpawn);
1105 }
1106
1107 private void Spawn(HashSet<BasePlayer> players, Vector3 spawn)
1108 {
1109 foreach (var player in players)
1110 {
1111 duelsData.Kits[player.UserIDString] = _kit;
1112
1113 if (!DuelTerritory(player.transform.position) || !duelsData.Homes.ContainsKey(player.UserIDString))
1114 {
1115 var ppos = player.transform.position;
1116 if (IsOnConstruction(ppos)) ppos.y += 1; // prevent player from becoming stuck or dying when teleported home
1117 duelsData.Homes[player.UserIDString] = ppos.ToString();
1118 }
1119
1120 RemoveFromQueue(player.UserIDString);
1121 ins.Teleport(player, spawn);
1122
1123 if (immunityTime >= 1)
1124 {
1125 dataImmunity[player.UserIDString] = TimeStamp() + immunityTime;
1126 dataImmunitySpawns[player.UserIDString] = spawn;
1127 }
1128 }
1129 }
1130 }
1131
1132 public class DuelKitItem
1133 {
1134 public string ammo;
1135 public int amount;
1136 public string container;
1137 public List<string> mods;
1138 public string shortname;
1139 public ulong skin;
1140 public int slot;
1141 }
1142
1143 public class BetInfo
1144 {
1145 public string trigger; // the trigger used to request this as a bet
1146 public int amount; // amount the player bet
1147 public int itemid; // the unique identifier of the item
1148 public int max; // the maximum amount allowed to bet on this item
1149
1150 public bool Equals(BetInfo bet)
1151 {
1152 return bet.amount == amount && bet.itemid == itemid;
1153 }
1154 }
1155
1156 public class DuelingZone : FacepunchBehaviour // Thanks @Jake_Rich for helping me get this started!
1157 {
1158 private HashSet<BasePlayer> _players = new HashSet<BasePlayer>();
1159 private HashSet<BasePlayer> _waiting = new HashSet<BasePlayer>();
1160 private Vector3 _zonePos;
1161 private List<Vector3> _duelSpawns = new List<Vector3>(); // spawn points generated on the fly
1162
1163 public bool IsLocked { get; set; }
1164
1165 public int Kills { get; set; }
1166
1167 public int TotalPlayers
1168 {
1169 get
1170 {
1171 return _players.Count;
1172 }
1173 }
1174
1175 public List<BasePlayer> Players
1176 {
1177 get
1178 {
1179 return _players.ToList();
1180 }
1181 }
1182
1183 public List<Vector3> Spawns
1184 {
1185 get
1186 {
1187 var spawns = GetSpawnPoints(this); // get custom spawn points if any exist
1188
1189 return spawns == null || spawns.Count < 2 ? _duelSpawns : spawns;
1190 }
1191 }
1192
1193 public bool IsFull
1194 {
1195 get
1196 {
1197 return TotalPlayers + _waiting.Count + 2 > playersPerZone || IsLocked;
1198 }
1199 }
1200
1201 public Vector3 Position
1202 {
1203 get
1204 {
1205 return _zonePos;
1206 }
1207 }
1208
1209 private void OnDestroy()
1210 {
1211 Destroy(this);
1212 }
1213
1214 private void OnTriggerEnter(Collider col)
1215 {
1216 ins.Puts("OnTriggerEnter: 1");
1217 var m = col?.ToBaseEntity() as BaseMountable;
1218 ins.Puts(m.ShortPrefabName);
1219
1220 if (m.IsValid())
1221 {
1222 ins.Puts("OnTriggerEnter: m.IsValid()");
1223 EjectMountable(m);
1224 }
1225 }
1226
1227 private void EjectMountable(BaseMountable m)
1228 {
1229 ins.Puts("EjectMountable: 1");
1230 var position = ((m.transform.position.XZ3D() - _zonePos.XZ3D()).normalized * (zoneRadius + 10f)) + _zonePos; // credits k1lly0u
1231 float y = TerrainMeta.HighestPoint.y + 250f;
1232
1233 RaycastHit hit;
1234 if (Physics.Raycast(position + new Vector3(0f, y, 0f), Vector3.down, out hit, position.y + y + 1f, targetLayer, QueryTriggerInteraction.Ignore))
1235 {
1236 ins.Puts("EjectMountable: 2");
1237 position.y = hit.point.y;
1238 }
1239 else position.y = GetSpawnHeight(position);
1240 ins.Puts("EjectMountable: 3");
1241
1242 if (m.transform.position.y > position.y)
1243 {
1244 ins.Puts("EjectMountable: 4");
1245 position.y = m.transform.position.y;
1246 }
1247
1248 if (m is MiniCopter)
1249 {
1250 ins.Puts("EjectMountable: 5");
1251 m.transform.position = position;
1252 }
1253 else m.transform.position = m.mountAnchor.transform.position = position;
1254
1255 ins.Puts("EjectMountable: 6");
1256 m.TransformChanged();
1257 }
1258
1259 private static float GetSpawnHeight(Vector3 target)
1260 {
1261 float y = TerrainMeta.HeightMap.GetHeight(target);
1262 float w = TerrainMeta.WaterMap.GetHeight(target);
1263 float p = TerrainMeta.HighestPoint.y + 250f;
1264 RaycastHit hit;
1265
1266 if (Physics.Raycast(new Vector3(target.x, w, target.z), Vector3.up, out hit, p, Layers.Mask.World))
1267 {
1268 y = Mathf.Max(y, hit.point.y);
1269
1270 if (Physics.Raycast(new Vector3(target.x, hit.point.y + 0.5f, target.z), Vector3.up, out hit, p, Layers.Mask.World))
1271 {
1272 y = Mathf.Max(y, hit.point.y);
1273 }
1274 }
1275
1276 return Mathf.Max(y, w);
1277 }
1278
1279 public void Setup(Vector3 position)
1280 {
1281 _zonePos = position;
1282 _duelSpawns = GetAutoSpawns(this);
1283
1284 var collider = gameObject.GetComponent<SphereCollider>() ?? gameObject.AddComponent<SphereCollider>();
1285 collider.radius = zoneRadius + 1.5f;
1286 collider.isTrigger = true;
1287 collider.center = Vector3.zero;
1288 gameObject.layer = (int)Layer.Trigger;
1289 }
1290
1291 public float Distance(Vector3 position)
1292 {
1293 position.y = 0f;
1294 return Vector3.Distance(new Vector3(_zonePos.x, 0f, _zonePos.z), position);
1295 }
1296
1297 public bool? AddWaiting(BasePlayer player, BasePlayer target)
1298 {
1299 if (IsFull)
1300 return false;
1301
1302 if (requiredDuelMoney > 0.0 && ins.Economics != null)
1303 {
1304 double playerMoney = Convert.ToDouble(ins.Economics.Call("Balance", player.userID));
1305 double targetMoney = Convert.ToDouble(ins.Economics.Call("Balance", target.userID));
1306
1307 if (playerMoney < requiredDuelMoney || targetMoney < requiredDuelMoney)
1308 {
1309 RemoveFromQueue(player.UserIDString);
1310 RemoveFromQueue(target.UserIDString);
1311 player.ChatMessage(ins.msg("MoneyRequired", player.UserIDString, requiredDuelMoney));
1312 target.ChatMessage(ins.msg("MoneyRequired", target.UserIDString, requiredDuelMoney));
1313 return null;
1314 }
1315
1316 bool playerWithdrawn = Convert.ToBoolean(ins.Economics.Call("Withdraw", player.userID, requiredDuelMoney));
1317 bool targetWithdrawn = Convert.ToBoolean(ins.Economics.Call("Withdraw", target.userID, requiredDuelMoney));
1318
1319 if (!playerWithdrawn || !targetWithdrawn)
1320 {
1321 RemoveFromQueue(player.UserIDString);
1322 RemoveFromQueue(target.UserIDString);
1323 return null;
1324 }
1325 }
1326
1327 _waiting.Add(player);
1328 _waiting.Add(target);
1329
1330 return true;
1331 }
1332
1333 public bool IsWaiting(BasePlayer player)
1334 {
1335 return _waiting.Contains(player);
1336 }
1337
1338 public void AddPlayer(BasePlayer player)
1339 {
1340 _waiting.Remove(player);
1341 _players.Add(player);
1342 }
1343
1344 public void RemovePlayer(string playerId)
1345 {
1346 _players.RemoveWhere(player => !player);
1347
1348 foreach (var player in _players.ToList())
1349 {
1350 if (player.UserIDString == playerId)
1351 {
1352 _players.Remove(player);
1353 _waiting.Remove(player);
1354 break;
1355 }
1356 }
1357 }
1358
1359 public bool HasPlayer(string playerId)
1360 {
1361 return _players.Any(player => player.UserIDString == playerId);
1362 }
1363
1364 public void Kill()
1365 {
1366 foreach (var player in _players.ToList())
1367 EjectPlayer(player);
1368
1369 foreach (var player in BasePlayer.activePlayerList.Union(BasePlayer.sleepingPlayerList, new PlayerComparer()))
1370 {
1371 if (Distance(player.transform.position) <= zoneRadius)
1372 {
1373 EndSpectate(player);
1374 ins.SendHome(player);
1375 }
1376 }
1377
1378 duelingZones.Remove(this);
1379 _players.Clear();
1380 Destroy(this);
1381 }
1382 }
1383
1384 private object OnDangerousOpen(Vector3 treasurePos)
1385 {
1386 return DuelTerritory(treasurePos) ? (object)false : null;
1387 }
1388
1389 private object OnPlayerDeathMessage(BasePlayer victim, HitInfo info) // private plugin hook
1390 {
1391 return DuelTerritory(victim.transform.position) ? (object)false : null;
1392 }
1393
1394 private void Init()
1395 {
1396 SubscribeHooks(false); // turn off all hooks immediately
1397 }
1398
1399 private void Loaded()
1400 {
1401 ins = this;
1402 LoadVariables();
1403
1404 monuments = UnityEngine.Object.FindObjectsOfType<MonumentInfo>().Select(monument => monument.transform.position).ToList();
1405 duelsFile = Interface.Oxide.DataFileSystem.GetFile(Name);
1406
1407 try
1408 {
1409 duelsData = duelsFile.ReadObject<StoredData>();
1410 }
1411 catch { }
1412
1413 if (duelsData == null)
1414 duelsData = new StoredData();
1415 }
1416
1417 private void OnServerInitialized()
1418 {
1419 foreach (var bet in duelingBets.ToList()) // 0.1.5 fix - check itemList after server has initialized
1420 {
1421 if (ItemManager.itemList.Find(def => def.itemid == bet.itemid) == null)
1422 {
1423 Puts("Bet itemid {0} is invalid.", bet.itemid);
1424 duelingBets.Remove(bet);
1425 }
1426 }
1427
1428 if (useAnnouncement)
1429 announceTimer = timer.Repeat(1800f, 0, () => DuelAnnouncement(false));
1430
1431 eventTimer = timer.Once(0.5f, () => CheckDuelistMortality()); // kill players who haven't finished their duel in time. remove temporary immunity for duelers when it expires
1432
1433 if (!resetDuelists && BuildingManager.server.buildingDictionary.Count == 0)
1434 {
1435 if (duelsData.VictoriesSeed.Count > 0 && duelsData.VictoriesSeed.Values.Any(x => x > 0))
1436 {
1437 resetDuelists = true;
1438 }
1439 }
1440
1441 if (resetDuelists) // map wipe detected - award duelers and reset the data for the seed only
1442 {
1443 ResetDuelists();
1444 resetDuelists = false;
1445 }
1446
1447 if (BasePlayer.activePlayerList.Count == 0)
1448 {
1449 RemoveZeroStats();
1450 ResetTemporaryData();
1451 }
1452
1453 if (ZoneManager != null)
1454 SetupZoneManager();
1455
1456 init = true;
1457 SetupZones();
1458
1459 if (duelingZones.Count > 0 && autoEnable)
1460 duelsData.DuelsEnabled = true;
1461
1462 UpdateStability();
1463 CheckZoneHooks(true);
1464
1465 if (guiAutoEnable)
1466 {
1467 Subscribe(nameof(OnPlayerConnected));
1468
1469 foreach (var player in BasePlayer.activePlayerList)
1470 OnPlayerConnected(player);
1471 }
1472
1473 if (useWorkshopSkins)
1474 webrequest.Enqueue("http://s3.amazonaws.com/s3.playrust.com/icons/inventory/rust/schema.json", null, GetWorkshopIDs, this, Core.Libraries.RequestMethod.GET);
1475 }
1476
1477 private void OnServerSave()
1478 {
1479 timer.Once(5f, () => SaveData());
1480 }
1481
1482 private void OnNewSave(string filename)
1483 {
1484 resetDuelists = true;
1485 }
1486
1487 public void SaveData()
1488 {
1489 if (duelsFile != null && duelsData != null)
1490 {
1491 duelsFile.WriteObject(duelsData);
1492 }
1493 }
1494
1495 private void DestroyAll()
1496 {
1497 foreach (var zone in duelingZones.ToList())
1498 {
1499 UnityEngine.Object.Destroy(zone.gameObject);
1500 }
1501 }
1502
1503 private void Unload()
1504 {
1505 var objects = UnityEngine.Object.FindObjectsOfType(typeof(Tracker));
1506
1507 if (objects != null)
1508 foreach (var gameObj in objects)
1509 UnityEngine.Object.Destroy(gameObj);
1510
1511 DestroyAll();
1512 announceTimer?.Destroy();
1513 eventTimer?.Destroy();
1514
1515 foreach (var match in tdmMatches.ToList())
1516 match.End();
1517
1518 foreach (var zone in duelingZones.ToList())
1519 {
1520 RemoveEntities(zone);
1521 zone.Kill();
1522 }
1523
1524 tdmMatches.Clear();
1525 duelingZones.Clear();
1526 ResetTemporaryData();
1527 DestroyAllUI();
1528 }
1529
1530 private void OnPluginLoaded(Plugin plugin)
1531 {
1532 if (plugin.Title == "Economics")
1533 Economics = plugin;
1534 else if (plugin.Title == "ServerRewards")
1535 ServerRewards = plugin;
1536 else if (plugin.Title == "Kits")
1537 Kits = plugin;
1538 else if (plugin.Title == "ZoneManager")
1539 ZoneManager = plugin;
1540 else if (plugin.Title == "LustyMap")
1541 LustyMap = plugin;
1542 else if (plugin.Title == "PermaMap")
1543 PermaMap = plugin;
1544 }
1545
1546 private void OnPluginUnloaded(Plugin plugin)
1547 {
1548 if (plugin.Title == "Economics")
1549 Economics = null;
1550 else if (plugin.Title == "ServerRewards")
1551 ServerRewards = null;
1552 else if (plugin.Title == "Kits")
1553 Kits = null;
1554 else if (plugin.Title == "ZoneManager")
1555 ZoneManager = null;
1556 else if (plugin.Title == "LustyMap")
1557 LustyMap = null;
1558 else if (plugin.Title == "PermaMap")
1559 PermaMap = null;
1560 }
1561
1562 private object CanNetworkTo(BasePlayer player, BasePlayer target)
1563 {
1564 if (player == null || target == null || player == target || visibleToAdmins && target.IsAdmin) // 0.1.3 fix: check if player is null
1565 return null;
1566
1567 if (dataDuelists.Count == 0 && spectators.Count == 0 && tdmMatches.Count == 0)
1568 {
1569 Unsubscribe(nameof(CanNetworkTo)); // nothing else to do right now, unsubscribe the hook
1570 return null;
1571 }
1572
1573 if (DuelTerritory(player.transform.position))
1574 {
1575 if (!player.IsConnected && player.inventory.AllItems().Count() == 0)
1576 return false;
1577
1578 if (dataDuelists.ContainsKey(player.UserIDString)) // 1v1 check
1579 return dataDuelists[player.UserIDString] == target.UserIDString ? null : (object)false;
1580
1581 if (spectators.Contains(player.UserIDString)) // spectator check
1582 return spectators.Contains(target.UserIDString) ? null : (object)false;
1583
1584 if (InMatch(player)) // tdm check
1585 return InMatch(target) ? null : (object)false;
1586 }
1587
1588 return null;
1589 }
1590
1591 private object CanNetworkTo(HeldEntity heldEntity, BasePlayer target)
1592 {
1593 return CanNetworkTo(heldEntity?.GetOwnerPlayer(), target); // 0.1.3 fix: check if player is null
1594 }
1595
1596 private void OnPlayerDisconnected(BasePlayer player, string reason)
1597 {
1598 var match = GetMatch(player);
1599
1600 if (match != null && !match.IsStarted && match.EitherEmpty)
1601 match.End();
1602
1603 if (dataDuelists.ContainsKey(player.UserIDString))
1604 {
1605 string uid = player.UserIDString;
1606
1607 if (!dcsBlock.Contains(uid))
1608 {
1609 dcsBlock.Add(uid);
1610 timer.Once(60f, () => dcsBlock.Remove(uid));
1611 }
1612
1613 duelsData.AutoReady.Remove(player.UserIDString);
1614 OnDuelistLost(player, true);
1615 RemoveDuelist(player.UserIDString);
1616 ResetDuelist(player.UserIDString, false);
1617 }
1618 else if (match != null && match.IsStarted && !match.IsOver)
1619 {
1620 string uid = player.UserIDString;
1621
1622 if (!dcsBlock.Contains(uid))
1623 {
1624 dcsBlock.Add(uid);
1625 timer.Once(60f, () => dcsBlock.Remove(uid));
1626 }
1627
1628 duelsData.AutoReady.Remove(player.UserIDString);
1629 player.inventory.Strip();
1630 DefeatMessage(player, match);
1631 match.CanRematch = false;
1632 match.RemoveMatchPlayer(player);
1633 }
1634 else if (IsSpectator(player))
1635 {
1636 EndSpectate(player);
1637 SendHome(player);
1638 }
1639
1640 if (dataDuelists.Count == 0 && tdmMatches.Count == 0 && spectators.Count == 0)
1641 Unsubscribe(nameof(OnPlayerDisconnected)); // nothing else to do right now, unsubscribe the hook
1642 }
1643
1644 private void OnPlayerConnected(BasePlayer player)
1645 {
1646 if (player?.net == null)
1647 return;
1648
1649 if (!player.CanInteract())
1650 {
1651 timer.Once(1f, () => OnPlayerConnected(player));
1652 return;
1653 }
1654
1655 createUI.Remove(player.UserIDString);
1656 cmdDUI(player, szUIChatCommand, new string[0]);
1657 }
1658
1659 private void OnPlayerSleepEnded(BasePlayer player) // setup the player
1660 {
1661 if (IsDueling(player))
1662 {
1663 foreach (var zone in duelingZones)
1664 {
1665 if (zone.IsWaiting(player))
1666 {
1667 if (deathTime > 0)
1668 {
1669 player.ChatMessage(msg("ExecutionTime", player.UserIDString, deathTime));
1670 dataDeath[player.UserIDString] = TimeStamp() + deathTime * 60;
1671 }
1672
1673 EndSpectate(player);
1674 GivePlayerKit(player);
1675 Track(player, true);
1676 Metabolize(player, true);
1677
1678 if (useInvisibility)
1679 Disappear(player);
1680
1681 if (DestroyUI(player) && !createUI.Contains(player.UserIDString))
1682 createUI.Add(player.UserIDString);
1683
1684 CheckAutoReady(player);
1685 zone.AddPlayer(player);
1686 Interface.Oxide.CallHook("EnableBypass", player.userID);
1687 return;
1688 }
1689 }
1690 }
1691 else if (InDeathmatch(player))
1692 {
1693 var match = GetMatch(player);
1694
1695 if (deathTime > 0)
1696 {
1697 player.ChatMessage(msg("ExecutionTime", player.UserIDString, deathTime));
1698 dataDeath[player.UserIDString] = TimeStamp() + deathTime * 60;
1699 }
1700
1701 if (DestroyUI(player) && !createUI.Contains(player.UserIDString))
1702 createUI.Add(player.UserIDString);
1703
1704 EndSpectate(player);
1705 GivePlayerKit(player);
1706 Track(player, true);
1707 Metabolize(player, true);
1708 match.GiveShirt(player);
1709 CheckAutoReady(player);
1710 Interface.Oxide.CallHook("EnableBypass", player.userID);
1711 return;
1712 }
1713 else if (announcements.ContainsKey(player.UserIDString))
1714 {
1715 CreateAnnouncementUI(player, announcements[player.UserIDString]);
1716 announcements.Remove(player.UserIDString);
1717 }
1718
1719 if (dataDuelists.Count == 0 && tdmMatches.Count == 0 && announcements.Count == 0) // nothing else to do right now, unsubscribe the hook
1720 Unsubscribe(nameof(OnPlayerSleepEnded));
1721 }
1722
1723 private void OnPlayerRespawned(BasePlayer player)
1724 {
1725 if (DuelTerritory(player.transform.position) && !InEvent(player) && !spectators.Contains(player.UserIDString))
1726 {
1727 var spawnPoint = ServerMgr.FindSpawnPoint();
1728 int retries = 25;
1729
1730 while (DuelTerritory(spawnPoint.pos) && --retries > 0)
1731 spawnPoint = ServerMgr.FindSpawnPoint();
1732
1733 Teleport(player, spawnPoint.pos);
1734 }
1735 }
1736
1737 private void OnEntityKill(BaseNetworkable entity)
1738 {
1739 if (respawnWalls)
1740 {
1741 var e = entity as BaseEntity;
1742
1743 if (e?.transform != null && e.ShortPrefabName.Contains("wall.external.high"))
1744 {
1745 RecreateZoneWall(e.PrefabName, e.transform.position, e.transform.rotation, e.OwnerID);
1746 }
1747 }
1748 }
1749
1750 public void Track(BasePlayer player, bool enable)
1751 {
1752 if (enable && player.GetComponent<Tracker>() == null)
1753 {
1754 player.gameObject.AddComponent<Tracker>();
1755 }
1756 else if (!enable && player.GetComponent<Tracker>() != null)
1757 {
1758 GameObject.Destroy(player.GetComponent<Tracker>());
1759 }
1760 }
1761
1762 public void RecreateZoneWall(string prefab, Vector3 pos, Quaternion rot, ulong ownerId)
1763 {
1764 if (DuelTerritory(pos) && duelsData.DuelZones.Any(entry => GetOwnerId(entry.Key) == ownerId))
1765 CreateZoneWall(prefab, pos, rot, ownerId);
1766 }
1767
1768 public BaseEntity CreateZoneWall(string prefab, Vector3 pos, Quaternion rot, ulong ownerId)
1769 {
1770 var e = GameManager.server.CreateEntity(prefab, pos, rot, false);
1771
1772 if (e != null)
1773 {
1774 e.OwnerID = ownerId;
1775 e.Spawn();
1776 e.gameObject.SetActive(true);
1777 return e;
1778 }
1779
1780 return null;
1781 }
1782
1783 private void OnEntityDeath(BaseEntity entity, HitInfo hitInfo) // 0.1.16 fix for player suiciding
1784 {
1785 if (entity == null)
1786 return;
1787
1788 if (respawnWalls && entity.transform != null && entity.ShortPrefabName.Contains("wall.external.high"))
1789 {
1790 RecreateZoneWall(entity.PrefabName, entity.transform.position, entity.transform.rotation, entity.OwnerID);
1791 return;
1792 }
1793
1794 var victim = entity as BasePlayer;
1795
1796 if (victim == null)
1797 return;
1798
1799 if (spectators.Contains(victim.UserIDString))
1800 EndSpectate(victim);
1801
1802 if (IsDueling(victim))
1803 {
1804 victim.inventory.Strip();
1805 OnDuelistLost(victim, true);
1806 }
1807 else if (InDeathmatch(victim))
1808 {
1809 victim.inventory.Strip();
1810 var match = GetMatch(victim);
1811
1812 DefeatMessage(victim, match);
1813 match.RemoveMatchPlayer(victim);
1814 }
1815 }
1816
1817 private void OnPlayerHealthChange(BasePlayer player, float oldValue, float newValue)
1818 {
1819 if (newValue < 6f)
1820 {
1821 if (IsDueling(player))
1822 {
1823 player.health = 6f;
1824 player.inventory.Strip();
1825 OnDuelistLost(player, false);
1826 }
1827 else if (InDeathmatch(player))
1828 {
1829 player.health = 6f;
1830 player.inventory.Strip();
1831
1832 var match = GetMatch(player);
1833 DefeatMessage(player, match);
1834 match.RemoveMatchPlayer(player);
1835 }
1836 }
1837 }
1838
1839 private void DefeatMessage(BasePlayer victim, GoodVersusEvilMatch match)
1840 {
1841 if (tdmAttackers.ContainsKey(victim.UserIDString))
1842 {
1843 var info = tdmAttackers[victim.UserIDString];
1844
1845 if (tdmServerDeaths || duelsData.ChatEx.Count > 0)
1846 {
1847 foreach (var target in BasePlayer.activePlayerList.Where(p => p?.displayName != null))
1848 {
1849 if (duelsData.Chat.Contains(target.UserIDString) && target != victim)
1850 continue;
1851
1852 target.ChatMessage(msg("MatchPlayerDefeated", target.UserIDString, victim.displayName, info.AttackerName, info.Weapon, info.BoneName, info.Distance));
1853 }
1854 }
1855 else if (tdmMatchDeaths)
1856 match.MessageAll("MatchPlayerDefeated", victim.displayName, info.AttackerName, info.Weapon, info.BoneName, info.Distance);
1857
1858 if (guiAnnounceUITime > 0f)
1859 {
1860 if (sendDefeatedHome)
1861 announcements[victim.UserIDString] = msg("MatchPlayerDefeated", victim.UserIDString, victim.displayName, info.AttackerName, info.Weapon, info.BoneName, info.Distance);
1862 else
1863 CreateAnnouncementUI(victim, msg("MatchPlayerDefeated", victim.UserIDString, victim.displayName, info.AttackerName, info.Weapon, info.BoneName, info.Distance));
1864 }
1865
1866 tdmAttackers.Remove(victim.UserIDString);
1867 UpdateMatchStats(victim.UserIDString, false, false, true, false);
1868 UpdateMatchStats(info.AttackerId, false, false, false, true);
1869 }
1870 }
1871
1872 private void OnDuelistLost(BasePlayer victim, bool sendHome)
1873 {
1874 RemoveEntities(victim.userID);
1875
1876 if (!dataDuelists.ContainsKey(victim.UserIDString))
1877 {
1878 NextTick(() => SendHome(victim));
1879 return;
1880 }
1881
1882 string attackerId = dataDuelists[victim.UserIDString];
1883 var attacker = BasePlayer.Find(attackerId);
1884 string attackerName = attacker?.displayName ?? GetDisplayName(attackerId); // get the attackers name. null check for self inflicted
1885
1886 dataDeath.Remove(victim.UserIDString); // remove them from automatic deaths
1887 dataDeath.Remove(attackerId);
1888 dataDuelists.Remove(victim.UserIDString); // unset their status as duelers
1889 dataDuelists.Remove(attackerId);
1890 victim.inventory.Strip();
1891 Metabolize(victim, false);
1892 Track(victim, false);
1893
1894 if (!duelsData.LossesSeed.ContainsKey(victim.UserIDString)) duelsData.LossesSeed.Add(victim.UserIDString, 1);
1895 else duelsData.LossesSeed[victim.UserIDString]++;
1896 if (!duelsData.Losses.ContainsKey(victim.UserIDString)) duelsData.Losses.Add(victim.UserIDString, 1);
1897 else duelsData.Losses[victim.UserIDString]++;
1898 if (!duelsData.VictoriesSeed.ContainsKey(attackerId)) duelsData.VictoriesSeed.Add(attackerId, 1);
1899 else duelsData.VictoriesSeed[attackerId]++;
1900 if (!duelsData.Victories.ContainsKey(attackerId)) duelsData.Victories.Add(attackerId, 1);
1901 else duelsData.Victories[attackerId]++;
1902 duelsData.TotalDuels++;
1903
1904 int victimLossesSeed = duelsData.LossesSeed[victim.UserIDString];
1905 int victimVictoriesSeed = duelsData.VictoriesSeed.ContainsKey(victim.UserIDString) ? duelsData.VictoriesSeed[victim.UserIDString] : 0;
1906 int attackerLossesSeed = duelsData.LossesSeed.ContainsKey(attackerId) ? duelsData.LossesSeed[attackerId] : 0;
1907 int attackerVictoriesSeed = duelsData.VictoriesSeed[attackerId];
1908 var bet = duelsData.Bets.ContainsKey(attackerId) && duelsData.Bets.ContainsKey(victim.UserIDString) && duelsData.Bets[attackerId].Equals(duelsData.Bets[victim.UserIDString]) && !IsAllied(victim, attacker) ? duelsData.Bets[attackerId] : null; // victim bet his attacker and lost, use later to add a claim for the attacker
1909
1910 Puts(RemoveFormatting(msg("DuelDeathMessage", null, attackerName, attackerVictoriesSeed, attackerLossesSeed, victim.displayName, victimVictoriesSeed, victimLossesSeed, Math.Round(attacker?.health ?? 0f, 2), bet != null ? msg("BetWon", null, bet.trigger, bet.amount) : ""))); // send message to console
1911 Interface.CallHook("OnDuelistDefeated", attacker, victim);
1912
1913 if (guiAnnounceUITime > 0f)
1914 {
1915 if (sendDefeatedHome)
1916 {
1917 announcements[victim.UserIDString] = msg("DuelDeathMessage", victim.UserIDString, attackerName, attackerVictoriesSeed, attackerLossesSeed, victim.displayName, victimVictoriesSeed, victimLossesSeed, Math.Round(attacker?.health ?? 0f, 2), bet != null ? msg("BetWon", null, bet.trigger, bet.amount) : "");
1918 announcements[attackerId] = msg("DuelDeathMessage", attackerId, attackerName, attackerVictoriesSeed, attackerLossesSeed, victim.displayName, victimVictoriesSeed, victimLossesSeed, Math.Round(attacker?.health ?? 0f, 2), bet != null ? msg("BetWon", null, bet.trigger, bet.amount) : "");
1919 }
1920 else
1921 {
1922 CreateAnnouncementUI(victim, msg("DuelDeathMessage", victim.UserIDString, attackerName, attackerVictoriesSeed, attackerLossesSeed, victim.displayName, victimVictoriesSeed, victimLossesSeed, Math.Round(attacker?.health ?? 0f, 2), bet != null ? msg("BetWon", null, bet.trigger, bet.amount) : ""));
1923 CreateAnnouncementUI(attacker, msg("DuelDeathMessage", attackerId, attackerName, attackerVictoriesSeed, attackerLossesSeed, victim.displayName, victimVictoriesSeed, victimLossesSeed, Math.Round(attacker?.health ?? 0f, 2), bet != null ? msg("BetWon", null, bet.trigger, bet.amount) : ""));
1924 }
1925 }
1926
1927 foreach (var target in BasePlayer.activePlayerList.Where(p => p?.displayName != null))
1928 {
1929 if (duelsData.Chat.Contains(target.UserIDString) && target != victim && target != attacker)
1930 continue;
1931
1932 if (!broadcastDefeat && !duelsData.ChatEx.Contains(target.UserIDString) && target != victim && target != attacker)
1933 continue;
1934
1935 string betWon = bet != null ? msg("BetWon", target.UserIDString, bet.trigger, bet.amount) : "";
1936 target.ChatMessage(msg("DuelDeathMessage", target.UserIDString, attackerName, attackerVictoriesSeed, attackerLossesSeed, victim.displayName, victimVictoriesSeed, victimLossesSeed, Math.Round(attacker?.health ?? 0f, 2), betWon));
1937 }
1938
1939 if (bet != null && attacker != null) // award the bet to the attacker
1940 {
1941 var claimBet = new BetInfo
1942 {
1943 itemid = bet.itemid,
1944 amount = bet.amount * 2,
1945 trigger = bet.trigger
1946 };
1947
1948 if (!duelsData.ClaimBets.ContainsKey(attackerId))
1949 duelsData.ClaimBets.Add(attackerId, new List<BetInfo>());
1950
1951 duelsData.ClaimBets[attackerId].Add(claimBet);
1952 duelsData.Bets.Remove(attackerId);
1953 duelsData.Bets.Remove(victim.UserIDString);
1954 Puts(msg("ConsoleBetWon", null, attacker.displayName, attacker.UserIDString, victim.displayName, victim.UserIDString));
1955 attacker.ChatMessage(msg("NotifyBetWon", attacker.UserIDString, szDuelChatCommand));
1956 }
1957
1958 ulong attackeruId = Convert.ToUInt64(attackerId);
1959
1960 RemoveDuelist(attackerId);
1961 RemoveEntities(attackeruId);
1962 AwardPlayer(attackeruId, economicsMoney, serverRewardsPoints);
1963
1964 Interface.Oxide.CallHook("DisableBypass", victim.userID);
1965 Interface.Oxide.CallHook("DisableBypass", attackeruId);
1966
1967 if (attacker != null)
1968 {
1969 attacker.inventory.Strip();
1970 Metabolize(attacker, false);
1971 Track(attacker, false);
1972 }
1973
1974 var zone = RemoveDuelist(victim.UserIDString);
1975
1976 if (zoneCounter > 0 && zone != null) // if new zones are set to spawn every X duels then increment by 1
1977 {
1978 if (++zone.Kills >= zoneCounter && zone.TotalPlayers == 0)
1979 {
1980 RemoveDuelZone(zone);
1981 SetupDuelZone(null, GetZoneName()); // x amount of duels completed. time to relocate and start all over! changing the dueling zones location keeps things mixed up and entertaining for everyone. especially when there's issues with terrain
1982 SaveData();
1983 }
1984 }
1985
1986 if (dataDuelists.Count == 0 && tdmMatches.Count == 0)
1987 Unsubscribe(nameof(OnPlayerHealthChange));
1988
1989 if (sendHome || dcsBlock.Contains(victim.UserIDString))
1990 {
1991 NextTick(() =>
1992 {
1993 SendHome(attacker);
1994 SendHome(victim);
1995 });
1996
1997 if (dcsBlock.Contains(victim.UserIDString))
1998 return;
1999 }
2000
2001 if (attacker != null)
2002 {
2003 if (victim.IsConnected && attacker.IsConnected)
2004 {
2005 var rematch = new Rematch();
2006 rematches.Add(rematch);
2007 rematch.Duelists.Add(attacker);
2008 rematch.Duelists.Add(victim);
2009 rematch.Notify();
2010 }
2011
2012 if (!InEvent(attacker) && !InEvent(victim) && !sendHome)
2013 {
2014 StartSpectate(attacker);
2015 StartSpectate(victim);
2016 }
2017 }
2018 }
2019
2020 public string GetZoneName()
2021 {
2022 return (duelsData.DuelZones.Count + 1).ToString();
2023 }
2024
2025 public void SendDuelistsHome()
2026 {
2027 foreach (var entry in dataDuelists.ToList())
2028 {
2029 if (duelsData.Homes.ContainsKey(entry.Key))
2030 {
2031 var target = BasePlayer.Find(entry.Key);
2032
2033 if (target != null && DuelTerritory(target.transform.position))
2034 {
2035 target.inventory.Strip();
2036 SendHome(target);
2037 }
2038 }
2039
2040 ResetDuelist(entry.Key);
2041 }
2042 }
2043
2044 public void SendSpectatorsHome()
2045 {
2046 foreach (var player in BasePlayer.activePlayerList)
2047 {
2048 if (IsSpectator(player))
2049 {
2050 EndSpectate(player);
2051 }
2052 }
2053 }
2054
2055 private static void StartSpectate(BasePlayer player)
2056 {
2057 if (!player || !player.IsConnected)
2058 return;
2059
2060 if (GetDuelZone(player.transform.position) == null)
2061 {
2062 ins.SendHome(player);
2063 return;
2064 }
2065
2066 if (!player.CanInteract())
2067 {
2068 if (player.IsDead())
2069 player.RespawnAt(player.transform.position, default(Quaternion));
2070
2071 ins.timer.Once(1f, () => StartSpectate(player));
2072 return;
2073 }
2074
2075 spectators.Add(player.UserIDString);
2076 player.ChatMessage(ins.msg("BeginSpectating", player.UserIDString));
2077 player.inventory.Strip();
2078 player.health = 100f;
2079 player.metabolism.bleeding.value = 0f;
2080 player.StopWounded();
2081 CreateDefeatUI(player);
2082 Disappear(player);
2083 }
2084
2085 private static void EndSpectate(BasePlayer player)
2086 {
2087 CuiHelper.DestroyUi(player, "DuelistUI_Defeat");
2088
2089 if (spectators.Contains(player.UserIDString))
2090 {
2091 if (playerHealth > 0f && player.IsAlive())
2092 player.health = playerHealth;
2093
2094 spectators.Remove(player.UserIDString);
2095 player.SendNetworkUpdate();
2096 player.ChatMessage(ins.msg("EndSpectating", player.UserIDString));
2097 }
2098 }
2099
2100 public void HealDamage(BaseCombatEntity entity)
2101 {
2102 timer.Once(1f, () =>
2103 {
2104 if (entity != null && !entity.IsDestroyed && entity.health < entity.MaxHealth())
2105 {
2106 entity.health = entity.MaxHealth();
2107 entity.SendNetworkUpdate();
2108 }
2109 });
2110 }
2111
2112 public void CancelDamage(HitInfo hitInfo)
2113 {
2114 if (hitInfo != null)
2115 {
2116 hitInfo.damageTypes = new DamageTypeList();
2117 hitInfo.DidHit = false;
2118 hitInfo.HitEntity = null;
2119 hitInfo.Initiator = null;
2120 hitInfo.DoHitEffects = false;
2121 hitInfo.HitMaterial = 0;
2122 }
2123 }
2124
2125 private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
2126 {
2127 if (entity == null || entity.net == null || entity.IsDestroyed || entity.transform == null)
2128 return null;
2129
2130 if (DuelTerritory(entity.transform.position, 1f))
2131 {
2132 if (entity is BuildingBlock || entity.name.Contains("deploy") || entity.name.Contains("wall.external.high") || entity.name.Contains("building"))
2133 {
2134 CancelDamage(hitInfo);
2135 HealDamage(entity);
2136 return true;
2137 }
2138 }
2139
2140 if (hitInfo == null || hitInfo.Initiator == null || hitInfo.Initiator.IsDestroyed || hitInfo.Initiator.transform == null || !hitInfo.hasDamage)
2141 return null;
2142
2143 var victim = entity as BasePlayer;
2144 var attacker = hitInfo.Initiator as BasePlayer;
2145 var pointStart = hitInfo.Initiator?.transform?.position ?? hitInfo.PointStart; // 0.1.6 border fix
2146 var pointEnd = entity.transform.position;
2147 bool adt = DuelTerritory(pointStart);
2148 bool vdt = DuelTerritory(pointEnd);
2149
2150 if (adt && entity is BaseHelicopter)
2151 return false;
2152
2153 if (victim?.transform != null && hitInfo.Initiator != null && hitInfo.Initiator.ShortPrefabName.Contains("wall.external.high") && vdt) // 1.0.2 - exploit fix
2154 return false;
2155
2156 if (victim != null && victim.transform != null && attacker != null && victim == attacker) // allow player to suicide and self inflict
2157 {
2158 if (hitInfo.damageTypes.Has(DamageType.Suicide) && InEvent(victim))
2159 {
2160 string uid = victim.UserIDString;
2161
2162 if (!dcsBlock.Contains(uid))
2163 {
2164 dcsBlock.Add(uid);
2165 timer.Once(60f, () => dcsBlock.Remove(uid));
2166 }
2167 }
2168
2169 return null;
2170 }
2171
2172 if (victim != null && hitInfo.damageTypes.GetMajorityDamageType() == DamageType.Fall && !dataImmunity.ContainsKey(victim.UserIDString))
2173 return null;
2174
2175 if (attacker?.transform != null && spectators.Contains(attacker.UserIDString)) // 0.1.27: someone will find a way to abuse spectate mode so we'll prevent that now
2176 {
2177 if (!adt)
2178 {
2179 EndSpectate(attacker);
2180 SendHome(attacker);
2181 }
2182
2183 CancelDamage(hitInfo);
2184 return true;
2185 }
2186
2187 if (hitInfo.Initiator != null && !hitInfo.Initiator.IsDestroyed && hitInfo.Initiator.IsNpc && (adt || vdt)) // 1.2.0
2188 {
2189 if (hitInfo.Initiator is BaseNpc)
2190 {
2191 var npc = hitInfo.Initiator as BaseNpc;
2192
2193 if (npc != null)
2194 {
2195 if (putToSleep)
2196 {
2197 npc.SetAiFlag(BaseNpc.AiFlags.Sleeping, true);
2198 npc.CurrentBehaviour = BaseNpc.Behaviour.Sleep;
2199 }
2200 else if (killNpc)
2201 {
2202 npc.Kill();
2203 }
2204
2205 return true;
2206 }
2207 }
2208 else if (hitInfo.Initiator is BasePlayer)
2209 {
2210 var npc = hitInfo.Initiator as BasePlayer;
2211
2212 if (npc != null)
2213 {
2214 if (npc is Scientist)
2215 {
2216 (npc as Scientist).LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
2217 }
2218 else if (npc is NPCMurderer)
2219 {
2220 (npc as NPCMurderer).LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
2221 }
2222
2223 npc.Kill();
2224 CancelDamage(hitInfo);
2225 return true;
2226 }
2227 }
2228 }
2229
2230 if (dataDuelists.Count > 0)
2231 {
2232 if (attacker?.transform != null && IsDueling(attacker) && victim != null && dataDuelists[attacker.UserIDString] != victim.UserIDString) // 0.1.8 check attacker then victim
2233 return false; // prevent attacker from doing damage to others
2234
2235 if (victim?.transform != null && IsDueling(victim)) // 1.2.0 NRE get_transform
2236 {
2237 if (victim.health == 6f)
2238 return true;
2239
2240 if (dataImmunity.ContainsKey(victim.UserIDString))
2241 return true; // immunity timer
2242
2243 if (hitInfo.Initiator is BaseHelicopter)
2244 return true; // protect duelers from helicopters
2245
2246 if (attacker?.transform != null && dataDuelists[victim.UserIDString] != attacker.UserIDString)
2247 return true; // prevent attacker from doing damage to others
2248
2249 hitInfo.damageTypes.ScaleAll(damageScaleAmount);
2250 return null;
2251 }
2252 }
2253
2254 if (tdmMatches.Count > 0)
2255 {
2256 if (attacker?.transform != null && InDeathmatch(attacker) && victim != null)
2257 {
2258 var match = GetMatch(attacker);
2259
2260 if (match.GetTeam(victim) == Team.None)
2261 return true;
2262
2263 if (!dmFF && match.GetTeam(victim) == match.GetTeam(attacker))
2264 return true; // FF
2265 }
2266
2267 if (victim?.transform != null && InDeathmatch(victim))
2268 {
2269 if (dataImmunity.ContainsKey(victim.UserIDString))
2270 return true;
2271
2272 if (hitInfo.Initiator is BaseHelicopter)
2273 return true;
2274
2275 if (attacker?.transform != null)
2276 {
2277 if (GetMatch(attacker) == null)
2278 return true;
2279
2280 if (victim.health == 6f)
2281 return true;
2282
2283 if (tdmAttackers.ContainsKey(victim.UserIDString))
2284 tdmAttackers.Remove(victim.UserIDString);
2285
2286 string weapon = attacker.GetActiveItem()?.info?.displayName?.english ?? hitInfo?.WeaponPrefab?.ShortPrefabName ?? "??";
2287
2288 if (weapon.EndsWith(".entity"))
2289 {
2290 var def = ItemManager.FindItemDefinition(weapon.Replace(".entity", "").Replace("_", "."));
2291 weapon = def?.displayName.translated ?? weapon.Replace(".entity", "").Replace("_", "").SentenceCase();
2292 }
2293
2294 tdmAttackers.Add(victim.UserIDString, new AttackerInfo());
2295 tdmAttackers[victim.UserIDString].AttackerName = attacker.displayName;
2296 tdmAttackers[victim.UserIDString].AttackerId = attacker.UserIDString;
2297 tdmAttackers[victim.UserIDString].Distance = Math.Round(Vector3.Distance(attacker.transform.position, victim.transform.position), 2).ToString();
2298 tdmAttackers[victim.UserIDString].BoneName = FormatBone(hitInfo.boneName).TrimEnd(); //StringPool.Get(hitInfo.boneName)
2299 tdmAttackers[victim.UserIDString].Weapon = weapon;
2300 }
2301
2302 hitInfo.damageTypes.ScaleAll(damageScaleAmount);
2303 return null;
2304 }
2305 }
2306
2307 if (victim?.transform != null && attacker?.transform != null) // 1.1.1 - fix for players standing on the edge of a zone for protection
2308 {
2309 if (vdt && !InEvent(victim))
2310 return null;
2311
2312 if (adt && !InEvent(attacker))
2313 return null;
2314 }
2315
2316 if (adt && !vdt)
2317 return true; // block all damage to the outside
2318
2319 if (!adt && vdt)
2320 return true; // block all damage to the inside
2321
2322 return null;
2323 }
2324
2325 class Explosives : FacepunchBehaviour
2326 {
2327 WorldItem worldItem;
2328 float minExplosionRadius = 1f;
2329 float explosionRadius = 5f;
2330 int layers = 141568;
2331
2332 void Awake()
2333 {
2334 worldItem = GetComponent<WorldItem>();
2335 }
2336
2337 void OnDestroy()
2338 {
2339 var damageTypes = new List<DamageTypeEntry>
2340 {
2341 new DamageTypeEntry()
2342 {
2343 amount = 25f,
2344 type = DamageType.Explosion
2345 }
2346 };
2347
2348 Effect.server.Run("some resource", worldItem.PivotPoint(), worldItem.transform.forward, null, true);
2349 DamageUtil.RadiusDamage(worldItem, worldItem.LookupPrefab(), worldItem.CenterPoint(), minExplosionRadius, explosionRadius, damageTypes, layers, true);
2350 Destroy(this);
2351 }
2352 }
2353
2354 void OnEntitySpawned(WorldItem worldItem)
2355 {
2356 NextTick(() =>
2357 {
2358 if (worldItem != null && !worldItem.IsDestroyed && worldItem.item != null && worldItem.item.info.shortname == "arrow.bone")
2359 {
2360 worldItem.gameObject.AddComponent<Explosives>();
2361 }
2362 });
2363 }
2364
2365 private void OnEntitySpawned(BaseNetworkable entity)
2366 {
2367 if (entity == null || entity.transform == null || !DuelTerritory(entity.transform.position))
2368 return;
2369
2370 var e = entity as BaseEntity;
2371
2372 if (e != null && e.IsNpc)
2373 {
2374 NextTick(() =>
2375 {
2376 if (entity != null && !entity.IsDestroyed)
2377 {
2378 entity.Kill();
2379 }
2380 });
2381
2382 return;
2383 }
2384
2385 if (noStability && entity is BuildingBlock)
2386 {
2387 var block = entity as BuildingBlock;
2388
2389 if (block.OwnerID == 0 || permission.UserHasGroup(block.OwnerID.ToString(), "admin"))
2390 {
2391 block.grounded = true;
2392 return;
2393 }
2394 }
2395
2396 if (dataDuelists.Count == 0 && tdmMatches.Count == 0)
2397 return;
2398
2399 if (prefabs.ContainsKey(entity.PrefabName) && e.name.Contains("barricade."))
2400 {
2401 if (morphBarricadesStoneWalls || morphBarricadesWoodenWalls)
2402 {
2403 var wall = CreateZoneWall(morphBarricadesStoneWalls ? heswPrefab : hewwPrefab, e.transform.position, e.transform.rotation, e.OwnerID);
2404
2405 if (wall != null)
2406 {
2407 if (!e.IsDestroyed)
2408 e.Kill();
2409 if (!duelEntities.ContainsKey(wall.OwnerID))
2410 duelEntities.Add(wall.OwnerID, new List<BaseEntity>() { wall });
2411 else duelEntities[wall.OwnerID].Add(wall);
2412 }
2413
2414 return;
2415 }
2416 }
2417
2418 if (entity is PlayerCorpse || entity.name.Contains("item_drop_backpack"))
2419 {
2420 NextTick(() =>
2421 {
2422 if (entity != null && !entity.IsDestroyed)
2423 {
2424 entity.Kill();
2425 }
2426 });
2427 }
2428 else if (entity is WorldItem)
2429 {
2430 if (duelsData.Homes.Count > 0)
2431 {
2432 NextTick(() => // prevent rpc kick by using NextTick since we're also hooking OnItemDropped
2433 {
2434 if (entity != null && !entity.IsDestroyed) // we must check this or you will still be rpc kicked
2435 {
2436 var worldItem = entity as WorldItem; // allow thrown weapons / destroy items which are dropped by players and on death
2437
2438 if (worldItem != null && worldItem.item != null && !IsThrownWeapon(worldItem.item))
2439 entity.Kill();
2440 }
2441 });
2442
2443 if (entity != null && !entity.IsDestroyed)
2444 {
2445 timer.Repeat(0.1f, 20, () => // track the item to make sure it wasn't thrown out of the dueling zone
2446 {
2447 if (entity != null && !entity.IsDestroyed && !DuelTerritory(entity.transform.position))
2448 entity.Kill(); // destroy items which are dropped from inside to outside of the zone
2449 });
2450 }
2451 }
2452 }
2453 }
2454
2455 object CanBuild(Planner planner, Construction prefab, Construction.Target target)
2456 {
2457 var player = planner.GetOwnerPlayer();
2458
2459 if (player.IsAdmin)
2460 return null;
2461
2462 var position = player.transform.position;
2463 var buildPos = position + player.eyes.BodyForward() * 4f; // get the estimated position of where the player is trying to build at
2464 var up = buildPos + Vector3.up + new Vector3(0f, 0.6f, 0f);
2465
2466 buildPos.y = Mathf.Max(position.y, up.y); // adjust the cursor position to our best estimate
2467
2468 if (DuelTerritory(buildPos, buildingBlockExtensionRadius)) // extend the distance slightly
2469 {
2470 if (deployables.Count > 0)
2471 {
2472 var kvp = prefabs.FirstOrDefault(x => x.Key == prefab.fullName);
2473
2474 if (!string.IsNullOrEmpty(kvp.Value) && deployables.ContainsKey(kvp.Value) && deployables[kvp.Value])
2475 {
2476 if (dataDuelists.ContainsKey(player.UserIDString) || InMatch(player))
2477 {
2478 return null;
2479 }
2480 }
2481 }
2482
2483 player.ChatMessage(msg("Building is blocked!", player.UserIDString));
2484 return false;
2485 }
2486
2487 return null;
2488 }
2489
2490 private void OnLootEntity(BasePlayer player, BaseEntity entity) // stop all players from looting anything inside of dueling zones. this allows server owners to setup duels anywhere without worry.
2491 {
2492 if (player != null && (IsDueling(player) || InDeathmatch(player) || IsSpectator(player)))
2493 timer.Once(0.01f, player.EndLooting);
2494
2495 if (dataDuelists.Count == 0 && tdmMatches.Count == 0 && spectators.Count == 0)
2496 Unsubscribe(nameof(OnLootEntity));
2497 }
2498
2499 private object OnCreateWorldProjectile(HitInfo info, Item item) // prevents thrown items from becoming stuck in players when they respawn and requiring them to relog to remove them
2500 {
2501 if (info == null)
2502 return null;
2503
2504 if (dataDuelists.Count == 0 && tdmMatches.Count == 0)
2505 {
2506 Unsubscribe(nameof(OnCreateWorldProjectile));
2507 return null;
2508 }
2509
2510 var victim = info.HitEntity as BasePlayer;
2511 var attacker = info.Initiator as BasePlayer;
2512
2513 if (victim != null && (IsDueling(victim) || InDeathmatch(victim)))
2514 return false; // block it
2515
2516 if (attacker != null && (IsDueling(attacker) || InDeathmatch(attacker)))
2517 return false;
2518
2519 return null;
2520 }
2521
2522 private void OnItemDropped(Item item, BaseEntity entity)
2523 {
2524 if (dataDuelists.Count == 0 && tdmMatches.Count == 0) // nothing left to do here, unsubscribe the hook
2525 {
2526 Unsubscribe(nameof(OnItemDropped));
2527 return;
2528 }
2529
2530 if (item.GetOwnerPlayer() == null)
2531 return;
2532
2533 var player = item.GetOwnerPlayer();
2534
2535 if (!IsThrownWeapon(item) && (IsDueling(player) || InDeathmatch(player)))
2536 item.Remove(0.01f); // do NOT allow players to drop items
2537 }
2538
2539 private object IsPrisoner(BasePlayer player) // Random Warps
2540 {
2541 return IsDueling(player) || InDeathmatch(player) ? (object)true : null;
2542 }
2543
2544 private object CanEventJoin(BasePlayer player) // EventManager
2545 {
2546 return IsDueling(player) || InDeathmatch(player) ? msg("CannotEventJoin", player.UserIDString) : null;
2547 }
2548
2549 private object canRemove(BasePlayer player) // RemoverTool
2550 {
2551 return init && DuelTerritory(player.transform.position) ? (object)false : null;
2552 }
2553
2554 private object CanTrade(BasePlayer player) // Trade
2555 {
2556 return init && DuelTerritory(player.transform.position) ? (object)false : null;
2557 }
2558
2559 private object CanBank(BasePlayer player)
2560 {
2561 return init && DuelTerritory(player.transform.position) ? msg("CannotBank", player.UserIDString) : null;
2562 }
2563
2564 private object CanOpenBackpack(BasePlayer player)
2565 {
2566 return init && DuelTerritory(player.transform.position) ? msg("CommandNotAllowed", player.UserIDString) : null;
2567 }
2568
2569 private object canShop(BasePlayer player) // Shop and ServerRewards
2570 {
2571 return init && DuelTerritory(player.transform.position) ? msg("CannotShop", player.UserIDString) : null;
2572 }
2573
2574 private object CanShop(BasePlayer player)
2575 {
2576 return init && DuelTerritory(player.transform.position) ? msg("CannotShop", player.UserIDString) : null;
2577 }
2578
2579 private object CanBePenalized(BasePlayer player) // ZLevels Remastered
2580 {
2581 return init && (DuelTerritory(player.transform.position) || dataDuelists.ContainsKey(player.UserIDString)) ? (object)false : null;
2582 }
2583
2584 private object canTeleport(BasePlayer player) // 0.1.2: block teleport from NTeleportation plugin
2585 {
2586 return init && DuelTerritory(player.transform.position) ? msg("CannotTeleport", player.UserIDString) : null;
2587 }
2588
2589 private object CanTeleport(BasePlayer player) // 0.1.2: block teleport from MagicTeleportation plugin
2590 {
2591 return init && DuelTerritory(player.transform.position) ? msg("CannotTeleport", player.UserIDString) : null;
2592 }
2593
2594 private object CanJoinTDMEvent(BasePlayer player)
2595 {
2596 return init && DuelTerritory(player.transform.position) ? (object)false : null;
2597 }
2598
2599 private object CanEntityTakeDamage(BaseEntity entity, HitInfo hitinfo) // TruePVE!!!! <3 @ignignokt84
2600 {
2601 return init && entity?.transform != null && entity is BasePlayer && DuelTerritory(entity.transform.position) ? (object)true : null;
2602 }
2603
2604 object OnPlayerCommand(BasePlayer player, string command)
2605 {
2606 if (!init || !player.IsValid() || player.transform == null || !DuelTerritory(player.transform.position))
2607 {
2608 return null;
2609 }
2610
2611 if (useBlacklistCommands && blacklistCommands.Any(entry => entry.TrimStart('/').Equals(command, StringComparison.OrdinalIgnoreCase)))
2612 {
2613 player.ChatMessage(msg("CommandNotAllowed", player.UserIDString));
2614 return true;
2615 }
2616
2617 if (useWhitelistCommands && !whitelistCommands.Any(entry => entry.TrimStart('/').Equals(command, StringComparison.OrdinalIgnoreCase)))
2618 {
2619 player.ChatMessage(msg("CommandNotAllowed", player.UserIDString));
2620 return true;
2621 }
2622
2623 return null;
2624 }
2625
2626 object OnUserCommand(IPlayer p, string command)
2627 {
2628 BasePlayer player = p.Object as BasePlayer;
2629 if (!init || !player.IsValid() || player.transform == null || !DuelTerritory(player.transform.position))
2630 {
2631 return null;
2632 }
2633
2634 if (player.IsValid())
2635 {
2636 if (useBlacklistCommands && blacklistCommands.Any(entry => entry.TrimStart('/').Equals(command, StringComparison.OrdinalIgnoreCase)))
2637 {
2638 return true;
2639 }
2640
2641 if (useWhitelistCommands && !whitelistCommands.Any(entry => entry.TrimStart('/').Equals(command, StringComparison.OrdinalIgnoreCase)))
2642 {
2643 return true;
2644 }
2645 }
2646
2647 return null;
2648 }
2649
2650 object OnServerCommand(ConsoleSystem.Arg arg)
2651 {
2652 var player = arg.Player();
2653
2654 if (!player.IsValid() || !DuelTerritory(player.transform.position))
2655 {
2656 return null;
2657 }
2658
2659 string command = arg.cmd.FullName;
2660
2661 if (useBlacklistCommands && blacklistCommands.Any(entry => entry.Replace("/", "").Equals(command, StringComparison.OrdinalIgnoreCase)))
2662 {
2663 player.ChatMessage(msg("CommandNotAllowed", player.UserIDString));
2664 return true;
2665 }
2666
2667 if (useWhitelistCommands && !whitelistCommands.Any(entry => entry.Replace("/", "").Equals(command, StringComparison.OrdinalIgnoreCase)))
2668 {
2669 player.ChatMessage(msg("CommandNotAllowed", player.UserIDString));
2670 return true;
2671 }
2672
2673 return null;
2674 }
2675
2676 public void CheckAutoReady(BasePlayer player)
2677 {
2678 if (duelsData.AutoReady.Contains(player.UserIDString))
2679 {
2680 if (!readyUiList.Contains(player.UserIDString))
2681 {
2682 ToggleReadyUI(player);
2683 }
2684 }
2685 else if (readyUiList.Contains(player.UserIDString))
2686 {
2687 CuiHelper.DestroyUi(player, "DuelistUI_Ready");
2688 readyUiList.Remove(player.UserIDString);
2689 }
2690 }
2691
2692 public void ToggleAutoReady(BasePlayer player)
2693 {
2694 if (duelsData.AutoReady.Contains(player.UserIDString))
2695 duelsData.AutoReady.Remove(player.UserIDString);
2696 else
2697 duelsData.AutoReady.Add(player.UserIDString);
2698
2699 player.ChatMessage(msg(duelsData.AutoReady.Contains(player.UserIDString) ? "RematchAutoOn" : "RematchAutoOff", player.UserIDString));
2700
2701 if (DuelTerritory(player.transform.position))
2702 CreateDefeatUI(player);
2703
2704 if (duelistUI.Contains(player.UserIDString))
2705 RefreshUI(player);
2706 }
2707
2708 public void ReadyUp(BasePlayer player)
2709 {
2710 var rematch = rematches.FirstOrDefault(x => x.HasPlayer(player));
2711
2712 if (rematch == null)
2713 {
2714 ToggleAutoReady(player);
2715 player.ChatMessage(msg("RematchNone", player.UserIDString));
2716 return;
2717 }
2718
2719 if (rematch.Ready.Contains(player))
2720 {
2721 player.ChatMessage(msg("RematchAcceptedAlready", player.UserIDString));
2722 ToggleAutoReady(player);
2723 }
2724 else
2725 {
2726 player.ChatMessage(msg("RematchAccepted", player.UserIDString));
2727 rematch.Ready.Add(player);
2728 }
2729
2730 if (rematch.IsReady())
2731 {
2732 rematch.Start();
2733 rematches.Remove(rematch);
2734 }
2735 }
2736
2737 public void cmdTDM(BasePlayer player, string command, string[] args)
2738 {
2739 if (player.IsAdmin && args.Length == 1 && args[0] == "showall" && tdmMatches.Count > 0)
2740 {
2741 foreach (var match in tdmMatches)
2742 {
2743 player.ChatMessage(msg("InMatchListGood", player.UserIDString, match.GetNames(Team.Good)));
2744 player.ChatMessage(msg("InMatchListEvil", player.UserIDString, match.GetNames(Team.Evil)));
2745 }
2746
2747 return;
2748 }
2749
2750 if (!autoAllowAll && !duelsData.Allowed.Contains(player.UserIDString))
2751 {
2752 player.ChatMessage(msg("MustAllowDuels", player.UserIDString, szDuelChatCommand));
2753 return;
2754 }
2755
2756 if (IsDueling(player))
2757 {
2758 player.ChatMessage(msg("AlreadyInADuel", player.UserIDString));
2759 return;
2760 }
2761
2762 var deathmatch = tdmMatches.FirstOrDefault(x => x.GetTeam(player) != Team.None);
2763
2764 if (deathmatch != null && deathmatch.IsStarted)
2765 {
2766 player.ChatMessage(msg("MatchStartedAlready", player.UserIDString));
2767 return;
2768 }
2769
2770 if (args.Length == 0)
2771 {
2772 if (deathmatch == null)
2773 {
2774 if (!autoAllowAll)
2775 player.ChatMessage(msg("HelpAllow", player.UserIDString, szDuelChatCommand));
2776
2777 player.ChatMessage(msg("MatchChallenge0", player.UserIDString, szMatchChatCommand));
2778 player.ChatMessage(msg("MatchChallenge2", player.UserIDString, szMatchChatCommand));
2779 player.ChatMessage(msg("MatchChallenge3", player.UserIDString, szMatchChatCommand));
2780 player.ChatMessage(msg("MatchAccept", player.UserIDString, szMatchChatCommand));
2781 player.ChatMessage(msg("MatchCancel", player.UserIDString, szMatchChatCommand));
2782 player.ChatMessage(msg("MatchLeave", player.UserIDString, szMatchChatCommand));
2783 player.ChatMessage(msg("MatchSize", player.UserIDString, szMatchChatCommand, minDeathmatchSize));
2784 player.ChatMessage(msg("MatchKickBan", player.UserIDString, szMatchChatCommand));
2785 player.ChatMessage(msg("MatchSetCode", player.UserIDString, szMatchChatCommand));
2786 player.ChatMessage(msg("MatchTogglePublic", player.UserIDString, szMatchChatCommand));
2787 player.ChatMessage(msg("MatchKit", player.UserIDString, szMatchChatCommand));
2788 player.ChatMessage(msg("UI_Help", player.UserIDString, szUIChatCommand));
2789 }
2790 else
2791 {
2792 player.ChatMessage(msg("MatchLeave", player.UserIDString, szMatchChatCommand));
2793
2794 if (!deathmatch.IsHost(player))
2795 return;
2796
2797 player.ChatMessage(msg("MatchCancel", player.UserIDString, szMatchChatCommand));
2798 player.ChatMessage(msg("MatchSize", player.UserIDString, szMatchChatCommand, minDeathmatchSize));
2799 player.ChatMessage(msg("MatchKickBan", player.UserIDString, szMatchChatCommand));
2800 player.ChatMessage(msg("MatchSetCode", player.UserIDString, szMatchChatCommand));
2801 player.ChatMessage(msg("MatchTogglePublic", player.UserIDString, szMatchChatCommand));
2802 player.ChatMessage(msg("MatchKit", player.UserIDString, szMatchChatCommand));
2803 player.ChatMessage(msg("InMatchListGood", player.UserIDString, deathmatch.GetNames(Team.Good)));
2804 player.ChatMessage(msg("InMatchListEvil", player.UserIDString, deathmatch.GetNames(Team.Evil)));
2805 }
2806
2807 return;
2808 }
2809
2810 RemoveRequests(player);
2811
2812 switch (args[0].ToLower())
2813 {
2814 case "autoready":
2815 {
2816 ToggleAutoReady(player);
2817 return;
2818 }
2819 case "rematch":
2820 case "ready":
2821 {
2822 ReadyUp(player);
2823 return;
2824 }
2825 case "kit":
2826 {
2827 if (deathmatch != null)
2828 {
2829 if (!deathmatch.IsHost(player))
2830 {
2831 player.ChatMessage(msg("MatchKitSet", player.UserIDString, deathmatch.Kit));
2832 return;
2833 }
2834
2835 if (args.Length == 2)
2836 {
2837 string kit = GetVerifiedKit(args[1]);
2838
2839 if (string.IsNullOrEmpty(kit))
2840 {
2841 player.ChatMessage(msg("MatchChallenge0", player.UserIDString, szMatchChatCommand));
2842 player.ChatMessage(msg("KitDoesntExist", player.UserIDString, args[1]));
2843
2844 string kits = string.Join(", ", VerifiedKits.ToArray());
2845
2846 if (!string.IsNullOrEmpty(kits))
2847 player.ChatMessage("Kits: " + kits);
2848 }
2849 else
2850 deathmatch.Kit = kit;
2851 }
2852 else
2853 player.ChatMessage(msg("MatchKit", player.UserIDString));
2854 }
2855 else
2856 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
2857
2858 return;
2859 }
2860 case "kickban":
2861 {
2862 if (deathmatch != null)
2863 {
2864 if (!deathmatch.IsHost(player))
2865 {
2866 player.ChatMessage(msg("MatchNotAHost", player.UserIDString));
2867 return;
2868 }
2869
2870 if (args.Length == 2)
2871 {
2872 var target = BasePlayer.Find(args[1]);
2873
2874 if (target != null)
2875 {
2876 if (deathmatch.GetTeam(target) == deathmatch.GetTeam(player))
2877 {
2878 if (deathmatch.Ban(target))
2879 player.ChatMessage(msg("MatchBannedUser", player.UserIDString, target.displayName));
2880 else
2881 player.ChatMessage(msg("MatchCannotBan", player.UserIDString));
2882 }
2883 else
2884 player.ChatMessage(msg("MatchPlayerNotFound", player.UserIDString, target.displayName));
2885 }
2886 else
2887 player.ChatMessage(msg("PlayerNotFound", player.UserIDString, args[1]));
2888 }
2889 else
2890 player.ChatMessage(msg("MatchKickBan", player.UserIDString));
2891 }
2892 else
2893 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
2894
2895 break;
2896 }
2897 case "setcode":
2898 {
2899 if (deathmatch != null)
2900 {
2901 if (deathmatch.IsHost(player))
2902 {
2903 if (args.Length == 2)
2904 deathmatch.SetCode(player, args[1]);
2905
2906 player.ChatMessage(msg("MatchCodeIs", player.UserIDString, deathmatch.GetTeam(player) == Team.Evil ? deathmatch.Code(Team.Evil) : deathmatch.Code(Team.Good)));
2907 }
2908 else
2909 player.ChatMessage(msg("MatchNotAHost", player.UserIDString));
2910 }
2911 else
2912 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
2913
2914 break;
2915 }
2916 case "cancel":
2917 case "decline":
2918 {
2919 if (deathmatch != null)
2920 {
2921 if (deathmatch.IsHost(player))
2922 {
2923 deathmatch.MessageAll("MatchCancelled", player.displayName);
2924 deathmatch.End();
2925
2926 if (tdmMatches.Contains(deathmatch))
2927 {
2928 tdmMatches.Remove(deathmatch);
2929 matchUpdateRequired = true;
2930 }
2931 }
2932 else
2933 player.ChatMessage(msg("MatchNotAHost", player.UserIDString));
2934 }
2935 else // also handle cancelling a match request
2936 {
2937 if (tdmRequests.ContainsValue(player.UserIDString))
2938 {
2939 var entry = tdmRequests.First(kvp => kvp.Value == player.UserIDString);
2940 var target = BasePlayer.Find(entry.Key);
2941
2942 if (target != null)
2943 target.ChatMessage(msg("MatchCancelled", target.UserIDString, player.displayName));
2944
2945 player.ChatMessage(msg("MatchCancelled", player.UserIDString, player.displayName));
2946 tdmRequests.Remove(entry.Key);
2947 return;
2948 }
2949
2950 if (tdmRequests.ContainsKey(player.UserIDString))
2951 {
2952 var target = BasePlayer.Find(tdmRequests[player.UserIDString]);
2953
2954 if (target != null)
2955 target.ChatMessage(msg("MatchCancelled", player.UserIDString, player.displayName));
2956
2957 player.ChatMessage(msg("MatchCancelled", player.UserIDString, player.displayName));
2958 tdmRequests.Remove(player.UserIDString);
2959 return;
2960 }
2961
2962 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
2963 }
2964
2965 break;
2966 }
2967 case "size":
2968 {
2969 if (deathmatch != null)
2970 {
2971 if (args.Length == 2)
2972 {
2973 if (args[1].All(char.IsDigit))
2974 {
2975 if (deathmatch.IsHost(player))
2976 {
2977 int size = Convert.ToInt32(args[1]);
2978
2979 if (size < minDeathmatchSize)
2980 size = deathmatch.TeamSize;
2981
2982 if (size > maxDeathmatchSize)
2983 size = maxDeathmatchSize;
2984
2985 if (deathmatch.TeamSize != size)
2986 deathmatch.TeamSize = size; // sends message to all players in the match
2987 }
2988 else
2989 player.ChatMessage(msg("MatchNotAHost", player.UserIDString));
2990 }
2991 else
2992 player.ChatMessage(msg("InvalidNumber", player.UserIDString, args[1]));
2993 }
2994 else
2995 player.ChatMessage(msg("MatchSizeSyntax", player.UserIDString, szMatchChatCommand));
2996 }
2997 else
2998 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
2999
3000 break;
3001 }
3002 case "accept":
3003 {
3004 if (InEvent(player))
3005 {
3006 player.ChatMessage(msg("AlreadyDueling", player.UserIDString));
3007 return;
3008 }
3009
3010 if (!tdmRequests.ContainsValue(player.UserIDString))
3011 {
3012 player.ChatMessage(msg("MatchNoneRequested", player.UserIDString));
3013 return;
3014 }
3015
3016 var kvp = tdmRequests.First(entry => entry.Value == player.UserIDString);
3017 var target = BasePlayer.Find(kvp.Key);
3018
3019 tdmRequests.Remove(kvp.Key);
3020
3021 if (!target || !target.IsConnected)
3022 {
3023 player.ChatMessage(msg("MatchPlayerOffline", player.UserIDString));
3024 break;
3025 }
3026
3027 SetupTeams(player, target);
3028 break;
3029 }
3030 case "leave":
3031 {
3032 if (deathmatch != null)
3033 {
3034 deathmatch.RemoveMatchPlayer(player);
3035 player.ChatMessage(msg("MatchPlayerLeft", player.UserIDString));
3036 }
3037 else
3038 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
3039
3040 break;
3041 }
3042 case "any":
3043 {
3044 if (tdmMatches.Count == 0)
3045 {
3046 player.ChatMessage(msg("MatchNoMatchesExist", player.UserIDString, szMatchChatCommand));
3047 return;
3048 }
3049
3050 if (deathmatch != null)
3051 {
3052 deathmatch.RemoveMatchPlayer(player);
3053 player.ChatMessage(msg("MatchPlayerLeft", player.UserIDString));
3054 }
3055
3056 foreach (var match in tdmMatches)
3057 {
3058 if (match.IsBanned(player.userID) || match.IsFull())
3059 continue;
3060
3061 if (!match.IsFull(Team.Good) && match.AlliedTo(player, Team.Good))
3062 {
3063 match.AddMatchPlayer(player, Team.Good);
3064 return;
3065 }
3066
3067 if (!match.IsFull(Team.Evil) && match.AlliedTo(player, Team.Evil))
3068 {
3069 match.AddMatchPlayer(player, Team.Evil);
3070 return;
3071 }
3072
3073 if (match.IsPublic)
3074 {
3075 if (!match.IsFull(Team.Good))
3076 {
3077 match.AddMatchPlayer(player, Team.Good);
3078 return;
3079 }
3080
3081 if (!match.IsFull(Team.Evil))
3082 {
3083 match.AddMatchPlayer(player, Team.Evil);
3084 return;
3085 }
3086 }
3087 }
3088
3089 player.ChatMessage(msg("MatchNoTeamFoundAny", player.UserIDString, args[0]));
3090 break;
3091 }
3092 case "public":
3093 {
3094 if (deathmatch != null)
3095 {
3096 if (!deathmatch.IsHost(player))
3097 {
3098 player.ChatMessage(msg("MatchNotAHost", player.UserIDString));
3099 return;
3100 }
3101
3102 deathmatch.IsPublic = !deathmatch.IsPublic;
3103 }
3104 else
3105 player.ChatMessage(msg("MatchDoesntExist", player.UserIDString, szMatchChatCommand));
3106
3107 break;
3108 }
3109 default:
3110 {
3111 if (args.Length > 1)
3112 {
3113 SetPlayerZone(player, args);
3114
3115 foreach (string arg in args)
3116 {
3117 string kit = GetVerifiedKit(arg);
3118
3119 if (!string.IsNullOrEmpty(kit))
3120 {
3121 tdmKits[player.UserIDString] = kit;
3122 break;
3123 }
3124 }
3125 }
3126
3127 var target = BasePlayer.Find(args[0]);
3128
3129 if (target != null)
3130 {
3131 if (target == player)
3132 {
3133 player.ChatMessage(msg("PlayerNotFound", player.UserIDString, args[0]));
3134 return;
3135 }
3136
3137 if (deathmatch != null)
3138 {
3139 player.ChatMessage(msg("MatchCannotChallengeAgain", player.UserIDString));
3140 return;
3141 }
3142
3143 if (InMatch(target) || tdmRequests.ContainsValue(target.UserIDString))
3144 {
3145 player.ChatMessage(msg("MatchCannotChallenge", player.UserIDString, target.displayName));
3146 return;
3147 }
3148
3149 if (!IsNewman(player))
3150 {
3151 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
3152 return;
3153 }
3154
3155 if (!IsNewman(target))
3156 {
3157 player.ChatMessage(msg("TargetMustBeNaked", player.UserIDString));
3158 return;
3159 }
3160
3161 player.ChatMessage(msg("MatchRequestSent", player.UserIDString, target.displayName));
3162 target.ChatMessage(msg("MatchRequested", target.UserIDString, player.displayName, szMatchChatCommand));
3163
3164 string uid = player.UserIDString;
3165 tdmRequests.Remove(uid);
3166 tdmRequests.Add(uid, target.UserIDString);
3167 timer.Once(60f, () => tdmRequests.Remove(uid));
3168 return;
3169 }
3170
3171 if (tdmMatches.Count == 0)
3172 {
3173 player.ChatMessage(msg("MatchNoMatchesExist", player.UserIDString, szMatchChatCommand));
3174 return;
3175 }
3176
3177 if (deathmatch != null)
3178 {
3179 deathmatch.RemoveMatchPlayer(player);
3180 player.ChatMessage(msg("MatchPlayerLeft", player.UserIDString));
3181 }
3182
3183 foreach (var match in tdmMatches)
3184 {
3185 if (match.IsBanned(player.userID))
3186 continue;
3187
3188 if (match.Code(Team.Good).Equals(args[0], StringComparison.OrdinalIgnoreCase))
3189 {
3190 match.AddMatchPlayer(player, Team.Good);
3191 return;
3192 }
3193 else if (match.Code(Team.Evil).Equals(args[0], StringComparison.OrdinalIgnoreCase))
3194 {
3195 match.AddMatchPlayer(player, Team.Evil);
3196 return;
3197 }
3198 }
3199
3200 player.ChatMessage(msg("MatchNoTeamFoundCode", player.UserIDString, args[0]));
3201
3202 }
3203 break;
3204 }
3205 }
3206
3207 public void cmdQueue(BasePlayer player, string command, string[] args)
3208 {
3209 if (!init || !player || !player.IsConnected)
3210 return;
3211
3212 if (!player.CanInteract())
3213 {
3214 timer.Once(1f, () => cmdQueue(player, command, args));
3215 return;
3216 }
3217
3218 if (!autoAllowAll && !duelsData.Allowed.Contains(player.UserIDString))
3219 {
3220 player.ChatMessage(msg("MustAllowDuels", player.UserIDString, szDuelChatCommand));
3221 return;
3222 }
3223
3224 if (InMatch(player))
3225 {
3226 player.ChatMessage(msg("MatchTeamed", player.UserIDString));
3227 return;
3228 }
3229
3230 if (IsDueling(player))
3231 {
3232 player.ChatMessage(msg("AlreadyInADuel", player.UserIDString));
3233 return;
3234 }
3235
3236 RemoveRequests(player);
3237
3238 if (!IsNewman(player))
3239 {
3240 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
3241 return;
3242 }
3243
3244 if (player.IsAdmin)
3245 {
3246 player.ChatMessage(msg("InQueueList", player.UserIDString));
3247 player.ChatMessage(string.Join(", ", duelsData.Queued.Select(kvp => GetDisplayName(kvp.Value)).ToArray()));
3248 }
3249
3250 if (!duelsData.Queued.ContainsValue(player.UserIDString))
3251 {
3252 long stamp = TimeStamp();
3253
3254 while (duelsData.Queued.ContainsKey(stamp))
3255 stamp++;
3256
3257 duelsData.Queued.Add(stamp, player.UserIDString);
3258 player.ChatMessage(msg("InQueueSuccess", player.UserIDString));
3259 CheckQueue();
3260 return;
3261 }
3262
3263 if (RemoveFromQueue(player.UserIDString))
3264 player.ChatMessage(msg("NoLongerQueued", player.UserIDString));
3265 }
3266
3267 private void cmdLadder(BasePlayer player, string command, string[] args)
3268 {
3269 if (!init)
3270 return;
3271
3272 bool onLadder = false;
3273 bool life = args.Any(arg => arg.ToLower().Contains("life"));
3274 var sorted = life ? duelsData.Victories.ToList() : duelsData.VictoriesSeed.ToList();
3275 sorted.Sort((x, y) => y.Value.CompareTo(x.Value));
3276
3277 player.ChatMessage(msg(life ? "TopAll" : "Top", player.UserIDString, sorted.Count));
3278
3279 for (int i = 0; i < 10; i++)
3280 {
3281 if (i >= sorted.Count)
3282 break;
3283
3284 if (sorted[i].Key == player.UserIDString)
3285 onLadder = true; // 0.1.2: fix for ranks showing user on ladder twice
3286
3287 string name = GetDisplayName(sorted[i].Key);
3288 int losses = 0;
3289
3290 if (life)
3291 losses = duelsData.Losses.ContainsKey(sorted[i].Key) ? duelsData.Losses[sorted[i].Key] : 0;
3292 else
3293 losses = duelsData.LossesSeed.ContainsKey(sorted[i].Key) ? duelsData.LossesSeed[sorted[i].Key] : 0;
3294
3295 double ratio = losses > 0 ? Math.Round(sorted[i].Value / (double)losses, 2) : sorted[i].Value;
3296 string message = msg("TopFormat", player.UserIDString, (i + 1).ToString(), name, sorted[i].Value, losses, ratio);
3297 Player.Message(player, message, Convert.ToUInt64(sorted[i].Key));
3298 }
3299
3300 if (!onLadder && !life && duelsData.VictoriesSeed.ContainsKey(player.UserIDString))
3301 {
3302 int index = sorted.FindIndex(kvp => kvp.Key == player.UserIDString);
3303 int losses = duelsData.LossesSeed.ContainsKey(player.UserIDString) ? duelsData.LossesSeed[player.UserIDString] : 0;
3304 double ratio = losses > 0 ? Math.Round(duelsData.VictoriesSeed[player.UserIDString] / (double)losses, 2) : duelsData.VictoriesSeed[player.UserIDString];
3305 string message = msg("TopFormat", player.UserIDString, index, player.displayName, duelsData.VictoriesSeed[player.UserIDString], losses, ratio);
3306 Player.Message(player, message, player.userID);
3307 }
3308
3309 if (!onLadder && life && duelsData.Victories.ContainsKey(player.UserIDString))
3310 {
3311 int index = sorted.FindIndex(kvp => kvp.Key == player.UserIDString);
3312 int losses = duelsData.Losses.ContainsKey(player.UserIDString) ? duelsData.Losses[player.UserIDString] : 0;
3313 double ratio = losses > 0 ? Math.Round(duelsData.Victories[player.UserIDString] / (double)losses, 2) : duelsData.Victories[player.UserIDString];
3314 string message = msg("TopFormat", player.UserIDString, index, player.displayName, duelsData.Victories[player.UserIDString], losses, ratio);
3315 Player.Message(player, message, player.userID);
3316 }
3317
3318 if (!life) player.ChatMessage(msg("LadderLife", player.UserIDString, szDuelChatCommand));
3319 sorted.Clear();
3320 sorted = null;
3321 }
3322
3323 private void ccmdDuel(ConsoleSystem.Arg arg)
3324 {
3325 if (!arg.IsAdmin)
3326 return;
3327
3328 string id = arg.Player()?.UserIDString ?? null;
3329
3330 if (arg.HasArgs(1))
3331 {
3332 switch (arg.Args[0].ToLower())
3333 {
3334 case "resetseed":
3335 {
3336 duelsData.VictoriesSeed.Clear();
3337 duelsData.LossesSeed.Clear();
3338 duelsData.MatchKillsSeed.Clear();
3339 duelsData.MatchDeathsSeed.Clear();
3340 duelsData.MatchLossesSeed.Clear();
3341 duelsData.MatchVictoriesSeed.Clear();
3342 duelsData.MatchSizesVictoriesSeed.Clear();
3343 duelsData.MatchSizesVictoriesSeed.Clear();
3344 arg.ReplyWith(msg("ResetSeed", arg.Player()?.UserIDString));
3345 break;
3346 }
3347 case "removeall":
3348 {
3349 if (duelingZones.Count > 0)
3350 {
3351 foreach (var zone in duelingZones.ToList())
3352 {
3353 EjectPlayers(zone);
3354 arg.ReplyWith(msg("RemovedZoneAt", id, zone.Position));
3355 RemoveDuelZone(zone);
3356 }
3357
3358 duelsData.DuelZones.Clear();
3359 SaveData();
3360 }
3361 else
3362 arg.ReplyWith(msg("NoZoneExists", id));
3363
3364 break;
3365 }
3366 case "1":
3367 case "enable":
3368 case "on":
3369 {
3370 if (duelsData.DuelsEnabled)
3371 {
3372 arg.ReplyWith(msg("DuelsEnabledAlready", id));
3373 return;
3374 }
3375
3376 duelsData.DuelsEnabled = true;
3377 arg.ReplyWith(msg("DuelsNowEnabled", id));
3378 DuelAnnouncement(false);
3379 SaveData();
3380 return;
3381 }
3382 case "0":
3383 case "disable":
3384 case "off":
3385 {
3386 if (!duelsData.DuelsEnabled)
3387 {
3388 arg.ReplyWith(msg("DuelsDisabledAlready", id));
3389 return;
3390 }
3391
3392 duelsData.DuelsEnabled = false;
3393 arg.ReplyWith(msg(dataDuelists.Count > 0 ? "DuelsNowDisabled" : "DuelsNowDisabledEmpty", id));
3394 SendDuelistsHome();
3395 SendSpectatorsHome();
3396 SaveData();
3397 return;
3398 }
3399 case "new":
3400 {
3401 if (duelsData.DuelZones.Count >= zoneAmount)
3402 {
3403 arg.ReplyWith(msg("ZoneLimit", id, zoneAmount));
3404 return;
3405 }
3406
3407 string zoneName = arg.Args.Length > 1 ? string.Join(" ", arg.Args.Skip(1).ToArray()) : GetZoneName();
3408
3409 if (SetupDuelZone(null, zoneName) != Vector3.zero)
3410 arg.ReplyWith(msg("ZoneCreated", id));
3411
3412 return;
3413 }
3414 default:
3415 {
3416 arg.ReplyWith(string.Format("{0} on|off|new|removeall|resetseed", szDuelChatCommand));
3417 break;
3418 }
3419 }
3420 }
3421 else
3422 arg.ReplyWith(string.Format("{0} on|off|new|removeall|resetseed", szDuelChatCommand));
3423 }
3424
3425 public void cmdDuel(BasePlayer player, string command, string[] args)
3426 {
3427 if (!init)
3428 return;
3429
3430 if (IsEventBanned(player.UserIDString))
3431 {
3432 player.ChatMessage(msg("Banned", player.UserIDString));
3433 return;
3434 }
3435
3436 if (dcsBlock.Contains(player.UserIDString))
3437 {
3438 player.ChatMessage(msg("SuicideBlock", player.UserIDString));
3439 return;
3440 }
3441
3442 if (args.Length >= 1 && args[0] == "ladder")
3443 {
3444 cmdLadder(player, command, args);
3445 return;
3446 }
3447
3448 if (!duelsData.DuelsEnabled)
3449 {
3450 if (!args.Any(arg => arg.ToLower() == "on"))
3451 player.ChatMessage(msg("DuelsDisabled", player.UserIDString));
3452
3453 if (!player.IsAdmin)
3454 return;
3455 }
3456
3457 bool noZone = duelsData.DuelZones.Count == 0 || duelingZones.Count == 0;
3458
3459 if (noZone)
3460 {
3461 if (!args.Any(arg => arg.ToLower() == "new") && !args.Any(arg => arg.ToLower() == "removeall") && !args.Any(arg => arg.ToLower() == "custom"))
3462 player.ChatMessage(msg("NoZoneExists", player.UserIDString));
3463
3464 if (!player.IsAdmin)
3465 return;
3466 }
3467
3468 if (!noZone && !duelsData.DuelsEnabled && !args.Any(arg => arg.ToLower() == "on"))
3469 player.ChatMessage(msg("DuelsMustBeEnabled", player.UserIDString, szDuelChatCommand));
3470
3471 if (IsDueling(player) && !player.IsAdmin)
3472 return;
3473
3474 if (args.Length == 0)
3475 {
3476 player.ChatMessage(msg("HelpDuels", player.UserIDString, duelsData.TotalDuels.ToString("N0")));
3477 player.ChatMessage(msg("ZoneNames", player.UserIDString, duelsData.DuelZones.Count, string.Join(" ", duelsData.DuelZones.Values.Take(10).ToArray())));
3478
3479 if (!autoAllowAll)
3480 player.ChatMessage(msg("HelpAllow", player.UserIDString, szDuelChatCommand));
3481
3482 player.ChatMessage(msg("HelpBlock", player.UserIDString, szDuelChatCommand));
3483 player.ChatMessage(msg("HelpChallenge", player.UserIDString, szDuelChatCommand));
3484 player.ChatMessage(msg("HelpAccept", player.UserIDString, szDuelChatCommand));
3485 player.ChatMessage(msg("HelpCancel", player.UserIDString, szDuelChatCommand));
3486 player.ChatMessage(msg("HelpChat", player.UserIDString, szDuelChatCommand));
3487 player.ChatMessage(msg("HelpQueue", player.UserIDString, szQueueChatCommand));
3488 player.ChatMessage(msg("HelpLadder", player.UserIDString, szDuelChatCommand));
3489 player.ChatMessage(msg("HelpKit", player.UserIDString, szDuelChatCommand));
3490
3491 if (allowBets)
3492 player.ChatMessage(msg("HelpBet", player.UserIDString, szDuelChatCommand));
3493
3494 if (tdmEnabled)
3495 player.ChatMessage(msg("HelpTDM", player.UserIDString, szMatchChatCommand));
3496
3497 player.ChatMessage(msg("UI_Help", player.UserIDString, szUIChatCommand));
3498
3499 if (player.IsAdmin)
3500 {
3501 player.ChatMessage(msg("HelpDuelAdmin", player.UserIDString, szDuelChatCommand));
3502 player.ChatMessage(msg("HelpDuelAdminRefundAll", player.UserIDString, szDuelChatCommand));
3503 }
3504
3505 return;
3506 }
3507
3508 switch (args[0].ToLower())
3509 {
3510 case "autoready":
3511 {
3512 ToggleAutoReady(player);
3513 return;
3514 }
3515 case "rematch":
3516 case "ready":
3517 {
3518 ReadyUp(player);
3519 return;
3520 }
3521 case "resetseed":
3522 {
3523 if (player.IsAdmin)
3524 {
3525 duelsData.VictoriesSeed.Clear();
3526 duelsData.LossesSeed.Clear();
3527 duelsData.MatchKillsSeed.Clear();
3528 duelsData.MatchDeathsSeed.Clear();
3529 duelsData.MatchLossesSeed.Clear();
3530 duelsData.MatchVictoriesSeed.Clear();
3531 duelsData.MatchSizesVictoriesSeed.Clear();
3532 duelsData.MatchSizesVictoriesSeed.Clear();
3533 player.ChatMessage(msg("ResetSeed", player.UserIDString));
3534 Puts("{0] ({1}): {2}", player.displayName, player.UserIDString, msg("ResetSeed", player.UserIDString));
3535 }
3536 break;
3537 }
3538 case "remove_all_walls":
3539 {
3540 if (player.IsAdmin)
3541 {
3542 int removed = 0;
3543
3544 if (respawnWalls)
3545 {
3546 Unsubscribe(nameof(OnEntityKill));
3547 }
3548
3549 foreach (var entity in GetWallEntities().ToList())
3550 {
3551 if (entity.OwnerID > 0 && !entity.OwnerID.IsSteamId())
3552 {
3553 entity.Kill();
3554 removed++;
3555 }
3556 }
3557
3558 if (respawnWalls)
3559 {
3560 Subscribe(nameof(OnEntityKill));
3561 }
3562
3563 player.ChatMessage(msg("RemovedXWalls", player.UserIDString, removed));
3564 }
3565 break;
3566 }
3567 case "remove_all":
3568 {
3569 if (player.IsAdmin && args.Length == 2 && args[1].All(char.IsDigit))
3570 {
3571 ulong ownerId = ulong.Parse(args[1]);
3572 int removed = 0;
3573
3574 if (respawnWalls)
3575 {
3576 Unsubscribe(nameof(OnEntityKill));
3577 }
3578
3579 foreach (var entity in GetWallEntities().ToList())
3580 {
3581 if (entity.OwnerID == ownerId)
3582 {
3583 entity.Kill();
3584 removed++;
3585 }
3586 }
3587
3588 if (respawnWalls)
3589 {
3590 Subscribe(nameof(OnEntityKill));
3591 }
3592
3593 player.ChatMessage(msg("RemovedXWalls", player.UserIDString, removed));
3594 }
3595 break;
3596 }
3597 case "0":
3598 case "disable":
3599 case "off":
3600 {
3601 if (player.IsAdmin)
3602 {
3603 if (!duelsData.DuelsEnabled)
3604 {
3605 player.ChatMessage(msg("DuelsDisabledAlready", player.UserIDString));
3606 return;
3607 }
3608
3609 duelsData.DuelsEnabled = false;
3610 player.ChatMessage(msg(dataDuelists.Count > 0 ? "DuelsNowDisabled" : "DuelsNowDisabledEmpty", player.UserIDString));
3611 SendDuelistsHome();
3612 SendSpectatorsHome();
3613 SaveData();
3614 }
3615 break;
3616 }
3617 case "1":
3618 case "enable":
3619 case "on":
3620 {
3621 if (player.IsAdmin)
3622 {
3623 if (duelsData.DuelsEnabled)
3624 {
3625 player.ChatMessage(msg("DuelsEnabledAlready", player.UserIDString));
3626 return;
3627 }
3628
3629 duelsData.DuelsEnabled = true;
3630 player.ChatMessage(msg("DuelsNowEnabled", player.UserIDString));
3631 DuelAnnouncement(false);
3632 SaveData();
3633 }
3634 break;
3635 }
3636 case "custom":
3637 case "me":
3638 {
3639 if (player.IsAdmin)
3640 {
3641 if (duelsData.DuelZones.Count >= zoneAmount)
3642 {
3643 player.ChatMessage(msg("ZoneLimit", player.UserIDString, zoneAmount));
3644 return;
3645 }
3646
3647 RaycastHit hit;
3648 if (Physics.Raycast(player.eyes.HeadRay(), out hit, Mathf.Infinity, wallMask))
3649 {
3650 if (DuelTerritory(hit.point, 5f))
3651 {
3652 player.ChatMessage(msg("ZoneExists", player.UserIDString));
3653 return;
3654 }
3655
3656 string zoneName = args.Length > 1 ? string.Join(" ", args.Skip(1).ToArray()) : GetZoneName();
3657 var zone = SetupDuelZone(hit.point, null, zoneName);
3658 int i = 0;
3659
3660 foreach (var spawn in zone.Spawns)
3661 player.SendConsoleCommand("ddraw.text", 30f, Color.yellow, spawn, ++i);
3662
3663 UpdateStability();
3664
3665 if (zoneCounter > 0) player.ChatMessage($"Zone will reset after {zoneCounter} duels. Disable by setting `Create New Zone Every X Duels [0 = disabled]` to `0` in the config.");
3666 }
3667 else
3668 player.ChatMessage(msg("FailedRaycast", player.UserIDString));
3669 }
3670 break;
3671 }
3672 case "remove":
3673 {
3674 if (player.IsAdmin)
3675 {
3676 if (duelingZones.Count > 0)
3677 {
3678 var zone = GetDuelZone(player.transform.position);
3679
3680 if (zone == null)
3681 {
3682 player.ChatMessage(msg("NoZoneFound", player.UserIDString));
3683 return;
3684 }
3685
3686 EjectPlayers(zone);
3687 RemoveDuelZone(zone);
3688 player.ChatMessage(msg("RemovedZone", player.UserIDString));
3689 }
3690
3691 }
3692 break;
3693 }
3694 case "removeall":
3695 {
3696 if (player.IsAdmin)
3697 {
3698 if (duelingZones.Count > 0)
3699 {
3700 foreach (var zone in duelingZones.ToList())
3701 {
3702 EjectPlayers(zone);
3703 player.ChatMessage(msg("RemovedZoneAt", player.UserIDString, zone.Position));
3704 RemoveDuelZone(zone);
3705 }
3706
3707 duelsData.DuelZones.Clear();
3708 SaveData();
3709 }
3710 else
3711 player.ChatMessage(msg("NoZoneExists", player.UserIDString));
3712 }
3713 break;
3714 }
3715 case "spawns":
3716 {
3717 if (player.IsAdmin)
3718 {
3719 if (args.Length >= 2)
3720 {
3721 switch (args[1].ToLower())
3722 {
3723 case "add":
3724 AddSpawnPoint(player, true);
3725 break;
3726 case "here":
3727 AddSpawnPoint(player, false);
3728 break;
3729 case "remove":
3730 RemoveSpawnPoint(player);
3731 break;
3732 case "removeall":
3733 RemoveSpawnPoints(player);
3734 break;
3735 case "wipe":
3736 WipeSpawnPoints(player);
3737 break;
3738 default:
3739 SendSpawnHelp(player);
3740 break;
3741 }
3742
3743 return;
3744 }
3745
3746 SendSpawnHelp(player);
3747
3748 int i = 0;
3749 float dist = float.MaxValue;
3750 DuelingZone destZone = null;
3751
3752 foreach (var zone in duelingZones)
3753 {
3754 if (zone.Distance(player.transform.position) > zoneRadius + 200f)
3755 continue;
3756
3757 float distance = zone.Distance(player.transform.position);
3758
3759 if (distance < dist)
3760 {
3761 dist = distance;
3762 destZone = zone;
3763 }
3764 }
3765
3766 if (destZone != null)
3767 foreach (var spawn in destZone.Spawns)
3768 player.SendConsoleCommand("ddraw.text", 30f, Color.yellow, spawn, ++i);
3769 }
3770 break;
3771 }
3772 case "rename":
3773 {
3774 if (player.IsAdmin)
3775 {
3776 if (args.Length > 1)
3777 {
3778 var zone = GetDuelZone(player.transform.position);
3779
3780 if (zone == null)
3781 {
3782 player.ChatMessage(msg("NoZoneFound", player.UserIDString));
3783 return;
3784 }
3785
3786 string zoneName = string.Join(" ", args.Skip(1).ToArray());
3787 duelsData.DuelZones[zone.Position.ToString()] = zoneName;
3788 player.ChatMessage(msg("ZoneRenamed", player.UserIDString, zoneName));
3789 }
3790 else
3791 player.ChatMessage(msg("ZoneRename", player.UserIDString, szDuelChatCommand));
3792
3793 return;
3794 }
3795 break;
3796 }
3797 case "new":
3798 {
3799 if (player.IsAdmin)
3800 {
3801 if (duelsData.DuelZones.Count >= zoneAmount)
3802 {
3803 player.ChatMessage(msg("ZoneLimit", player.UserIDString, zoneAmount));
3804 return;
3805 }
3806
3807 string zoneName = GetZoneName();
3808 var _nameArgs = args.Where(arg => arg.ToLower() != "tp").ToArray();
3809
3810 if (_nameArgs.Count() > 0)
3811 zoneName = string.Join(" ", _nameArgs);
3812
3813 var zonePos = SetupDuelZone(null, zoneName);
3814
3815 if (zonePos != Vector3.zero)
3816 {
3817 player.ChatMessage(msg("ZoneCreated", player.UserIDString));
3818
3819 if (args.Any(arg => arg.ToLower() == "tp"))
3820 {
3821 Player.Teleport(player, zonePos);
3822 }
3823 }
3824
3825 }
3826 break;
3827 }
3828 case "tpm":
3829 {
3830 if (player.IsAdmin)
3831 {
3832 float dist = float.MaxValue;
3833 var dest = Vector3.zero;
3834 var matches = tdmMatches.Any(m => m.IsStarted) ? tdmMatches.Where(m => m.IsStarted).ToList() : tdmMatches.ToList(); // 0.1.17 if multiple zones then choose from active ones if any exist
3835
3836 foreach (var match in matches)
3837 {
3838 float distance = match.Zone.Distance(player.transform.position);
3839
3840 if (matches.Count > 1 && distance < zoneRadius * 4f) // move admin to the next nearest zone
3841 continue;
3842
3843 if (distance < dist)
3844 {
3845 dist = distance;
3846 dest = match.Zone.Position;
3847 }
3848 }
3849
3850 if (dest != Vector3.zero)
3851 Player.Teleport(player, dest);
3852 }
3853 break;
3854 }
3855 case "tp":
3856 {
3857 if (player.IsAdmin)
3858 {
3859 float dist = float.MaxValue;
3860 var dest = Vector3.zero;
3861 var zones = duelingZones.Count > 3 && duelingZones.Any(zone => zone.TotalPlayers > 0) ? duelingZones.Where(zone => zone.TotalPlayers > 0).ToList() : duelingZones; // 0.1.17 if multiple zones then choose from active ones if any exist
3862
3863 foreach (var zone in zones)
3864 {
3865 float distance = zone.Distance(player.transform.position);
3866
3867 if (zones.Count > 1 && distance < zoneRadius * 4f) // move admin to the next nearest zone
3868 continue;
3869
3870 if (distance < dist)
3871 {
3872 dist = distance;
3873 dest = zone.Position;
3874 }
3875 }
3876
3877 if (dest != Vector3.zero)
3878 Player.Teleport(player, dest);
3879 }
3880 break;
3881 }
3882 case "save":
3883 {
3884 if (player.IsAdmin)
3885 {
3886 SaveData();
3887 player.ChatMessage(msg("DataSaved", player.UserIDString));
3888 }
3889 }
3890 break;
3891 case "ban":
3892 {
3893 if (player.IsAdmin && args.Length >= 2)
3894 {
3895 string targetId = args[1].IsSteamId() ? args[1] : BasePlayer.Find(args[1])?.UserIDString ?? null;
3896
3897 if (string.IsNullOrEmpty(targetId))
3898 {
3899 player.ChatMessage(msg("PlayerNotFound", player.UserIDString, args[1]));
3900 return;
3901 }
3902
3903 if (!duelsData.Bans.ContainsKey(targetId))
3904 {
3905 duelsData.Bans.Add(targetId, player.UserIDString);
3906 player.ChatMessage(msg("AddedBan", player.UserIDString, targetId));
3907 }
3908 else
3909 {
3910 duelsData.Bans.Remove(targetId);
3911 player.ChatMessage(msg("RemovedBan", player.UserIDString, targetId));
3912 }
3913
3914 SaveData();
3915 }
3916 break;
3917 }
3918 case "announce":
3919 {
3920 if (player.IsAdmin)
3921 DuelAnnouncement(true);
3922
3923 break;
3924 }
3925 case "claim":
3926 {
3927 if (!duelsData.ClaimBets.ContainsKey(player.UserIDString))
3928 {
3929 player.ChatMessage(msg("NoBetsToClaim", player.UserIDString));
3930 return;
3931 }
3932
3933 foreach (var bet in duelsData.ClaimBets[player.UserIDString].ToList())
3934 {
3935 var item = ItemManager.CreateByItemID(bet.itemid, bet.amount);
3936
3937 if (item == null)
3938 {
3939 continue;
3940 }
3941
3942 if (!item.MoveToContainer(player.inventory.containerMain, -1))
3943 {
3944 var position = player.transform.position;
3945 item.Drop(position + new Vector3(0f, 1f, 0f) + position / 2f, (position + new Vector3(0f, 0.2f, 0f)) * 8f); // Credit: Slack comment by @visagalis
3946 }
3947
3948 string message = msg("PlayerClaimedBet", player.UserIDString, item.info.displayName.translated, item.amount);
3949
3950 player.ChatMessage(message);
3951 Puts("{0} ({1}) - {2}", player.displayName, player.UserIDString, message);
3952 duelsData.ClaimBets[player.UserIDString].Remove(bet);
3953
3954 if (duelsData.ClaimBets[player.UserIDString].Count == 0)
3955 {
3956 duelsData.ClaimBets.Remove(player.UserIDString);
3957 player.ChatMessage(msg("AllBetsClaimed", player.UserIDString));
3958 }
3959 }
3960 return;
3961 }
3962 case "queue":
3963 case "que":
3964 case "q":
3965 {
3966 cmdQueue(player, command, args);
3967 return;
3968 }
3969 case "chat":
3970 {
3971 if (broadcastDefeat)
3972 {
3973 if (!duelsData.Chat.Contains(player.UserIDString))
3974 duelsData.Chat.Add(player.UserIDString);
3975 else
3976 duelsData.Chat.Remove(player.UserIDString);
3977
3978 player.ChatMessage(msg(duelsData.Chat.Contains(player.UserIDString) ? "DuelChatOff" : "DuelChatOn", player.UserIDString));
3979 }
3980 else
3981 {
3982 if (!duelsData.ChatEx.Contains(player.UserIDString))
3983 duelsData.ChatEx.Add(player.UserIDString);
3984 else
3985 duelsData.ChatEx.Remove(player.UserIDString);
3986
3987 player.ChatMessage(msg(duelsData.ChatEx.Contains(player.UserIDString) ? "DuelChatOn" : "DuelChatOff", player.UserIDString));
3988 }
3989 return;
3990 }
3991 case "kit":
3992 {
3993 string kits = string.Join(", ", VerifiedKits.ToArray());
3994
3995 if (args.Length == 2 && !string.IsNullOrEmpty(kits))
3996 {
3997 string kit = GetVerifiedKit(args[1]);
3998
3999 if (!string.IsNullOrEmpty(kit))
4000 {
4001 duelsData.CustomKits[player.UserIDString] = kit;
4002 player.ChatMessage(msg("KitSet", player.UserIDString, kit));
4003 }
4004 else
4005 player.ChatMessage(msg("KitDoesntExist", player.UserIDString, args[1]));
4006
4007 return;
4008 }
4009
4010 if (duelsData.CustomKits.ContainsKey(player.UserIDString))
4011 {
4012 duelsData.CustomKits.Remove(player.UserIDString);
4013 player.ChatMessage(msg("ResetKit", player.UserIDString));
4014 }
4015
4016 player.ChatMessage(string.IsNullOrEmpty(kits) ? msg("KitsNotConfigured", player.UserIDString) : "Kits: " + kits);
4017 return;
4018 }
4019 case "allow":
4020 {
4021 if (!duelsData.Allowed.Contains(player.UserIDString))
4022 {
4023 duelsData.Allowed.Add(player.UserIDString);
4024 player.ChatMessage(msg("PlayerRequestsOn", player.UserIDString));
4025 return;
4026 }
4027
4028 duelsData.Allowed.Remove(player.UserIDString);
4029 player.ChatMessage(msg("PlayerRequestsOff", player.UserIDString));
4030 RemoveRequests(player);
4031 return;
4032 }
4033 case "block":
4034 {
4035 if (args.Length >= 2)
4036 {
4037 var target = BasePlayer.Find(args[1]);
4038
4039 if (!target)
4040 {
4041 player.ChatMessage(msg("PlayerNotFound", player.UserIDString, args[1]));
4042 return;
4043 }
4044
4045 if (!duelsData.BlockedUsers.ContainsKey(player.UserIDString))
4046 {
4047 duelsData.BlockedUsers.Add(player.UserIDString, new List<string>());
4048 }
4049 else if (duelsData.BlockedUsers[player.UserIDString].Contains(target.UserIDString))
4050 {
4051 duelsData.BlockedUsers[player.UserIDString].Remove(target.UserIDString);
4052
4053 if (duelsData.BlockedUsers[player.UserIDString].Count == 0)
4054 duelsData.BlockedUsers.Remove(player.UserIDString);
4055
4056 player.ChatMessage(msg("UnblockedRequestsFrom", player.UserIDString, target.displayName));
4057 return;
4058 }
4059
4060 duelsData.BlockedUsers[player.UserIDString].Add(target.UserIDString);
4061 player.ChatMessage(msg("BlockedRequestsFrom", player.UserIDString, target.displayName));
4062 return;
4063 }
4064
4065 if (duelsData.Allowed.Contains(player.UserIDString))
4066 {
4067 duelsData.Allowed.Remove(player.UserIDString);
4068 player.ChatMessage(msg("PlayerRequestsOff", player.UserIDString));
4069 RemoveRequests(player);
4070 return;
4071 }
4072
4073 player.ChatMessage(msg("AlreadyBlocked", player.UserIDString));
4074 return;
4075 }
4076 case "bet":
4077 {
4078 if (!allowBets)
4079 {
4080 player.ChatMessage("Betting is disabled.");
4081 break;
4082 }
4083
4084 if (duelingBets.Count == 0)
4085 {
4086 player.ChatMessage(msg("NoBetsConfigured", player.UserIDString));
4087 return;
4088 }
4089
4090 if (args.Length == 2)
4091 {
4092 switch (args[1].ToLower())
4093 {
4094 case "refundall":
4095 {
4096 if (player.IsAdmin)
4097 {
4098 if (duelsData.Bets.Count == 0)
4099 {
4100 player.ChatMessage(msg("NoBetsToRefund", player.UserIDString));
4101 return;
4102 }
4103
4104 foreach (var kvp in duelsData.Bets.ToList())
4105 {
4106 var target = BasePlayer.Find(kvp.Key);
4107 if (target == null) continue;
4108
4109 Item item = ItemManager.CreateByItemID(kvp.Value.itemid, kvp.Value.amount);
4110
4111 if (item == null)
4112 continue;
4113
4114 if (!item.MoveToContainer(target.inventory.containerMain, -1, true) && !item.MoveToContainer(target.inventory.containerBelt, -1, true))
4115 {
4116 item.Remove(0.01f);
4117 continue;
4118 }
4119
4120 target.ChatMessage(msg("RefundAllPlayerNotice", target.UserIDString, item.info.displayName.translated, item.amount));
4121 player.ChatMessage(msg("RefundAllAdminNotice", player.UserIDString, target.displayName, target.UserIDString, item.info.displayName.english, item.amount));
4122 duelsData.Bets.Remove(kvp.Key);
4123 }
4124
4125 if (duelsData.Bets.Count > 0) player.ChatMessage(msg("BetsRemaining", player.UserIDString, duelsData.Bets.Count));
4126 else player.ChatMessage(msg("AllBetsRefunded", player.UserIDString));
4127 SaveData();
4128 return;
4129 }
4130
4131 break;
4132 }
4133 case "forfeit":
4134 {
4135 if (allowBetRefund) // prevent operator error ;)
4136 {
4137 cmdDuel(player, command, new[] { "bet", "refund" });
4138 return;
4139 }
4140
4141 if (!allowBetForfeit)
4142 {
4143 player.ChatMessage(msg("CannotForfeit", player.UserIDString));
4144 return;
4145 }
4146
4147 if (duelsData.Bets.ContainsKey(player.UserIDString))
4148 {
4149 if (dataRequests.ContainsKey(player.UserIDString) || dataRequests.ContainsValue(player.UserIDString))
4150 {
4151 player.ChatMessage(msg("CannotForfeitRequestDuel", player.UserIDString));
4152 return;
4153 }
4154
4155 if (dataDuelists.ContainsKey(player.UserIDString))
4156 {
4157 player.ChatMessage(msg("CannotForfeitInDuel", player.UserIDString));
4158 return;
4159 }
4160
4161 duelsData.Bets.Remove(player.UserIDString);
4162 player.ChatMessage(msg("BetForfeit", player.UserIDString));
4163 SaveData();
4164 }
4165 else
4166 player.ChatMessage(msg("NoBetToForfeit", player.UserIDString));
4167
4168 return;
4169 }
4170 case "cancel":
4171 case "refund":
4172 {
4173 if (!allowBetRefund && !player.IsAdmin)
4174 {
4175 player.ChatMessage(msg("CannotRefund", player.UserIDString));
4176 return;
4177 }
4178
4179 if (duelsData.Bets.ContainsKey(player.UserIDString))
4180 {
4181 if (dataRequests.ContainsKey(player.UserIDString) || dataRequests.ContainsValue(player.UserIDString))
4182 {
4183 player.ChatMessage(msg("CannotRefundRequestDuel", player.UserIDString));
4184 return;
4185 }
4186
4187 if (dataDuelists.ContainsKey(player.UserIDString))
4188 {
4189 player.ChatMessage(msg("CannotRefundInDuel", player.UserIDString));
4190 return;
4191 }
4192
4193 var bet = duelsData.Bets[player.UserIDString];
4194
4195 Item item = ItemManager.CreateByItemID(bet.itemid, bet.amount);
4196
4197 if (!item.MoveToContainer(player.inventory.containerMain, -1, true))
4198 {
4199 if (!item.MoveToContainer(player.inventory.containerBelt, -1, true))
4200 {
4201 var position = player.transform.position;
4202 item.Drop(position + new Vector3(0f, 1f, 0f) + position / 2f, (position + new Vector3(0f, 0.2f, 0f)) * 8f); // Credit: Slack comment by @visagalis
4203 }
4204 }
4205
4206 duelsData.Bets.Remove(player.UserIDString);
4207 player.ChatMessage(msg("BetRefunded", player.UserIDString));
4208 SaveData();
4209 }
4210 else
4211 player.ChatMessage(msg("NoBetToRefund", player.UserIDString));
4212
4213 return;
4214 }
4215 default:
4216 break;
4217 }
4218 }
4219
4220 if (duelsData.Bets.ContainsKey(player.UserIDString))
4221 {
4222 var bet = duelsData.Bets[player.UserIDString];
4223
4224 player.ChatMessage(msg("AlreadyBetting", player.UserIDString, bet.trigger, bet.amount));
4225
4226 if (allowBetRefund)
4227 player.ChatMessage(msg("ToRefundUse", player.UserIDString, szDuelChatCommand));
4228 else if (allowBetForfeit)
4229 player.ChatMessage(msg("ToForfeitUse", player.UserIDString, szDuelChatCommand));
4230
4231 return;
4232 }
4233
4234 if (args.Length < 3)
4235 {
4236 player.ChatMessage(msg("AvailableBets", player.UserIDString));
4237
4238 foreach (var betInfo in duelingBets)
4239 player.ChatMessage(string.Format("{0} (max: {1})", betInfo.trigger, betInfo.max));
4240
4241 player.ChatMessage(msg("BetSyntax", player.UserIDString, szDuelChatCommand));
4242 return;
4243 }
4244
4245 int betAmount;
4246 if (!int.TryParse(args[2], out betAmount))
4247 {
4248 player.ChatMessage(msg("InvalidNumber", player.UserIDString, args[2]));
4249 return;
4250 }
4251
4252 if (betAmount > 500 && betAmount % 500 != 0)
4253 {
4254 player.ChatMessage(msg("MultiplesOnly", player.UserIDString));
4255 return;
4256 }
4257
4258 foreach (var betInfo in duelingBets)
4259 {
4260 if (betInfo.trigger.ToLower() == args[1].ToLower())
4261 {
4262 CreateBet(player, betAmount, betInfo);
4263 return;
4264 }
4265 }
4266
4267 player.ChatMessage(msg("InvalidBet", player.UserIDString, args[1]));
4268 return;
4269 }
4270 case "accept":
4271 case "a":
4272 case "y":
4273 case "yes":
4274 {
4275 if (!autoAllowAll && !duelsData.Allowed.Contains(player.UserIDString))
4276 {
4277 player.ChatMessage(msg("MustAllowDuels", player.UserIDString, szDuelChatCommand));
4278 return;
4279 }
4280
4281 if (InEvent(player))
4282 {
4283 player.ChatMessage(msg("AlreadyDueling", player.UserIDString));
4284 return;
4285 }
4286
4287 if (!dataRequests.ContainsValue(player.UserIDString))
4288 {
4289 player.ChatMessage(msg("NoRequestsReceived", player.UserIDString));
4290 return;
4291 }
4292
4293 if (!IsNewman(player))
4294 {
4295 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
4296 return;
4297 }
4298
4299 BasePlayer target = null;
4300
4301 foreach (var kvp in dataRequests)
4302 {
4303 if (kvp.Value == player.UserIDString)
4304 {
4305 target = BasePlayer.Find(kvp.Key);
4306
4307 if (target == null || !target.IsConnected)
4308 {
4309 player.ChatMessage(string.Format("DuelCancelledFor", player.UserIDString, GetDisplayName(kvp.Key)));
4310 dataRequests.Remove(kvp.Key);
4311 return;
4312 }
4313
4314 break;
4315 }
4316 }
4317
4318 if (!IsNewman(target))
4319 {
4320 player.ChatMessage(msg("TargetMustBeNaked", player.UserIDString));
4321 target.ChatMessage(msg("MustBeNaked", target.UserIDString));
4322 return;
4323 }
4324
4325 duelsData.Restricted.Remove(player.UserIDString);
4326 duelsData.Restricted.Remove(target.UserIDString);
4327
4328 if (!SelectZone(player, target))
4329 {
4330 player.ChatMessage(msg("AllZonesFull", player.UserIDString, duelingZones.Count, playersPerZone));
4331 target.ChatMessage(msg("AllZonesFull", target.UserIDString, duelingZones.Count, playersPerZone));
4332 }
4333
4334 return;
4335 }
4336 case "cancel":
4337 case "decline":
4338 {
4339 if (IsDueling(player))
4340 return;
4341
4342 if (!autoAllowAll && !duelsData.Allowed.Contains(player.UserIDString))
4343 {
4344 player.ChatMessage(msg("MustAllowDuels", player.UserIDString, szDuelChatCommand));
4345 return;
4346 }
4347
4348 var entry = dataRequests.FirstOrDefault(kvp => dataRequests.ContainsKey(player.UserIDString) ? kvp.Key == player.UserIDString : kvp.Value == player.UserIDString);
4349
4350 if (!string.IsNullOrEmpty(entry.Key))
4351 {
4352 var target = BasePlayer.Find(entry.Key) ?? BasePlayer.Find(entry.Value);
4353
4354 if (target != null)
4355 target.ChatMessage(msg("DuelCancelledWith", target.UserIDString, player.displayName));
4356
4357 player.ChatMessage(msg("DuelCancelComplete", player.UserIDString));
4358 dataRequests.Remove(entry.Key);
4359 return;
4360 }
4361
4362 player.ChatMessage(msg("NoPendingRequests", player.UserIDString));
4363 return;
4364 }
4365 default:
4366 {
4367 if (!autoAllowAll && !duelsData.Allowed.Contains(player.UserIDString))
4368 {
4369 player.ChatMessage(msg("MustAllowDuels", player.UserIDString, szDuelChatCommand));
4370 return;
4371 }
4372
4373 if (IsDueling(player))
4374 {
4375 player.ChatMessage(msg("AlreadyDueling", player.UserIDString));
4376 return;
4377 }
4378
4379 if (duelsData.Restricted.Contains(player.UserIDString) && !player.IsAdmin)
4380 {
4381 player.ChatMessage(msg("MustWaitToRequestAgain", player.UserIDString, 1));
4382 return;
4383 }
4384
4385 if (!IsNewman(player))
4386 {
4387 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
4388 return;
4389 }
4390
4391 var target = BasePlayer.Find(args[0]);
4392
4393 if (target == null || target == player) //if (target == null || (target == player && target.userID != 76561198212544308))
4394 {
4395 player.ChatMessage(msg("PlayerNotFound", player.UserIDString, args[0]));
4396 return;
4397 }
4398
4399 if (duelsData.BlockedUsers.ContainsKey(target.UserIDString) && duelsData.BlockedUsers[target.UserIDString].Contains(player.UserIDString))
4400 {
4401 player.ChatMessage(msg("CannotRequestThisPlayer", player.UserIDString));
4402 return;
4403 }
4404
4405 if (IsDueling(target))
4406 {
4407 player.ChatMessage(msg("TargetAlreadyDueling", player.UserIDString, target.displayName));
4408 return;
4409 }
4410
4411 if (!autoAllowAll && !duelsData.Allowed.Contains(target.UserIDString))
4412 {
4413 player.ChatMessage(msg("NotAllowedYet", player.UserIDString, target.displayName, szDuelChatCommand));
4414 return;
4415 }
4416
4417 if (dataRequests.ContainsKey(player.UserIDString))
4418 {
4419 player.ChatMessage(msg("MustWaitForAccept", player.UserIDString, GetDisplayName(dataRequests[player.UserIDString])));
4420 return;
4421 }
4422
4423 if (dataRequests.ContainsValue(target.UserIDString))
4424 {
4425 player.ChatMessage(msg("PendingRequestAlready", player.UserIDString));
4426 return;
4427 }
4428
4429 if (duelsData.Bets.ContainsKey(player.UserIDString) && !duelsData.Bets.ContainsKey(target.UserIDString))
4430 {
4431 var bet = duelsData.Bets[player.UserIDString];
4432
4433 player.ChatMessage(msg("TargetHasNoBet", player.UserIDString, target.displayName));
4434 player.ChatMessage(msg("YourBet", player.UserIDString, bet.trigger, bet.amount));
4435 return;
4436 }
4437
4438 if (duelsData.Bets.ContainsKey(target.UserIDString) && !duelsData.Bets.ContainsKey(player.UserIDString))
4439 {
4440 var targetBet = duelsData.Bets[target.UserIDString];
4441 player.ChatMessage(msg("MustHaveSameBet", player.UserIDString, target.displayName, targetBet.trigger, targetBet.amount));
4442 return;
4443 }
4444
4445 if (duelsData.Bets.ContainsKey(player.UserIDString) && duelsData.Bets.ContainsKey(target.UserIDString))
4446 {
4447 var playerBet = duelsData.Bets[player.UserIDString];
4448 var targetBet = duelsData.Bets[target.UserIDString];
4449
4450 if (!playerBet.Equals(targetBet))
4451 {
4452 player.ChatMessage(msg("BetsDoNotMatch", player.UserIDString, playerBet.trigger, playerBet.amount, targetBet.trigger, targetBet.amount));
4453 return;
4454 }
4455 }
4456
4457 if (args.Length > 1)
4458 SetPlayerZone(player, args.Skip(1).ToArray());
4459
4460 dataRequests.Add(player.UserIDString, target.UserIDString);
4461 target.ChatMessage(msg("DuelRequestReceived", target.UserIDString, player.displayName, szDuelChatCommand));
4462 player.ChatMessage(msg("DuelRequestSent", player.UserIDString, target.displayName, szDuelChatCommand));
4463
4464 if (RemoveFromQueue(player.UserIDString))
4465 player.ChatMessage(msg("RemovedFromQueueRequest", player.UserIDString));
4466
4467 string targetName = target.displayName;
4468 string playerId = player.UserIDString;
4469
4470 if (!duelsData.Restricted.Contains(playerId))
4471 duelsData.Restricted.Add(playerId);
4472
4473 timer.In(60f, () =>
4474 {
4475 duelsData.Restricted.Remove(playerId);
4476
4477 if (dataRequests.ContainsKey(playerId))
4478 {
4479 if (player != null && !IsDueling(player))
4480 player.ChatMessage(msg("RequestTimedOut", playerId, targetName));
4481
4482 dataRequests.Remove(playerId);
4483 }
4484 });
4485
4486 break;
4487 }
4488 } // end switch
4489 }
4490
4491 public DuelingZone GetPlayerZone(BasePlayer player, int size)
4492 {
4493 if (playerZones.ContainsKey(player.UserIDString))
4494 {
4495 var kvp = duelsData.DuelZones.FirstOrDefault(entry => entry.Value.Equals(playerZones[player.UserIDString], StringComparison.OrdinalIgnoreCase));
4496
4497 playerZones.Remove(player.UserIDString);
4498
4499 if (!string.IsNullOrEmpty(kvp.Key))
4500 {
4501 var zone = duelingZones.FirstOrDefault(x => x.Position.ToString() == kvp.Key);
4502
4503 if (size > 2)
4504 return zone == null || zone.IsLocked || zone.Spawns.Count < (requireTeamSize ? size * 2 : 2) ? null : zone;
4505
4506 return zone == null || zone.IsLocked || zone.Spawns.Count < requiredMinSpawns || zone.Spawns.Count > requiredMaxSpawns ? null : zone;
4507 }
4508 }
4509
4510 return null;
4511 }
4512
4513 public bool SetPlayerZone(BasePlayer player, string[] args)
4514 {
4515 foreach (string arg in args)
4516 {
4517 if (duelsData.DuelZones.Values.Any(zoneName => zoneName.Equals(arg, StringComparison.OrdinalIgnoreCase)))
4518 {
4519 string zoneName = duelsData.DuelZones.Values.First(x => x.Equals(arg, StringComparison.OrdinalIgnoreCase));
4520 player.ChatMessage(msg("ZoneSet", player.UserIDString, zoneName));
4521 playerZones[player.UserIDString] = zoneName;
4522 return true;
4523 }
4524 }
4525
4526 return false;
4527 }
4528
4529 public void SetupTeams(BasePlayer player, BasePlayer target)
4530 {
4531 if (!IsNewman(player))
4532 {
4533 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
4534 target.ChatMessage(msg("DuelMustBeNaked", target.UserIDString, player.displayName));
4535 return;
4536 }
4537
4538 if (!IsNewman(target))
4539 {
4540 target.ChatMessage(msg("MustBeNaked", target.UserIDString));
4541 player.ChatMessage(msg("DuelMustBeNaked", player.UserIDString, target.displayName));
4542 return;
4543 }
4544
4545 RemoveFromQueue(player.UserIDString);
4546 RemoveFromQueue(target.UserIDString);
4547
4548 var match = new GoodVersusEvilMatch();
4549 match.Setup(player, target);
4550
4551 SubscribeHooks(true);
4552 }
4553
4554 private void ResetTemporaryData() // keep our datafile cleaned up by removing entries which are temporary
4555 {
4556 if (duelsData == null)
4557 duelsData = new StoredData();
4558
4559 dataDuelists.Clear();
4560 dataRequests.Clear();
4561 dataImmunity.Clear();
4562 dataImmunitySpawns.Clear();
4563 duelsData.Restricted.Clear();
4564 dataDeath.Clear();
4565 duelsData.Queued.Clear();
4566 duelsData.Homes.Clear();
4567 duelsData.Kits.Clear();
4568 SaveData();
4569 }
4570
4571 public DuelingZone RemoveDuelist(string playerId)
4572 {
4573 foreach (var zone in duelingZones)
4574 {
4575 if (zone.HasPlayer(playerId))
4576 {
4577 zone.RemovePlayer(playerId);
4578 return zone;
4579 }
4580 }
4581
4582 return null;
4583 }
4584
4585 public void ResetDuelist(string targetId, bool removeHome = true) // remove a dueler from the datafile
4586 {
4587 duelsData.Kits.Remove(targetId);
4588 duelsData.Restricted.Remove(targetId);
4589 dataImmunity.Remove(targetId);
4590 dataImmunitySpawns.Remove(targetId);
4591 dataDuelists.Remove(targetId);
4592 dataRequests.Remove(targetId);
4593 dataDeath.Remove(targetId);
4594
4595 if (removeHome)
4596 duelsData.Homes.Remove(targetId);
4597
4598 if (duelingZones.Count > 0)
4599 RemoveDuelist(targetId);
4600
4601 RemoveFromQueue(targetId);
4602 }
4603
4604 private void RemoveZeroStats() // someone enabled duels but never joined one. remove them to keep the datafile cleaned up
4605 {
4606 foreach (string targetId in duelsData.Allowed.ToList())
4607 {
4608 if (!duelsData.Losses.ContainsKey(targetId) && !duelsData.Victories.ContainsKey(targetId)) // no permanent stats
4609 {
4610 ResetDuelist(targetId);
4611 duelsData.Allowed.Remove(targetId);
4612 }
4613 }
4614 }
4615
4616 public void SetupZoneManager()
4617 {
4618 var zoneIds = ZoneManager?.Call("GetZoneIDs");
4619
4620 if (zoneIds != null && zoneIds is string[])
4621 {
4622 foreach (var zoneId in (string[])zoneIds)
4623 {
4624 var zoneLoc = ZoneManager?.Call("GetZoneLocation", zoneId);
4625
4626 if (zoneLoc is Vector3 && (Vector3)zoneLoc != Vector3.zero)
4627 {
4628 var position = (Vector3)zoneLoc;
4629 var zoneRadius = ZoneManager?.Call("GetZoneRadius", zoneId);
4630 float distance = 0f;
4631
4632 if (zoneRadius is float && (float)zoneRadius > 0f)
4633 {
4634 distance = (float)zoneRadius;
4635 }
4636 else
4637 {
4638 var zoneSize = ZoneManager?.Call("GetZoneSize", zoneId);
4639
4640 if (zoneSize is Vector3 && (Vector3)zoneSize != Vector3.zero)
4641 {
4642 var size = (Vector3)zoneSize;
4643 distance = Mathf.Max(size.x, size.y);
4644 }
4645 }
4646
4647 if (distance > 0f)
4648 {
4649 distance += Duelist.zoneRadius + 5f;
4650 managedZones[position] = distance;
4651 }
4652 }
4653 }
4654 }
4655 }
4656
4657 public void SetupZones()
4658 {
4659 if (duelsData.ZoneIds.Count > 0)
4660 {
4661 foreach (string id in duelsData.ZoneIds)
4662 {
4663 duelsData.DuelZones[id] = GetZoneName();
4664 }
4665
4666 duelsData.ZoneIds.Clear();
4667 SaveData();
4668 }
4669
4670 if (duelsData.DuelZones.Count > zoneAmount) // zoneAmount was changed in the config file so remove existing zones until we're at the new cap
4671 {
4672 int removed = 0;
4673
4674 do
4675 {
4676 string zoneId = duelsData.DuelZones.First().Key;
4677 var zonePos = zoneId.ToVector3();
4678
4679 if (spAutoRemove && duelsData.Spawns.Count > 0)
4680 foreach (string spawn in duelsData.Spawns.ToList())
4681 if (Vector3.Distance(spawn.ToVector3(), zonePos) <= zoneRadius)
4682 duelsData.Spawns.Remove(spawn);
4683
4684 duelsData.AutoGeneratedSpawns.Remove(zoneId);
4685 duelsData.DuelZones.Remove(zoneId);
4686 removed += RemoveZoneWalls(GetOwnerId(zoneId));
4687 } while (duelsData.DuelZones.Count > zoneAmount);
4688
4689 if (removed > 0)
4690 Puts(msg("RemovedXWallsCustom", null, removed));
4691 }
4692
4693 var entities = autoSetup && (duelsData.DuelZones.Count < zoneAmount || duelsData.DuelZones.Count > 0) ? GetWallEntities() : null; // don't cache if we don't need to
4694
4695 foreach (var entry in duelsData.DuelZones) // create all zones that don't already exist
4696 SetupDuelZone(entry.Key.ToVector3(), entities, entry.Value);
4697
4698 if (autoSetup && duelsData.DuelZones.Count < zoneAmount) // create each dueling zone that is missing. if this fails then console will be notified
4699 {
4700 int attempts = Math.Max(zoneAmount, 5); // 0.1.10 fix - infinite loop fix for when zone radius is too large to fit on the map
4701 int created = 0;
4702
4703 do
4704 {
4705 if (SetupDuelZone(entities, GetZoneName()) != Vector3.zero)
4706 created++;
4707 } while (duelsData.DuelZones.Count < zoneAmount && --attempts > 0);
4708
4709 if (attempts <= 0)
4710 {
4711 if (created > 0)
4712 Puts(msg("SupportCreated", null, created));
4713 else
4714 Puts(msg("SupportInvalidConfig"));
4715 }
4716 }
4717
4718 if (duelingZones.Count > 0)
4719 Puts(msg("ZonesSetup", null, duelingZones.Count));
4720 }
4721
4722 public Vector3 SetupDuelZone(List<BaseEntity> entities, string zoneName) // starts the process of creating a new or existing zone and then setting up it's own spawn points around the circumference of the zone
4723 {
4724 var zonePos = FindDuelingZone(); // a complex process to search the map for a suitable area
4725
4726 if (zonePos == Vector3.zero) // unfortunately we weren't able to find a location. this is likely due to an extremely high entity count. just try again.
4727 return Vector3.zero;
4728
4729 SetupDuelZone(zonePos, entities, zoneName);
4730 return zonePos;
4731 }
4732
4733 public DuelingZone SetupDuelZone(Vector3 zonePos, List<BaseEntity> entities, string zoneName)
4734 {
4735 if (!duelsData.DuelZones.ContainsKey(zonePos.ToString()))
4736 duelsData.DuelZones.Add(zonePos.ToString(), zoneName);
4737
4738 var newZone = new GameObject().AddComponent<DuelingZone>();
4739
4740 newZone.Setup(zonePos);
4741 duelingZones.Add(newZone);
4742
4743 if (duelingZones.Count == 1)
4744 {
4745 Subscribe(nameof(OnPlayerRespawned));
4746 Subscribe(nameof(OnEntityTakeDamage));
4747 Subscribe(nameof(OnEntitySpawned));
4748 Subscribe(nameof(CanBuild));
4749 }
4750
4751 CreateZoneWalls(newZone.Position, zoneRadius, zoneUseWoodenWalls ? hewwPrefab : heswPrefab, entities);
4752 return newZone;
4753 }
4754
4755 public List<BaseEntity> GetWallEntities()
4756 {
4757 var entities = new List<BaseEntity>();
4758
4759 foreach (var e in BaseNetworkable.serverEntities)
4760 {
4761 if (e != null && e.ShortPrefabName.Contains("wall.external.high"))
4762 {
4763 var entity = e as BaseEntity;
4764
4765 if (entity != null)
4766 {
4767 entities.Add(entity);
4768 }
4769 }
4770 }
4771
4772 return entities;
4773 }
4774
4775 public int RemoveZoneWalls(ulong ownerId)
4776 {
4777 int removed = 0;
4778
4779 foreach (var entity in GetWallEntities().ToList())
4780 {
4781 if (entity.OwnerID == ownerId)
4782 {
4783 entity.Kill();
4784 removed++;
4785 }
4786 }
4787
4788 return removed;
4789 }
4790
4791 public bool ZoneWallsExist(ulong ownerId, List<BaseEntity> entities)
4792 {
4793 if (entities == null || entities.Count < 3)
4794 entities = GetWallEntities();
4795
4796 return entities.Any(entity => entity.OwnerID == ownerId);
4797 }
4798
4799 public void CreateZoneWalls(Vector3 center, float zoneRadius, string prefab, List<BaseEntity> entities, BasePlayer player = null)
4800 {
4801 if (!useZoneWalls)
4802 return;
4803
4804 var tick = DateTime.Now;
4805 ulong ownerId = GetOwnerId(center.ToString());
4806
4807 if (ZoneWallsExist(ownerId, entities))
4808 return;
4809
4810 float maxHeight = -200f;
4811 float minHeight = 200f;
4812 int spawned = 0;
4813 int raycasts = Mathf.CeilToInt(360 / zoneRadius * 0.1375f);
4814
4815 foreach (var position in GetCircumferencePositions(center, zoneRadius, raycasts, 0f)) // get our positions and perform the calculations for the highest and lowest points of terrain
4816 {
4817 RaycastHit hit;
4818 if (Physics.Raycast(new Vector3(position.x, position.y + 200f, position.z), Vector3.down, out hit, Mathf.Infinity, wallMask))
4819 {
4820 maxHeight = Mathf.Max(hit.point.y, maxHeight); // calculate the highest point of terrain
4821 minHeight = Mathf.Min(hit.point.y, minHeight); // calculate the lowest point of terrain
4822 center.y = minHeight; // adjust the spawn point of our walls to that of the lowest point of terrain
4823 }
4824 }
4825
4826 float gap = prefab == heswPrefab ? 0.3f : 0.5f; // the distance used so that each wall fits closer to the other so players cannot throw items between the walls
4827 int stacks = Mathf.CeilToInt((maxHeight - minHeight) / 6f) + extraWallStacks; // get the amount of walls to stack onto each other to go above the highest point
4828 float next = 360 / zoneRadius - gap; // the distance apart each wall will be from the other
4829
4830 for (int i = 0; i < stacks; i++) // create our loop to spawn each stack
4831 {
4832 foreach (var position in GetCircumferencePositions(center, zoneRadius, next, center.y)) // get a list positions where each positions difference is the width of a high external stone wall. specify the height since we've already calculated what's required
4833 {
4834 float groundHeight = TerrainMeta.HeightMap.GetHeight(new Vector3(position.x, position.y + 6f, position.z));
4835
4836 if (groundHeight > position.y + 9f) // 0.1.13 improved distance check underground
4837 continue;
4838
4839 if (useLeastAmount && position.y - groundHeight > 6f + extraWallStacks * 6f)
4840 continue;
4841
4842 var entity = GameManager.server.CreateEntity(prefab, position, default(Quaternion), false);
4843
4844 if (entity != null)
4845 {
4846 entity.OwnerID = ownerId; // set a unique identifier so the walls can be easily removed later
4847 entity.transform.LookAt(center, Vector3.up); // have each wall look at the center of the zone
4848 entity.Spawn(); // spawn into the game
4849 entity.gameObject.SetActive(true); // 0.1.16: fix for animals and explosives passing through walls. set it active after it spawns otherwise AntiHack will throw ProjectileHack: Line of sight warnings each time the entity is hit
4850 spawned++; // our counter
4851 }
4852 else
4853 return; // invalid prefab, return or cause massive server lag
4854
4855 if (stacks == i - 1)
4856 {
4857 RaycastHit hit;
4858 if (Physics.Raycast(new Vector3(position.x, position.y + 6f, position.z), Vector3.down, out hit, 12f, worldMask))
4859 stacks++; // 0.1.16 fix where rocks could allow a boost in or out of the top of a zone
4860 }
4861 }
4862
4863 center.y += 6f; // increase the positions height by one high external stone wall's height
4864 }
4865
4866 if (player == null)
4867 Puts(msg("GeneratedWalls", null, spawned, stacks, FormatPosition(center), (DateTime.Now - tick).TotalSeconds));
4868 else
4869 player.ChatMessage(msg("GeneratedWalls", player.UserIDString, spawned, stacks, FormatPosition(center), (DateTime.Now - tick).TotalSeconds));
4870
4871 Subscribe(nameof(OnEntityTakeDamage));
4872 }
4873
4874 public static void EjectPlayers(DuelingZone zone)
4875 {
4876 foreach (var player in zone.Players)
4877 {
4878 EjectPlayer(player);
4879 }
4880 }
4881
4882 public static void EjectPlayer(BasePlayer player)
4883 {
4884 if (player == null)
4885 return;
4886
4887 player.inventory.Strip();
4888 ins.ResetDuelist(player.UserIDString, false);
4889 ins.SendHome(player);
4890 }
4891
4892 public void RemoveDuelZone(DuelingZone zone)
4893 {
4894 string uid = zone.Position.ToString();
4895
4896 foreach (string playerId in spectators.ToList())
4897 {
4898 var player = BasePlayer.Find(playerId);
4899
4900 if (player == null)
4901 {
4902 spectators.Remove(playerId);
4903 continue;
4904 }
4905
4906 if (zone.Distance(player.transform.position) <= zoneRadius)
4907 {
4908 EndSpectate(player);
4909 SendHome(player);
4910 }
4911 }
4912
4913 var match = tdmMatches.FirstOrDefault(x => x.Zone != null && x.Zone == zone);
4914
4915 if (match != null)
4916 match.End();
4917
4918 if (spAutoRemove && duelsData.Spawns.Count > 0)
4919 foreach (var spawn in zone.Spawns)
4920 duelsData.Spawns.Remove(spawn.ToString());
4921
4922 duelsData.DuelZones.Remove(uid);
4923 duelsData.AutoGeneratedSpawns.Remove(uid);
4924 RemoveEntities(zone);
4925 RemoveZoneWalls(GetOwnerId(uid));
4926 zone.Kill();
4927
4928 if (duelingZones.Count == 0 && tdmMatches.Count == 0)
4929 {
4930 SubscribeHooks(false);
4931 CheckZoneHooks();
4932 }
4933 }
4934
4935 public void RemoveEntities(ulong playerId)
4936 {
4937 if (duelEntities.ContainsKey(playerId))
4938 {
4939 foreach (var e in duelEntities[playerId].ToList())
4940 if (e != null && !e.IsDestroyed)
4941 e.Kill();
4942
4943 duelEntities.Remove(playerId);
4944 }
4945 }
4946
4947 public void RemoveEntities(DuelingZone zone)
4948 {
4949 foreach (var entry in duelEntities.ToList())
4950 {
4951 foreach (var entity in entry.Value.ToList())
4952 {
4953 if (entity == null || entity.IsDestroyed)
4954 {
4955 duelEntities[entry.Key].Remove(entity);
4956 continue;
4957 }
4958
4959 if (zone.Distance(entity.transform.position) <= zoneRadius + 1f)
4960 {
4961 duelEntities[entry.Key].Remove(entity);
4962 entity.Kill();
4963 }
4964 }
4965 }
4966 }
4967
4968 public static DuelingZone GetDuelZone(Vector3 startPos, float offset = 1f)
4969 {
4970 return duelingZones.FirstOrDefault(zone => zone.Distance(startPos) <= zoneRadius + offset);
4971 }
4972
4973 public void SendHome(BasePlayer player) // send a player home to the saved location that they joined from
4974 {
4975 if (player != null && duelsData.Homes.ContainsKey(player.UserIDString))
4976 {
4977 if (player.IsDead() && !player.IsConnected && !respawnDeadDisconnect)
4978 {
4979 duelsData.Homes.Remove(player.UserIDString);
4980 return;
4981 }
4982
4983 if (player.IsSleeping() || player.HasPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot))
4984 {
4985 timer.Once(2f, () => SendHome(player));
4986 return;
4987 }
4988
4989 RemoveEntities(player.userID);
4990 var homePos = duelsData.Homes[player.UserIDString].ToVector3();
4991
4992 if (DuelTerritory(homePos) && !player.IsAdmin)
4993 {
4994 var bags = SleepingBag.FindForPlayer(player.userID, true).ToList();
4995
4996 if (bags.Count > 0)
4997 {
4998 bags.Sort((x, y) => x.net.ID.CompareTo(y.net.ID));
4999 homePos = bags[0].transform.position;
5000 homePos.y += 0.25f;
5001 }
5002 else
5003 {
5004 homePos = ServerMgr.FindSpawnPoint().pos;
5005 }
5006 }
5007
5008 if (player.IsDead())
5009 {
5010 if (sendDeadHome)
5011 player.RespawnAt(homePos, default(Quaternion));
5012 else
5013 player.Respawn();
5014
5015 if (playerHealth > 0f)
5016 player.health = playerHealth;
5017 }
5018 else
5019 Teleport(player, homePos);
5020
5021 //GiveRespawnLoot(player);
5022 GiveItems(player, duelsData.Items[player.userID]);
5023 duelsData.Homes.Remove(player.UserIDString);
5024
5025 if (guiAutoEnable || createUI.Contains(player.UserIDString))
5026 OnPlayerConnected(player);
5027
5028 if (readyUiList.Contains(player.UserIDString))
5029 ToggleReadyUI(player);
5030 }
5031 }
5032
5033 private List<SaveItem> GetPlayerItems(BasePlayer player)
5034 {
5035 var itemsList = new List<SaveItem>();
5036 foreach (var item in player.inventory.containerWear.itemList)
5037 {
5038 if (item != null)
5039 {
5040 var iteminfo = ItemToSave(item, "wear");
5041 itemsList.Add(iteminfo);
5042 }
5043 }
5044 foreach (var item in player.inventory.containerMain.itemList)
5045 {
5046 if (item != null)
5047 {
5048 var iteminfo = ItemToSave(item, "main");
5049 itemsList.Add(iteminfo);
5050 }
5051 }
5052 foreach (var item in player.inventory.containerBelt.itemList)
5053 {
5054 if (item != null)
5055 {
5056 var iteminfo = ItemToSave(item, "belt");
5057 itemsList.Add(iteminfo);
5058 }
5059 }
5060 return itemsList;
5061 }
5062
5063 private SaveItem ItemToSave(Item item, string container)
5064 {
5065 var saveItem = new SaveItem
5066 {
5067 Amount = item.amount,
5068 Container = container,
5069 SkinID = item.skin,
5070 Blueprint = item.blueprintTarget,
5071 ShortName = item.info.shortname,
5072 Condition = item.condition,
5073 DataInt = item.instanceData?.dataInt ?? 0,
5074 Name = item.name,
5075 Text = item.text,
5076 Change = 100,
5077 Weapon = null,
5078 Content = null
5079 };
5080 if (item.info.category == ItemCategory.Weapon)
5081 {
5082 var weapon = item.GetHeldEntity() as BaseProjectile;
5083 if (weapon != null)
5084 {
5085 saveItem.Weapon = new Weapon
5086 {
5087 ammoType = weapon.primaryMagazine.ammoType.shortname,
5088 ammoAmount = weapon.primaryMagazine.contents
5089 };
5090 }
5091 }
5092 if (item.contents != null)
5093 {
5094 saveItem.Content = new List<ItemContent>();
5095 foreach (var cont in item.contents.itemList)
5096 {
5097 saveItem.Content.Add(new ItemContent()
5098 {
5099 Amount = cont.amount,
5100 Condition = cont.condition,
5101 ShortName = cont.info.shortname
5102 });
5103 }
5104 }
5105 return saveItem;
5106 }
5107
5108 private void GiveItems(BasePlayer player, List<SaveItem> Items)
5109 {
5110 if (player == null || Items == null)
5111 {
5112 return;
5113 }
5114
5115 for (var i = 0; i < Items.Count; i++)
5116 {
5117 var savedItem = Items[i];
5118 GiveItem(player,
5119 BuildItem(savedItem.ShortName, savedItem.Amount, savedItem.SkinID, savedItem.Condition, savedItem.Blueprint,
5120 savedItem.DataInt, savedItem.Name, savedItem.Text, savedItem.Weapon, savedItem.Content),
5121 savedItem.Container == "belt" ? player.inventory.containerBelt :
5122 savedItem.Container == "wear" ? player.inventory.containerWear : player.inventory.containerMain);
5123 }
5124 }
5125
5126 private static void GiveItem(BasePlayer player, Item item, ItemContainer cont = null)
5127 {
5128 var inv = player?.inventory;
5129 if (player == null || item == null || inv == null)
5130 {
5131 return;
5132 }
5133
5134 if (cont != null)
5135 {
5136 if (!item.MoveToContainer(cont))
5137 {
5138 item.Drop(player.GetCenter(), player.GetDropVelocity());
5139 }
5140 }
5141 else
5142 {
5143 player.GiveItem(item, BaseEntity.GiveItemReason.PickedUp);
5144 }
5145 }
5146
5147 private static Item BuildItem(string ShortName, int Amount, ulong SkinID, float Condition, int blueprintTarget, int DataInt, string Name, string Text, Weapon weapon, List<ItemContent> Content)
5148 {
5149 var item = ItemManager.CreateByName(ShortName, Amount > 1 ? Amount : 1, SkinID);
5150 item.condition = Condition;
5151
5152 if (DataInt > 0)
5153 {
5154 item.instanceData = new ProtoBuf.Item.InstanceData
5155 {
5156 ShouldPool = false,
5157 dataInt = DataInt
5158 };
5159 }
5160
5161 item.text = Text;
5162
5163 if (Name != null)
5164 {
5165 item.name = Name;
5166 }
5167
5168 if (blueprintTarget != 0)
5169 {
5170 item.blueprintTarget = blueprintTarget;
5171 }
5172
5173 if (weapon != null)
5174 {
5175 var baseProjectile = item.GetHeldEntity() as BaseProjectile;
5176 if (baseProjectile != null)
5177 {
5178 baseProjectile.primaryMagazine.contents = weapon.ammoAmount;
5179 baseProjectile.primaryMagazine.ammoType = ItemManager.FindItemDefinition(weapon.ammoType);
5180 }
5181 }
5182 if (Content != null)
5183 {
5184 for (var i = 0; i < Content.Count; i++)
5185 {
5186 var cont = Content[i];
5187 var new_cont = ItemManager.CreateByName(cont.ShortName, cont.Amount);
5188 new_cont.condition = cont.Condition;
5189 new_cont.MoveToContainer(item.contents);
5190 }
5191 }
5192 return item;
5193 }
5194
5195 public void GiveRespawnLoot(BasePlayer player)
5196 {
5197 if (PermaMap != null && permission.UserHasPermission(player.UserIDString, "permamap.use") && player.inventory.containerBelt.GetSlot(6) == null)
5198 PermaMap?.Call("DoMap", player);
5199
5200 if (respawnLoot.Count > 0)
5201 {
5202 player.inventory.Strip();
5203
5204 foreach (var entry in respawnLoot)
5205 {
5206 Item item = ItemManager.CreateByName(entry.shortname, entry.amount, entry.skin);
5207
5208 if (item == null)
5209 continue;
5210
5211 var container = entry.container == "wear" ? player.inventory.containerWear : entry.container == "belt" ? player.inventory.containerBelt : player.inventory.containerMain;
5212
5213 if (!item.MoveToContainer(container, entry.slot))
5214 {
5215 item.Remove(0f);
5216 }
5217 }
5218 }
5219 else if (!string.IsNullOrEmpty(autoKitName) && Kits != null && IsKit(autoKitName))
5220 {
5221 player.inventory.Strip();
5222 Kits.Call("GiveKit", player, autoKitName);
5223 }
5224 }
5225
5226 private void UpdateStability()
5227 {
5228 if (noStability)
5229 {
5230 Subscribe(nameof(OnEntitySpawned));
5231
5232 foreach (var block in BaseNetworkable.serverEntities.Where(e => e != null && e.transform != null && e is BuildingBlock && DuelTerritory(e.transform.position)).Cast<BuildingBlock>().ToList())
5233 {
5234 if (block.grounded)
5235 continue;
5236
5237 if (block.OwnerID == 0 || permission.UserHasGroup(block.OwnerID.ToString(), "admin"))
5238 block.grounded = true;
5239 }
5240 }
5241 }
5242
5243 private void CheckZoneHooks(bool message = false)
5244 {
5245 if (respawnWalls && duelingZones.Count > 0)
5246 {
5247 Subscribe(nameof(OnEntityDeath));
5248 Subscribe(nameof(OnEntityKill));
5249 }
5250 }
5251
5252 public static void Disappear(BasePlayer player) // credit Wulf.
5253 {
5254 var connections = BasePlayer.activePlayerList.Where(active => active != player && active.IsConnected).Select(active => active.net.connection).ToList();
5255
5256 if (Net.sv.write.Start())
5257 {
5258 Net.sv.write.PacketID(Message.Type.EntityDestroy);
5259 Net.sv.write.EntityID(player.net.ID);
5260 Net.sv.write.UInt8((byte)BaseNetworkable.DestroyMode.None);
5261 Net.sv.write.Send(new SendInfo(connections));
5262 }
5263
5264 var held = player.GetHeldEntity();
5265 if (held != null && Net.sv.write.Start())
5266 {
5267 Net.sv.write.PacketID(Network.Message.Type.EntityDestroy);
5268 Net.sv.write.EntityID(held.net.ID);
5269 Net.sv.write.UInt8((byte)BaseNetworkable.DestroyMode.None);
5270 Net.sv.write.Send(new SendInfo(connections));
5271 }
5272 }
5273
5274 private void CheckDuelistMortality()
5275 {
5276 eventTimer = timer.Once(0.5f, () => CheckDuelistMortality());
5277
5278 if (dataImmunity.Count > 0) // each player that spawns into a dueling zone is given immunity for X seconds. here we'll keep track of this and remove their immunities
5279 {
5280 var timeStamp = TimeStamp();
5281
5282 foreach (var kvp in dataImmunity.ToList())
5283 {
5284 var player = BasePlayer.Find(kvp.Key);
5285 long time = kvp.Value - timeStamp;
5286
5287 if (time <= 0)
5288 {
5289 dataImmunity.Remove(kvp.Key);
5290 dataImmunitySpawns.Remove(kvp.Key);
5291
5292 if (!player || !player.IsConnected)
5293 continue;
5294
5295 CuiHelper.DestroyUi(player, "DuelistUI_Countdown");
5296 player.ChatMessage(msg("ImmunityFaded", player.UserIDString));
5297 }
5298 else if (player != null && player.IsConnected)
5299 {
5300 if (noMovement && dataImmunitySpawns.ContainsKey(player.UserIDString))
5301 {
5302 var dest = dataImmunitySpawns[player.UserIDString];
5303 player.Teleport(dest);
5304 }
5305
5306 CreateCountdownUI(player, time.ToString());
5307 }
5308 }
5309 }
5310
5311 if (dataDeath.Count > 0) // keep track of how long the match has been going on for, and if it's been too long then kill the player off.
5312 {
5313 var timeStamp = TimeStamp();
5314
5315 foreach (var kvp in dataDeath.ToList())
5316 {
5317 if (kvp.Value - timeStamp <= 0)
5318 {
5319 var target = BasePlayer.Find(kvp.Key);
5320 dataDeath.Remove(kvp.Key);
5321
5322 if (!target || !target.IsConnected || (!IsDueling(target) && !InDeathmatch(target)))
5323 continue;
5324
5325 target.inventory.Strip();
5326 OnEntityDeath(target, null);
5327 }
5328 }
5329 }
5330
5331 UpdateMatchUI();
5332 }
5333
5334 public void SubscribeHooks(bool flag) // we're using lots of temporary and permanent hooks so we'll turn off the temporary hooks when the plugin is loaded, and unsubscribe to others inside of their hooks when they're no longer in use
5335 {
5336 if (!init || !flag)
5337 {
5338 Unsubscribe(nameof(OnPlayerDisconnected));
5339 Unsubscribe(nameof(CanNetworkTo));
5340 Unsubscribe(nameof(OnItemDropped));
5341 Unsubscribe(nameof(OnPlayerSleepEnded));
5342 Unsubscribe(nameof(OnCreateWorldProjectile));
5343 Unsubscribe(nameof(OnLootEntity));
5344 Unsubscribe(nameof(OnPlayerRespawned));
5345 Unsubscribe(nameof(OnEntityTakeDamage));
5346 Unsubscribe(nameof(OnEntitySpawned));
5347 Unsubscribe(nameof(CanBuild));
5348 Unsubscribe(nameof(OnPlayerHealthChange));
5349 Unsubscribe(nameof(OnEntityDeath));
5350 Unsubscribe(nameof(OnEntityKill));
5351 //Unsubscribe(nameof(OnPlayerCommand));
5352 Unsubscribe(nameof(OnPlayerConnected));
5353 return;
5354 }
5355
5356 Subscribe(nameof(OnPlayerDisconnected));
5357
5358 if (useInvisibility)
5359 Subscribe(nameof(CanNetworkTo));
5360
5361 Subscribe(nameof(OnItemDropped));
5362 Subscribe(nameof(OnPlayerSleepEnded));
5363 Subscribe(nameof(OnCreateWorldProjectile));
5364 Subscribe(nameof(OnLootEntity));
5365 Subscribe(nameof(OnEntitySpawned));
5366
5367 if (!allowPlayerDeaths)
5368 Subscribe(nameof(OnPlayerHealthChange));
5369
5370 Subscribe(nameof(OnEntityTakeDamage));
5371 Subscribe(nameof(OnEntityDeath));
5372
5373 if (useBlacklistCommands || useWhitelistCommands)
5374 {
5375 Subscribe(nameof(OnPlayerCommand));
5376 }
5377
5378 if (respawnWalls)
5379 Subscribe(nameof(OnEntityKill));
5380 }
5381
5382 // Helper methods which are essential for the plugin to function. Do not modify these.
5383
5384 [HookMethod("DuelistTerritory")]
5385 public bool DuelistTerritory(Vector3 position) // API
5386 {
5387 return DuelTerritory(position);
5388 }
5389
5390 public static bool DuelTerritory(Vector3 position, float offset = 5f) // 0.1.21: arena can be inside of the zone at any height
5391 {
5392 return init && duelingZones.Any(zone => Vector3Ex.Distance2D(zone.Position, position) <= zoneRadius + offset);
5393 }
5394
5395 public ulong GetOwnerId(string uid)
5396 {
5397 return Convert.ToUInt64(Math.Abs(uid.GetHashCode()));
5398 }
5399
5400 [HookMethod("inEvent")]
5401 public bool inEvent(BasePlayer player)
5402 {
5403 return InEvent(player);
5404 }
5405
5406 public static bool InEvent(BasePlayer player)
5407 {
5408 return player != null && (dataDuelists.ContainsKey(player.UserIDString) || tdmMatches.Any(match => match.GetTeam(player) != Team.None));
5409 }
5410
5411 public static bool IsDueling(BasePlayer player)
5412 {
5413 return init && player != null && duelsData != null && duelingZones.Count > 0 && player != null && dataDuelists.ContainsKey(player.UserIDString) && DuelTerritory(player.transform.position);
5414 }
5415
5416 public bool InDeathmatch(BasePlayer player)
5417 {
5418 return init && player != null && tdmMatches.Any(team => team.GetTeam(player) != Team.None) && DuelTerritory(player.transform.position);
5419 }
5420
5421 public static bool IsSpectator(BasePlayer player)
5422 {
5423 return init && player != null && spectators.Contains(player.UserIDString) && DuelTerritory(player.transform.position);
5424 }
5425
5426 public bool IsEventBanned(string targetId)
5427 {
5428 return duelsData.Bans.ContainsKey(targetId);
5429 }
5430
5431 public static long TimeStamp() => (DateTime.Now.Ticks - DateTime.Parse("01/01/1970 00:00:00").Ticks) / 10000000;
5432
5433 public string GetDisplayName(string targetId)
5434 {
5435 return covalence.Players.FindPlayerById(targetId)?.Name ?? targetId;
5436 }
5437
5438 public void Log(string file, string message, bool timestamp = false)
5439 {
5440 LogToFile(file, $"[{DateTime.Now}] {message}", this, timestamp);
5441 }
5442
5443 public GoodVersusEvilMatch GetMatch(BasePlayer player)
5444 {
5445 return tdmMatches.FirstOrDefault(team => team.GetTeam(player) != Team.None);
5446 }
5447
5448 public static bool InMatch(BasePlayer target)
5449 {
5450 return tdmMatches.Any(team => team.GetTeam(target) != Team.None);
5451 }
5452
5453 public static bool IsOnConstruction(Vector3 position)
5454 {
5455 position.y += 1f;
5456 RaycastHit hit;
5457
5458 return Physics.Raycast(position, Vector3.down, out hit, 1.5f, ins.constructionMask) && hit.GetEntity() != null;
5459 }
5460
5461 public bool Teleport(BasePlayer player, Vector3 destination)
5462 {
5463 if (player == null || destination == Vector3.zero) // don't send a player to their death. this should never happen
5464 return false;
5465
5466 timer.Once(0.01f, () =>
5467 {
5468 if (player != null)
5469 {
5470 player.EndLooting();
5471 }
5472 });
5473
5474 if (DuelTerritory(destination))
5475 {
5476 var rematch = rematches.FirstOrDefault(x => x.HasPlayer(player));
5477
5478 if (rematch != null)
5479 rematches.Remove(rematch);
5480 }
5481
5482 if (player.IsWounded())
5483 player.StopWounded();
5484
5485 player.metabolism.bleeding.value = 0;
5486
5487 if (playerHealth > 0f && player.health < playerHealth)
5488 player.health = playerHealth;
5489
5490 if (player.IsConnected)
5491 player.StartSleeping();
5492
5493 player.Teleport(destination);
5494
5495 if (player.IsConnected && (Vector3.Distance(player.transform.position, destination) > 50f || !DuelTerritory(destination))) // 1.1.2 reduced from 150 to 100
5496 {
5497 player.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true);
5498 player.ClientRPCPlayer(null, player, "StartLoading");
5499 player.UpdateNetworkGroup();
5500 player.SendEntityUpdate();
5501 player.SendNetworkUpdateImmediate(false);
5502 }
5503 else player.SendNetworkUpdateImmediate(false);
5504
5505 LustyMap?.Call(DuelTerritory(destination) ? "DisableMaps" : "EnableMaps", player);
5506 return true;
5507 }
5508
5509 public bool IsThrownWeapon(Item item)
5510 {
5511 if (item == null)
5512 return false;
5513
5514 if (item.info.category == ItemCategory.Weapon || item.info.category == ItemCategory.Tool)
5515 {
5516 if (item.info.stackable > 1)
5517 return false;
5518
5519 var weapon = item?.GetHeldEntity() as BaseProjectile;
5520
5521 if (weapon == null)
5522 return true;
5523
5524 if (weapon.primaryMagazine.capacity > 0)
5525 return false;
5526 }
5527
5528 return false;
5529 }
5530
5531 public Vector3 RandomDropPosition() // CargoPlane.RandomDropPosition()
5532 {
5533 var vector = Vector3.zero;
5534 float num = 100f, x = TerrainMeta.Size.x / 3f;
5535 do
5536 {
5537 vector = Vector3Ex.Range(-x, x);
5538 } while (filter.GetFactor(vector) == 0f && --num > 0f);
5539 vector.y = 0f;
5540 return vector;
5541 }
5542
5543 public Vector3 FindDuelingZone()
5544 {
5545 var tick = DateTime.Now; // create a timestamp to see how long this process takes
5546 var position = Vector3.zero;
5547 int maxRetries = 500; // 0.1.9: increased due to rock collider detection. 0.1.10 rock collider detection removed but amount not changed
5548 int retries = maxRetries; // servers with high entity counts will require this
5549
5550 if (managedZones.Count == 0 && ZoneManager != null)
5551 SetupZoneManager();
5552
5553 do
5554 {
5555 position = RandomDropPosition();
5556
5557 foreach (var monument in monuments)
5558 {
5559 if (Vector3.Distance(position, monument) < 150f) // don't put the dueling zone inside of a monument. players will throw a shit fit
5560 {
5561 position = Vector3.zero;
5562 break;
5563 }
5564 }
5565
5566 if (position == Vector3.zero)
5567 continue;
5568
5569 if (managedZones.Count > 0)
5570 {
5571 foreach (var zone in managedZones)
5572 {
5573 if (Vector3.Distance(zone.Key, position) <= zone.Value)
5574 {
5575 position = Vector3.zero; // blocked by zone manager
5576 break;
5577 }
5578 }
5579 }
5580
5581 if (position == Vector3.zero)
5582 continue;
5583
5584 position.y = TerrainMeta.HeightMap.GetHeight(position) + 100f; // setup the hit
5585
5586 RaycastHit hit;
5587 if (Physics.Raycast(position, Vector3.down, out hit, position.y, groundMask))
5588 {
5589 position.y = Mathf.Max(hit.point.y, TerrainMeta.HeightMap.GetHeight(position)); // get the max height
5590
5591 var colliders = Pool.GetList<Collider>();
5592 Vis.Colliders(position, zoneRadius + 15f, colliders, blockedMask, QueryTriggerInteraction.Collide); // get all colliders using the provided layermask
5593
5594 if (colliders.Count > 0) // if any colliders were found from the blockedMask then we don't want this as our dueling zone. retry.
5595 position = Vector3.zero;
5596
5597 Pool.FreeList(ref colliders);
5598
5599 if (position != Vector3.zero) // so far so good, let's measure the highest and lowest points of the terrain, and count the amount of water colliders
5600 {
5601 var positions = GetCircumferencePositions(position, zoneRadius - 15f, 1f, 0f); // gather positions around the purposed zone
5602 float min = 200f;
5603 float max = -200f;
5604 int water = 0;
5605
5606 foreach (var pos in positions)
5607 {
5608 if (Physics.Raycast(new Vector3(pos.x, pos.y + 100f, pos.z), Vector3.down, 100.5f, waterMask)) //look for water
5609 water++; // count the amount of water colliders
5610
5611 min = Mathf.Min(pos.y, min); // set the lowest and highest points of the terrain
5612 max = Mathf.Max(pos.y, max);
5613 }
5614
5615 if (max - min > maxIncline || position.y - min > maxIncline) // the incline is too steep to be suitable for a dueling zone, retry.
5616 position = Vector3.zero;
5617
5618 if (water > positions.Count / 4) // too many water colliders, retry.
5619 position = Vector3.zero;
5620
5621 positions.Clear();
5622 }
5623 }
5624 else
5625 position = Vector3.zero; // found water instead of land
5626
5627 if (position == Vector3.zero)
5628 continue;
5629
5630 if (DuelTerritory(position, zoneRadius + 15f)) // check if position overlaps an existing zone
5631 position = Vector3.zero; // overlaps, retry.
5632 } while (position == Vector3.zero && --retries > 0); // prevent infinite loops
5633
5634 if (position != Vector3.zero)
5635 Puts(msg("FoundZone", null, maxRetries - retries, (DateTime.Now - tick).TotalMilliseconds)); // we found a dueling zone! return the position to be assigned, spawn the zone and the spawn points!
5636
5637 return position;
5638 }
5639
5640 public List<Vector3> GetCircumferencePositions(Vector3 center, float radius, float next, float y) // as the name implies
5641 {
5642 var positions = new List<Vector3>();
5643 float degree = 0f;
5644
5645 while (degree < 360)
5646 {
5647 float angle = (float)(2 * Math.PI / 360) * degree;
5648 float x = center.x + radius * (float)Math.Cos(angle);
5649 float z = center.z + radius * (float)Math.Sin(angle);
5650 var position = new Vector3(x, 0f, z);
5651
5652 position.y = y == 0f ? TerrainMeta.HeightMap.GetHeight(position) : y;
5653 positions.Add(position);
5654
5655 degree += next;
5656 }
5657
5658 return positions;
5659 }
5660
5661 public static List<Vector3> GetAutoSpawns(DuelingZone zone)
5662 {
5663 var spawns = new List<Vector3>();
5664 string key = zone.Position.ToString();
5665
5666 if (duelsData.AutoGeneratedSpawns.ContainsKey(key) && duelsData.AutoGeneratedSpawns[key].Count > 0)
5667 spawns.AddRange(duelsData.AutoGeneratedSpawns[key].Select(spawn => spawn.ToVector3())); // use cached spawn points
5668
5669 if (!duelsData.AutoGeneratedSpawns.ContainsKey(key))
5670 duelsData.AutoGeneratedSpawns.Add(key, new List<string>());
5671
5672 if (spawns.Count < 2)
5673 spawns = ins.CreateSpawnPoints(zone.Position); // create spawn points on the fly
5674
5675 duelsData.AutoGeneratedSpawns[key] = spawns.Select(spawn => spawn.ToString()).ToList();
5676 return spawns;
5677 }
5678
5679 public List<Vector3> CreateSpawnPoints(Vector3 center)
5680 {
5681 var positions = new List<Vector3>(); // 0.1.1 bugfix: spawn point height (y) wasn't being modified when indexing the below foreach list. instead, create a copy of each position and return a new list (cause: can't modify members of value types without changing the collection and invalidating the enumerator. bug: index the value type and change the value. result: list did not propagate)
5682
5683 // create spawn points slightly inside of the dueling zone so they don't spawn inside of walls
5684 foreach (var position in GetCircumferencePositions(center, zoneRadius - 15f, 10f, 0f))
5685 {
5686 var hits = Physics.RaycastAll(new Vector3(position.x, TerrainMeta.HighestPoint.y + 200f, position.z), Vector3.down, Mathf.Infinity);
5687
5688 if (hits.Count() > 0) // low failure rate
5689 {
5690 float y = TerrainMeta.HeightMap.GetHeight(position);
5691
5692 if (avoidWaterSpawns && TerrainMeta.WaterMap.GetHeight(position) - y > 0.8f)
5693 continue; // 0.1.16: better method to check water level
5694
5695 foreach (var hit in hits)
5696 {
5697 switch (LayerMask.LayerToName(hit.collider.gameObject.layer))
5698 {
5699 case "Construction":
5700 if (!hit.GetEntity()) // 0.1.2 bugfix: spawn points floating when finding a collider with no entity
5701 continue;
5702
5703 y = Mathf.Max(hit.point.y, y);
5704 break;
5705 case "Deployed":
5706 if (!hit.GetEntity()) // 0.1.2 ^
5707 continue;
5708
5709 y = Mathf.Max(hit.point.y, y);
5710 break;
5711 case "World":
5712 case "Terrain":
5713 y = Mathf.Max(hit.point.y, y);
5714 break;
5715 }
5716 }
5717
5718 positions.Add(new Vector3(position.x, y, position.z));
5719 }
5720 }
5721
5722 return positions;
5723 }
5724
5725 public bool ResetDuelists() // reset all data for the wipe after assigning awards
5726 {
5727 if (AssignDuelists())
5728 {
5729 if (resetSeed)
5730 {
5731 duelsData.VictoriesSeed.Clear();
5732 duelsData.LossesSeed.Clear();
5733 duelsData.MatchKillsSeed.Clear();
5734 duelsData.MatchDeathsSeed.Clear();
5735 duelsData.MatchLossesSeed.Clear();
5736 duelsData.MatchVictoriesSeed.Clear();
5737 duelsData.MatchSizesVictoriesSeed.Clear();
5738 duelsData.MatchSizesVictoriesSeed.Clear();
5739 }
5740
5741 duelsData.DuelZones.Clear();
5742 duelsData.Bets.Clear();
5743 duelsData.ClaimBets.Clear();
5744 duelsData.Spawns.Clear();
5745 duelsData.AutoGeneratedSpawns.Clear();
5746 ResetTemporaryData();
5747 }
5748
5749 return true;
5750 }
5751
5752 public bool AssignDuelists()
5753 {
5754 if (!recordStats || duelsData.VictoriesSeed.Count == 0)
5755 return true; // nothing to do here, return
5756
5757 foreach (var target in covalence.Players.All) // remove player awards from previous wipe
5758 {
5759 if (permission.UserHasPermission(target.Id, duelistPerm))
5760 permission.RevokeUserPermission(target.Id, duelistPerm);
5761
5762 if (permission.UserHasGroup(target.Id, duelistGroup))
5763 permission.RemoveUserGroup(target.Id, duelistGroup);
5764 }
5765
5766 if (permsToGive <= 0) // check now incase the user disabled awards later on
5767 return true;
5768
5769 var duelists = duelsData.VictoriesSeed.ToList(); // sort the data
5770 duelists.Sort((x, y) => y.Value.CompareTo(x.Value));
5771
5772 int added = 0;
5773
5774 for (int i = 0; i < duelists.Count; i++) // now assign it
5775 {
5776 var target = covalence.Players.FindPlayerById(duelists[i].Key);
5777
5778 if (target == null || target.IsBanned || target.IsAdmin)
5779 continue;
5780
5781 permission.GrantUserPermission(target.Id, duelistPerm.ToLower(), this);
5782 permission.AddUserGroup(target.Id, duelistGroup.ToLower());
5783
5784 Log("awards", msg("Awards", null, target.Name, target.Id, duelists[i].Value), true);
5785 Puts(msg("Granted", null, target.Name, target.Id, duelistPerm, duelistGroup));
5786
5787 if (++added >= permsToGive)
5788 break;
5789 }
5790
5791 if (added > 0)
5792 Puts(msg("Logged", null, string.Format("{0}{1}{2}_{3}-{4}.txt", Interface.Oxide.LogDirectory, Path.DirectorySeparatorChar, Name.Replace(" ", "").ToLower(), "awards", DateTime.Now.ToString("yyyy-MM-dd"))));
5793
5794 return true;
5795 }
5796
5797 public bool IsNewman(BasePlayer player) // count the players items. exclude rocks and torchs
5798 {
5799 //if (bypassNewmans || saveRestoreEnabled)
5800 return true;
5801
5802 int count = player.inventory.AllItems().Count();
5803
5804 if (permission.UserHasPermission(player.UserIDString, "permamap.use") && player.inventory.containerBelt.GetSlot(6) != null)
5805 count -= 1;
5806
5807 count -= respawnLoot.Sum(entry => GetAmount(player, entry.shortname));
5808
5809 return count == 0;
5810 }
5811
5812 public int GetAmount(BasePlayer player, string shortname)
5813 {
5814 return player.inventory.AllItems().Where(x => x.info.shortname == shortname.ToLower()).Sum(item => item.amount);
5815 }
5816
5817 public static bool RemoveFromQueue(string targetId)
5818 {
5819 foreach (var kvp in duelsData.Queued)
5820 {
5821 if (kvp.Value == targetId)
5822 {
5823 duelsData.Queued.Remove(kvp.Key);
5824 return true;
5825 }
5826 }
5827
5828 return false;
5829 }
5830
5831 public void CheckQueue()
5832 {
5833 if (duelsData.Queued.Count < 2 || !duelsData.DuelsEnabled)
5834 return;
5835
5836 string playerId = duelsData.Queued.Values.ElementAt(0);
5837 string targetId = duelsData.Queued.Values.ElementAt(1);
5838 var player = BasePlayer.Find(playerId);
5839 var target = BasePlayer.Find(targetId);
5840
5841 if (player == null || !player.CanInteract() || InMatch(player))
5842 {
5843 if (RemoveFromQueue(playerId))
5844 CheckQueue();
5845
5846 return;
5847 }
5848
5849 if (target == null || !player.CanInteract() || InMatch(player))
5850 {
5851 if (RemoveFromQueue(targetId))
5852 CheckQueue();
5853
5854 return;
5855 }
5856
5857 if (!IsNewman(player))
5858 {
5859 if (RemoveFromQueue(player.UserIDString))
5860 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
5861 return;
5862 }
5863
5864 if (!IsNewman(target))
5865 {
5866 if (RemoveFromQueue(target.UserIDString))
5867 target.ChatMessage(msg("MustBeNaked", target.UserIDString));
5868 return;
5869 }
5870
5871 SelectZone(player, target);
5872 }
5873
5874 public bool SelectZone(BasePlayer player, BasePlayer target)
5875 {
5876 var lastZone = GetPlayerZone(player, 2) ?? GetPlayerZone(target, 2) ?? GetDuelZone(player.transform.position) ?? GetDuelZone(target.transform.position);
5877
5878 if (lastZone != null)
5879 {
5880 var success = lastZone.AddWaiting(player, target);
5881
5882 if (success != null && success is bool && (bool)success)
5883 {
5884 Initiate(player, target, false, lastZone);
5885 return true;
5886 }
5887 }
5888
5889 var zones = duelingZones.Where(zone => !zone.IsFull && !zone.IsLocked && zone.Spawns.Count >= requiredMinSpawns && zone.Spawns.Count <= requiredMaxSpawns).ToList();
5890
5891 while (zones.Count > 0)
5892 {
5893 var zone = zones.GetRandom();
5894 var success = zone.AddWaiting(player, target);
5895
5896 if (success == null) // user must pay the duel entry fee first
5897 return true;
5898
5899 if (success is bool && (bool)success)
5900 {
5901 Initiate(player, target, false, zone);
5902 return true;
5903 }
5904
5905 zones.Remove(zone);
5906 };
5907
5908 return false;
5909 }
5910
5911 public string GetKit(BasePlayer player, BasePlayer target)
5912 {
5913 string kit = GetRandomKit();
5914
5915 if (duelsData.CustomKits.ContainsKey(player.UserIDString) && duelsData.CustomKits.ContainsKey(target.UserIDString))
5916 {
5917 string playerKit = duelsData.CustomKits[player.UserIDString];
5918 string targetKit = duelsData.CustomKits[target.UserIDString];
5919
5920 if (playerKit.Equals(targetKit, StringComparison.CurrentCultureIgnoreCase))
5921 {
5922 return GetVerifiedKit(playerKit) ?? kit;
5923 }
5924 }
5925
5926 return kit;
5927 }
5928
5929 public void VerifyKits()
5930 {
5931 if (Kits != null)
5932 {
5933 foreach (string kit in lpDuelingKits.ToList())
5934 if (!IsKit(kit))
5935 lpDuelingKits.Remove(kit);
5936
5937 foreach (string kit in hpDuelingKits.ToList())
5938 if (!IsKit(kit))
5939 hpDuelingKits.Remove(kit);
5940 }
5941 }
5942
5943 public string GetRandomKit()
5944 {
5945 VerifyKits();
5946
5947 string kit = Random.value < lesserKitChance && lpDuelingKits.Count > 0 ? lpDuelingKits.GetRandom() : null;
5948
5949 if (kit == null && hpDuelingKits.Count > 0)
5950 kit = hpDuelingKits.GetRandom();
5951
5952 if (kit == null)
5953 {
5954 if (customKits.Count > 0 && _hpDuelingKits.All(entry => customKits.Keys.Select(key => key.ToLower()).Contains(entry.ToLower()))) // 0.1.17 compatibility when adding custom kits to kits section
5955 {
5956 if (_lpDuelingKits.All(entry => customKits.Keys.Select(key => key.ToLower()).Contains(entry.ToLower())))
5957 {
5958 if (Random.value < lesserKitChance) kit = _lpDuelingKits.GetRandom();
5959 else kit = _hpDuelingKits.GetRandom();
5960
5961 return customKits.Keys.First(entry => kit.Equals(entry, StringComparison.CurrentCultureIgnoreCase));
5962 }
5963 }
5964
5965 if (customKits.Count > 0)
5966 {
5967 kit = customKits.ToList().GetRandom().Key;
5968 }
5969 }
5970
5971 return kit;
5972 }
5973
5974 public void Initiate(BasePlayer player, BasePlayer target, bool checkInventory, DuelingZone destZone)
5975 {
5976 try
5977 {
5978 if (player == null || target == null || destZone == null)
5979 return;
5980
5981 dataRequests.Remove(player.UserIDString);
5982 dataRequests.Remove(target.UserIDString);
5983
5984 if (checkInventory)
5985 {
5986 if (!IsNewman(player))
5987 {
5988 player.ChatMessage(msg("MustBeNaked", player.UserIDString));
5989 target.ChatMessage(msg("DuelMustBeNaked", target.UserIDString, player.displayName));
5990 return;
5991 }
5992
5993 if (!IsNewman(target))
5994 {
5995 target.ChatMessage(msg("MustBeNaked", player.UserIDString));
5996 player.ChatMessage(msg("DuelMustBeNaked", player.UserIDString, target.displayName));
5997 return;
5998 }
5999 }
6000
6001 if (!DuelTerritory(player.transform.position) || !duelsData.Homes.ContainsKey(player.UserIDString))
6002 {
6003 var ppos = player.transform.position;
6004 if (IsOnConstruction(ppos)) ppos.y += 1; // prevent player from becoming stuck or dying when teleported home
6005 duelsData.Homes[player.UserIDString] = ppos.ToString();
6006 duelsData.Items[player.userID] = GetPlayerItems(player);
6007 }
6008
6009 if (!DuelTerritory(target.transform.position) || !duelsData.Homes.ContainsKey(target.UserIDString))
6010 {
6011 var tpos = target.transform.position;
6012 if (IsOnConstruction(tpos)) tpos.y += 1;
6013 duelsData.Homes[target.UserIDString] = tpos.ToString();
6014 duelsData.Items[target.userID] = GetPlayerItems(target);
6015 }
6016
6017 var playerSpawn = destZone.Spawns.GetRandom();
6018 var targetSpawn = playerSpawn;
6019 float dist = -100f;
6020
6021 foreach (var spawn in destZone.Spawns) // get the furthest spawn point away from the player and assign it to target
6022 {
6023 float distance = Vector3.Distance(spawn, playerSpawn);
6024
6025 if (distance > dist)
6026 {
6027 dist = distance;
6028 targetSpawn = spawn;
6029 }
6030 }
6031
6032 string kit = GetKit(player, target);
6033 duelsData.Kits[player.UserIDString] = kit;
6034 duelsData.Kits[target.UserIDString] = kit;
6035
6036 Teleport(player, playerSpawn);
6037 Teleport(target, targetSpawn);
6038
6039 if (debugMode)
6040 Puts($"{player.displayName} and {target.displayName} have entered a duel.");
6041
6042 RemoveFromQueue(player.UserIDString);
6043 RemoveFromQueue(target.UserIDString);
6044
6045 if (immunityTime >= 1)
6046 {
6047 dataImmunity[player.UserIDString] = TimeStamp() + immunityTime;
6048 dataImmunity[target.UserIDString] = TimeStamp() + immunityTime;
6049 dataImmunitySpawns[player.UserIDString] = playerSpawn;
6050 dataImmunitySpawns[target.UserIDString] = targetSpawn;
6051 }
6052
6053 dataDuelists[player.UserIDString] = target.UserIDString;
6054 dataDuelists[target.UserIDString] = player.UserIDString;
6055 SubscribeHooks(true);
6056
6057 player.ChatMessage(msg("NowDueling", player.UserIDString, target.displayName));
6058 target.ChatMessage(msg("NowDueling", target.UserIDString, player.displayName));
6059
6060 }
6061 catch (Exception ex)
6062 {
6063 SubscribeHooks(false);
6064 duelsData.DuelsEnabled = false;
6065 init = false;
6066 SaveData();
6067
6068 Puts("---");
6069 Puts("Plugin disabled: {0} --- {1}", ex.Message, ex.StackTrace);
6070 Puts("---");
6071
6072 ResetDuelist(player.UserIDString);
6073 ResetDuelist(target.UserIDString);
6074 }
6075 }
6076
6077 // manually check as players may not be in a clan or on a friends list
6078
6079 public bool IsAllied(string playerId, string targetId)
6080 {
6081 var player = BasePlayer.Find(playerId);
6082 var target = BasePlayer.Find(targetId);
6083
6084 return player == null || target == null ? false : IsAllied(player, target);
6085 }
6086
6087 public bool IsAllied(BasePlayer player, BasePlayer target)
6088 {
6089 if (player.IsAdmin && target.IsAdmin)
6090 return false;
6091
6092 return IsInSameClan(player, target) || IsAuthorizing(player, target) || IsBunked(player, target) || IsCodeAuthed(player, target) || IsInSameBase(player, target);
6093 }
6094
6095 private bool IsInSameClan(BasePlayer player, BasePlayer target) // 1st method
6096 {
6097 if (player == null || target == null)
6098 return true;
6099
6100 if (player.displayName.StartsWith("[") && player.displayName.Contains("]"))
6101 {
6102 if (target.displayName.StartsWith("[") && target.displayName.Contains("]"))
6103 {
6104 string playerClan = player.displayName.Substring(0, player.displayName.IndexOf("]"));
6105 string targetClan = target.displayName.Substring(0, target.displayName.IndexOf("]"));
6106
6107 return playerClan == targetClan;
6108 }
6109 }
6110
6111 return false;
6112 }
6113
6114 private bool IsAuthorizing(BasePlayer player, BasePlayer target) // 2nd method.
6115 {
6116 var privs = BaseNetworkable.serverEntities.Where(e => e != null && e is BuildingPrivlidge).Cast<BuildingPrivlidge>();
6117
6118 return privs.Any(priv => priv.IsAuthed(player) && priv.IsAuthed(target));
6119 }
6120
6121 private bool IsBunked(BasePlayer player, BasePlayer target) // 3rd method. thanks @i_love_code for helping with this too
6122 {
6123 var targetBags = SleepingBag.FindForPlayer(target.userID, true);
6124
6125 if (targetBags.Count() > 0)
6126 foreach (var pbag in SleepingBag.FindForPlayer(player.userID, true))
6127 if (targetBags.Any(tbag => Vector3.Distance(pbag.transform.position, tbag.transform.position) < 25f))
6128 return true;
6129
6130 return false;
6131 }
6132
6133 private bool IsCodeAuthed(BasePlayer player, BasePlayer target) // 4th method. nice and clean linq
6134 {
6135 foreach (var codelock in BaseNetworkable.serverEntities.Where(e => e != null && e is CodeLock).Cast<CodeLock>())
6136 {
6137 if (codelock.whitelistPlayers.Any(id => id == player.userID))
6138 {
6139 if (codelock.whitelistPlayers.Any(id => id == target.userID))
6140 {
6141 return true;
6142 }
6143 }
6144 }
6145
6146 return false;
6147 }
6148
6149 /*private bool IsInSameBase(BasePlayer player, BasePlayer target) // 5th method - TODO: can be exploited
6150 {
6151 var privs = BaseNetworkable.serverEntities.Where(e => e != null && e is BuildingPrivlidge).Cast<BuildingPrivlidge>();
6152
6153 foreach (var priv in privs.Where(p => p != null && p.IsAuthed(player)))
6154 {
6155 var building = priv.GetBuilding();
6156
6157 if (building != null && building.HasBuildingBlocks() && building.buildingBlocks.Any(block => block.OwnerID == target.userID))
6158 {
6159 return true;
6160 }
6161 }
6162
6163 foreach (var priv in privs.Where(p => p != null && p.IsAuthed(target)))
6164 {
6165 var building = priv.GetBuilding();
6166
6167 if (building != null && building.HasBuildingBlocks() && building.buildingBlocks.Any(block => block.OwnerID == player.userID))
6168 {
6169 return true;
6170 }
6171 }
6172
6173 return false;
6174 }*/
6175
6176 private bool IsInSameBase(BasePlayer player, BasePlayer target) // 5th method
6177 {
6178 bool _sharesBase = false; // to free the pooled lists
6179 var privs = BaseNetworkable.serverEntities.Where(e => e != null && e is BuildingPrivlidge).Cast<BuildingPrivlidge>();
6180
6181 foreach (var priv in privs.Where(p => p != null && p.IsAuthed(player)))
6182 {
6183 var colliders = Pool.GetList<Collider>();
6184 Vis.Colliders(priv.transform.position, 30f, colliders, constructionMask, QueryTriggerInteraction.Collide);
6185
6186 foreach (var collider in colliders)
6187 {
6188 var entity = collider.GetComponent<BaseEntity>();
6189
6190 if (entity != null && entity.OwnerID == target.userID)
6191 {
6192 _sharesBase = true;
6193 break;
6194 }
6195 }
6196
6197 Pool.FreeList(ref colliders); // free it.
6198
6199 if (_sharesBase)
6200 return true;
6201 }
6202
6203 foreach (var priv in privs.Where(p => p != null && p.IsAuthed(target)))
6204 {
6205 var colliders = Pool.GetList<Collider>();
6206 Vis.Colliders(priv.transform.position, 30f, colliders, constructionMask, QueryTriggerInteraction.Collide);
6207
6208 foreach (var collider in colliders)
6209 {
6210 var entity = collider.GetComponent<BaseEntity>();
6211
6212 if (entity != null && entity.OwnerID == player.userID)
6213 {
6214 _sharesBase = true;
6215 break;
6216 }
6217 }
6218
6219 Pool.FreeList(ref colliders);
6220 }
6221
6222 return _sharesBase;
6223 }
6224
6225 public void Metabolize(BasePlayer player, bool set) // we don't want the elements to harm players since the zone can spawn anywhere on the map!
6226 {
6227 if (player == null)
6228 return;
6229
6230 if (set)
6231 {
6232 player.health = 100f;
6233 player.metabolism.temperature.min = 32; // immune to cold
6234 player.metabolism.temperature.max = 32;
6235 player.metabolism.temperature.value = 32;
6236 player.metabolism.oxygen.min = 1; // immune to drowning
6237 player.metabolism.oxygen.value = 1;
6238 player.metabolism.poison.value = 0; // if they ate raw meat
6239 player.metabolism.calories.value = player.metabolism.calories.max;
6240 player.metabolism.hydration.value = player.metabolism.hydration.max;
6241 player.metabolism.wetness.max = 0;
6242 player.metabolism.wetness.value = 0;
6243 }
6244 else
6245 {
6246 player.metabolism.oxygen.min = 0;
6247 player.metabolism.oxygen.max = 1;
6248 player.metabolism.temperature.min = -100;
6249 player.metabolism.temperature.max = 100;
6250 player.metabolism.wetness.min = 0;
6251 player.metabolism.wetness.max = 1;
6252 }
6253
6254 player.metabolism.SendChangesToClient();
6255 }
6256
6257 public bool IsKit(string kit)
6258 {
6259 return Kits.Call<bool>("isKit", kit);
6260 }
6261
6262 public static void AwardPlayer(ulong playerId, double money, int points)
6263 {
6264 if (money == 0.0 && points == 0)
6265 return;
6266
6267 var player = BasePlayer.FindByID(playerId);
6268
6269 if (money > 0.0)
6270 {
6271 if (ins.Economics != null)
6272 {
6273 ins.Economics?.Call("Deposit", playerId, money);
6274
6275 if (player != null)
6276 player.ChatMessage(ins.msg("EconomicsDeposit", player.UserIDString, money));
6277 }
6278 }
6279
6280 if (points > 0)
6281 {
6282 if (ins.ServerRewards != null)
6283 {
6284 var success = ins.ServerRewards?.Call("AddPoints", playerId, points);
6285
6286 if (player != null && success != null && success is bool && (bool)success)
6287 player.ChatMessage(ins.msg("ServerRewardPoints", player.UserIDString, points));
6288 }
6289 }
6290 }
6291
6292 public void GivePlayerKit(BasePlayer player)
6293 {
6294 if (player == null)
6295 return;
6296
6297 string kit = duelsData.Kits.ContainsKey(player.UserIDString) ? duelsData.Kits[player.UserIDString] : string.Empty;
6298
6299 duelsData.Kits.Remove(player.UserIDString);
6300 player.inventory.Strip();
6301
6302 if (!string.IsNullOrEmpty(kit))
6303 {
6304 if (Kits != null && IsKit(kit))
6305 {
6306 object success = ins.Kits.Call("GiveKit", player, kit);
6307
6308 if (success is bool && (bool)success)
6309 {
6310 return;
6311 }
6312 }
6313
6314 if (string.IsNullOrEmpty(kit))
6315 {
6316 kit = duelsData.CustomKits.ContainsKey(player.UserIDString) ? duelsData.CustomKits[player.UserIDString] : null;
6317 }
6318
6319 if (GiveCustomKit(player, kit))
6320 {
6321 return;
6322 }
6323 }
6324
6325 if (Kits != null)
6326 {
6327 kit = GetRandomKit();
6328
6329 if (!string.IsNullOrEmpty(kit))
6330 {
6331 object success = ins.Kits.Call("GiveKit", player, kit);
6332
6333 if (success is bool && (bool)success)
6334 {
6335 return;
6336 }
6337 }
6338 }
6339
6340 // give a basic kit when no kit is provided, or the provided kit is invalid
6341 player.inventory.GiveItem(ItemManager.CreateByItemID(1443579727, 1, 0)); // bow
6342 player.inventory.GiveItem(ItemManager.CreateByItemID(-1234735557, 50, 0)); // arrows
6343 player.inventory.GiveItem(ItemManager.CreateByItemID(1602646136, 1, 0)); // stone spear
6344 player.inventory.GiveItem(ItemManager.CreateByItemID(-2072273936, 5, 0)); // bandage
6345 player.inventory.GiveItem(ItemManager.CreateByItemID(254522515, 3, 0)); // medkit
6346 player.inventory.GiveItem(ItemManager.CreateByItemID(1079279582, 4, 0)); // syringe
6347 }
6348
6349 public bool GiveCustomKit(BasePlayer player, string kit)
6350 {
6351 if (string.IsNullOrEmpty(kit) || customKits.Count == 0 || !customKits.ContainsKey(kit))
6352 return false;
6353
6354 bool success = false;
6355
6356 foreach (var dki in customKits[kit])
6357 {
6358 Item item = ItemManager.CreateByName(dki.shortname, dki.amount, dki.skin);
6359
6360 if (item == null)
6361 {
6362 var def = ItemManager.FindItemDefinition(dki.shortname);
6363
6364 if (def != null)
6365 item = ItemManager.CreateByItemID(def.itemid, dki.amount, dki.skin);
6366
6367 if (item == null)
6368 continue;
6369 }
6370
6371 if (item.skin == 0 && useRandomSkins)
6372 {
6373 var skins = GetItemSkins(item.info);
6374
6375 if (skins.Count > 0)
6376 item.skin = skins.GetRandom();
6377 }
6378
6379 if (dki.mods != null)
6380 {
6381 foreach (string shortname in dki.mods)
6382 {
6383 Item mod = ItemManager.CreateByName(shortname, 1);
6384
6385 if (mod != null)
6386 item.contents.AddItem(mod.info, 1);
6387 }
6388 }
6389
6390 var heldEntity = item.GetHeldEntity();
6391
6392 if (heldEntity != null)
6393 {
6394 if (item.skin != 0)
6395 heldEntity.skinID = item.skin;
6396
6397 var weapon = heldEntity as BaseProjectile;
6398
6399 if (weapon != null)
6400 {
6401 if (!string.IsNullOrEmpty(dki.ammo))
6402 {
6403 var def = ItemManager.FindItemDefinition(dki.ammo);
6404
6405 if (def != null)
6406 weapon.primaryMagazine.ammoType = def;
6407 }
6408
6409 weapon.primaryMagazine.contents = 0; // unload the old ammo
6410 weapon.SendNetworkUpdateImmediate(false); // update
6411 weapon.primaryMagazine.contents = weapon.primaryMagazine.capacity; // load new ammo
6412 }
6413 }
6414
6415 var container = dki.container == "belt" ? player.inventory.containerBelt : dki.container == "wear" ? player.inventory.containerWear : player.inventory.containerMain;
6416
6417 item.MarkDirty();
6418 if (item.MoveToContainer(container, dki.slot < 0 || dki.slot > container.capacity - 1 ? -1 : dki.slot, true))
6419 {
6420 success = true;
6421 }
6422 else
6423 {
6424 item.Remove(0f);
6425 }
6426 }
6427
6428 return success;
6429 }
6430
6431 private void DuelAnnouncement(bool bypass)
6432 {
6433 if (!bypass && (!duelsData.DuelsEnabled || !useAnnouncement))
6434 return;
6435
6436 if (BasePlayer.activePlayerList.Count < 3)
6437 return;
6438
6439 string console = msg("DuelAnnouncement");
6440 string disabled = msg("Disabled");
6441
6442 console = console.Replace("{duelChatCommand}", !string.IsNullOrEmpty(szDuelChatCommand) ? szDuelChatCommand : disabled);
6443 console = console.Replace("{ladderCommand}", !string.IsNullOrEmpty(szDuelChatCommand) ? string.Format("{0} ladder", szDuelChatCommand) : disabled);
6444 console = console.Replace("{queueCommand}", !string.IsNullOrEmpty(szQueueChatCommand) ? szQueueChatCommand : disabled);
6445
6446 if (allowBets)
6447 console += msg("DuelAnnouncementBetsSuffix", null, szDuelChatCommand);
6448
6449 Puts(RemoveFormatting(console));
6450
6451 foreach (var player in BasePlayer.activePlayerList.Where(p => p?.displayName != null))
6452 {
6453 string message = msg("DuelAnnouncement", player.UserIDString);
6454
6455 message = message.Replace("{duelChatCommand}", !string.IsNullOrEmpty(szDuelChatCommand) ? szDuelChatCommand : disabled);
6456 message = message.Replace("{ladderCommand}", !string.IsNullOrEmpty(szDuelChatCommand) ? string.Format("{0} ladder", szDuelChatCommand) : disabled);
6457 message = message.Replace("{queueCommand}", !string.IsNullOrEmpty(szQueueChatCommand) ? szQueueChatCommand : disabled);
6458
6459 if (allowBets)
6460 message += msg("DuelAnnouncementBetsSuffix", player.UserIDString, szDuelChatCommand);
6461
6462 player.ChatMessage(string.Format("{0} <color=#C0C0C0>{1}</color>", lang.GetMessage("Prefix", this, player.UserIDString), message));
6463 }
6464 }
6465
6466 public bool CreateBet(BasePlayer player, int betAmount, BetInfo betInfo)
6467 {
6468 if (betAmount > betInfo.max) // adjust the bet to the maximum since they clearly want to do this
6469 betAmount = betInfo.max;
6470
6471 int amount = player.inventory.GetAmount(betInfo.itemid);
6472
6473 if (amount == 0)
6474 {
6475 player.ChatMessage(msg("BetZero", player.UserIDString));
6476 return false;
6477 }
6478
6479 if (amount < betAmount) // obviously they're just trying to see how this works. we won't adjust it here.
6480 {
6481 player.ChatMessage(msg("BetNotEnough", player.UserIDString));
6482 return false;
6483 }
6484
6485 var takenItems = new List<Item>();
6486 int takenAmount = player.inventory.Take(takenItems, betInfo.itemid, betAmount);
6487
6488 if (takenAmount == betAmount)
6489 {
6490 var bet = new BetInfo
6491 {
6492 itemid = betInfo.itemid,
6493 amount = betAmount,
6494 trigger = betInfo.trigger
6495 };
6496
6497 duelsData.Bets.Add(player.UserIDString, bet);
6498
6499 string message = msg("BetPlaced", player.UserIDString, betInfo.trigger, betAmount);
6500
6501 if (allowBetRefund)
6502 message += msg("BetRefundSuffix", player.UserIDString, szDuelChatCommand);
6503 else if (allowBetForfeit)
6504 message += msg("BetForfeitSuffix", player.UserIDString, szDuelChatCommand);
6505
6506 player.ChatMessage(message);
6507 Puts("{0} bet {1} ({2})", player.displayName, betInfo.trigger, betAmount);
6508
6509 foreach (Item item in takenItems.ToList())
6510 item.Remove(0.1f);
6511
6512 return true;
6513 }
6514
6515 if (takenItems.Count > 0)
6516 {
6517 foreach (Item item in takenItems.ToList())
6518 player.GiveItem(item, BaseEntity.GiveItemReason.Generic);
6519
6520 takenItems.Clear();
6521 }
6522
6523 return false;
6524 }
6525
6526 private void GetWorkshopIDs(int code, string response)
6527 {
6528 if (!string.IsNullOrEmpty(response) && code == 200)
6529 {
6530 var items = JsonConvert.DeserializeObject<ItemSchema>(response).items;
6531
6532 foreach (var item in items)
6533 {
6534 if (string.IsNullOrEmpty(item.itemshortname) || string.IsNullOrEmpty(item.workshopdownload))
6535 continue;
6536
6537 if (!workshopskinsCache.ContainsKey(item.itemshortname))
6538 workshopskinsCache.Add(item.itemshortname, new List<ulong>());
6539
6540 workshopskinsCache[item.itemshortname].Add(Convert.ToUInt64(item.workshopdownload));
6541 }
6542 }
6543 }
6544
6545 public List<ulong> GetItemSkins(ItemDefinition def)
6546 {
6547 if (!skinsCache.ContainsKey(def.shortname))
6548 {
6549 var skins = new List<ulong>();
6550
6551 skins.AddRange(def.skins.Select(skin => Convert.ToUInt64(skin.id)));
6552
6553 if (useWorkshopSkins && workshopskinsCache.ContainsKey(def.shortname))
6554 {
6555 skins.AddRange(workshopskinsCache[def.shortname]);
6556 workshopskinsCache.Remove(def.shortname);
6557 }
6558
6559 if (skins.Contains(0uL))
6560 skins.Remove(0uL);
6561
6562 skinsCache.Add(def.shortname, skins);
6563 }
6564
6565 return skinsCache[def.shortname];
6566 }
6567
6568 private void RemoveRequests(BasePlayer player)
6569 {
6570 foreach (var entry in dataRequests.ToList())
6571 {
6572 if (entry.Key == player.UserIDString || entry.Value == player.UserIDString)
6573 {
6574 dataRequests.Remove(entry.Key);
6575 }
6576 }
6577 }
6578
6579 private static void UpdateMatchSizeStats(string playerId, bool winner, bool loser, int teamSize)
6580 {
6581 string key = teamSize.ToString();
6582
6583 if (winner)
6584 {
6585 if (!duelsData.MatchSizesVictoriesSeed.ContainsKey(key)) duelsData.MatchSizesVictoriesSeed.Add(key, new Dictionary<string, int>());
6586 if (!duelsData.MatchSizesVictories.ContainsKey(key)) duelsData.MatchSizesVictories.Add(key, new Dictionary<string, int>());
6587 if (!duelsData.MatchSizesVictoriesSeed[key].ContainsKey(playerId)) duelsData.MatchSizesVictoriesSeed[key].Add(playerId, 1);
6588 else duelsData.MatchSizesVictoriesSeed[key][playerId]++;
6589 if (!duelsData.MatchSizesVictories[key].ContainsKey(playerId)) duelsData.MatchSizesVictories[key].Add(playerId, 1);
6590 else duelsData.MatchSizesVictories[key][playerId]++;
6591 }
6592 if (loser)
6593 {
6594 if (!duelsData.MatchSizesLossesSeed.ContainsKey(key)) duelsData.MatchSizesLossesSeed.Add(key, new Dictionary<string, int>());
6595 if (!duelsData.MatchSizesLosses.ContainsKey(key)) duelsData.MatchSizesLosses.Add(key, new Dictionary<string, int>());
6596 if (!duelsData.MatchSizesLossesSeed[key].ContainsKey(playerId)) duelsData.MatchSizesLossesSeed[key].Add(playerId, 1);
6597 else duelsData.MatchSizesLossesSeed[key][playerId]++;
6598 if (!duelsData.MatchSizesLosses[key].ContainsKey(playerId)) duelsData.MatchSizesLosses[key].Add(playerId, 1);
6599 else duelsData.MatchSizesLosses[key][playerId]++;
6600 }
6601 }
6602
6603 private static void UpdateMatchStats(string playerId, bool winner, bool loser, bool death, bool kill)
6604 {
6605 if (winner)
6606 {
6607 if (!duelsData.MatchVictories.ContainsKey(playerId)) duelsData.MatchVictories.Add(playerId, 1);
6608 else duelsData.MatchVictories[playerId]++;
6609 if (!duelsData.MatchVictoriesSeed.ContainsKey(playerId)) duelsData.MatchVictoriesSeed.Add(playerId, 1);
6610 else duelsData.MatchVictoriesSeed[playerId]++;
6611 }
6612 if (loser)
6613 {
6614 if (!duelsData.MatchLosses.ContainsKey(playerId)) duelsData.MatchLosses.Add(playerId, 1);
6615 else duelsData.MatchLosses[playerId]++;
6616 if (!duelsData.MatchLossesSeed.ContainsKey(playerId)) duelsData.MatchLossesSeed.Add(playerId, 1);
6617 else duelsData.MatchLossesSeed[playerId]++;
6618 }
6619 if (death)
6620 {
6621 if (!duelsData.MatchDeaths.ContainsKey(playerId)) duelsData.MatchDeaths.Add(playerId, 1);
6622 else duelsData.MatchDeaths[playerId]++;
6623 if (!duelsData.MatchDeathsSeed.ContainsKey(playerId)) duelsData.MatchDeathsSeed.Add(playerId, 1);
6624 else duelsData.MatchDeathsSeed[playerId]++;
6625 }
6626 if (kill)
6627 {
6628 if (!duelsData.MatchKills.ContainsKey(playerId)) duelsData.MatchKills.Add(playerId, 1);
6629 else duelsData.MatchKills[playerId]++;
6630 if (!duelsData.MatchKillsSeed.ContainsKey(playerId)) duelsData.MatchKillsSeed.Add(playerId, 1);
6631 else duelsData.MatchKillsSeed[playerId]++;
6632 }
6633 }
6634
6635 #region SpawnPoints
6636
6637 public void SendSpawnHelp(BasePlayer player)
6638 {
6639 player.ChatMessage(msg("SpawnCount", player.UserIDString, duelsData.Spawns.Count));
6640 player.ChatMessage(msg("SpawnAdd", player.UserIDString, szDuelChatCommand));
6641 player.ChatMessage(msg("SpawnHere", player.UserIDString, szDuelChatCommand));
6642 player.ChatMessage(msg("SpawnRemove", player.UserIDString, szDuelChatCommand, spRemoveOneMaxDistance));
6643 player.ChatMessage(msg("SpawnRemoveAll", player.UserIDString, szDuelChatCommand, spRemoveAllMaxDistance));
6644 player.ChatMessage(msg("SpawnWipe", player.UserIDString, szDuelChatCommand));
6645 }
6646
6647 public void AddSpawnPoint(BasePlayer player, bool useHit)
6648 {
6649 var spawn = player.transform.position;
6650
6651 if (useHit)
6652 {
6653 RaycastHit hit;
6654 if (!Physics.Raycast(player.eyes.HeadRay(), out hit, Mathf.Infinity, wallMask))
6655 {
6656 player.ChatMessage(msg("FailedRaycast", player.UserIDString));
6657 return;
6658 }
6659
6660 spawn = hit.point;
6661 }
6662
6663 if (duelsData.Spawns.Contains(spawn.ToString()))
6664 {
6665 player.ChatMessage(msg("SpawnExists", player.UserIDString));
6666 return;
6667 }
6668
6669 duelsData.Spawns.Add(spawn.ToString());
6670 player.SendConsoleCommand("ddraw.text", spDrawTime, Color.green, spawn, "+S");
6671 player.ChatMessage(msg("SpawnAdded", player.UserIDString, FormatPosition(spawn)));
6672 }
6673
6674 public void RemoveSpawnPoint(BasePlayer player)
6675 {
6676 float radius = spRemoveOneMaxDistance;
6677 var spawn = Vector3.zero;
6678 float dist = radius;
6679
6680 foreach (var entry in duelsData.Spawns.ToList())
6681 {
6682 var _spawn = entry.ToVector3();
6683 float distance = Vector3.Distance(player.transform.position, _spawn);
6684
6685 if (distance < dist)
6686 {
6687 dist = distance;
6688 spawn = _spawn;
6689 }
6690 }
6691
6692 if (spawn != Vector3.zero)
6693 {
6694 duelsData.Spawns.Remove(spawn.ToString());
6695 player.SendConsoleCommand("ddraw.text", spDrawTime, Color.red, spawn, "-S");
6696 player.ChatMessage(msg("SpawnRemoved", player.UserIDString, 1));
6697 }
6698 else
6699 player.ChatMessage(msg("SpawnNoneFound", player.UserIDString, radius));
6700 }
6701
6702 public void RemoveSpawnPoints(BasePlayer player)
6703 {
6704 int count = 0;
6705
6706 foreach (var entry in duelsData.Spawns.ToList())
6707 {
6708 var spawn = entry.ToVector3();
6709
6710 if (Vector3.Distance(player.transform.position, spawn) <= spRemoveAllMaxDistance)
6711 {
6712 count++;
6713 duelsData.Spawns.Remove(entry);
6714 player.SendConsoleCommand("ddraw.text", spDrawTime, Color.red, spawn, "-S");
6715 }
6716 }
6717
6718 if (count == 0)
6719 player.ChatMessage(msg("SpawnNoneFound", player.UserIDString, spRemoveAllMaxDistance));
6720 else
6721 player.ChatMessage(msg("SpawnRemoved", player.UserIDString, count));
6722 }
6723
6724 public void WipeSpawnPoints(BasePlayer player)
6725 {
6726 if (duelsData.Spawns.Count == 0)
6727 {
6728 player.ChatMessage(msg("SpawnNoneExist", player.UserIDString));
6729 return;
6730 }
6731
6732 var spawns = duelsData.Spawns.Select(spawn => spawn.ToVector3()).ToList();
6733
6734 foreach (var spawn in spawns)
6735 player.SendConsoleCommand("ddraw.text", 30f, Color.red, spawn, "-S");
6736
6737 int amount = duelsData.Spawns.Count();
6738 duelsData.Spawns.Clear();
6739 spawns.Clear();
6740 player.ChatMessage(msg("SpawnWiped", player.UserIDString, amount));
6741 }
6742
6743 public static List<Vector3> GetSpawnPoints(DuelingZone zone)
6744 {
6745 return duelsData.Spawns.Select(entry => entry.ToVector3()).Where(spawn => zone.Distance(spawn) < zoneRadius).ToList();
6746 }
6747
6748 public string FormatBone(string source)
6749 {
6750 if (string.IsNullOrEmpty(source))
6751 return "Chest";
6752
6753 foreach (var entry in boneTags)
6754 source = source.Replace(entry.Key, entry.Value);
6755
6756 return string.Join(" ", source.Split(' ').Select(str => str.SentenceCase()).ToArray());
6757 }
6758
6759 public string FormatPosition(Vector3 position)
6760 {
6761 string x = position.x.ToString("N2");
6762 string y = position.y.ToString("N2");
6763 string z = position.z.ToString("N2");
6764
6765 return $"{x} {y} {z}";
6766 }
6767
6768 #endregion
6769
6770 #region UI Creation
6771
6772 private readonly List<string> createUI = new List<string>();
6773 private readonly List<string> duelistUI = new List<string>();
6774 private readonly List<string> kitsUI = new List<string>();
6775 private readonly List<string> matchesUI = new List<string>();
6776
6777 [ConsoleCommand("UI_DuelistCommand")]
6778 private void ccmdDuelistUI(ConsoleSystem.Arg arg)
6779 {
6780 var player = arg.Player();
6781
6782 if (!player || !arg.HasArgs())
6783 return;
6784
6785 switch (arg.Args[0].ToLower())
6786 {
6787 case "accept":
6788 {
6789 if (dataRequests.ContainsValue(player.UserIDString))
6790 {
6791 cmdDuel(player, szDuelChatCommand, new[] { "accept" });
6792 break;
6793 }
6794 if (tdmRequests.ContainsValue(player.UserIDString))
6795 {
6796 cmdTDM(player, szMatchChatCommand, new[] { "accept" });
6797 break;
6798 }
6799
6800 player.ChatMessage(msg("NoPendingRequests2", player.UserIDString));
6801 break;
6802 }
6803 case "decline":
6804 {
6805 if (dataRequests.ContainsKey(player.UserIDString) || dataRequests.ContainsValue(player.UserIDString))
6806 {
6807 cmdDuel(player, szDuelChatCommand, new[] { "decline" });
6808 break;
6809 }
6810
6811 var deathmatch = tdmMatches.FirstOrDefault(x => x.GetTeam(player) != Team.None);
6812
6813 if (deathmatch != null || tdmRequests.ContainsValue(player.UserIDString) || tdmRequests.ContainsKey(player.UserIDString))
6814 {
6815 cmdTDM(player, szMatchChatCommand, new[] { "decline" });
6816 break;
6817 }
6818
6819 player.ChatMessage(msg("NoPendingRequests", player.UserIDString));
6820 break;
6821 }
6822 case "closeui":
6823 {
6824 DestroyUI(player);
6825 return;
6826 }
6827 case "kits":
6828 {
6829 ToggleKitUI(player);
6830 break;
6831 }
6832 case "public":
6833 {
6834 cmdTDM(player, szMatchChatCommand, new[] { "public" });
6835 break;
6836 }
6837 case "requeue":
6838 {
6839 if (IsDueling(player) || InDeathmatch(player))
6840 return;
6841
6842 if (sendHomeRequeue)
6843 {
6844 CuiHelper.DestroyUi(player, "DuelistUI_Defeat");
6845 SendHome(player);
6846 }
6847 else CreateDefeatUI(player);
6848 cmdQueue(player, szQueueChatCommand, new string[0]);
6849 return;
6850 }
6851 case "queue":
6852 {
6853 if (IsDueling(player) || InDeathmatch(player))
6854 break;
6855
6856 cmdQueue(player, szQueueChatCommand, new string[0]);
6857 break;
6858 }
6859 case "respawn":
6860 {
6861 CuiHelper.DestroyUi(player, "DuelistUI_Defeat");
6862
6863 if (!InEvent(player) && DuelTerritory(player.transform.position))
6864 SendHome(player);
6865
6866 return;
6867 }
6868 case "ready":
6869 case "readyon":
6870 case "readyoff":
6871 {
6872 ReadyUp(player);
6873
6874 if (DuelTerritory(player.transform.position))
6875 {
6876 CreateDefeatUI(player);
6877 return;
6878 }
6879
6880 break;
6881 }
6882 case "tdm":
6883 {
6884 ToggleMatchUI(player);
6885 break;
6886 }
6887 case "kit":
6888 {
6889 if (arg.Args.Length != 2)
6890 return;
6891
6892 var match = GetMatch(player);
6893
6894 if (match != null && match.IsHost(player))
6895 {
6896 if (!match.IsStarted)
6897 match.Kit = GetVerifiedKit(arg.Args[1]);
6898
6899 break;
6900 }
6901
6902 if (duelsData.CustomKits.ContainsKey(player.UserIDString) && duelsData.CustomKits[player.UserIDString] == arg.Args[1])
6903 {
6904 duelsData.CustomKits.Remove(player.UserIDString);
6905 player.ChatMessage(msg("ResetKit", player.UserIDString));
6906 break;
6907 }
6908
6909 string kit = GetVerifiedKit(arg.Args[1]);
6910
6911 if (string.IsNullOrEmpty(kit))
6912 break;
6913
6914 duelsData.CustomKits[player.UserIDString] = kit;
6915 player.ChatMessage(msg("KitSet", player.UserIDString, kit));
6916 break;
6917 }
6918 case "joinmatch":
6919 {
6920 if (arg.Args.Length != 2)
6921 return;
6922
6923 if (IsDueling(player))
6924 break;
6925
6926 var match = GetMatch(player);
6927
6928 if (match != null)
6929 {
6930 if (match.IsStarted)
6931 break;
6932
6933 match.RemoveMatchPlayer(player);
6934 }
6935
6936 var newMatch = tdmMatches.FirstOrDefault(x => x.Id == arg.Args[1] && x.IsPublic);
6937
6938 if (newMatch == null || newMatch.IsFull() || newMatch.IsStarted || newMatch.IsOver)
6939 {
6940 player.ChatMessage(msg("MatchNoLongerValid", player.UserIDString));
6941 break;
6942 }
6943
6944 if (newMatch.GetTeam(player) != Team.None)
6945 break;
6946
6947 newMatch.AddMatchPlayer(player, !newMatch.IsFull(Team.Good) ? Team.Good : Team.Evil);
6948
6949 if (matchesUI.Contains(player.UserIDString))
6950 {
6951 CuiHelper.DestroyUi(player, "DuelistUI_Matches");
6952 matchesUI.Remove(player.UserIDString);
6953 }
6954
6955 break;
6956 }
6957 case "size":
6958 {
6959 if (arg.Args.Length != 2 || !arg.Args[1].All(char.IsDigit))
6960 break;
6961
6962 cmdTDM(player, szMatchChatCommand, new[] { "size", arg.Args[1] });
6963 break;
6964 }
6965 case "any":
6966 {
6967 cmdTDM(player, szMatchChatCommand, new[] { "any" });
6968 break;
6969 }
6970 }
6971
6972 RefreshUI(player);
6973 }
6974
6975 public void DestroyAllUI()
6976 {
6977 foreach (var player in BasePlayer.activePlayerList)
6978 {
6979 DestroyUI(player);
6980 }
6981 }
6982
6983 public bool DestroyUI(BasePlayer player)
6984 {
6985 CuiHelper.DestroyUi(player, "DuelistUI_Options");
6986 CuiHelper.DestroyUi(player, "DuelistUI_Kits");
6987 CuiHelper.DestroyUi(player, "DuelistUI_Matches");
6988 CuiHelper.DestroyUi(player, "DuelistUI_Announcement");
6989 CuiHelper.DestroyUi(player, "DuelistUI_Defeat");
6990 CuiHelper.DestroyUi(player, "DuelistUI_Countdown");
6991
6992 if (readyUiList.Contains(player.UserIDString))
6993 {
6994 CuiHelper.DestroyUi(player, "DuelistUI_Ready");
6995 readyUiList.Remove(player.UserIDString);
6996 }
6997
6998 if (duelistUI.Contains(player.UserIDString))
6999 {
7000 duelistUI.Remove(player.UserIDString);
7001 return true;
7002 }
7003
7004 return false;
7005 }
7006
7007 public void ccmdDUI(ConsoleSystem.Arg arg)
7008 {
7009 var player = arg.Player();
7010
7011 if (!player)
7012 return;
7013
7014 if (arg.HasArgs(1))
7015 {
7016 switch (arg.Args[0].ToLower())
7017 {
7018 case "on":
7019 {
7020 cmdDUI(player, szUIChatCommand, new string[0]);
7021 return;
7022 }
7023 case "off":
7024 {
7025 DestroyUI(player);
7026 return;
7027 }
7028 }
7029 }
7030
7031 if (duelistUI.Contains(player.UserIDString))
7032 DestroyUI(player);
7033 else
7034 cmdDUI(player, szUIChatCommand, new string[0]);
7035 }
7036
7037 public void cmdDUI(BasePlayer player, string command, string[] args)
7038 {
7039 DestroyUI(player);
7040 var buttons = new List<string>
7041 {
7042 "UI_Accept",
7043 "UI_Decline",
7044 "UI_Kits",
7045 "UI_Public",
7046 "UI_Queue",
7047 "UI_TDM",
7048 "UI_Any",
7049 duelsData.AutoReady.Contains(player.UserIDString) ? "UI_ReadyOn" : "UI_ReadyOff",
7050 };
7051 var element = UI.CreateElementContainer("DuelistUI_Options", "0 0 0 0.5", "0.915 0.148", "0.981 0.441", guiUseCursor);
7052
7053 if (guiUseCloseButton)
7054 UI.CreateButton(ref element, "DuelistUI_Options", "0.29 0.49 0.69 0.5", "X", 14, "0.7 0.9", "0.961 0.98", "UI_DuelistCommand closeui");
7055
7056 for (int number = 0; number < buttons.Count; number++)
7057 {
7058 var pos = UI.CalcButtonPos(number + 1, 2.075f);
7059 string uicommand = buttons[number].Replace("UI_", "").ToLower();
7060 string text = msg(buttons[number], player.UserIDString);
7061 UI.CreateButton(ref element, "DuelistUI_Options", "0.29 0.49 0.69 0.5", text, 14, $"{pos[0]} {pos[1]}", $"{pos[2]} {pos[3]}", $"UI_DuelistCommand {uicommand}");
7062 }
7063
7064 if (!duelistUI.Contains(player.UserIDString))
7065 duelistUI.Add(player.UserIDString);
7066
7067 CuiHelper.AddUi(player, element);
7068 }
7069
7070 public void RefreshUI(BasePlayer player)
7071 {
7072 cmdDUI(player, szUIChatCommand, new string[0]);
7073
7074 if (kitsUI.Contains(player.UserIDString))
7075 {
7076 kitsUI.Remove(player.UserIDString);
7077 ToggleKitUI(player);
7078 }
7079 if (matchesUI.Contains(player.UserIDString))
7080 {
7081 matchesUI.Remove(player.UserIDString);
7082 ToggleMatchUI(player);
7083 }
7084 }
7085
7086 public void ToggleMatchUI(BasePlayer player)
7087 {
7088 if (matchesUI.Contains(player.UserIDString))
7089 {
7090 CuiHelper.DestroyUi(player, "DuelistUI_Matches");
7091 matchesUI.Remove(player.UserIDString);
7092 return;
7093 }
7094
7095 if (kitsUI.Contains(player.UserIDString))
7096 {
7097 CuiHelper.DestroyUi(player, "DuelistUI_Kits");
7098 kitsUI.Remove(player.UserIDString);
7099 }
7100
7101 var element = UI.CreateElementContainer("DuelistUI_Matches", "0 0 0 0.5", "0.669 0.148", "0.903 0.541");
7102 var matches = tdmMatches.Where(x => x.IsPublic && !x.IsStarted && !x.IsFull()).ToList();
7103
7104 for (int number = 0; number < matches.Count; number++)
7105 {
7106 var pos = UI.CalcButtonPos(number);
7107 UI.CreateButton(ref element, "DuelistUI_Matches", "0.29 0.49 0.69 0.5", matches[number].Versus, 12, $"{pos[0]} {pos[1]}", $"{pos[2]} {pos[3]}", $"UI_DuelistCommand joinmatch {matches[number].Id}");
7108 }
7109
7110 var match = GetMatch(player);
7111 string teamSize = msg("UI_TeamSize", player.UserIDString);
7112
7113 for (int size = Math.Max(2, minDeathmatchSize); size < maxDeathmatchSize + 1; size++)
7114 {
7115 var pos = UI.CalcButtonPos(size + matches.Count);
7116 string color = match != null && match.TeamSize == size || size == minDeathmatchSize ? "0.69 0.49 0.29 0.5" : "0.29 0.49 0.69 0.5";
7117 UI.CreateButton(ref element, "DuelistUI_Matches", color, teamSize + size, 12, $"{pos[0]} {pos[1]}", $"{pos[2]} {pos[3]}", $"UI_DuelistCommand size {size}");
7118 }
7119
7120 if (matches.Count == 0)
7121 UI.CreateLabel(ref element, "DuelistUI_Matches", "1 1 1 1", msg("NoMatchesExistYet", player.UserIDString), 14, "0.047 0.73", "1 0.89");
7122
7123 CuiHelper.AddUi(player, element);
7124 matchesUI.Add(player.UserIDString);
7125 }
7126
7127 public void ToggleKitUI(BasePlayer player)
7128 {
7129 if (kitsUI.Contains(player.UserIDString))
7130 {
7131 CuiHelper.DestroyUi(player, "DuelistUI_Kits");
7132 kitsUI.Remove(player.UserIDString);
7133 return;
7134 }
7135
7136 if (matchesUI.Contains(player.UserIDString))
7137 {
7138 CuiHelper.DestroyUi(player, "DuelistUI_Matches");
7139 matchesUI.Remove(player.UserIDString);
7140 }
7141
7142 var element = UI.CreateElementContainer("DuelistUI_Kits", "0 0 0 0.5", "0.669 0.148", "0.903 0.541");
7143 var kits = VerifiedKits;
7144 string kit = duelsData.CustomKits.ContainsKey(player.UserIDString) ? duelsData.CustomKits[player.UserIDString] : null;
7145
7146 for (int number = 0; number < kits.Count; number++)
7147 {
7148 var pos = UI.CalcButtonPos(number);
7149 UI.CreateButton(ref element, "DuelistUI_Kits", kits[number] == kit ? "0.69 0.49 0.29 0.5" : "0.29 0.49 0.69 0.5", kits[number], 12, $"{pos[0]} {pos[1]}", $"{pos[2]} {pos[3]}", $"UI_DuelistCommand kit {kits[number]}");
7150 }
7151
7152 CuiHelper.AddUi(player, element);
7153 kitsUI.Add(player.UserIDString);
7154 }
7155
7156 public static void CreateAnnouncementUI(BasePlayer player, string text)
7157 {
7158 if (!player || !player.IsConnected || guiAnnounceUITime <= 0f)
7159 return;
7160
7161 var element = UI.CreateElementContainer("DuelistUI_Announcement", "0 0 0 0.5", "-0.027 0.92", "1.026 0.9643", false, "Hud");
7162
7163 UI.CreateLabel(ref element, "DuelistUI_Announcement", "", text, 18, "0 0", "1 1");
7164 CuiHelper.DestroyUi(player, "DuelistUI_Announcement");
7165 CuiHelper.AddUi(player, element);
7166
7167 ins.timer.Once(guiAnnounceUITime, () => CuiHelper.DestroyUi(player, "DuelistUI_Announcement"));
7168 }
7169
7170 public void CreateCountdownUI(BasePlayer player, string text)
7171 {
7172 var element = UI.CreateElementContainer("DuelistUI_Countdown", "0 0 0 0.5", "0.484 0.92", "0.527 0.9643", false, "Hud");
7173
7174 UI.CreateLabel(ref element, "DuelistUI_Countdown", "1 0.1 0.1 1", text, 20, "0 0", "1 1");
7175 CuiHelper.DestroyUi(player, "DuelistUI_Countdown");
7176 CuiHelper.AddUi(player, element);
7177 }
7178
7179 public static void ToggleReadyUI(BasePlayer player)
7180 {
7181 if (!player || !player.IsConnected)
7182 return;
7183
7184 if (readyUiList.Contains(player.UserIDString))
7185 {
7186 CuiHelper.DestroyUi(player, "DuelistUI_Ready");
7187 readyUiList.Remove(player.UserIDString);
7188 return;
7189 }
7190
7191 var element = UI.CreateElementContainer("DuelistUI_Ready", "0 0 0 0.5", "0.475 0.158", "0.573 0.21");
7192 UI.CreateButton(ref element, "DuelistUI_Ready", "0.29 0.49 0.69 0.5", ins.msg(duelsData.AutoReady.Contains(player.UserIDString) ? "UI_ReadyOn" : "UI_ReadyOff", player.UserIDString), 18, "0.016 0.081", "0.984 0.919", "UI_DuelistCommand ready");
7193 CuiHelper.AddUi(player, element);
7194 readyUiList.Add(player.UserIDString);
7195 }
7196
7197 public static void CreateDefeatUI(BasePlayer player)
7198 {
7199 if (!player || !player.IsConnected)
7200 return;
7201
7202 var element = UI.CreateElementContainer("DuelistUI_Defeat", "0 0 0 0.5", "0.436 0.133", "0.534 0.307", guiUseCursor);
7203
7204 UI.CreateButton(ref element, "DuelistUI_Defeat", "0.29 0.49 0.69 0.5", ins.msg("UI_Respawn", player.UserIDString), 18, "0.016 0.679", "0.984 0.976", "UI_DuelistCommand respawn");
7205 UI.CreateButton(ref element, "DuelistUI_Defeat", "0.29 0.49 0.69 0.5", ins.msg("UI_Requeue", player.UserIDString), 18, "0.016 0.357", "0.984 0.655", "UI_DuelistCommand requeue");
7206 UI.CreateButton(ref element, "DuelistUI_Defeat", "0.29 0.49 0.69 0.5", ins.msg(duelsData.AutoReady.Contains(player.UserIDString) ? "UI_ReadyOn" : "UI_ReadyOff", player.UserIDString), 18, "0.016 0.024", "0.984 0.333", "UI_DuelistCommand ready");
7207 CuiHelper.DestroyUi(player, "DuelistUI_Defeat");
7208 CuiHelper.AddUi(player, element);
7209
7210 if (readyUiList.Contains(player.UserIDString))
7211 {
7212 CuiHelper.DestroyUi(player, "DuelistUI_Ready");
7213 readyUiList.Remove(player.UserIDString);
7214 }
7215 }
7216
7217 private void UpdateMatchUI()
7218 {
7219 if (!matchUpdateRequired)
7220 return;
7221
7222 matchUpdateRequired = false;
7223
7224 foreach (string userId in matchesUI.ToList())
7225 {
7226 matchesUI.Remove(userId);
7227 var player = BasePlayer.Find(userId);
7228
7229 if (player != null && player.IsConnected)
7230 {
7231 CuiHelper.DestroyUi(player, "DuelistUI_Matches");
7232 ToggleMatchUI(player);
7233 }
7234 }
7235 }
7236
7237 public class UI // Credit: Absolut
7238 {
7239 public static CuiElementContainer CreateElementContainer(string panelName, string color, string aMin, string aMax, bool cursor = false, string parent = "Overlay")
7240 {
7241 var NewElement = new CuiElementContainer
7242 {
7243 {
7244 new CuiPanel
7245 {
7246 Image =
7247 {
7248 Color = color
7249 },
7250 RectTransform =
7251 {
7252 AnchorMin = aMin,
7253 AnchorMax = aMax
7254 },
7255 CursorEnabled = cursor
7256 },
7257 new CuiElement().Parent = parent,
7258 panelName
7259 }
7260 };
7261 return NewElement;
7262 }
7263
7264 public static void CreateLabel(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter)
7265 {
7266 container.Add(new CuiLabel
7267 {
7268 Text =
7269 {
7270 Color = color,
7271 FontSize = size,
7272 Align = align,
7273 FadeIn = 1.0f,
7274 Text = text
7275 },
7276 RectTransform =
7277 {
7278 AnchorMin = aMin,
7279 AnchorMax = aMax
7280 }
7281 },
7282 panel);
7283 }
7284
7285 public static void CreateButton(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, string command, TextAnchor align = TextAnchor.MiddleCenter, string labelColor = "")
7286 {
7287 container.Add(new CuiButton
7288 {
7289 Button =
7290 {
7291 Color = color,
7292 Command = command,
7293 FadeIn = 1.0f
7294 },
7295 RectTransform =
7296 {
7297 AnchorMin = aMin,
7298 AnchorMax = aMax
7299 },
7300 Text =
7301 {
7302 Text = text,
7303 FontSize = size,
7304 Align = align,
7305 Color = labelColor
7306 }
7307 },
7308 panel);
7309 }
7310
7311 public static float[] CalcButtonPos(int number, float dMinOffset = 1f)
7312 {
7313 Vector2 position = new Vector2(0.03f, 0.889f);
7314 Vector2 dimensions = new Vector2(0.45f * dMinOffset, 0.1f);
7315 float offsetY = 0;
7316 float offsetX = 0;
7317 if (number >= 0 && number < 9)
7318 {
7319 offsetY = (-0.01f - dimensions.y) * number;
7320 }
7321 if (number > 8 && number < 19)
7322 {
7323 offsetY = (-0.01f - dimensions.y) * (number - 9);
7324 offsetX = (0.04f + dimensions.x) * 1;
7325 }
7326 Vector2 offset = new Vector2(offsetX, offsetY);
7327 Vector2 posMin = position + offset;
7328 Vector2 posMax = posMin + dimensions;
7329 return new[] { posMin.x, posMin.y, posMax.x, posMax.y };
7330 }
7331 }
7332
7333 #endregion
7334
7335 #region Config
7336
7337 private bool Changed;
7338 private static string szMatchChatCommand;
7339 private static string szDuelChatCommand;
7340 private string szQueueChatCommand;
7341 private readonly string duelistPerm = "duelist.dd";
7342 private readonly string duelistGroup = "duelist";
7343 private static float zoneRadius;
7344 private int deathTime;
7345 private static int immunityTime;
7346 private int zoneCounter;
7347 private static readonly List<string> _hpDuelingKits = new List<string>();
7348 private static readonly List<string> _lpDuelingKits = new List<string>();
7349 private static readonly List<string> hpDuelingKits = new List<string>();
7350 private static readonly List<string> lpDuelingKits = new List<string>();
7351 private readonly List<BetInfo> duelingBets = new List<BetInfo>();
7352 private bool recordStats = true;
7353 private int permsToGive = 3;
7354 private float maxIncline;
7355 private bool allowBetForfeit;
7356 private bool allowBetRefund;
7357 private bool allowBets;
7358 private bool putToSleep;
7359 private bool killNpc;
7360 private bool useAnnouncement;
7361 private bool autoSetup;
7362 private static bool broadcastDefeat;
7363 private double economicsMoney;
7364 private static double requiredDuelMoney;
7365 private int serverRewardsPoints;
7366 private float damageScaleAmount;
7367 private int zoneAmount;
7368 private static int playersPerZone;
7369 private bool visibleToAdmins;
7370 private float spDrawTime;
7371 private float spRemoveOneMaxDistance;
7372 private float spRemoveAllMaxDistance;
7373 private bool spAutoRemove;
7374 private bool avoidWaterSpawns;
7375 private bool useInvisibility;
7376 private int extraWallStacks;
7377 private bool useZoneWalls;
7378 private bool zoneUseWoodenWalls;
7379 private float buildingBlockExtensionRadius;
7380 private bool autoAllowAll;
7381 private bool useRandomSkins;
7382 private static float playerHealth;
7383 private bool dmFF;
7384 private static int minDeathmatchSize;
7385 private int maxDeathmatchSize;
7386 private bool autoEnable;
7387 private static ulong teamGoodShirt;
7388 private static ulong teamEvilShirt;
7389 private static string teamShirt;
7390 private static double teamEconomicsMoney;
7391 private static int teamServerRewardsPoints;
7392 private static float lesserKitChance;
7393 private bool tdmEnabled;
7394 private bool useLeastAmount;
7395 private bool tdmServerDeaths;
7396 private bool tdmMatchDeaths;
7397 private List<string> whitelistCommands = new List<string>();
7398 private bool useWhitelistCommands;
7399 private List<string> blacklistCommands = new List<string>();
7400 private bool useBlacklistCommands;
7401 private bool bypassNewmans;
7402 private bool saveRestoreEnabled;
7403 private List<DuelKitItem> respawnLoot = new List<DuelKitItem>();
7404 private bool respawnDeadDisconnect;
7405 private static bool sendDeadHome;
7406 private bool resetSeed;
7407 private bool noStability;
7408 private bool noMovement;
7409 private static bool requireTeamSize;
7410 private static int requiredMinSpawns;
7411 private static int requiredMaxSpawns;
7412 private bool guiAutoEnable;
7413 private static bool guiUseCursor;
7414 private string szUIChatCommand;
7415 private bool useWorkshopSkins;
7416 private bool respawnWalls;
7417 private bool allowPlayerDeaths;
7418 private bool morphBarricadesStoneWalls;
7419 private bool morphBarricadesWoodenWalls;
7420 private bool guiUseCloseButton;
7421 private string autoKitName;
7422 private static float guiAnnounceUITime;
7423 private static bool sendDefeatedHome;
7424 private bool sendHomeRequeue;
7425 private static bool sendHomeSpectatorWhenRematchTimesOut;
7426
7427 private List<object> RespawnLoot
7428 {
7429 get
7430 {
7431 return new List<object>
7432 {
7433 new DuelKitItem
7434 {
7435 shortname = "rock",
7436 amount = 1,
7437 skin = 0,
7438 container = "belt",
7439 slot = -1
7440 },
7441 new DuelKitItem
7442 {
7443 shortname = "torch",
7444 amount = 1,
7445 skin = 0,
7446 container = "belt",
7447 slot = -1
7448 }
7449 };
7450 }
7451 }
7452
7453 private List<object> BlacklistedCommands
7454 {
7455 get
7456 {
7457 return new List<object>
7458 {
7459 "/tp",
7460 "/remove",
7461 "/bank",
7462 "/shop",
7463 "/event",
7464 "/rw",
7465 "/home",
7466 "/trade"
7467 };
7468 }
7469 }
7470
7471 private List<object> WhitelistedCommands
7472 {
7473 get
7474 {
7475 return new List<object>
7476 {
7477 "/report",
7478 "/pm",
7479 "/r",
7480 "/help"
7481 };
7482 }
7483 }
7484
7485 private List<object> DefaultBets
7486 {
7487 get
7488 {
7489 return new List<object>
7490 {
7491 new Dictionary<string, object>
7492 {
7493 ["trigger"] = "stone",
7494 ["max"] = 50000,
7495 ["itemid"] = -2099697608
7496 },
7497 new Dictionary<string, object>
7498 {
7499 ["trigger"] = "sulfur",
7500 ["max"] = 50000,
7501 ["itemid"] = -1581843485
7502 },
7503 new Dictionary<string, object>
7504 {
7505 ["trigger"] = "fragment",
7506 ["max"] = 50000,
7507 ["itemid"] = 69511070
7508 },
7509 new Dictionary<string, object>
7510 {
7511 ["trigger"] = "charcoal",
7512 ["max"] = 50000,
7513 ["itemid"] = -1938052175
7514 },
7515 new Dictionary<string, object>
7516 {
7517 ["trigger"] = "gp",
7518 ["max"] = 25000,
7519 ["itemid"] = -265876753
7520 },
7521 new Dictionary<string, object>
7522 {
7523 ["trigger"] = "hqm",
7524 ["max"] = 1000,
7525 ["itemid"] = 317398316
7526 },
7527 new Dictionary<string, object>
7528 {
7529 ["trigger"] = "c4",
7530 ["max"] = 10,
7531 ["itemid"] = 1248356124
7532 },
7533 new Dictionary<string, object>
7534 {
7535 ["trigger"] = "rocket",
7536 ["max"] = 6,
7537 ["itemid"] = -742865266
7538 }
7539 };
7540 }
7541 }
7542
7543 private static Dictionary<string, List<DuelKitItem>> customKits = new Dictionary<string, List<DuelKitItem>>();
7544
7545 private Dictionary<string, object> DefaultKits
7546 {
7547 get
7548 {
7549 return new Dictionary<string, object>
7550 {
7551 ["Hunting Bow"] = new List<object>
7552 {
7553 new DuelKitItem
7554 {
7555 shortname = "bow.hunting",
7556 amount = 1,
7557 skin = 0,
7558 container = "belt",
7559 slot = -1
7560 },
7561 new DuelKitItem
7562 {
7563 shortname = "arrow.wooden",
7564 amount = 50,
7565 skin = 0,
7566 container = "belt",
7567 slot = -1
7568 },
7569 new DuelKitItem
7570 {
7571 shortname = "spear.stone",
7572 amount = 1,
7573 skin = 0,
7574 container = "belt",
7575 slot = -1
7576 },
7577 new DuelKitItem
7578 {
7579 shortname = "bandage",
7580 amount = 5,
7581 skin = 0,
7582 container = "belt",
7583 slot = -1
7584 },
7585 new DuelKitItem
7586 {
7587 shortname = "syringe.medical",
7588 amount = 5,
7589 skin = 0,
7590 container = "belt",
7591 slot = -1
7592 },
7593 new DuelKitItem
7594 {
7595 shortname = "largemedkit",
7596 amount = 5,
7597 skin = 0,
7598 container = "belt",
7599 slot = -1
7600 },
7601 new DuelKitItem
7602 {
7603 shortname = "burlap.gloves",
7604 amount = 1,
7605 skin = 0,
7606 container = "wear",
7607 slot = -1
7608 },
7609 new DuelKitItem
7610 {
7611 shortname = "burlap.headwrap",
7612 amount = 1,
7613 skin = 0,
7614 container = "wear",
7615 slot = -1
7616 },
7617 new DuelKitItem
7618 {
7619 shortname = "burlap.shirt",
7620 amount = 1,
7621 skin = 0,
7622 container = "wear",
7623 slot = -1
7624 },
7625 new DuelKitItem
7626 {
7627 shortname = "burlap.shoes",
7628 amount = 1,
7629 skin = 0,
7630 container = "wear",
7631 slot = -1
7632 },
7633 new DuelKitItem
7634 {
7635 shortname = "burlap.trousers",
7636 amount = 1,
7637 skin = 0,
7638 container = "wear",
7639 slot = -1
7640 }
7641 },
7642 ["Assault Rifle and Bolt Action Rifle"] = new List<object>
7643 {
7644 new DuelKitItem
7645 {
7646 shortname = "rifle.ak",
7647 amount = 1,
7648 skin = 0,
7649 container = "belt",
7650 slot = -1,
7651 ammo = "ammo.rifle",
7652 mods = new List<string>
7653 {
7654 "weapon.mod.lasersight"
7655 }
7656 },
7657 new DuelKitItem
7658 {
7659 shortname = "rifle.bolt",
7660 amount = 1,
7661 skin = 0,
7662 container = "belt",
7663 slot = -1,
7664 ammo = "ammo.rifle",
7665 mods = new List<string>
7666 {
7667 "weapon.mod.lasersight",
7668 "weapon.mod.small.scope"
7669 }
7670 },
7671 new DuelKitItem
7672 {
7673 shortname = "largemedkit",
7674 amount = 5,
7675 skin = 0,
7676 container = "belt",
7677 slot = -1
7678 },
7679 new DuelKitItem
7680 {
7681 shortname = "bandage",
7682 amount = 5,
7683 skin = 0,
7684 container = "belt",
7685 slot = -1
7686 },
7687 new DuelKitItem
7688 {
7689 shortname = "syringe.medical",
7690 amount = 5,
7691 skin = 0,
7692 container = "belt",
7693 slot = -1
7694 },
7695 new DuelKitItem
7696 {
7697 shortname = "bearmeat.cooked",
7698 amount = 10,
7699 skin = 0,
7700 container = "belt",
7701 slot = -1
7702 },
7703 new DuelKitItem
7704 {
7705 shortname = "hoodie",
7706 amount = 1,
7707 skin = 0,
7708 container = "wear",
7709 slot = -1
7710 },
7711 new DuelKitItem
7712 {
7713 shortname = "metal.facemask",
7714 amount = 1,
7715 skin = 0,
7716 container = "wear",
7717 slot = -1
7718 },
7719 new DuelKitItem
7720 {
7721 shortname = "metal.plate.torso",
7722 amount = 1,
7723 skin = 0,
7724 container = "wear",
7725 slot = -1
7726 },
7727 new DuelKitItem
7728 {
7729 shortname = "pants",
7730 amount = 1,
7731 skin = 0,
7732 container = "wear",
7733 slot = -1
7734 },
7735 new DuelKitItem
7736 {
7737 shortname = "burlap.gloves",
7738 amount = 1,
7739 skin = 0,
7740 container = "wear",
7741 slot = -1
7742 },
7743 new DuelKitItem
7744 {
7745 shortname = "shoes.boots",
7746 amount = 1,
7747 skin = 0,
7748 container = "wear",
7749 slot = -1
7750 },
7751 new DuelKitItem
7752 {
7753 shortname = "ammo.rifle",
7754 amount = 200,
7755 skin = 0,
7756 container = "main",
7757 slot = -1
7758 },
7759 new DuelKitItem
7760 {
7761 shortname = "weapon.mod.flashlight",
7762 amount = 1,
7763 skin = 0,
7764 container = "main",
7765 slot = -1
7766 },
7767 new DuelKitItem
7768 {
7769 shortname = "weapon.mod.small.scope",
7770 amount = 1,
7771 skin = 0,
7772 container = "main",
7773 slot = -1
7774 }
7775 },
7776 ["Semi-Automatic Pistol"] = new List<object>
7777 {
7778 new DuelKitItem
7779 {
7780 shortname = "pistol.semiauto",
7781 amount = 1,
7782 skin = 0,
7783 container = "belt",
7784 slot = -1,
7785 ammo = "ammo.pistol",
7786 mods = new List<string>
7787 {
7788 "weapon.mod.lasersight"
7789 }
7790 },
7791 new DuelKitItem
7792 {
7793 shortname = "largemedkit",
7794 amount = 5,
7795 skin = 0,
7796 container = "belt",
7797 slot = -1
7798 },
7799 new DuelKitItem
7800 {
7801 shortname = "bandage",
7802 amount = 5,
7803 skin = 0,
7804 container = "belt",
7805 slot = -1
7806 },
7807 new DuelKitItem
7808 {
7809 shortname = "syringe.medical",
7810 amount = 5,
7811 skin = 0,
7812 container = "belt",
7813 slot = -1
7814 },
7815 new DuelKitItem
7816 {
7817 shortname = "bearmeat.cooked",
7818 amount = 10,
7819 skin = 0,
7820 container = "belt",
7821 slot = -1
7822 },
7823 new DuelKitItem
7824 {
7825 shortname = "hoodie",
7826 amount = 1,
7827 skin = 0,
7828 container = "wear",
7829 slot = -1
7830 },
7831 new DuelKitItem
7832 {
7833 shortname = "metal.facemask",
7834 amount = 1,
7835 skin = 0,
7836 container = "wear",
7837 slot = -1
7838 },
7839 new DuelKitItem
7840 {
7841 shortname = "metal.plate.torso",
7842 amount = 1,
7843 skin = 0,
7844 container = "wear",
7845 slot = -1
7846 },
7847 new DuelKitItem
7848 {
7849 shortname = "pants",
7850 amount = 1,
7851 skin = 0,
7852 container = "wear",
7853 slot = -1
7854 },
7855 new DuelKitItem
7856 {
7857 shortname = "burlap.gloves",
7858 amount = 1,
7859 skin = 0,
7860 container = "wear",
7861 slot = -1
7862 },
7863 new DuelKitItem
7864 {
7865 shortname = "shoes.boots",
7866 amount = 1,
7867 skin = 0,
7868 container = "wear",
7869 slot = -1
7870 },
7871 new DuelKitItem
7872 {
7873 shortname = "ammo.pistol",
7874 amount = 200,
7875 skin = 0,
7876 container = "main",
7877 slot = -1
7878 },
7879 new DuelKitItem
7880 {
7881 shortname = "weapon.mod.flashlight",
7882 amount = 1,
7883 skin = 0,
7884 container = "main",
7885 slot = -1
7886 }
7887 },
7888 ["Pump Shotgun"] = new List<object>
7889 {
7890 new DuelKitItem
7891 {
7892 shortname = "shotgun.pump",
7893 amount = 1,
7894 skin = 0,
7895 container = "belt",
7896 slot = -1,
7897 ammo = "ammo.shotgun.slug",
7898 mods = new List<string>
7899 {
7900 "weapon.mod.lasersight"
7901 }
7902 },
7903 new DuelKitItem
7904 {
7905 shortname = "largemedkit",
7906 amount = 5,
7907 skin = 0,
7908 container = "belt",
7909 slot = -1
7910 },
7911 new DuelKitItem
7912 {
7913 shortname = "bandage",
7914 amount = 5,
7915 skin = 0,
7916 container = "belt",
7917 slot = -1
7918 },
7919 new DuelKitItem
7920 {
7921 shortname = "syringe.medical",
7922 amount = 5,
7923 skin = 0,
7924 container = "belt",
7925 slot = -1
7926 },
7927 new DuelKitItem
7928 {
7929 shortname = "bearmeat.cooked",
7930 amount = 10,
7931 skin = 0,
7932 container = "belt",
7933 slot = -1
7934 },
7935 new DuelKitItem
7936 {
7937 shortname = "hoodie",
7938 amount = 1,
7939 skin = 0,
7940 container = "wear",
7941 slot = -1
7942 },
7943 new DuelKitItem
7944 {
7945 shortname = "metal.facemask",
7946 amount = 1,
7947 skin = 0,
7948 container = "wear",
7949 slot = -1
7950 },
7951 new DuelKitItem
7952 {
7953 shortname = "metal.plate.torso",
7954 amount = 1,
7955 skin = 0,
7956 container = "wear",
7957 slot = -1
7958 },
7959 new DuelKitItem
7960 {
7961 shortname = "pants",
7962 amount = 1,
7963 skin = 0,
7964 container = "wear",
7965 slot = -1
7966 },
7967 new DuelKitItem
7968 {
7969 shortname = "burlap.gloves",
7970 amount = 1,
7971 skin = 0,
7972 container = "wear",
7973 slot = -1
7974 },
7975 new DuelKitItem
7976 {
7977 shortname = "shoes.boots",
7978 amount = 1,
7979 skin = 0,
7980 container = "wear",
7981 slot = -1
7982 },
7983 new DuelKitItem
7984 {
7985 shortname = "ammo.shotgun.slug",
7986 amount = 200,
7987 skin = 0,
7988 container = "main",
7989 slot = -1
7990 },
7991 new DuelKitItem
7992 {
7993 shortname = "weapon.mod.flashlight",
7994 amount = 1,
7995 skin = 0,
7996 container = "main",
7997 slot = -1
7998 }
7999 }
8000 };
8001 }
8002 }
8003
8004 protected override void LoadDefaultMessages() // holy shit this took forever.
8005 {
8006 lang.RegisterMessages(new Dictionary<string, string>
8007 {
8008 ["Awards"] = "{0} ({1}) duels won {2}",
8009 ["Granted"] = "Granted {0} ({1}) permission {2} for group {3}",
8010 ["Logged"] = "Duelists have been logged to: {0}",
8011 ["Indestructible"] = "This object belongs to the server and is indestructible!",
8012 ["Building is blocked!"] = "<color=red>Building is blocked inside of dueling zones!</color>",
8013 ["TopAll"] = "[ <color=#ffff00>Top Duelists Of All Time ({0})</color> ]:",
8014 ["Top"] = "[ <color=#ffff00>Top Duelists ({0})</color> ]:",
8015 ["NoLongerQueued"] = "You are no longer in queue for a duel.",
8016 ["InQueueSuccess"] = "You are now in queue for a duel. You will teleport instantly when a match is available.",
8017 ["MustBeNaked"] = "<color=red>You must be naked before you can duel.</color>",
8018 ["AlreadyInADuel"] = "You cannot queue for a duel while already in a duel!",
8019 ["MustAllowDuels"] = "You must allow duels first! Type: <color=orange>/{0} allow</color>",
8020 ["DuelsDisabled"] = "Duels are disabled.",
8021 ["NoZoneExists"] = "No dueling zone exists.",
8022 ["Banned"] = "You are banned from duels.",
8023 ["FoundZone"] = "Took {0} tries ({1}ms) to get a dueling zone.",
8024 ["ImmunityFaded"] = "Your immunity has faded.",
8025 ["NotifyBetWon"] = "You have won your bet! To claim type <color=orange>/{0} claim</color>.",
8026 ["ConsoleBetWon"] = "{0} ({1}) won his bet against {2} ({3})!",
8027 ["DuelDeathMessage"] = "<color=silver><color=lime>{0}</color> (<color=lime>W</color>: <color=orange>{1}</color> / <color=red>L</color>: <color=orange>{2}</color>) has defeated <color=lime>{3}</color> (<color=lime>W</color>: <color=orange>{4}</color> / <color=red>L</color>: <color=orange>{5}</color>) in a duel with <color=green>{6}</color> health left.{7}</color>",
8028 ["BetWon"] = " Bet won: <color=lime>{0}</color> (<color=lime>{1}</color>)",
8029 ["ExecutionTime"] = "You have <color=red>{0} minutes</color> to win the duel before you are executed.",
8030 ["FailedZone"] = "Failed to create a dueling zone, please try again.",
8031 ["FailedSetup"] = "Failed to setup the zone, please try again.",
8032 ["FailedRaycast"] = "Look towards the ground, and try again.",
8033 ["BetPlaced"] = "Your bet {0} ({1}) has been placed.",
8034 ["BetForfeitSuffix"] = " Type <color=orange>/{0} bet forfeit</color> to forfeit your bet.",
8035 ["BetRefundSuffix"] = " Type <color=orange>/{0} bet refund</color> to refund your bet.",
8036 ["BetNotEnough"] = "Bet cancelled. You do not have enough to bet this amount!",
8037 ["BetZero"] = "Bet cancelled. You do not have this item in your inventory.",
8038 ["DuelAnnouncement"] = "Type <color=orange>/{duelChatCommand}</color> for information on the dueling system. See your standing on the leaderboard by using <color=orange>/{ladderCommand}</color>. Type <color=orange>/{queueCommand}</color> to enter the dueling queue now!",
8039 ["DuelAnnouncementBetsSuffix"] = " Feeling lucky? Use <color=orange>/{0} bet</color> to create a bet!",
8040 ["ZoneCreated"] = "Dueling zone created successfully.",
8041 ["RemovedZone"] = "Removed dueling zone.",
8042 ["RemovedBan"] = "Unbanned {0}",
8043 ["AddedBan"] = "Banned {0}",
8044 ["PlayerNotFound"] = "{0} not found. Try being more specific or use a steam id.",
8045 ["RequestTimedOut"] = "Request timed out to duel <color=lime>{0}</color>",
8046 ["RemovedFromQueueRequest"] = "You have been removed from the dueling queue since you have requested to duel another player.",
8047 ["RemovedFromDuel"] = "You have been removed from your duel.",
8048 ["BetsDoNotMatch"] = "Your bet {0} ({1}) does not match {2} ({3})",
8049 ["InvalidBet"] = "Invalid bet '{0}'",
8050 ["BetSyntax"] = "Syntax: /{0} bet <item> <amount> - resources must be refined",
8051 ["AvailableBets"] = "Available Bets:",
8052 ["MustHaveSameBet"] = "{0} is betting: {1} ({2}). You must have the same bet to duel this player.",
8053 ["NoBetsToRefund"] = "There are no bets to refund.",
8054 ["Disabled"] = "Disabled",
8055 ["HelpDuelBet"] = "<color=silver><color=orange>/{0} bet</color> - place a bet towards your next duel.</color>",
8056 ["HelpDuelAdmin"] = "<color=orange>Admin: /{0} on|off</color> - enable/disable duels",
8057 ["HelpDuelAdminRefundAll"] = "<color=orange>Admin: /{0} bet refundall</color> - refund all bets for all players",
8058 ["DuelsDisabledAlready"] = "Duels are already disabled!",
8059 ["DuelsNowDisabled"] = "Duels disabled. Sending duelers home.",
8060 ["DuelsEnabledAlready"] = "Duels are already enabled!",
8061 ["DuelsNowEnabled"] = "Duels enabled",
8062 ["NoBetsToClaim"] = "You have no bets to claim.",
8063 ["PlayerClaimedBet"] = "Claimed bet {0} ({1})",
8064 ["AllBetsClaimed"] = "You have claimed all of your bets.",
8065 ["DuelChatOff"] = "You will no longer see duel death messages.",
8066 ["DuelChatOn"] = "You will now see duel death messages.",
8067 ["PlayerRequestsOn"] = "Players may now request to duel you. You will be removed from this list if you do not duel.",
8068 ["PlayerRequestsOff"] = "Players may no longer request to duel you.",
8069 ["BlockedRequestsFrom"] = "Blocked duel requests from: <color=lime>{0}</color>",
8070 ["UnblockedRequestsFrom"] = "Removed block on duel requests from: <color=lime>{0}</color>",
8071 ["AlreadyBlocked"] = "You have already blocked players from requesting duels.",
8072 ["NoBetsConfigured"] = "No bets are configured.",
8073 ["RefundAllPlayerNotice"] = "Server administrator has refunded your bet: {0} ({1})",
8074 ["RefundAllAdminNotice"] = "Refunded {0} ({1}): {2} ({3})",
8075 ["BetsRemaining"] = "Bet items remaining in database: {0}",
8076 ["AllBetsRefunded"] = "All dueling bets refunded",
8077 ["CannotForfeit"] = "You cannot forfeit bets on this server.",
8078 ["CannotForfeitRequestDuel"] = "You cannot forfeit a bet while requesting a duel!",
8079 ["CannotForfeitInDuel"] = "You cannot forfeit a bet while dueling!",
8080 ["CannotRefundRequestDuel"] = "You cannot refund a bet while requesting a duel!",
8081 ["CannotRefundInDuel"] = "You cannot refund a bet while dueling!",
8082 ["BetForfeit"] = "You forfeit your bet!",
8083 ["NoBetToForfeit"] = "You do not have an active bet to forfeit.",
8084 ["NoBetToRefund"] = "You do not have an active bet to refund.",
8085 ["CannotRefund"] = "You cannot refund bets on this server.",
8086 ["BetRefunded"] = "You have refunded your bet.",
8087 ["AlreadyBetting"] = "You are already betting! Your bet: {0} ({1})",
8088 ["ToRefundUse"] = "To refund your bet, type: <color=orange>/{0} bet refund</color>",
8089 ["ToForfeitUse"] = "To forfeit your bet, type: <color=orange>/{0} bet forfeit</color>. Refunds are not allowed.",
8090 ["InvalidNumber"] = "Invalid number: {0}",
8091 ["MultiplesOnly"] = "Number must be a multiple of 500. ie: 500, 1000, 2000, 5000, 10000, 15000",
8092 ["NoRequestsReceived"] = "No players have requested a duel with you.",
8093 ["DuelCancelledFor"] = "<color=lime>{0}</color> has cancelled the duel!",
8094 ["NoPendingRequests"] = "You have no pending request to cancel.",
8095 ["DuelCancelledWith"] = "<color=lime>{0}</color> has cancelled the duel request.",
8096 ["DuelCancelComplete"] = "Duel request cancelled.",
8097 ["MustWaitToRequestAgain"] = "You must wait <color=red>{0} minute(s)</color> from the last time you requested a duel to request another.",
8098 ["AlreadyDueling"] = "You are already dueling another player!",
8099 ["CannotRequestThisPlayer"] = "You are not allowed to request duels with this player.",
8100 ["TargetAlreadyDueling"] = "<color=lime>{0}</color> is already dueling another player!",
8101 ["NotAllowedYet"] = "<color=lime>{0}</color> has not enabled duel requests yet. They must type <color=orange>/{1} allow</color>",
8102 ["MustWaitForAccept"] = "You have requested a duel with <color=lime>{0}</color> already. You must wait for this player to accept the duel.",
8103 ["PendingRequestAlready"] = "This player has a duel request pending already.",
8104 ["TargetHasNoBet"] = "You have an active bet going. <color=lime>{0}</color> must have the same bet to duel you.",
8105 ["YourBet"] = "Your bet: {0} ({1})",
8106 ["WoundedQueue"] = "You cannot duel while either player is wounded.",
8107 ["DuelMustBeNaked"] = "Duel cancelled: <color=lime>{0}</color> inventory is not empty.",
8108 ["LadderLife"] = "<color=#5A625B>Use <color=yellow>/{0} ladder life</color> to see all time stats</color>",
8109 ["EconomicsDeposit"] = "You have received <color=yellow>${0}</color>!",
8110 ["ServerRewardPoints"] = "You have received <color=yellow>{0} RP</color>!",
8111 ["DuelsMustBeEnabled"] = "Use '/{0} on' to enable dueling on the server.",
8112 ["DataSaved"] = "Data has been saved.",
8113 ["DuelsNowDisabledEmpty"] = "Duels disabled.",
8114 ["CannotTeleport"] = "You are not allowed to teleport from a dueling zone.",
8115 ["AllZonesFull"] = "All zones are currently full. Zones: {0}. Limit Per Zone: {1}",
8116 ["NoZoneFound"] = "No zone found. You must stand inside of the zone to remove it.",
8117 ["RemovedZoneAt"] = "Removed zone at {0}",
8118 ["CannotDuel"] = "You are not allowed to duel at the moment.",
8119 ["LeftZone"] = "<color=red>You were found outside of the dueling zone while dueling. Your items have been removed.</color>",
8120 ["SpawnAdd"] = "<color=orange>/{0} spawns add</color> - add a spawn point at the position you are looking at.",
8121 ["SpawnHere"] = "<color=orange>/{0} spawns here</color> - add a spawn point at your position.",
8122 ["SpawnRemove"] = "<color=orange>/{0} spawns remove</color> - removes the nearest spawn point within <color=orange>{1}m</color>.",
8123 ["SpawnRemoveAll"] = "<color=orange>/{0} spawns removeall</color> - remove all spawn points within <color=orange>{1}m</color>.",
8124 ["SpawnWipe"] = "<color=orange>/{0} spawns wipe</color> - wipe all spawn points.",
8125 ["SpawnWiped"] = "<color=red>{0}</color> spawns points wiped.",
8126 ["SpawnCount"] = "<color=green>{0}</color> spawn points in database.",
8127 ["SpawnNoneFound"] = "No custom spawn points found within <color=orange>{0}m</color>.",
8128 ["SpawnAdded"] = "Spawn point added at {0}",
8129 ["SpawnRemoved"] = "Removed <color=red>{0}</color> spawn(s)",
8130 ["SpawnExists"] = "This spawn point exists already.",
8131 ["SpawnNoneExist"] = "No spawn points exist.",
8132 ["ZoneExists"] = "A dueling zone already exists here.",
8133 ["ZoneLimit"] = "Zone limit reached ({0}). You must manually remove an existing zone before creating a new one.",
8134 ["CannotEventJoin"] = "You are not allowed to join this event while dueling.",
8135 ["KitDoesntExist"] = "This kit doesn't exist: {0}",
8136 ["KitSet"] = "Custom kit set to {0}. This kit will be used when both players have the same custom kit.",
8137 ["KitsNotConfigured"] = "No kits have been configured for dueling.",
8138 ["RemovedXWalls"] = "Removed {0} walls.",
8139 ["SupportCreated"] = "{0} new dueling zones were created, however the total amount was not met. Please lower the radius, increase Maximum Incline On Hills, or reload the plugin to try again.",
8140 ["SupportInvalidConfig"] = "Invalid zone radius detected in the configuration file for this map size. Please lower the radius, increase Maximum Incline On Hills, or reload the plugin to try again.",
8141 ["WallSyntax"] = "Use <color=orange>/{0} walls [radius] <wood|stone></color>, or stand inside of an existing area with walls and use <color=orange>/{0} walls</color> to remove them.",
8142 ["GeneratedWalls"] = "Generated {0} arena walls {1} high at {2} in {3}ms",
8143 ["ResetKit"] = "You are no longer using a custom kit.",
8144 ["HelpDuels"] = "<color=#183a0e><size=18>DUELIST ({0})</size></color><color=#5A625B>\nDuel other players.</color>",
8145 ["HelpAllow"] = "<color=#5A397A>/{0} allow</color><color=#5A625B> • Toggle requests for duels</color>",
8146 ["HelpBlock"] = "<color=#5A397A>/{0} block <name></color><color=#5A625B> • Toggle block requests for a player</color>",
8147 ["HelpChallenge"] = "<color=#5A397A>/{0} <name></color><color=#5A625B> • Challenge another player</color>",
8148 ["HelpAccept"] = "<color=#5A397A>/{0} accept</color><color=#5A625B> • Accept a challenge</color>",
8149 ["HelpCancel"] = "<color=#5A397A>/{0} cancel</color><color=#5A625B> • Cancel your duel request</color>",
8150 ["HelpQueue"] = "<color=#5A397A>/{0}</color><color=#5A625B> • Join duel queue</color>",
8151 ["HelpChat"] = "<color=#5A397A>/{0} chat</color><color=#5A625B> • Toggle duel death messages</color>",
8152 ["HelpLadder"] = "<color=#5A397A>/{0} ladder</color><color=#5A625B> • Show top 10 duelists</color>",
8153 ["HelpBet"] = "<color=#5A397A>/{0} bet</color><color=#5A625B> • Place a bet towards a duel</color>",
8154 ["TopFormat"] = "<color=#666666><color=#5A625B>{0}.</color> <color=#00FF00>{1}</color> (<color=#008000>W:{2}</color> • <color=#ff0000>L:{3} </color> • <color=#4c0000>WLR:{4}</color>)</color>",
8155 ["NowDueling"] = "<color=#ff0000>You are now dueling <color=#00FF00>{0}</color>!</color>",
8156 ["MoneyRequired"] = "Both players must be able to pay an entry fee of <color=#008000>${0}</color> to duel.",
8157 ["CannotShop"] = "You are not allowed to shop while dueling.",
8158 ["DuelRequestSent"] = "Sent request to duel <color=lime>{0}</color>. Request expires in 1 minute. Use <color=orange>/{1} cancel</color> to cancel this request.",
8159 ["DuelRequestReceived"] = "<color=lime>{0}</color> has requested a duel. You have 1 minute to type <color=orange>/{1} accept</color> to accept the duel, or use <color=orange>/{1} decline</color> to decline immediately.",
8160 ["MatchQueued"] = "You have entered the deathmatch queue. The match will start when a dueling zone becomes available.",
8161 ["MatchTeamed"] = "You are not allowed to do this while on a deathmatch team.",
8162 ["MatchNoMatchesExist"] = "No matches exist. Challenge a player by using <color=orange>/{0} name</color>",
8163 ["MatchStarted"] = "Your match is starting versus: <color=yellow>{0}</color>",
8164 ["MatchStartedAlready"] = "Your match has already started. You must wait for it to end.",
8165 ["MatchPlayerLeft"] = "You have removed yourself from your deathmatch team.",
8166 ["MatchCannotChallenge"] = "{0} is already in a match.",
8167 ["MatchCannotChallengeAgain"] = "You can only challenge one player at a time.",
8168 ["MatchRequested"] = "<color=lime>{0}</color> has requested a deathmatch. Use <color=orange>/{1} accept</color> to accept this challenge.",
8169 ["MatchRequestSent"] = "Match request sent to <color=lime>{0}</color>.",
8170 ["MatchNoneRequested"] = "No one has challenged you to a deathmatch yet.",
8171 ["MatchPlayerOffline"] = "The player challenging you is no longer online.",
8172 ["MatchSizeChanged"] = "Deathmatch changed to <color=yellow>{0}v{0}</color>.",
8173 ["MatchOpened"] = "Your deathmatch is now open for private invitation. Friends may use <color=orange>/{0} any</color>, and players may use <color=orange>/{0} {1}</color> to join your team. Use <color=orange>/{0} public</color> to toggle invitations as public or private.",
8174 ["MatchCancelled"] = "{0} has cancelled the deathmatch.",
8175 ["MatchNotAHost"] = "You must be a host of a deathmatch to use this command.",
8176 ["MatchDoesntExist"] = "You are not in a deathmatch. Challenge a player by using <color=orange>/{0} name</color>.",
8177 ["MatchSizeSyntax"] = "Invalid syntax, use /{0} size #",
8178 ["MatchTeamFull"] = "Team is full ({0} players)",
8179 ["MatchJoinedTeam"] = "{0} joined {1} ({2}/{3}). {4} ({5}/{3})",
8180 ["MatchNoPlayersLeft"] = "No players are left on the opposing team. Match cancelled.",
8181 ["MatchChallenge2"] = "<color=#5A397A>/{0} any</color><color=#5A625B> • Join any match where a friend is the host</color>",
8182 ["MatchChallenge3"] = "<color=#5A397A>/{0} <code></color><color=#5A625B> • Join a match with the provided code</color>",
8183 ["MatchAccept"] = "<color=#5A397A>/{0} accept</color><color=#5A625B> • Accept a challenge</color>",
8184 ["MatchCancel"] = "<color=#5A397A>/{0} cancel</color><color=#5A625B> • Cancel your match request</color>",
8185 ["MatchLeave"] = "<color=#5A397A>/{0} cancel</color><color=#5A625B> • Leave your match</color>",
8186 ["MatchSize"] = "<color=#5A397A>/{0} size #</color><color=#5A625B> • Set your match size ({1}v{1}) [Hosts Only]</color>",
8187 ["MatchKickBan"] = "<color=#5A397A>/{0} kickban id/name</color><color=#5A625B> • Kickban a player from the match [Host Only]</color>",
8188 ["MatchSetCode"] = "<color=#5A397A>/{0} setcode [code]</color><color=#5A625B> • Change or see your code [Host Only]</color>",
8189 ["MatchTogglePublic"] = "<color=#5A397A>/{0} public</color><color=#5A625B> • Toggle match as public or private invitation [Host Only]</color>",
8190 ["MatchDefeat"] = "<color=silver><color=lime>{0}</color> has defeated <color=lime>{1}</color> in a <color=yellow>{2}v{2}</color> deathmatch!</color>",
8191 ["MatchIsNotNaked"] = "Match cannot start because <color=lime>{0}</color> is not naked. Next queue check in 30 seconds.",
8192 ["MatchCannotBan"] = "You cannot ban this player, or this player is already banned.",
8193 ["MatchBannedUser"] = "You have banned <color=lime>{0}</color> from your team.",
8194 ["MatchPlayerNotFound"] = "<color=lime>{0}</color> is not on your team.",
8195 ["MatchCodeIs"] = "Your code is: {0}",
8196 ["InQueueList"] = "Players in the queue:",
8197 ["HelpTDM"] = "<color=#5A397A>/{0}</color><color=#5A625B> • Create a team deathmatch</color>",
8198 ["InMatchListGood"] = "Good Team: {0}",
8199 ["InMatchListEvil"] = "Evil Team: {0}",
8200 ["MatchNoTeamFoundCode"] = "No team could be found for you with the provided code: {0}",
8201 ["MatchNoTeamFoundAny"] = "No team could be found with a friend as the host. Use a code instead.",
8202 ["MatchPublic"] = "Your match is now open to the public.",
8203 ["MatchPrivate"] = "Your match is now private and requires a code, or to be a friend to join.",
8204 ["CannotBank"] = "You are not allowed to bank while dueling.",
8205 ["TargetMustBeNaked"] = "<color=red>The person you are challenging must be naked before you can challenge them.</color>",
8206 ["MatchKit"] = "<color=#5A397A>/{0} kit <name></color><color=#5A625B> • Changes the kit used [Host Only]</color>",
8207 ["MatchKitSet"] = "Kit set to: <color=yellow>{0}</color>",
8208 ["MatchChallenge0"] = "<color=#5A397A>/{0} <name> [kitname]</color><color=#5A625B> • Challenge another player and set the kit if specified</color>",
8209 ["MatchPlayerDefeated"] = "<color=silver><color=lime>{0}</color> was killed by <color=lime>{1}</color> using <color=red>{2}</color> (<color=red>{3}: {4}m</color>)</color>",
8210 ["CommandNotAllowed"] = "You are not allowed to use this command right now.",
8211 ["HelpKit"] = "<color=#5A397A>/{0} kit</color><color=#5A625B> • Pick a kit</color>",
8212 ["RemovedXWallsCustom"] = "Removed {0} walls due to the deletion of zones which exceed the Max Zone cap.",
8213 ["ZonesSetup"] = "Initialized {0} existing dueling zones.",
8214 ["ArenasSetup"] = "{0} existing arenas are now protected.",
8215 ["NoPendingRequests2"] = "You have no pending request to accept.",
8216 ["MatchNoLongerValid"] = "You cannot join this match anymore.",
8217 ["NoMatchesExistYet"] = "No matches exist yet.",
8218 ["UI_Accept"] = "Accept",
8219 ["UI_Decline"] = "Decline",
8220 ["UI_Kits"] = "Kits",
8221 ["UI_Public"] = "Public",
8222 ["UI_Queue"] = "Queue",
8223 ["UI_TDM"] = "TDM",
8224 ["UI_TeamSize"] = "Set Team Size: ",
8225 ["UI_Any"] = "Any",
8226 ["UI_Help"] = "<color=#5A397A>/{0}</color><color=#5A625B> • Show Duelist User Interface</color>",
8227 ["ResetSeed"] = "Stats for this seed have been reset.",
8228 ["RematchNone"] = "No rematches are available for you.",
8229 ["RematchNotify"] = "A rematch is available for {0} seconds. Click Ready to join, or type /{1} ready",
8230 ["UI_Ready"] = "Ready",
8231 ["RematchAccepted"] = "You have accepted the rematch.",
8232 ["RematchAcceptedAlready"] = "You have accepted the rematch already!",
8233 ["RematchTimedOut"] = "Your rematch timed out.",
8234 ["RematchFailed"] = "The rematch failed to start. Not all players were ready.",
8235 ["RematchFailed2"] = "The rematch failed to open. Not all players are available.",
8236 ["RematchAutoOn"] = "You will now automatically ready up for rematches.",
8237 ["RematchAutoOff"] = "You will no longer automatically ready up for rematches.",
8238 ["UI_Respawn"] = "Respawn",
8239 ["UI_Requeue"] = "Requeue",
8240 ["BeginSpectating"] = "You are now spectating.",
8241 ["EndSpectating"] = "You are no longer a spectator.",
8242 ["UI_ReadyOn"] = "<color=red>Ready On</color>",
8243 ["UI_ReadyOff"] = "Ready Off",
8244 ["SuicideBlock"] = "<color=red>You have suicided or disconnected in a duel and must wait up to 60 seconds to duel again.</color>",
8245 ["ZoneRenamed"] = "Zone renamed to {0}",
8246 ["ZoneNames"] = "<color=#183a0e>Zone Names ({0}):</color> {1}",
8247 ["ZoneRename"] = "/{0} rename <name>",
8248 ["ZoneSet"] = "Zone set to: {0}",
8249 ["Prefix"] = "[ <color=#406B35>Duelist</color> ]: ",
8250 }, this);
8251 }
8252
8253 public List<string> VerifiedKits
8254 {
8255 get
8256 {
8257 VerifyKits();
8258
8259 var list = new List<string>();
8260
8261 if (hpDuelingKits.Count > 0)
8262 list.AddRange(hpDuelingKits);
8263
8264 if (lpDuelingKits.Count > 0)
8265 list.AddRange(lpDuelingKits);
8266
8267 if (list.Count == 0 && customKits.Count > 0)
8268 list.AddRange(customKits.Select(kvp => kvp.Key));
8269
8270 list.Sort();
8271 return list;
8272 }
8273 }
8274
8275 public string GetVerifiedKit(string kit)
8276 {
8277 string kits = string.Join(", ", VerifiedKits.ToArray());
8278
8279 if (!string.IsNullOrEmpty(kits))
8280 {
8281 if (customKits.Any(entry => entry.Key.Equals(kit, StringComparison.CurrentCultureIgnoreCase)))
8282 {
8283 return customKits.First(entry => entry.Key.Equals(kit, StringComparison.CurrentCultureIgnoreCase)).Key;
8284 }
8285 if (hpDuelingKits.Any(entry => entry.Equals(kit, StringComparison.CurrentCultureIgnoreCase)))
8286 {
8287 return hpDuelingKits.First(entry => entry.Equals(kit, StringComparison.CurrentCultureIgnoreCase));
8288 }
8289 if (lpDuelingKits.Any(entry => entry.Equals(kit, StringComparison.CurrentCultureIgnoreCase)))
8290 {
8291 return lpDuelingKits.First(entry => entry.Equals(kit, StringComparison.CurrentCultureIgnoreCase));
8292 }
8293 }
8294
8295 return null;
8296 }
8297
8298 private void LoadVariables()
8299 {
8300 putToSleep = Convert.ToBoolean(GetConfig("Animals", "Put To Sleep", true));
8301 killNpc = Convert.ToBoolean(GetConfig("Animals", "Die Instantly", false));
8302
8303 autoSetup = Convert.ToBoolean(GetConfig("Settings", "Auto Create Dueling Zone If Zone Does Not Exist", false));
8304 immunityTime = Convert.ToInt32(GetConfig("Settings", "Immunity Time", 10));
8305 deathTime = Convert.ToInt32(GetConfig("Settings", "Time To Duel In Minutes Before Death", 10));
8306 szDuelChatCommand = Convert.ToString(GetConfig("Settings", "Duel Command Name", "duel"));
8307 szQueueChatCommand = Convert.ToString(GetConfig("Settings", "Queue Command Name", "queue"));
8308 useAnnouncement = Convert.ToBoolean(GetConfig("Settings", "Allow Announcement", true));
8309 broadcastDefeat = Convert.ToBoolean(GetConfig("Settings", "Broadcast Defeat To All Players", true));
8310 damageScaleAmount = Convert.ToSingle(GetConfig("Settings", "Scale Damage Percent", 1f));
8311 useInvisibility = Convert.ToBoolean(GetConfig("Settings", "Use Invisibility", true));
8312 buildingBlockExtensionRadius = Convert.ToSingle(GetConfig("Settings", "Building Block Extension Radius", 30f));
8313 autoAllowAll = Convert.ToBoolean(GetConfig("Settings", "Disable Requirement To Allow Duels", false));
8314 useRandomSkins = Convert.ToBoolean(GetConfig("Settings", "Use Random Skins", true));
8315 playerHealth = Convert.ToSingle(GetConfig("Settings", "Player Health After Duel [0 = disabled]", 100f));
8316 autoEnable = Convert.ToBoolean(GetConfig("Settings", "Auto Enable Dueling If Zone(s) Exist", false));
8317 bypassNewmans = Convert.ToBoolean(GetConfig("Settings", "Bypass Naked Check And Strip Items Anyway", false));
8318 respawnDeadDisconnect = Convert.ToBoolean(GetConfig("Settings", "Respawn Dead Players On Disconnect", true));
8319 resetSeed = Convert.ToBoolean(GetConfig("Settings", "Reset Temporary Ladder Each Wipe", true));
8320 noStability = Convert.ToBoolean(GetConfig("Settings", "No Stability On Structures", true));
8321 noMovement = Convert.ToBoolean(GetConfig("Settings", "No Movement During Immunity", false));
8322 respawnWalls = Convert.ToBoolean(GetConfig("Settings", "Respawn Zone Walls On Death", false));
8323
8324 allowBetForfeit = Convert.ToBoolean(GetConfig("Betting", "Allow Bets To Be Forfeit", true));
8325 allowBetRefund = Convert.ToBoolean(GetConfig("Betting", "Allow Bets To Be Refunded", false));
8326 allowBets = Convert.ToBoolean(GetConfig("Betting", "Enabled", false));
8327
8328 zoneRadius = Convert.ToSingle(GetConfig("Zone", "Zone Radius (Min: 50, Max: 300)", 50f));
8329 zoneCounter = Convert.ToInt32(GetConfig("Zone", "Create New Zone Every X Duels [0 = disabled]", 10));
8330 maxIncline = Convert.ToSingle(GetConfig("Zone", "Maximum Incline On Hills", 40f));
8331 zoneAmount = Convert.ToInt32(GetConfig("Zone", "Max Zones [Min 1]", 1));
8332 playersPerZone = Convert.ToInt32(GetConfig("Zone", "Players Per Zone [Multiple Of 2]", 10));
8333 visibleToAdmins = Convert.ToBoolean(GetConfig("Zone", "Players Visible To Admins", true));
8334 avoidWaterSpawns = Convert.ToBoolean(GetConfig("Zone", "Avoid Creating Automatic Spawn Points In Water", true));
8335 extraWallStacks = Convert.ToInt32(GetConfig("Zone", "Extra High External Wall Stacks", 2));
8336 useZoneWalls = Convert.ToBoolean(GetConfig("Zone", "Use Arena Wall Generation", true));
8337 zoneUseWoodenWalls = Convert.ToBoolean(GetConfig("Zone", "Use Wooden Walls", false));
8338 useLeastAmount = Convert.ToBoolean(GetConfig("Zone", "Create Least Amount Of Walls", false));
8339
8340 foreach (var itemDef in ItemManager.GetItemDefinitions().ToList())
8341 {
8342 var mod = itemDef.GetComponent<ItemModDeployable>();
8343
8344 if (mod != null)
8345 {
8346 bool externalWall = mod.entityPrefab.resourcePath.Contains("external") && mod.entityPrefab.resourcePath.Contains("wall");
8347 bool barricade = mod.entityPrefab.resourcePath.Contains("barricade");
8348
8349 if (externalWall || barricade)
8350 {
8351 bool value = Convert.ToBoolean(GetConfig("Deployables", string.Format("Allow {0}", itemDef.displayName.translated), false));
8352
8353 if (!value)
8354 continue;
8355
8356 deployables[itemDef.displayName.translated] = value;
8357 prefabs[mod.entityPrefab.resourcePath] = itemDef.displayName.translated;
8358 }
8359 }
8360 }
8361
8362 morphBarricadesStoneWalls = Convert.ToBoolean(GetConfig("Deployables", "Morph Barricades Into High External Stone Walls", false));
8363 morphBarricadesWoodenWalls = Convert.ToBoolean(GetConfig("Deployables", "Morph Barricades Into High External Wooden Walls", false));
8364
8365 if (buildingBlockExtensionRadius < 20f)
8366 buildingBlockExtensionRadius = 20f;
8367
8368 if (zoneAmount < 1)
8369 zoneAmount = 1;
8370
8371 if (playersPerZone < 2)
8372 playersPerZone = 2;
8373 else if (playersPerZone % 2 != 0)
8374 playersPerZone++;
8375
8376 recordStats = Convert.ToBoolean(GetConfig("Ranked Ladder", "Enabled", true));
8377 permsToGive = Convert.ToInt32(GetConfig("Ranked Ladder", "Award Top X Players On Wipe", 3));
8378
8379 if (!permission.PermissionExists(duelistPerm)) // prevent warning
8380 permission.RegisterPermission(duelistPerm, this);
8381
8382 permission.CreateGroup(duelistGroup, duelistGroup, 0);
8383 permission.GrantGroupPermission(duelistGroup, duelistPerm, this);
8384
8385 var kits = GetConfig("Settings", "Kits", new List<object>
8386 {
8387 "kit_1",
8388 "kit_2",
8389 "kit_3"
8390 }) as List<object>;
8391
8392 if (kits != null && kits.Count > 0)
8393 {
8394 foreach (string kit in kits.Cast<string>().ToList())
8395 {
8396 if (!string.IsNullOrEmpty(kit) && !hpDuelingKits.Contains(kit))
8397 {
8398 hpDuelingKits.Add(kit); // 0.1.14 fix
8399 _hpDuelingKits.Add(kit); // 0.1.17 clone for Least Used Chance compatibility
8400 }
8401 }
8402 }
8403
8404 lesserKitChance = Convert.ToSingle(GetConfig("Settings", "Kits Least Used Chance", 0.25f));
8405 var lesserKits = GetConfig("Settings", "Kits Least Used", new List<object>
8406 {
8407 "kit_4",
8408 "kit_5",
8409 "kit_6"
8410 }) as List<object>;
8411
8412 if (lesserKits != null && lesserKits.Count > 0)
8413 {
8414 foreach (string kit in lesserKits.Cast<string>().ToList())
8415 {
8416 if (!string.IsNullOrEmpty(kit) && !lpDuelingKits.Contains(kit))
8417 {
8418 lpDuelingKits.Add(kit); // 0.1.16
8419 _lpDuelingKits.Add(kit); // 0.1.17 clone for Least Used Chance compatibility
8420 }
8421 }
8422 }
8423
8424 useWorkshopSkins = Convert.ToBoolean(GetConfig("Custom Kits", "Use Workshop Skins", true));
8425 var defaultKits = GetConfig("Custom Kits", "Kits", DefaultKits) as Dictionary<string, object>;
8426
8427 SetupCustomKits(defaultKits, ref customKits);
8428
8429 autoKitName = Convert.ToString(GetConfig("Respawn", "Give Kit If Respawn Items Are Empty", "autokit"));
8430 var defaultRespawn = GetConfig("Respawn", "Items", RespawnLoot) as List<object>;
8431
8432 SetupRespawnItems(defaultRespawn, ref respawnLoot);
8433
8434 var bets = GetConfig("Betting", "Bets", DefaultBets) as List<object>;
8435
8436 if (bets != null && bets.Count > 0)
8437 {
8438 foreach (var bet in bets)
8439 {
8440 if (bet is Dictionary<string, object>)
8441 {
8442 var dict = bet as Dictionary<string, object>;
8443
8444 if (dict.ContainsKey("trigger") && dict["trigger"] != null && dict["trigger"].ToString().Length > 0)
8445 {
8446 int max;
8447 if (dict.ContainsKey("max") && dict["max"] != null && int.TryParse(dict["max"].ToString(), out max) && max > 0)
8448 {
8449 int itemid;
8450 if (dict.ContainsKey("itemid") && dict["itemid"] != null && int.TryParse(dict["itemid"].ToString(), out itemid))
8451 {
8452 duelingBets.Add(new BetInfo
8453 {
8454 trigger = dict["trigger"].ToString(),
8455 itemid = itemid,
8456 max = max
8457 }); // 0.1.5 fix - remove itemlist find as it is null when server starts up and new config is created
8458 }
8459 }
8460 }
8461 }
8462 }
8463 }
8464
8465 if (immunityTime < 0)
8466 immunityTime = 0;
8467
8468 if (zoneRadius < 50f)
8469 zoneRadius = 50f;
8470 else if (zoneRadius > 300f)
8471 zoneRadius = 300f;
8472
8473 useBlacklistCommands = Convert.ToBoolean(GetConfig("Settings", "Blacklist Commands", false));
8474 blacklistCommands = (GetConfig("Settings", "Blacklisted Chat Commands", BlacklistedCommands) as List<object>).Where(o => o != null && o.ToString().Length > 0).Select(o => o.ToString().ToLower()).ToList();
8475 useWhitelistCommands = Convert.ToBoolean(GetConfig("Settings", "Whitelist Commands", false));
8476 whitelistCommands = (GetConfig("Settings", "Whitelisted Chat Commands", WhitelistedCommands) as List<object>).Where(o => o != null && o.ToString().Length > 0).Select(o => o.ToString().ToLower()).ToList();
8477
8478 if (!string.IsNullOrEmpty(szDuelChatCommand))
8479 {
8480 cmd.AddChatCommand(szDuelChatCommand, this, cmdDuel);
8481 cmd.AddConsoleCommand(szDuelChatCommand, this, nameof(ccmdDuel));
8482 whitelistCommands.Add("/" + szDuelChatCommand.ToLower());
8483 }
8484
8485 if (!string.IsNullOrEmpty(szQueueChatCommand))
8486 cmd.AddChatCommand(szQueueChatCommand, this, cmdQueue);
8487
8488 economicsMoney = Convert.ToDouble(GetConfig("Rewards", "Economics Money [0 = disabled]", 0.0));
8489 serverRewardsPoints = Convert.ToInt32(GetConfig("Rewards", "ServerRewards Points [0 = disabled]", 0));
8490 requiredDuelMoney = Convert.ToDouble(GetConfig("Rewards", "Required Money To Duel", 0.0));
8491
8492 spDrawTime = Convert.ToSingle(GetConfig("Spawns", "Draw Time", 30f));
8493 spRemoveOneMaxDistance = Convert.ToSingle(GetConfig("Spawns", "Remove Distance", 10f));
8494 spRemoveAllMaxDistance = Convert.ToSingle(GetConfig("Spawns", "Remove All Distance", zoneRadius));
8495 //spRemoveInRange = Convert.ToBoolean(GetConfig("Spawns", "Remove In Duel Zone Only", false));
8496 spAutoRemove = Convert.ToBoolean(GetConfig("Spawns", "Auto Remove On Zone Removal", false));
8497
8498 dmFF = Convert.ToBoolean(GetConfig("Deathmatch", "Friendly Fire", true));
8499 minDeathmatchSize = Convert.ToInt32(GetConfig("Deathmatch", "Min Team Size", 2));
8500 maxDeathmatchSize = Convert.ToInt32(GetConfig("Deathmatch", "Max Team Size", 5));
8501 teamEvilShirt = Convert.ToUInt64(GetConfig("Deathmatch", "Evil Shirt Skin", 14177));
8502 teamGoodShirt = Convert.ToUInt64(GetConfig("Deathmatch", "Good Shirt Skin", 101));
8503 teamShirt = Convert.ToString(GetConfig("Deathmatch", "Shirt Shortname", "tshirt"));
8504 teamEconomicsMoney = Convert.ToDouble(GetConfig("Deathmatch", "Economics Money [0 = disabled]", 0.0));
8505 teamServerRewardsPoints = Convert.ToInt32(GetConfig("Deathmatch", "ServerRewards Points [0 = disabled]", 0));
8506 tdmEnabled = Convert.ToBoolean(GetConfig("Deathmatch", "Enabled", true));
8507 szMatchChatCommand = Convert.ToString(GetConfig("Deathmatch", "Chat Command", "tdm"));
8508 tdmServerDeaths = Convert.ToBoolean(GetConfig("Deathmatch", "Announce Deaths To Server", false));
8509 tdmMatchDeaths = Convert.ToBoolean(GetConfig("Deathmatch", "Announce Deaths To Match", true));
8510
8511 requireTeamSize = Convert.ToBoolean(GetConfig("Advanced Options", "Require TDM Minimum Spawn Points To Be Equal Or Greater To The Number Of Players Joining", false));
8512 requiredMinSpawns = Convert.ToInt32(GetConfig("Advanced Options", "Require 1v1 Minimum Spawn Points To Be Equal Or Greater Than X", 2));
8513 requiredMaxSpawns = Convert.ToInt32(GetConfig("Advanced Options", "Require 1v1 Maximum Spawn Points To Be Less Than Or Equal To X", 200));
8514 allowPlayerDeaths = Convert.ToBoolean(GetConfig("Advanced Options", "Let Players Die Normally", false));
8515 sendDeadHome = Convert.ToBoolean(GetConfig("Advanced Options", "Send Dead Players Back Home", true));
8516 sendDefeatedHome = Convert.ToBoolean(GetConfig("Advanced Options", "Send Defeated Players Back Home", false));
8517
8518 if (requiredMinSpawns < 2)
8519 requiredMinSpawns = 2;
8520
8521 if (requiredMaxSpawns < 2)
8522 requiredMaxSpawns = 2;
8523
8524 if (tdmEnabled && !string.IsNullOrEmpty(szMatchChatCommand))
8525 {
8526 cmd.AddChatCommand(szMatchChatCommand, this, cmdTDM);
8527 whitelistCommands.Add("/" + szMatchChatCommand.ToLower());
8528 }
8529
8530 guiAutoEnable = Convert.ToBoolean(GetConfig("User Interface", "Auto Enable GUI For Players", false));
8531 szUIChatCommand = Convert.ToString(GetConfig("User Interface", "Chat Command", "dui"));
8532 guiUseCursor = Convert.ToBoolean(GetConfig("User Interface", "Use Cursor", false));
8533 guiUseCloseButton = Convert.ToBoolean(GetConfig("User Interface", "Show Close Button (X)", true));
8534 guiAnnounceUITime = Convert.ToSingle(GetConfig("User Interface", "Show Defeat Message UI For X Seconds", 7.5f));
8535
8536 sendHomeRequeue = Convert.ToBoolean(GetConfig("User Interface", "Send Spectators Home First When Clicking Requeue", false));
8537 sendHomeSpectatorWhenRematchTimesOut = Convert.ToBoolean(GetConfig("Spectators", "Send Home If Rematch Times Out", false));
8538
8539 if (guiAnnounceUITime < 1f)
8540 guiAnnounceUITime = 1f;
8541
8542 if (!string.IsNullOrEmpty(szUIChatCommand))
8543 {
8544 cmd.AddChatCommand(szUIChatCommand, this, cmdDUI);
8545 cmd.AddConsoleCommand(szUIChatCommand, this, nameof(ccmdDUI));
8546 }
8547
8548 if (Changed)
8549 {
8550 SaveConfig();
8551 Changed = false;
8552 }
8553 }
8554
8555 private void SetupRespawnItems(List<object> list, ref List<DuelKitItem> source)
8556 {
8557 if (list == null || list.Count == 0 || !(list is List<object>))
8558 {
8559 return;
8560 }
8561
8562 foreach (var entry in list)
8563 {
8564 var items = entry as Dictionary<string, object>; // DuelKitItem
8565 string container = null;
8566 string shortname = null;
8567 string ammo = null;
8568 int amount = int.MinValue;
8569 ulong skin = ulong.MaxValue;
8570 int slot = int.MinValue;
8571 var mods = new List<string>();
8572
8573 if (items != null && items.Count > 0)
8574 {
8575 foreach (var kvp in items) // DuelKitItem
8576 {
8577 switch (kvp.Key)
8578 {
8579 case "container":
8580 {
8581 if (kvp.Value != null && kvp.Value.ToString().Length > 0)
8582 container = kvp.Value.ToString();
8583 }
8584 break;
8585 case "shortname":
8586 {
8587 if (kvp.Value != null && kvp.Value.ToString().Length > 0)
8588 shortname = kvp.Value.ToString();
8589 }
8590 break;
8591 case "amount":
8592 {
8593 int num;
8594 if (int.TryParse(kvp.Value.ToString(), out num))
8595 amount = num;
8596 }
8597 break;
8598 case "skin":
8599 {
8600 ulong num;
8601 if (ulong.TryParse(kvp.Value.ToString(), out num))
8602 skin = num;
8603 }
8604 break;
8605 case "slot":
8606 {
8607 int num;
8608 if (int.TryParse(kvp.Value.ToString(), out num))
8609 slot = num;
8610 }
8611 break;
8612 case "ammo":
8613 {
8614 if (kvp.Value != null && kvp.Value.ToString().Length > 0)
8615 ammo = kvp.Value.ToString();
8616 }
8617 break;
8618 default:
8619 {
8620 if (kvp.Value is List<object>)
8621 {
8622 var _mods = kvp.Value as List<object>;
8623
8624 foreach (var mod in _mods)
8625 {
8626 if (mod != null && mod.ToString().Length > 0)
8627 {
8628 if (!mods.Contains(mod.ToString()))
8629 mods.Add(mod.ToString());
8630 }
8631 }
8632 }
8633 }
8634 break;
8635 }
8636
8637 }
8638 }
8639
8640 if (shortname == null || container == null || amount == int.MinValue || skin == ulong.MaxValue || slot == int.MinValue)
8641 {
8642 continue; // missing a key. invalid item
8643 }
8644
8645 source.Add(new DuelKitItem
8646 {
8647 amount = amount,
8648 container = container,
8649 shortname = shortname,
8650 skin = skin,
8651 slot = slot,
8652 ammo = ammo,
8653 mods = mods.Count > 0 ? mods : null
8654 });
8655 }
8656 }
8657
8658 private void SetupCustomKits(Dictionary<string, object> dict, ref Dictionary<string, List<DuelKitItem>> source)
8659 {
8660 if (dict == null && dict.Count == 0)
8661 {
8662 return;
8663 }
8664
8665 foreach (var kit in dict)
8666 {
8667 if (source.ContainsKey(kit.Key))
8668 source.Remove(kit.Key);
8669
8670 source.Add(kit.Key, new List<DuelKitItem>());
8671
8672 if (kit.Value is List<object>) // list of DuelKitItem
8673 {
8674 var objects = kit.Value as List<object>;
8675
8676 if (objects != null && objects.Count > 0)
8677 {
8678 foreach (var entry in objects)
8679 {
8680 if (entry is Dictionary<string, object>)
8681 {
8682 var items = entry as Dictionary<string, object>; // DuelKitItem
8683
8684 if (items == null || items.Count == 0)
8685 continue;
8686
8687 string container = null;
8688 string shortname = null;
8689 string ammo = null;
8690 int amount = int.MinValue;
8691 ulong skin = ulong.MaxValue;
8692 int slot = int.MinValue;
8693 var mods = new List<string>();
8694
8695 foreach (var kvp in items)
8696 {
8697 switch (kvp.Key)
8698 {
8699 case "container":
8700 {
8701 if (kvp.Value != null && kvp.Value.ToString().Length > 0)
8702 container = kvp.Value.ToString();
8703 }
8704 break;
8705 case "shortname":
8706 {
8707 if (kvp.Value != null && kvp.Value.ToString().Length > 0)
8708 shortname = kvp.Value.ToString();
8709 }
8710 break;
8711 case "amount":
8712 {
8713 int num;
8714 if (int.TryParse(kvp.Value.ToString(), out num))
8715 amount = num;
8716 }
8717 break;
8718 case "skin":
8719 {
8720 ulong num;
8721 if (ulong.TryParse(kvp.Value.ToString(), out num))
8722 skin = num;
8723 }
8724 break;
8725 case "slot":
8726 {
8727 int num;
8728 if (int.TryParse(kvp.Value.ToString(), out num))
8729 slot = num;
8730 }
8731 break;
8732 case "ammo":
8733 {
8734 if (kvp.Value != null && kvp.Value.ToString().Length > 0)
8735 ammo = kvp.Value.ToString();
8736 }
8737 break;
8738 default:
8739 {
8740 if (kvp.Value is List<object>)
8741 {
8742 var _mods = kvp.Value as List<object>;
8743
8744 foreach (var mod in _mods)
8745 {
8746 if (mod != null && mod.ToString().Length > 0)
8747 {
8748 if (!mods.Contains(mod.ToString()))
8749 mods.Add(mod.ToString());
8750 }
8751 }
8752 }
8753 }
8754 break;
8755 }
8756 }
8757
8758 if (shortname == null || container == null || amount == int.MinValue || skin == ulong.MaxValue || slot == int.MinValue)
8759 {
8760 source.Remove(kit.Key);
8761 continue; // missing a key. invalid item
8762 }
8763
8764 source[kit.Key].Add(new DuelKitItem
8765 {
8766 amount = amount,
8767 container = container,
8768 shortname = shortname,
8769 skin = skin,
8770 slot = slot,
8771 ammo = ammo,
8772 mods = mods.Count > 0 ? mods : null
8773 });
8774 }
8775 }
8776 }
8777 }
8778 }
8779 }
8780
8781 protected override void LoadDefaultConfig()
8782 {
8783 PrintWarning("Creating a new configuration file");
8784 Config.Clear();
8785 LoadVariables();
8786 }
8787
8788 private object GetConfig(string menu, string datavalue, object defaultValue)
8789 {
8790 var data = Config[menu] as Dictionary<string, object>;
8791 if (data == null)
8792 {
8793 data = new Dictionary<string, object>();
8794 Config[menu] = data;
8795 Changed = true;
8796 }
8797 object value;
8798 if (!data.TryGetValue(datavalue, out value))
8799 {
8800 value = defaultValue;
8801 data[datavalue] = value;
8802 Changed = true;
8803 }
8804 return value;
8805 }
8806
8807 private Dictionary<string, string> hexColors = new Dictionary<string, string>
8808 {
8809 ["<color=blue>"] = "<color=#0000FF>",
8810 ["<color=red>"] = "<color=#FF0000>",
8811 ["<color=yellow>"] = "<color=#FFFF00>",
8812 ["<color=lightblue>"] = "<color=#ADD8E6>",
8813 ["<color=orange>"] = "<color=#FFA500>",
8814 ["<color=silver>"] = "<color=#C0C0C0>",
8815 ["<color=magenta>"] = "<color=#FF00FF>",
8816 ["<color=green>"] = "<color=#008000>",
8817 ["<color=lime>"] = "<color=#00FF00>",
8818 };
8819
8820 private string msg(string key, string id = null, params object[] args)
8821 {
8822 var sb = new System.Text.StringBuilder(id == null ? RemoveFormatting(lang.GetMessage(key, this, id)) : lang.GetMessage(key, this, id));
8823
8824 foreach (var entry in hexColors)
8825 {
8826 if (sb.ToString().Contains(entry.Key))
8827 {
8828 sb.Replace(entry.Key, entry.Value);
8829 }
8830 }
8831
8832 return args.Length > 0 ? string.Format(sb.ToString(), args) : sb.ToString();
8833 }
8834
8835 public string RemoveFormatting(string source)
8836 {
8837 return source.Contains(">") ? Regex.Replace(source, "<.*?>", string.Empty) : source;
8838 }
8839
8840 #endregion
8841 }
8842}