· 6 years ago · Oct 30, 2019, 10:18 AM
1// Requires: EMInterface
2
3using System;
4using System.Collections.Generic;
5using System.Linq;
6using System.Globalization;
7using System.Reflection;
8using UnityEngine;
9using Oxide.Core;
10using Oxide.Core.Configuration;
11using Oxide.Core.Plugins;
12using Oxide.Game.Rust.Cui;
13using Newtonsoft.Json.Linq;
14using Rust;
15using Network;
16
17namespace Oxide.Plugins
18{
19 [Info("Event Manager", "Reneb / k1lly0u", "3.0.76", ResourceId = 740)]
20 [Description("A versitile arena event plugin")]
21 class EventManager : RustPlugin
22 {
23 #region Fields
24 [PluginReference] EMInterface EMInterface;
25 [PluginReference] Plugin Economics;
26 [PluginReference] Plugin FriendlyFire;
27 [PluginReference] Plugin Kits;
28 [PluginReference] Plugin ServerRewards;
29 [PluginReference] Plugin Spawns;
30 [PluginReference] Plugin ZoneManager;
31
32 MethodInfo killLifestory;
33
34 private static EventManager ins;
35 private static List<MessageData> popupQueue;
36 private static List<BasePlayer> unnetworkedPlayers;
37
38 private Dictionary<ulong, Timer> KillTimers;
39 private RestorationManager Restoration;
40
41 private GameTimer _GameTimer;
42 private WaveTimer _WaveTimer;
43 private DynamicConfigFile P_Stats;
44 private DynamicConfigFile RestoreData;
45
46 private bool _GodEnabled;
47 private bool debugEnabled = false;
48
49 private bool ResetNoCorpse;
50
51 private static string ScoreUI = "EMUI_Scoreboard";
52 private static string ClockUI = "EMUI_Timer";
53 private static string DeathUI = "EMUI_Death";
54 private static string TimerUI = "EMUI_DeathTimer";
55
56 private FieldInfo spectateFilter;
57
58 public Dictionary<ulong, PlayerStatistics> StatsCache;
59 public List<EMInterface.AEConfig> ValidAutoEvents;
60 public Dictionary<string, Events> ValidEvents;
61 public Events _Event;
62
63 public string _EventName;
64 public string _CurrentEventConfig;
65 public int _AutoEventNum = 0;
66
67 public bool _ForceNextConfig;
68 public string _NextConfigName;
69 public int _NextEventNum;
70
71 public bool _Open;
72 public bool _Started;
73 public bool _Prestarting;
74 public bool _Ended;
75 public bool _Pending;
76 public bool _Destoyed;
77 public bool _Launched;
78 public bool _RandomizeAuto;
79 public bool _TimerStarted;
80
81 public List<Timer> EventTimers;
82 public Dictionary<ulong, Timer> RespawnTimers;
83 public Dictionary<string, EventSetting> EventGames;
84 public ScoreData GameScores;
85 public SpawnManager SpawnCount;
86 public List<EventPlayer> EventPlayers;
87 public List<BasePlayer> Joiners;
88
89 public Statistics GameStatistics;
90 public GameMode EventMode;
91 #endregion
92
93 #region UI
94 #region Popup Messages
95 private static string Popup = "EMUI_Popupmsg";
96 private List<string> PopupPanels = new List<string>();
97 private int popUpCount = 0;
98
99 public void PopupMessage(string message)
100 {
101 popupQueue.Add(new MessageData(message, 6, ""));
102 UpdateMessages();
103 }
104 private CuiElementContainer CreateMessageEntry(int number, string panelName, MessageData data)
105 {
106 PopupPanels.Add(panelName);
107 var pos = GetFeedPosition(number);
108 var Main = UI.CreateElementContainer(panelName, "0 0 0 0", $"{pos[0]} {pos[1]}", $"{pos[2]} {pos[3]}", false, "Hud");
109 UI.CreateOutLineLabel(ref Main, panelName, "0 0 0 1", data.message, 17, "1 1", "0 0", "1 1");
110 return Main;
111 }
112 private float[] GetFeedPosition(int number)
113 {
114 Vector2 initialPos = new Vector2(0.25f, 0.89f);
115 Vector2 dimensions = new Vector2(0.5f, 0.04f);
116 var yPos = initialPos.y - ((dimensions.y + 0.005f) * number);
117 return new float[] { initialPos.x, yPos, initialPos.x + dimensions.x, yPos + dimensions.y };
118 }
119 private void UpdateMessages(bool destroyed = false)
120 {
121 if (destroyed)
122 {
123 if (popupQueue.Count > 0)
124 {
125 for (int i = 0; i < popupQueue.Count; i++)
126 {
127 if (i >= 3)
128 return;
129
130 var feed = popupQueue[i];
131 var panelName = feed.elementID;
132 if (!feed.started)
133 {
134 panelName = Popup + popUpCount;
135 popUpCount++;
136 feed.Begin(panelName);
137 }
138 AddUI(CreateMessageEntry(i, panelName, popupQueue[i]));
139 }
140 }
141 }
142 else
143 {
144 if (popupQueue.Count > 0 && popupQueue.Count < 3)
145 {
146 var feed = popupQueue[popupQueue.Count - 1];
147 var panelName = Popup + popUpCount;
148 popUpCount++;
149 AddUI(CreateMessageEntry(popupQueue.Count - 1, panelName, feed));
150 feed.Begin(panelName);
151 }
152 }
153 }
154 private void AddUI(CuiElementContainer element)
155 {
156 foreach (var player in EventPlayers)
157 {
158 if (player.inEvent && player.enabled)
159 CuiHelper.AddUi(player.GetPlayer(), element);
160 }
161 }
162 private void DestroyUpdate()
163 {
164 for (int i = 0; i < popupQueue.Count; i++)
165 {
166 foreach (var player in BasePlayer.activePlayerList)
167 {
168 CuiHelper.DestroyUi(player, popupQueue[i].elementID);
169 }
170 }
171 UpdateMessages(true);
172 }
173 private void DestroyPopupUI(BasePlayer player)
174 {
175 for (int i = 0; i < popupQueue.Count; i++)
176 {
177 CuiHelper.DestroyUi(player, popupQueue[i].elementID);
178 }
179 }
180 private void DestroyPopupUI(MessageData element)
181 {
182 if (!string.IsNullOrEmpty(element.elementID))
183 {
184 foreach (var player in BasePlayer.activePlayerList)
185 {
186 CuiHelper.DestroyUi(player, element.elementID);
187 }
188 }
189 popupQueue.Remove(element);
190 DestroyUpdate();
191 }
192 private void DestroyAllPopups()
193 {
194 foreach (var player in BasePlayer.activePlayerList)
195 foreach (var message in PopupPanels)
196 CuiHelper.DestroyUi(player, message);
197 PopupPanels.Clear();
198 }
199
200
201 #endregion
202
203 #region Scoreboards
204 public class ScoreData
205 {
206 public Dictionary<ulong, Scoreboard> Scores = new Dictionary<ulong, Scoreboard>();
207 public string ScoreType;
208 public string Additional;
209 }
210 public class Scoreboard
211 {
212 public string Name;
213 public int Position;
214 public int Score;
215 }
216
217 public void CreateScoreboard(BasePlayer player)
218 {
219 if (GameScores == null) return;
220 var scores = GameScores.Scores;
221 var type = GameScores.ScoreType;
222 var additional = GameScores.Additional;
223
224 var Main = UI.CreateElementContainer(ScoreUI, "0.1 0.1 0.1 0.7", "0.82 0.55", "0.99 0.98", false, "Hud");
225
226 int count = 0;
227 if (!string.IsNullOrEmpty(additional))
228 {
229 UI.CreateLabel(ref Main, ScoreUI, "", additional, 12, $"0.05 {GetHeight(count)}", $"0.95 {GetHeight(count) + 0.06f}");
230 count++;
231 }
232 if (scores != null)
233 {
234 var topScores = scores.Take(11);
235 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MainColor}>Name</color>", 12, $"0.25 {GetHeight(count)}", $"0.7 {GetHeight(count) + 0.06f}");
236 if (type != null) UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MainColor}>{type}</color>", 12, $"0.7 {GetHeight(count)}", $"0.95 {GetHeight(count) + 0.06f}");
237 count++;
238
239 if (scores.ContainsKey(player.userID) && scores[player.userID].Position > 10)
240 {
241 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MainColor}>{scores[player.userID].Position + 1}</color>", 12, $"0.05 {GetHeight(count)}", $"0.25 {GetHeight(count) + 0.06f}");
242 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MsgColor}>{scores[player.userID].Name}</color>", 12, $"0.25 {GetHeight(count)}", $"0.7 {GetHeight(count) + 0.06f}");
243 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MsgColor}>{scores[player.userID].Score}</color>", 12, $"0.7 {GetHeight(count)}", $"0.95 {GetHeight(count) + 0.06f}");
244 count++;
245 }
246 foreach (var score in topScores)
247 {
248 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MainColor}>{score.Value.Position + 1}</color>", 12, $"0.05 {GetHeight(count)}", $"0.25 {GetHeight(count) + 0.06f}");
249 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MsgColor}>{score.Value.Name}</color>", 12, $"0.25 {GetHeight(count)}", $"0.7 {GetHeight(count) + 0.06f}");
250 UI.CreateLabel(ref Main, ScoreUI, "", $"<color={configData.Messaging.MsgColor}>{score.Value.Score}</color>", 12, $"0.7 {GetHeight(count)}", $"0.95 {GetHeight(count) + 0.06f}");
251 count++;
252 }
253 }
254 DestroyScoreboard(player);
255 CuiHelper.AddUi(player, Main);
256 }
257 public void UpdateScoreboard(ScoreData data)
258 {
259 GameScores = data;
260 foreach (var eventPlayer in EventPlayers)
261 {
262 CreateScoreboard(eventPlayer.GetPlayer());
263 }
264 }
265 float GetHeight(int num) => 0.89f - (0.06f * num);
266 void DestroyScoreboard(BasePlayer player) => CuiHelper.DestroyUi(player, ScoreUI);
267
268 #endregion
269 #endregion
270
271 #region Oxide Hooks
272 void Loaded()
273 {
274 Unsubscribe(nameof(OnEntityTakeDamage));
275 Unsubscribe(nameof(OnPlayerAttack));
276 Unsubscribe(nameof(OnItemPickup));
277 Unsubscribe(nameof(OnRunPlayerMetabolism));
278 Unsubscribe(nameof(CanNetworkTo));
279 Unsubscribe(nameof(CanLootPlayer));
280 Unsubscribe(nameof(OnCreateWorldProjectile));
281
282 P_Stats = Interface.Oxide.DataFileSystem.GetFile("EventManager/Statistics");
283 RestoreData = Interface.Oxide.DataFileSystem.GetFile("EventManager/Restoration_data");
284 Restoration = new RestorationManager();
285
286 killLifestory = typeof(BasePlayer).GetMethod("LifeStoryEnd", (BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic));
287 }
288 void OnServerInitialized()
289 {
290 lang.RegisterMessages(Messages, this);
291 if (!Spawns)
292 {
293 PrintError("Unable to load EventManager - Spawns database not found. Please download Spawns database to continue");
294 rust.RunServerCommand("oxide.unload", new object[] { "EventManager" });
295 return;
296 }
297 if (!ZoneManager)
298 {
299 PrintError("Unable to load EventManager - ZoneManager not found. Please download ZoneManager to continue");
300 rust.RunServerCommand("oxide.unload", new object[] { "EventManager" });
301 return;
302 }
303 if (!Kits)
304 PrintError("Kits is not installed! Unable to issue any weapon kits");
305 SetVars();
306 LoadVariables();
307 LoadData();
308 ins = this;
309
310 foreach(var player in BasePlayer.activePlayerList)
311 CheckForRestore(player);
312
313 timer.In(5, ()=>
314 {
315 Interface.CallHook("RegisterGame");
316 ValidateAllEvents();
317 });
318 }
319 void Unload()
320 {
321 SaveStatistics();
322 SaveRestoreInfo();
323 if (_Open) CloseEvent();
324 if (_Started)
325 {
326 foreach(var eventPlayer in EventPlayers)
327 {
328 eventPlayer?.GetPlayer().DieInstantly();
329 UnityEngine.Object.DestroyImmediate(eventPlayer);
330 }
331 }
332 else DestroyGame();
333 }
334
335 void OnPlayerInit(BasePlayer player)
336 {
337 CheckForRestore(player);
338 }
339 void OnPlayerDisconnected(BasePlayer player)
340 {
341 if (Joiners.Contains(player))
342 Joiners.Remove(player);
343 if (GetUser(player))
344 LeaveEvent(player);
345 }
346 void OnPlayerRespawned(BasePlayer player)
347 {
348 if (!_Started && !_Open)
349 CheckForRestore(player);
350 else
351 {
352 var eventPlayer = GetUser(player);
353 if (eventPlayer == null) return;
354 if (!EventPlayers.Contains(eventPlayer))
355 {
356 UnityEngine.Object.DestroyImmediate(eventPlayer);
357 return;
358 }
359 if (eventPlayer.inEvent && !eventPlayer.isLeaving)
360 TeleportPlayerToEvent(player);
361 else if (!eventPlayer.isLeaving)
362 Restoration.RestorePlayer(player);
363 }
364 }
365
366 void OnPlayerAttack(BasePlayer player, HitInfo hitinfo)
367 {
368 var eventPlayer = GetUser(player);
369 if (eventPlayer == null || !eventPlayer.inEvent)
370 return;
371
372 if (hitinfo.HitEntity != null)
373 Interface.Oxide.CallHook("OnEventPlayerAttack", player, hitinfo);
374
375 if (hitinfo.IsProjectile())
376 AddStats(player, StatType.Shots);
377 return;
378 }
379 void OnEntityTakeDamage(BaseEntity entity, HitInfo info)
380 {
381 if (entity.GetComponent<EventCorpse>())
382 {
383 NullifyDamage(info);
384 return;
385 }
386 var player = entity.ToPlayer();
387 var attacker = info.InitiatorPlayer;
388
389 if (player != null)
390 {
391 var eventPlayer = GetUser(player);
392
393 if (eventPlayer != null)
394 {
395 if (eventPlayer.isLeaving || eventPlayer.isDead)
396 {
397 NullifyDamage(info);
398 return;
399 }
400 if (_GodEnabled)
401 {
402 NullifyDamage(info);
403 return;
404 }
405
406 float damageAmount = info.damageTypes.Total();
407 if (attacker != null)
408 {
409 if (!GetUser(attacker))
410 {
411 NullifyDamage(info);
412 return;
413 }
414 else
415 {
416 object multiply = Interface.CallHook("HasDamageMultiplier", player, attacker);
417 if (multiply is float)
418 {
419 damageAmount *= (float)multiply;
420 }
421 }
422 }
423
424 if (info.isHeadshot)
425 damageAmount *= 2;
426
427 if (player.health - damageAmount < 1)
428 {
429 NullifyDamage(info);
430 OnPlayerDeath(player, info);
431 return;
432 }
433 }
434 else if (attacker != null)
435 {
436 if (GetUser(attacker))
437 {
438 NullifyDamage(info);
439 return;
440 }
441 }
442 }
443 }
444 object OnCreateWorldProjectile(HitInfo info, Item item)
445 {
446 if (info?.InitiatorPlayer != null)
447 {
448 var eventPlayer = GetUser(info.InitiatorPlayer);
449 if (eventPlayer != null)
450 return false;
451 }
452 if (info?.HitEntity?.ToPlayer() != null)
453 {
454 var eventPlayer = GetUser(info.HitEntity.ToPlayer());
455 if (eventPlayer != null)
456 return false;
457 }
458 return null;
459 }
460 private void OnRunPlayerMetabolism(PlayerMetabolism metabolism, BaseCombatEntity entity, float delta)
461 {
462 var eventPlayer = GetUser(entity?.ToPlayer());
463 if (eventPlayer == null) return;
464 if (eventPlayer.isDead)
465 {
466 metabolism.bleeding.value = 0;
467 metabolism.poison.value = 0;
468 metabolism.radiation_level.value = 0;
469 metabolism.radiation_poison.value = 0;
470 metabolism.wetness.value = 0;
471 }
472 else
473 metabolism.bleeding.value = 0f;
474 }
475 object CanNetworkTo(BaseEntity entity, BasePlayer target)
476 {
477 var player = entity as BasePlayer;
478 var eventPlayer = GetUser(player ?? (entity as HeldEntity)?.GetOwnerPlayer());
479 if (eventPlayer == null || target == null)
480 return null;
481 if (unnetworkedPlayers.Contains(player))
482 return false;
483 return null;
484 }
485 void OnItemPickup(Item item, BasePlayer player)
486 {
487 if (_Started && GetUser(player) != null && _Event.DisableItemPickup)
488 {
489 item.Remove(0.01f);
490 SendMsg(player, "ItemPickup");
491 }
492 }
493 object CanLootPlayer(BasePlayer target, BasePlayer looter)
494 {
495 if (_Started && GetUser(looter) != null && _Event.DisableItemPickup)
496 {
497 SendMsg(looter, "NoLooting");
498 return true;
499 }
500 return null;
501 }
502
503 void SetVars()
504 {
505 EventGames = new Dictionary<string, EventSetting>();
506 EventPlayers = new List<EventPlayer>();
507 Joiners = new List<BasePlayer>();
508 EventTimers = new List<Timer>();
509 RespawnTimers = new Dictionary<ulong, Timer>();
510 ValidAutoEvents = new List<EMInterface.AEConfig>();
511 ValidEvents = new Dictionary<string, Events>();
512 KillTimers = new Dictionary<ulong, Timer>();
513 StatsCache = new Dictionary<ulong, PlayerStatistics>();
514 GameScores = new ScoreData();
515 SpawnCount = new SpawnManager();
516 Restoration = new RestorationManager();
517 popupQueue = new List<MessageData>();
518 unnetworkedPlayers = new List<BasePlayer>();
519
520 _Open = false;
521 _Started = false;
522 _Ended = true;
523 _Pending = false;
524 _Destoyed = true;
525 _Launched = false;
526 _GodEnabled = false;
527 _RandomizeAuto = false;
528 _ForceNextConfig = false;
529 _EventName = null;
530 _NextConfigName = null;
531 _NextEventNum = 0;
532 _CurrentEventConfig = null;
533 _Event = DefaultConfig;
534 EventMode = GameMode.Normal;
535
536 _TimerStarted = false;
537 _GameTimer = null;
538 }
539 void CheckForRestore(BasePlayer player)
540 {
541 if (player.IsSleeping() || player.HasPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot))
542 {
543 timer.Once(3, () => CheckForRestore(player));
544 return;
545 }
546 var eventPlayer = GetUser(player);
547 if (eventPlayer != null && eventPlayer.inEvent && _Started) return;
548
549 if (Restoration.HasPendingRestore(player.userID))
550 {
551 if (Restoration.ReadyToRestore(player))
552 {
553 if (!Restoration.RestorePlayer(player))
554 {
555 SendMsg(player, "failedRestore");
556 }
557 else SaveRestoreInfo();
558 }
559 }
560 }
561 #endregion
562
563 #region Config
564 private ConfigData configData;
565 class ConfigData
566 {
567 public EM_Messaging Messaging { get; set; }
568 public EM_Options Options { get; set; }
569 }
570 class EM_Options
571 {
572 public bool AllowClassSelectionOnDeath { get; set; }
573 public bool LaunchAutoEventsOnStartup { get; set; }
574 public int Battlefield_Timer { get; set; }
575 public int Required_AuthLevel { get; set; }
576 public bool DropCorpseOnDeath { get; set; }
577 public bool UseSpectateMode { get; set; }
578 public bool UseEconomicsAsTokens { get; set; }
579 public bool UseEventPrestart { get; set; }
580 public int EventPrestartTimer { get; set; }
581 }
582 class EM_Messaging
583 {
584 public bool AnnounceEvent { get; set; }
585 public bool AnnounceEvent_During { get; set; }
586 public int AnnounceEvent_Interval { get; set; }
587 public string MainColor { get; set; }
588 public string MsgColor { get; set; }
589 }
590 private void LoadVariables()
591 {
592 LoadConfigVariables();
593 SaveConfig();
594 }
595 private void LoadConfigVariables()
596 {
597 try
598 {
599 configData = Config.ReadObject<ConfigData>();
600 }
601 catch (Exception)
602 {
603 Puts("Invalid config file, restoring default");
604 LoadDefaultConfig();
605 }
606 }
607 protected override void LoadDefaultConfig()
608 {
609 var config = new ConfigData
610 {
611 Messaging = new EM_Messaging
612 {
613 AnnounceEvent_During = true,
614 AnnounceEvent_Interval = 120,
615 AnnounceEvent = true,
616 MainColor = "#FF8C00",
617 MsgColor = "#939393"
618 },
619 Options = new EM_Options
620 {
621 AllowClassSelectionOnDeath = true,
622 Battlefield_Timer = 1200,
623 DropCorpseOnDeath = true,
624 EventPrestartTimer = 30,
625 LaunchAutoEventsOnStartup = false,
626 Required_AuthLevel = 1,
627 UseEconomicsAsTokens = false,
628 UseSpectateMode = true,
629 UseEventPrestart = true
630 }
631 };
632 SaveConfig(config);
633 }
634 void SaveConfig(ConfigData config) => Config.WriteObject(config, true);
635 #endregion
636
637 #region Messaging
638 public void BroadcastEvent(string msg)
639 {
640 foreach (EventPlayer eventplayer in EventPlayers)
641 SendReply(eventplayer.GetPlayer(), $"<color={configData.Messaging.MainColor}>" + msg + "</color>");
642 }
643 public void BroadcastToChat(string message) => PrintToChat($"<color={configData.Messaging.MainColor}>{msg("Title")}</color><color={configData.Messaging.MsgColor}>{msg(message)}</color>");
644 static string msg(string key, BasePlayer player = null) => ins.lang.GetMessage(key, ins, player?.UserIDString);
645 private void SendMsg(BasePlayer player, string langkey, bool title = true)
646 {
647 string message = $"<color={configData.Messaging.MsgColor}>{msg(langkey)}</color>";
648 if (title) message = $"<color={configData.Messaging.MainColor}>{msg("Title")}</color>" + message;
649 SendReply(player, message);
650 }
651 void AnnounceEvent()
652 {
653 object success = Interface.Oxide.CallHook("OnEventAnnounce");
654 if (success is string)
655 BroadcastToChat((string)success);
656 else BroadcastToChat(string.Format(msg("eventOpen"), _Event.EventType));
657 }
658 void AnnounceDuringEvent()
659 {
660 if (configData.Messaging.AnnounceEvent_During)
661 {
662 if (_Open && _Started)
663 {
664 foreach (BasePlayer player in BasePlayer.activePlayerList)
665 {
666 if (!GetUser(player))
667 SendMsg(player, string.Format(msg("stillOpen"), _Event.EventType));
668 }
669 }
670 }
671 }
672
673 #endregion
674
675 #region Information Classes
676 public class Events
677 {
678 public bool CloseOnStart;
679 public bool DisableItemPickup;
680 public bool UseClassSelector;
681 public int MinimumPlayers;
682 public int MaximumPlayers;
683 public int ScoreLimit;
684 public int RespawnTimer;
685 public int GameRounds;
686 public int EnemiesToSpawn;
687 public string EventType;
688 public string Kit;
689 public string WeaponSet;
690 public string Spawnfile;
691 public string Spawnfile2;
692 public string ZoneID;
693 public GameMode GameMode;
694 public RespawnType RespawnType;
695 public SpawnType SpawnType;
696 }
697 public class EventSetting
698 {
699 public bool LockClothing;
700 public bool RequiresKit;
701 public bool RequiresSpawns;
702 public bool RequiresMultipleSpawns;
703 public bool CanUseClassSelector;
704 public bool CanPlayBattlefield;
705 public bool CanChooseRespawn;
706 public bool ForceCloseOnStart;
707 public bool IsRoundBased;
708 public bool SpawnsEnemies;
709 public string ScoreType;
710 }
711 public class EventInvItem
712 {
713 public int itemid;
714 public ulong skin;
715 public int amount;
716 public float condition;
717 public int ammo;
718 public string ammotype;
719 public ProtoBuf.Item.InstanceData instanceData;
720 public EventInvItem[] contents;
721 }
722 public class RestoreInfo
723 {
724 public Dictionary<InventoryType, List<EventInvItem>> inventory = new Dictionary<InventoryType, List<EventInvItem>>();
725 public float health, hydration, calories, x, y, z;
726 }
727
728 public class UI
729 {
730 static public CuiElementContainer CreateElementContainer(string panelName, string color, string aMin, string aMax, bool useCursor = false, string parent = "Overlay")
731 {
732 var NewElement = new CuiElementContainer()
733 {
734 {
735 new CuiPanel
736 {
737 Image = {Color = color},
738 RectTransform = {AnchorMin = aMin, AnchorMax = aMax},
739 CursorEnabled = useCursor
740 },
741 new CuiElement().Parent = parent,
742 panelName
743 }
744 };
745 return NewElement;
746 }
747 static public void CreatePanel(ref CuiElementContainer container, string panel, string color, string aMin, string aMax, bool cursor = false)
748 {
749 container.Add(new CuiPanel
750 {
751 Image = { Color = color },
752 RectTransform = { AnchorMin = aMin, AnchorMax = aMax },
753 CursorEnabled = cursor
754 },
755 panel);
756 }
757 static public void CreateLabel(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter)
758 {
759 container.Add(new CuiLabel
760 {
761 Text = { Color = color, FontSize = size, Align = align, Text = text },
762 RectTransform = { AnchorMin = aMin, AnchorMax = aMax }
763 },
764 panel);
765
766 }
767 static public void CreateOutLineLabel(ref CuiElementContainer container, string panel, string color, string text, int size, string distance, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter, string parent = "Overlay")
768 {
769 CuiElement textElement = new CuiElement
770 {
771 Name = CuiHelper.GetGuid(),
772 Parent = panel,
773 FadeOut = 0.2f,
774 Components =
775 {
776 new CuiTextComponent
777 {
778 Text = text,
779 FontSize = size,
780 Align = TextAnchor.MiddleCenter,
781 FadeIn = 0.2f
782 },
783 new CuiOutlineComponent
784 {
785 Distance = distance,
786 Color = color
787 },
788 new CuiRectTransformComponent
789 {
790 AnchorMin = aMin,
791 AnchorMax = aMax
792 }
793 }
794 };
795 container.Add(textElement);
796 }
797 static public void CreateButton(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, string command, TextAnchor align = TextAnchor.MiddleCenter)
798 {
799 container.Add(new CuiButton
800 {
801 Button = { Color = color, Command = command, FadeIn = 0f },
802 RectTransform = { AnchorMin = aMin, AnchorMax = aMax },
803 Text = { Text = text, FontSize = size, Align = align }
804 },
805 panel);
806 }
807 static public void ImageFromStorage(ref CuiElementContainer container, string panel, string png, string aMin, string aMax)
808 {
809 container.Add(new CuiElement
810 {
811 Name = CuiHelper.GetGuid(),
812 Parent = panel,
813 Components =
814 {
815 new CuiRawImageComponent {Png = png },
816 new CuiRectTransformComponent {AnchorMin = aMin, AnchorMax = aMax }
817 }
818 });
819 }
820 static public void ImageFromURL(ref CuiElementContainer container, string panel, string url, string aMin, string aMax)
821 {
822 container.Add(new CuiElement
823 {
824 Name = CuiHelper.GetGuid(),
825 Parent = panel,
826 Components =
827 {
828 new CuiRawImageComponent {Url = url },
829 new CuiRectTransformComponent {AnchorMin = aMin, AnchorMax = aMax }
830 }
831 });
832 }
833 static public string Color(string hexColor, float alpha)
834 {
835 if (hexColor.StartsWith("#"))
836 hexColor = hexColor.TrimStart('#');
837 int red = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
838 int green = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
839 int blue = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
840 return $"{(double)red / 255} {(double)green / 255} {(double)blue / 255} {alpha}";
841 }
842 }
843
844 public enum InventoryType { Main, Wear, Belt };
845 public enum GameMode
846 {
847 Normal,
848 Battlefield
849 }
850 public enum SpawnType
851 {
852 Random,
853 Consecutive
854 }
855 public enum RespawnType
856 {
857 None,
858 Timer,
859 Waves
860 }
861 #endregion
862
863 #region Components
864 public class EventPlayer : MonoBehaviour
865 {
866 private BasePlayer player;
867 private EventCorpse corpse;
868
869 public bool inEvent, isDead, isRespawning, isSpectating, OOB, isLeaving;
870 public string currentClass;
871 public int restoreAttempts;
872
873 void Awake()
874 {
875 inEvent = true;
876 isDead = false;
877 isRespawning = false;
878 isSpectating = false;
879 isLeaving = false;
880 OOB = false;
881 currentClass = string.Empty;
882 restoreAttempts = 0;
883 player = GetComponent<BasePlayer>();
884 }
885 private void OnDestroy()
886 {
887 AddToNetwork();
888 }
889 public BasePlayer GetPlayer() => player;
890 public void SetCorpse(EventCorpse corpse) => this.corpse = corpse ?? null;
891 public EventCorpse GetCorpse() => corpse ?? null;
892
893 public void RemoveFromNetwork(float refreshIn = 0)
894 {
895 if (!unnetworkedPlayers.Contains(player))
896 {
897 if (Net.sv.write.Start())
898 {
899 Net.sv.write.PacketID(Message.Type.EntityDestroy);
900 Net.sv.write.EntityID(player.net.ID);
901 Net.sv.write.UInt8((byte)BaseNetworkable.DestroyMode.None);
902 Net.sv.write.Send(new SendInfo(player.net.group.subscribers.Where(x => x.userid != player.userID).ToList()));
903 }
904
905 unnetworkedPlayers.Add(player);
906 if (refreshIn > 0)
907 InvokeHandler.Invoke(this, AddToNetwork, refreshIn);
908 }
909 }
910 public void AddToNetwork()
911 {
912 if (unnetworkedPlayers.Contains(player))
913 {
914 unnetworkedPlayers.Remove(player);
915 player.SendFullSnapshot();
916 }
917 }
918 }
919 public class EventCorpse : MonoBehaviour
920 {
921 public LootableCorpse entity;
922 void Awake()
923 {
924 entity = GetComponent<LootableCorpse>();
925 }
926 void OnDestroy()
927 {
928 if (entity == null) return;
929 if (entity?.containers == null) return;
930 foreach (var container in entity.containers)
931 {
932 Item[] array = container.itemList.ToArray();
933 for (int i = 0; i < array.Length; i++)
934 array[i].Remove(0f);
935 }
936 ItemManager.DoRemoves();
937 entity.DieInstantly();
938 }
939 }
940
941 private class GameTimer : MonoBehaviour
942 {
943 int timeRemaining;
944 void Awake() => timeRemaining = 0;
945 public void StartTimer(int time)
946 {
947 timeRemaining = time;
948 InvokeRepeating("TimerTick", 1f, 1f);
949 ins._TimerStarted = true;
950 }
951 void OnDestroy()
952 {
953 CancelInvoke("TimerTick");
954 ins._TimerStarted = false;
955 Destroy(this);
956 }
957 internal void TimerTick()
958 {
959 timeRemaining--;
960 if (timeRemaining == 0)
961 {
962 DestroyUI();
963 ins.CancelEvent(msg("TimeLimit"));
964 CancelInvoke("TimerTick");
965 }
966 else UpdateUITimer();
967 }
968 internal void UpdateUITimer()
969 {
970 string clockTime = "";
971 TimeSpan dateDifference = TimeSpan.FromSeconds(timeRemaining);
972 var hours = dateDifference.Hours;
973 var mins = dateDifference.Minutes;
974 var secs = dateDifference.Seconds;
975 if (hours > 0)
976 clockTime = string.Format("{0:00}:{1:00}:{2:00}", hours, mins, secs);
977 else clockTime = string.Format("{0:00}:{1:00}", mins, secs);
978
979 var CUI = UI.CreateElementContainer(ClockUI, "0.1 0.1 0.1 0.7", "0.45 0.95", "0.55 0.99", false, "Hud");
980 UI.CreateLabel(ref CUI, ClockUI, "", clockTime, 16, "0 0", "1 1");
981 foreach (var ePlayer in ins.EventPlayers)
982 {
983 CuiHelper.DestroyUi(ePlayer.GetPlayer(), ClockUI);
984 CuiHelper.AddUi(ePlayer.GetPlayer(), CUI);
985 }
986 }
987 internal void DestroyUI()
988 {
989 foreach (var player in BasePlayer.activePlayerList)
990 CuiHelper.DestroyUi(player, ClockUI);
991 }
992 }
993 private class WaveTimer : MonoBehaviour
994 {
995 private int waveTimer;
996 void Awake()
997 {
998 waveTimer = ins._Event.RespawnTimer;
999 InvokeRepeating("RespawnTick", 1f, 1f);
1000 }
1001 void OnDestroy() => CancelInvoke("RespawnTick");
1002 private void RespawnTick()
1003 {
1004 waveTimer--;
1005 if (waveTimer <= 0)
1006 waveTimer = ins._Event.RespawnTimer;
1007 }
1008 public int GetTime() => waveTimer;
1009 }
1010 public class SpawnManager
1011 {
1012 public int spawnsCountA = 0;
1013 public int spawnsCountB = 0;
1014 private int lastSpawnA = 0;
1015 private int lastSpawnB = 0;
1016
1017 public object GetSpawnPoint(string file, bool team = true, int min = -1, int max = -1)
1018 {
1019 if (ins._Event.SpawnType == SpawnType.Consecutive)
1020 {
1021 if (min > 0)
1022 {
1023 if (team)
1024 {
1025 if (lastSpawnA < min || (max > 0 && lastSpawnA > max))
1026 {
1027 lastSpawnA = min - 1;
1028 return GetNextSpawn(file, team);
1029 }
1030 }
1031 else
1032 {
1033 if (lastSpawnB < min || (max > 0 && lastSpawnB > max))
1034 {
1035 lastSpawnB = min - 1;
1036 return GetNextSpawn(file, team);
1037 }
1038 }
1039 }
1040 return GetNextSpawn(file, team);
1041 }
1042 else
1043 {
1044 if (min > 0)
1045 {
1046 if (max > 0)
1047 return GetRandomRange(file, min, max);
1048 else return GetRandomRange(file, min, 9999);
1049 }
1050 return GetRandomSpawn(file);
1051 }
1052 }
1053 private object GetRandomSpawn(string file) => ins.Spawns.Call("GetRandomSpawn", file);
1054 private object GetRandomRange(string file, int min, int max) => ins.Spawns.Call("GetRandomSpawnRange", file, min, max);
1055 private object GetNextSpawn(string file, bool team)
1056 {
1057 int number;
1058 if (team)
1059 {
1060 ++lastSpawnA;
1061 if (lastSpawnA >= spawnsCountA)
1062 lastSpawnA = 0;
1063 number = lastSpawnA;
1064 }
1065 else
1066 {
1067 ++lastSpawnB;
1068 if (lastSpawnB >= spawnsCountB)
1069 lastSpawnB = 0;
1070 number = lastSpawnB;
1071 }
1072 return ins.Spawns.Call("GetSpawn", file, number);
1073 }
1074 public void SetSpawnCount(bool team, int number)
1075 {
1076 if (team)
1077 spawnsCountA = number;
1078 else spawnsCountB = number;
1079 }
1080 }
1081 private class MessageData
1082 {
1083 public int timecount;
1084 public string elementID;
1085 public string message;
1086 public bool started;
1087
1088 public MessageData(string message, int timecount, string elementID)
1089 {
1090 this.timecount = timecount;
1091 this.elementID = elementID;
1092 this.message = message;
1093 started = false;
1094 }
1095 public void Begin(string elementID)
1096 {
1097 this.elementID = elementID;
1098 started = true;
1099 StartDestroy();
1100 }
1101 void StartDestroy()
1102 {
1103 if (timecount > 0)
1104 {
1105 timecount--;
1106 ins.timer.Once(1, () => StartDestroy());
1107 }
1108 else if (timecount == 0)
1109 {
1110 ins.DestroyPopupUI(this);
1111 }
1112 }
1113 }
1114
1115 private EventPlayer GetUser(BasePlayer player)
1116 {
1117 if (player == null) return null;
1118 var eventPlayer = player.GetComponent<EventPlayer>();
1119 if (eventPlayer == null) return null;
1120 return eventPlayer;
1121 }
1122 #endregion
1123
1124 #region Event Management
1125 public object SelectEvent(string name)
1126 {
1127 if (!(EventGames.ContainsKey(name))) return string.Format(msg("noEvent"), name);
1128 if (_Started || _Open) return msg("isAlreadyStarted");
1129 Interface.CallHook("OnSelectEventGamePost", new object[] { name });
1130 _Event.EventType = name;
1131 return true;
1132 }
1133 object SetEventDetails()
1134 {
1135 var success = SelectEvent(_Event.EventType);
1136 if (success is string) return (string)success;
1137 if (EventGames.ContainsKey(_Event.EventType))
1138 {
1139 EventMode = _Event.GameMode;
1140 if (EventGames[_Event.EventType].RequiresSpawns && !string.IsNullOrEmpty(_Event.Spawnfile))
1141 {
1142 Interface.CallHook("SetSpawnfile", true, _Event.Spawnfile);
1143 object count = Spawns.Call("GetSpawnsCount", _Event.Spawnfile);
1144 if (count is int)
1145 SpawnCount.SetSpawnCount(true, (int)count);
1146 }
1147 if (EventGames[_Event.EventType].RequiresMultipleSpawns && !string.IsNullOrEmpty(_Event.Spawnfile2))
1148 {
1149 Interface.CallHook("SetSpawnfile", false, _Event.Spawnfile2);
1150 object count = Spawns.Call("GetSpawnsCount", _Event.Spawnfile2);
1151 if (count is int)
1152 SpawnCount.SetSpawnCount(false, (int)count);
1153 }
1154 if (_Event.GameRounds > 0)
1155 Interface.CallHook("SetGameRounds", _Event.GameRounds);
1156 if (_Event.EnemiesToSpawn > 0)
1157 Interface.CallHook("SetEnemyCount", _Event.EnemiesToSpawn);
1158 if (!string.IsNullOrEmpty(EventGames[_Event.EventType].ScoreType))
1159 Interface.CallHook("SetScoreLimit", _Event.ScoreLimit);
1160 if (!string.IsNullOrEmpty(_Event.WeaponSet))
1161 Interface.CallHook("ChangeWeaponSet", _Event.WeaponSet);
1162 if (!string.IsNullOrEmpty(_Event.Kit))
1163 Interface.CallHook("OnSelectKit", _Event.Kit);
1164 if (!string.IsNullOrEmpty(_Event.ZoneID))
1165 {
1166 Interface.CallHook("SetEventZone", _Event.ZoneID);
1167 if (configData.Options.DropCorpseOnDeath)
1168 {
1169 var hasFlag = ZoneManager?.Call("HasFlag", _Event.ZoneID, "NoCorpse");
1170 if (hasFlag is bool && (bool)hasFlag)
1171 {
1172 ZoneManager?.Call("RemoveFlag", _Event.ZoneID, "NoCorpse");
1173 ResetNoCorpse = true;
1174 }
1175 }
1176 }
1177 if (EventGames[_Event.EventType].ForceCloseOnStart)
1178 _Event.CloseOnStart = true;
1179 if (!EventGames[_Event.EventType].CanChooseRespawn)
1180 {
1181 var type = Interface.CallHook("GetRespawnType");
1182 if (type != null)
1183 _Event.RespawnType = (RespawnType)type;
1184 var time = Interface.CallHook("GetRespawnTime");
1185 if (time != null)
1186 _Event.RespawnTimer = (int)time;
1187 }
1188 return null;
1189 }
1190 return "Error setting event details. Please check your settings";
1191 }
1192
1193 public object OpenEvent()
1194 {
1195 if (_Event == null) return msg("noneSelected");
1196 if (string.IsNullOrEmpty(_Event.EventType)) return msg("noTypeSelected");
1197 if (_Open) return string.Format(msg("isAlreadyOpen"), _Event.EventType);
1198 if (_Started && !_Open)
1199 {
1200 if (!EventGames[_Event.EventType].ForceCloseOnStart)
1201 {
1202 _Open = true;
1203 return null;
1204 }
1205 else return msg("cantOpen");
1206 }
1207 var success = ValidateEvent(_Event);
1208 if (success is string)
1209 return (string)success;
1210 success = SetEventDetails();
1211 if (success is string)
1212 return (string)success;
1213 success = Interface.Oxide.CallHook("CanEventOpen");
1214 if (success is string)
1215 return (string)success;
1216 EMInterface.EventVoting.OpenEventVoting(false);
1217
1218 _Open = true;
1219 GameScores = new ScoreData();
1220 EventPlayers = new List<EventPlayer>();
1221 Joiners = new List<BasePlayer>();
1222
1223 _EventName = _Event.EventType;
1224 if (_Event.GameMode == GameMode.Battlefield)
1225 _EventName = $"{msg("Battlefield")} - {_EventName}";
1226
1227 if (!string.IsNullOrEmpty(_CurrentEventConfig))
1228 _EventName = $"{_CurrentEventConfig} ({_Event.EventType})";
1229
1230 BroadcastToChat(string.Format(msg("eventOpen"), _EventName));
1231 Interface.Oxide.CallHook("OnEventOpenPost");
1232
1233 return true;
1234 }
1235 public object CloseEvent()
1236 {
1237 if (!_Open) return msg("eventAlreadyClosed");
1238 _Open = false;
1239 Interface.Oxide.CallHook("OnEventClosePost");
1240
1241 if (_Started)
1242 BroadcastToChat(msg("eventClose"));
1243 else
1244 {
1245 DestroyTimers();
1246 BroadcastToChat(msg("eventCancel"));
1247 EMInterface.EventVoting.OpenEventVoting(true);
1248 }
1249 return true;
1250 }
1251
1252 public object StartEvent()
1253 {
1254 object success = Interface.Oxide.CallHook("CanEventStart");
1255 if (success is string)
1256 return (string)success;
1257
1258 Subscribe(nameof(OnEntityTakeDamage));
1259 Subscribe(nameof(OnPlayerAttack));
1260 Subscribe(nameof(OnRunPlayerMetabolism));
1261 Subscribe(nameof(OnItemPickup));
1262 Subscribe(nameof(CanNetworkTo));
1263 Subscribe(nameof(CanLootPlayer));
1264 Subscribe(nameof(OnCreateWorldProjectile));
1265
1266 Interface.Oxide.CallHook("OnEventStartPre");
1267 BroadcastToChat(string.Format(msg("eventBegin"), _EventName));
1268 _Started = true;
1269 _Prestarting = true;
1270 _Ended = false;
1271 _Destoyed = false;
1272
1273 if (!GameStatistics.GamesPlayed.ContainsKey(_Event.EventType))
1274 GameStatistics.GamesPlayed.Add(_Event.EventType, 1);
1275 else GameStatistics.GamesPlayed[_Event.EventType]++;
1276
1277 DestroyTimers();
1278 PreStartEvent();
1279 return true;
1280 }
1281 public void PreStartEvent()
1282 {
1283 if (_Event.CloseOnStart)
1284 CloseEvent();
1285 EnableGod();
1286 CreateEventPlayers();
1287 }
1288
1289 public object EndEvent()
1290 {
1291 DestroyAllPopups();
1292
1293 if (_Ended) return msg("noGamePlaying");
1294
1295 EnableGod();
1296 RespawnAllPlayers();
1297
1298 _Open = false;
1299 _Started = false;
1300 _Pending = false;
1301
1302 DestroyTimers();
1303
1304 timer.In(3, () =>
1305 {
1306 Interface.CallHook("OnEventEndPre");
1307 for (int i = 0; i < EventPlayers.Count; i++)
1308 {
1309 BasePlayer player = EventPlayers[i].GetPlayer();
1310 DestroyScoreboard(player);
1311 CuiHelper.DestroyUi(player, ClockUI);
1312 }
1313
1314 BroadcastToChat(string.Format(msg("restoringPlayers"), _Event.EventType));
1315
1316 _Ended = true;
1317
1318 Restoration.StartRestoration();
1319 });
1320 return true;
1321 }
1322 void FinalizeGameEnd()
1323 {
1324 CalculateRanks();
1325 DestroyGame();
1326 Interface.Oxide.CallHook("OnEventEndPost");
1327 SaveRestoreInfo();
1328
1329 if (ResetNoCorpse)
1330 {
1331 ZoneManager?.Call("AddFlag", _Event.ZoneID, "NoCorpse");
1332 ResetNoCorpse = false;
1333 }
1334 EMInterface.EventVoting.OpenEventVoting(true);
1335 }
1336
1337 public void CancelEvent(string reason)
1338 {
1339 DestroyTimers();
1340
1341 if (_Open)
1342 CloseEvent();
1343
1344 object success = Interface.Oxide.CallHook("OnEventCancel");
1345 if (success is string)
1346 BroadcastToChat((string)success);
1347 else BroadcastToChat(string.Format(msg("EventCancelled"), _EventName, reason));
1348 EMInterface.EventVoting.OpenEventVoting(true);
1349 }
1350 public void CancelAutoEvent(string reason)
1351 {
1352 if (_Launched)
1353 _Launched = false;
1354
1355 CancelEvent(reason);
1356 }
1357 #endregion
1358
1359 #region Player TP Management
1360 private void MovePosition(BasePlayer player, Vector3 destination)
1361 {
1362 if (player.net?.connection != null)
1363 player.ClientRPCPlayer(null, player, "StartLoading");
1364 StartSleeping(player);
1365 player.MovePosition(destination);
1366 if (player.net?.connection != null)
1367 player.ClientRPCPlayer(null, player, "ForcePositionTo", destination);
1368 if (player.net?.connection != null)
1369 player.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true);
1370 player.UpdateNetworkGroup();
1371 player.SendNetworkUpdateImmediate(false);
1372 if (player.net?.connection == null) return;
1373 try { player.ClearEntityQueue(null); } catch { }
1374 player.SendFullSnapshot();
1375 }
1376 private void StartSleeping(BasePlayer player)
1377 {
1378 if (player.IsSleeping())
1379 return;
1380 player.SetPlayerFlag(BasePlayer.PlayerFlags.Sleeping, true);
1381 if (!BasePlayer.sleepingPlayerList.Contains(player))
1382 BasePlayer.sleepingPlayerList.Add(player);
1383 player.CancelInvoke("InventoryUpdate");
1384 }
1385 private object GetSpawnPosition(BasePlayer player)
1386 {
1387 var targetpos = SpawnCount.GetSpawnPoint(_Event.Spawnfile);
1388 if (targetpos is string)
1389 return null;
1390 var newpos = Interface.Oxide.CallHook("EventChooseSpawn", player, targetpos);
1391 if (newpos is Vector3)
1392 targetpos = newpos;
1393 return (Vector3)targetpos;
1394 }
1395 public void TeleportPlayerToEvent(BasePlayer player)
1396 {
1397 EMInterface.DestroyAllUI(player);
1398 var eventPlayer = GetUser(player);
1399 if (eventPlayer == null || player.net?.connection == null) return;
1400
1401 var targetpos = GetSpawnPosition(player);
1402 if (targetpos == null)
1403 {
1404 LeaveEvent(player);
1405 return;
1406 }
1407
1408 player.inventory.Strip();
1409 MovePosition(player, (Vector3)targetpos);
1410 timer.Once(2, () =>
1411 {
1412 var success = ConfirmTeleportation(player, (Vector3)targetpos);
1413 if (!success)
1414 {
1415 LeaveEvent(player);
1416 SendMsg(player, "tpError");
1417 }
1418 else
1419 {
1420 eventPlayer.isDead = false;
1421 ResetMetabolism(player);
1422
1423 if (_Started) Interface.Oxide.CallHook("OnEventPlayerSpawn", player);
1424 if (_Event.UseClassSelector && string.IsNullOrEmpty(eventPlayer.currentClass))
1425 {
1426 EMInterface.CloseMap(player);
1427 EMInterface.CreateMenuMain(player);
1428 EMInterface.ClassSelector(player);
1429 }
1430 }
1431 return;
1432 });
1433 }
1434 private bool ConfirmTeleportation(BasePlayer player, Vector3 position)
1435 {
1436 if (Vector3.Distance(player.transform.position, position) < 50)
1437 return true;
1438 return false;
1439 }
1440 #endregion
1441
1442 #region Death and Respawn Management
1443 void NullifyDamage(HitInfo info)
1444 {
1445 info.damageTypes = new DamageTypeList();
1446 info.HitEntity = null;
1447 info.HitMaterial = 0;
1448 info.PointStart = Vector3.zero;
1449 }
1450 void OnPlayerDeath(BasePlayer player, HitInfo hitinfo)
1451 {
1452 var eventPlayer = GetUser(player);
1453 if (eventPlayer == null) return;
1454
1455 eventPlayer.isDead = true;
1456 player.RemoveFromTriggers();
1457
1458 Interface.Oxide.CallHook("OnEventPlayerDeath", player, hitinfo);
1459
1460 AddStats(player, StatType.Deaths);
1461
1462 if (_Ended)
1463 return;
1464
1465 string attackerName = GetAttacker(player, hitinfo);
1466 switch (_Event.RespawnType)
1467 {
1468 case RespawnType.None:
1469 ResetPlayer(player);
1470 return;
1471 case RespawnType.Timer:
1472 AddRespawnTimer(player, attackerName, false);
1473 return;
1474 case RespawnType.Waves:
1475 AddRespawnTimer(player, attackerName, true);
1476 return;
1477 }
1478 }
1479 string GetAttacker(BasePlayer player, HitInfo hitinfo)
1480 {
1481 if (hitinfo?.Initiator == null)
1482 return string.Empty;
1483 if (hitinfo?.InitiatorPlayer != null)
1484 {
1485 var attacker = hitinfo.InitiatorPlayer;
1486 if (attacker != player)
1487 {
1488 if (GetUser(attacker))
1489 {
1490 AddStats(attacker, StatType.Kills);
1491 return attacker.displayName;
1492 }
1493 }
1494 }
1495 if (hitinfo.Initiator is BaseHelicopter)
1496 return msg("a Helicopter");
1497 if (hitinfo.Initiator is AutoTurret)
1498 return msg("a AutoTurret");
1499 if (hitinfo.Initiator is BaseNpc)
1500 return hitinfo.Initiator.ShortPrefabName;
1501 return string.Empty;
1502 }
1503
1504 public void ResetPlayer(BasePlayer player)
1505 {
1506 var eventPlayer = GetUser(player);
1507 if (eventPlayer == null) return;
1508
1509 var eventCorpse = eventPlayer.GetCorpse();
1510 if (eventCorpse != null)
1511 UnityEngine.Object.Destroy(eventCorpse);
1512
1513 if (eventPlayer.inEvent)
1514 {
1515 ResetMetabolism(player);
1516
1517 if (eventPlayer.isLeaving)
1518 {
1519 var restoreData = Restoration.GetPlayerData(player.userID);
1520 if (restoreData != null)
1521 {
1522 Vector3 homePos = new Vector3(restoreData.x, restoreData.y, restoreData.z);
1523 MovePosition(player, homePos);
1524 }
1525 else player.Respawn();
1526 }
1527 else
1528 {
1529 var targetpos = GetSpawnPosition(player);
1530 if (targetpos == null)
1531 {
1532 LeaveEvent(player);
1533 return;
1534 }
1535 player.MovePosition((Vector3)targetpos);
1536 player.ClientRPCPlayer(null, player, "ForcePositionTo", (Vector3)targetpos);
1537 player.SendNetworkUpdateImmediate();
1538 try { player.ClearEntityQueue(null); } catch { }
1539 }
1540
1541 eventPlayer.isRespawning = false;
1542 eventPlayer.isDead = false;
1543
1544 if (_Started && !eventPlayer.isLeaving)
1545 Interface.Oxide.CallHook("OnEventPlayerSpawn", player);
1546
1547 //eventPlayer.RemoveFromNetwork(1.8f);
1548 }
1549 else Restoration.RestorePlayer(player);
1550 }
1551 void ResetMetabolism(BasePlayer player)
1552 {
1553 player.SetPlayerFlag(BasePlayer.PlayerFlags.Wounded, false);
1554 player.metabolism.calories.value = player.metabolism.calories.max;
1555 player.metabolism.hydration.value = player.metabolism.hydration.max;
1556 player.metabolism.bleeding.value = 0;
1557 player.metabolism.radiation_level.value = 0;
1558 player.metabolism.radiation_poison.value = 0;
1559 player.metabolism.SendChangesToClient();
1560 }
1561
1562 #region Death UI
1563 LootableCorpse SpawnCorpse(BasePlayer player)
1564 {
1565 LootableCorpse lootableCorpse = player.DropCorpse("assets/prefabs/player/player_corpse.prefab") as LootableCorpse;
1566 if (lootableCorpse)
1567 {
1568 lootableCorpse.TakeFrom(player.inventory.containerMain, player.inventory.containerWear, player.inventory.containerBelt);
1569 lootableCorpse.playerName = player.displayName;
1570 lootableCorpse.playerSteamID = player.userID;
1571 lootableCorpse.transform.position = player.transform.position + Vector3.up;
1572 lootableCorpse.TakeChildren(player);
1573 lootableCorpse.Spawn();
1574 player.MovePosition(new Vector3(player.transform.position.x, -10, player.transform.position.z));
1575 return lootableCorpse;
1576 }
1577 return null;
1578 }
1579 public void AddRespawnTimer(BasePlayer player, string attackerName, bool isWave, bool showMsg = true)
1580 {
1581 var eventPlayer = GetUser(player);
1582 if (eventPlayer == null) return;
1583
1584 string message = string.Empty;
1585 if (string.IsNullOrEmpty(attackerName)) message = msg("suicide");
1586 else message = string.Format(msg("deathBy"), attackerName);
1587
1588 if (configData.Options.DropCorpseOnDeath)
1589 {
1590 var corpse = SpawnCorpse(player);
1591 if (corpse != null)
1592 eventPlayer.SetCorpse(corpse.gameObject.AddComponent<EventCorpse>());
1593 }
1594
1595 if (showMsg)
1596 DeathMessageUI(player, message);
1597
1598 player.inventory.Strip();
1599
1600 if (configData.Options.UseSpectateMode)
1601 StartSpectating(eventPlayer);
1602 else UnnetworkPlayer(eventPlayer);
1603
1604 if (isWave)
1605 DeathTimerUI(player, _WaveTimer.GetTime(), true);
1606 else
1607 DeathTimerUI(player, _Event.RespawnTimer, false);
1608 }
1609 void DeathMessageUI(BasePlayer player, string message)
1610 {
1611 var container = UI.CreateElementContainer(DeathUI, "0 0 0 0", "0.25 0.4", "0.75 0.6", false);
1612 UI.CreateLabel(ref container, DeathUI, "", message, 26, "0 0", "1 1");
1613 CuiHelper.AddUi(player, container);
1614 }
1615 void DeathTimerUI(BasePlayer player, int time, bool wave)
1616 {
1617 int timeRemaining = time;
1618 var canStartTimer = Interface.CallHook("FreezeRespawn", player);
1619 if (canStartTimer is bool && (bool)canStartTimer)
1620 {
1621 var deathMessage = Interface.CallHook("GetRespawnMsg");
1622 var container = UI.CreateElementContainer(TimerUI, "0 0 0 0", "0.25 0.7", "0.75 0.85", false);
1623 UI.CreateOutLineLabel(ref container, TimerUI, "0 0 0 1", deathMessage is string ? (string)deathMessage : msg("respawnWait"), 26, "1.0 1.0", "0 0", "1 1");
1624 CuiHelper.AddUi(player, container);
1625 }
1626 else
1627 {
1628 if (RespawnTimers.ContainsKey(player.userID))
1629 {
1630 RespawnTimers[player.userID].Destroy();
1631 RespawnTimers.Remove(player.userID);
1632 }
1633 RespawnTimers.Add(player.userID, timer.Repeat(1, timeRemaining + 1, () =>
1634 {
1635 CuiHelper.DestroyUi(player, TimerUI);
1636 if (timeRemaining <= 0)
1637 {
1638 CuiHelper.DestroyUi(player, "EMUI_Panel");
1639 EndRespawnScreen(player);
1640 return;
1641 }
1642 string message = msg("respawnTime");
1643 if (wave) message = msg("respawnWave");
1644 var container = UI.CreateElementContainer(TimerUI, "0 0 0 0", "0.25 0.7", "0.75 0.85", false);
1645 UI.CreateOutLineLabel(ref container, TimerUI, "0 0 0 1", string.Format(message, timeRemaining), 26, "1.0 1.0", "0 0", "1 1");
1646 CuiHelper.AddUi(player, container);
1647 timeRemaining--;
1648 }));
1649 }
1650 if (configData.Options.AllowClassSelectionOnDeath && _Event.UseClassSelector)
1651 EMInterface.DeathClassSelector(player);
1652 }
1653 void EndRespawnScreen(BasePlayer player)
1654 {
1655 if (RespawnTimers.ContainsKey(player.userID))
1656 {
1657 RespawnTimers[player.userID].Destroy();
1658 RespawnTimers.Remove(player.userID);
1659 }
1660 CuiHelper.DestroyUi(player, DeathUI);
1661 CuiHelper.DestroyUi(player, TimerUI);
1662
1663 if (configData.Options.UseSpectateMode)
1664 EndSpectating(player);
1665 else NetworkPlayer(player);
1666
1667 ResetPlayer(player);
1668 }
1669 public void RespawnAllPlayers()
1670 {
1671 foreach (EventPlayer eventPlayer in EventPlayers)
1672 {
1673 if (eventPlayer.isDead || eventPlayer.isRespawning)
1674 {
1675 EndRespawnScreen(eventPlayer.GetPlayer());
1676 }
1677 }
1678 }
1679 #endregion
1680
1681 #region Spectate
1682 void StartSpectating(EventPlayer eventPlayer)
1683 {
1684 var player = eventPlayer.GetPlayer();
1685 if (!eventPlayer.isSpectating)
1686 {
1687 eventPlayer.isSpectating = true;
1688 eventPlayer.isRespawning = true;
1689
1690 player.inventory.Strip();
1691 player.playerFlags = player.playerFlags | BasePlayer.PlayerFlags.Spectating;
1692 player.gameObject.SetLayerRecursive(10);
1693 player.CancelInvoke("MetabolismUpdate");
1694 player.CancelInvoke("InventoryUpdate");
1695 player.spectateFilter = "@123nofilter123";
1696
1697 if (configData.Options.DropCorpseOnDeath)
1698 {
1699 var entity = eventPlayer?.GetCorpse()?.entity;
1700 if (entity != null)
1701 {
1702 player.ClearEntityQueue(null);
1703 player.gameObject.Identity();
1704 player.SetParent(entity, 0);
1705 }
1706 }
1707 }
1708 }
1709 void EndSpectating(BasePlayer player)
1710 {
1711 var eventPlayer = GetUser(player);
1712 if (eventPlayer == null) return;
1713 if (eventPlayer.isSpectating)
1714 {
1715 if (configData.Options.DropCorpseOnDeath)
1716 {
1717 var parentEnt = player.GetParentEntity();
1718 if (parentEnt != null)
1719 parentEnt.RemoveChild(player);
1720 player.parentEntity.Set(null);
1721 player.parentBone = 0;
1722 }
1723 player.playerFlags = player.playerFlags & ~BasePlayer.PlayerFlags.Spectating;
1724 player.gameObject.SetLayerRecursive(17);
1725 eventPlayer.isSpectating = false;
1726 player.InvokeRepeating("InventoryUpdate", 1f, 0.1f * UnityEngine.Random.Range(0.99f, 1.01f));
1727 eventPlayer.isRespawning = false;
1728 }
1729 }
1730
1731 void UnnetworkPlayer(EventPlayer eventPlayer)
1732 {
1733 var player = eventPlayer.GetPlayer();
1734 if (!eventPlayer.isSpectating)
1735 {
1736 eventPlayer.isSpectating = true;
1737 eventPlayer.isRespawning = true;
1738 eventPlayer.RemoveFromNetwork(0);
1739 }
1740 }
1741 void NetworkPlayer(BasePlayer player)
1742 {
1743 var eventPlayer = GetUser(player);
1744 if (eventPlayer == null) return;
1745 if (eventPlayer.isSpectating)
1746 {
1747 eventPlayer.AddToNetwork();
1748 eventPlayer.isSpectating = false;
1749 eventPlayer.isRespawning = false;
1750 }
1751 }
1752 #endregion
1753 #endregion
1754
1755 #region Kit Management
1756 public void GivePlayerKit(BasePlayer player, string kit)
1757 {
1758 player.inventory.Strip();
1759 if (_Started)
1760 {
1761 if (_Event.UseClassSelector)
1762 {
1763 if (string.IsNullOrEmpty(player.GetComponent<EventPlayer>().currentClass))
1764 {
1765 EMInterface.CloseMap(player);
1766 EMInterface.CreateMenuMain(player);
1767 EMInterface.ClassSelector(player);
1768 }
1769 else timer.In(1, () => GiveClassKit(player));
1770 }
1771 else timer.In(1, ()=> GiveKit(player, kit));
1772 }
1773 }
1774 private void GiveKit(BasePlayer player, string kitname)
1775 {
1776 Kits?.Call("GiveKit", player, kitname);
1777 Interface.CallHook("OnEventKitGiven", player);
1778 Interface.CallHook("OnPlayerSelectClass", player); // Temp fix for headquarters
1779 }
1780 private void GiveClassKit(BasePlayer player)
1781 {
1782 GiveKit(player, player.GetComponent<EventPlayer>().currentClass);
1783 }
1784 #endregion
1785
1786 #region Zone Management
1787 void OnExitZone(string zoneId, BasePlayer player)
1788 {
1789 var eventPlayer = GetUser(player);
1790 if (eventPlayer == null) return;
1791 if (eventPlayer.isDead) return;
1792 if (!_Prestarting && _Started && zoneId.Equals(_Event.ZoneID))
1793 {
1794 eventPlayer.OOB = true;
1795 if (!KillTimers.ContainsKey(player.userID))
1796 {
1797 SendReply(player, msg("oobMsg").Replace("{MsgColor}", configData.Messaging.MsgColor).Replace("{MainColor}", configData.Messaging.MainColor));
1798 int time = 10;
1799 KillTimers.Add(player.userID, timer.Repeat(1, time, () =>
1800 {
1801 if (eventPlayer.OOB)
1802 {
1803 time--;
1804 SendReply(player, msg("oobMsg2").Replace("{MsgColor}", configData.Messaging.MsgColor).Replace("{MainColor}", configData.Messaging.MainColor).Replace("{time}", time.ToString()), false);
1805
1806 if (time == 0)
1807 {
1808 Effect.server.Run("assets/prefabs/tools/c4/effects/c4_explosion.prefab", player.transform.position);
1809 if (!eventPlayer.isDead)
1810 OnPlayerDeath(player, new HitInfo());
1811 PopupMessage(msg("oobMsg3").Replace("{MsgColor}", configData.Messaging.MsgColor).Replace("{MainColor}", configData.Messaging.MainColor).Replace("{playerName}", player.displayName));
1812 }
1813 }
1814 }));
1815 }
1816 }
1817 }
1818 void OnEnterZone(string zoneID, BasePlayer player)
1819 {
1820 var eventPlayer = GetUser(player);
1821 if (eventPlayer == null) return;
1822 if (!_Prestarting && _Started && zoneID.Equals(_Event.ZoneID))
1823 {
1824 eventPlayer.OOB = false;
1825 if (KillTimers.ContainsKey(player.userID))
1826 {
1827 KillTimers[player.userID].Destroy();
1828 KillTimers.Remove(player.userID);
1829 }
1830 }
1831 }
1832 #endregion
1833
1834 #region Player Event Management
1835 void EnableGod() => _GodEnabled = true;
1836 void DisableGod() => _GodEnabled = false;
1837
1838 public object JoinEvent(BasePlayer player)
1839 {
1840 var notNull = GetUser(player);
1841 if ((notNull && EventPlayers.Contains(notNull)) || Joiners.Contains(player))
1842 return msg("alreadyJoined");
1843
1844 object success = Interface.Oxide.CallHook("CanEventJoin", player);
1845 if (success is string)
1846 return (string)success;
1847
1848 killLifestory.Invoke(player, null);
1849
1850 UpdateName(player);
1851
1852 if (_Started)
1853 {
1854 player.inventory.crafting.CancelAll(true);
1855 var eventPlayer = player.gameObject.AddComponent<EventPlayer>();
1856 EventPlayers.Add(eventPlayer);
1857 BroadcastToChat(string.Format(msg("successJoined"), player.displayName, EventPlayers.Count));
1858 if (SetEventPlayer(eventPlayer))
1859 {
1860 HasJoinedEvent(player);
1861 Interface.Oxide.CallHook("OnEventJoinPost", player);
1862 TeleportPlayerToEvent(player);
1863 AddStats(player, StatType.Played);
1864 CreateScoreboard(player);
1865 }
1866 }
1867 else
1868 {
1869 Joiners.Add(player);
1870 BroadcastToChat(string.Format(msg("successJoined"), player.displayName, Joiners.Count));
1871 if (_Launched && _Open && !_Pending && Joiners.Count >= _Event.MinimumPlayers)
1872 {
1873 int timerStart = EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List[_AutoEventNum].TimeToStart;
1874 BroadcastToChat(string.Format(msg("reachedMinPlayers"), _EventName, timerStart));
1875
1876 _Pending = true;
1877 DestroyTimers();
1878 timer.Once(timerStart, () => StartEvent());
1879 }
1880 }
1881 return true;
1882 }
1883 public object LeaveEvent(BasePlayer player)
1884 {
1885 if (!_Started)
1886 {
1887 if (!Joiners.Contains(player))
1888 return msg("notInEvent");
1889 Joiners.Remove(player);
1890 BroadcastToChat(string.Format(msg("leftEvent"), player.displayName, Joiners.Count));
1891 }
1892 else
1893 {
1894 var eventPlayer = GetUser(player);
1895 if (eventPlayer == null || !EventPlayers.Contains(eventPlayer))
1896 return msg("notInEvent");
1897
1898 eventPlayer.isLeaving = true;
1899
1900 Interface.Oxide.CallHook("OnEventLeavePre");
1901 Interface.Oxide.CallHook("DisableBypass", player.userID);
1902
1903 if (!_Ended || _Started)
1904 BroadcastToChat(string.Format(msg("leftEvent"), player.displayName, (EventPlayers.Count - 1)));
1905
1906 Restoration.LeaveLoop(player);
1907 }
1908 return true;
1909 }
1910
1911 void CreateEventPlayers()
1912 {
1913 if (Joiners.Count > 0)
1914 {
1915 var player = Joiners[0];
1916 if (player != null)
1917 {
1918 if (GetUser(player) != null)
1919 {
1920 UnityEngine.Object.DestroyImmediate(player.GetComponent<EventPlayer>());
1921 CreateEventPlayers();
1922 return;
1923 }
1924 player.inventory.crafting.CancelAll(true);
1925 var eventPlayer = player.gameObject.AddComponent<EventPlayer>();
1926 EventPlayers.Add(eventPlayer);
1927
1928 if (SetEventPlayer(eventPlayer))
1929 {
1930 HasJoinedEvent(player);
1931 Joiners.Remove(player);
1932
1933 Interface.Oxide.CallHook("OnEventJoinPost", player);
1934
1935 if (!string.IsNullOrEmpty(_Event.ZoneID))
1936 ZoneManager?.Call("AddPlayerToZoneWhitelist", _Event.ZoneID, player);
1937
1938 TeleportPlayerToEvent(player);
1939 AddStats(player, StatType.Played);
1940 }
1941 }
1942 CreateEventPlayers();
1943 }
1944 else WaitToStart();
1945 }
1946 void WaitToStart()
1947 {
1948 if (configData.Options.UseEventPrestart && configData.Options.EventPrestartTimer > 0)
1949 {
1950 int remaining = configData.Options.EventPrestartTimer;
1951 EventTimers.Add(timer.Repeat(1, configData.Options.EventPrestartTimer, () =>
1952 {
1953 remaining--;
1954 if (remaining < 1)
1955 {
1956 foreach(var eventPlayer in EventPlayers)
1957 {
1958 var player = eventPlayer.GetPlayer();
1959 ResetMetabolism(player);
1960
1961 var targetpos = GetSpawnPosition(player);
1962 if (targetpos == null)
1963 {
1964 LeaveEvent(player);
1965 return;
1966 }
1967 player.MovePosition((Vector3)targetpos);
1968 player.ClientRPCPlayer(null, player, "ForcePositionTo", (Vector3)targetpos);
1969 player.SendNetworkUpdateImmediate();
1970 try { player.ClearEntityQueue(null); } catch { }
1971 }
1972 EventBegin();
1973 return;
1974 }
1975 popupQueue.Add(new MessageData(string.Format(msg("eventBeginIn"), remaining), 1, ""));
1976 UpdateMessages();
1977 }));
1978 }
1979 else
1980 EventBegin();
1981 }
1982 void EventBegin()
1983 {
1984 if (_Event.RespawnType == RespawnType.Waves)
1985 _WaveTimer = new GameObject().AddComponent<WaveTimer>();
1986 _Prestarting = false;
1987 DisableGod();
1988 Interface.Oxide.CallHook("OnEventStartPost");
1989 }
1990 bool SetEventPlayer(EventPlayer eventPlayer)
1991 {
1992 var player = eventPlayer.GetPlayer();
1993
1994 Interface.Oxide.CallHook("EnableBypass", player.userID);
1995 eventPlayer.enabled = true;
1996 eventPlayer.inEvent = true;
1997 Restoration.StorePlayer(player);
1998
1999 object lockInv = Interface.CallHook("LockingPlayerInventory", player);
2000
2001 if (EventGames[_Event.EventType].LockClothing && !player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked))
2002 {
2003 player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, true);
2004 lockInv = true;
2005 }
2006
2007 if (lockInv is bool && (bool)lockInv)
2008 player.inventory.SendSnapshot();
2009 return true;
2010 }
2011 #endregion
2012
2013 #region Event Methods
2014 public object RegisterEventGame(string name, EventSetting eventSettings, Events defaultConfig)
2015 {
2016 if (!EventGames.ContainsKey(name))
2017 EventGames.Add(name, eventSettings);
2018
2019 var success = ValidateEvent(defaultConfig);
2020 if (success is string)
2021 {
2022 PrintError($"Error generating a default event config for game: {name}\n{(string)success}");
2023 }
2024 else ValidEvents.Add($"{name} Default", defaultConfig);
2025
2026 Puts(string.Format("Registered event game: {0}", name));
2027
2028 return true;
2029 }
2030 void OnEventStartPost()
2031 {
2032 DestroyTimers();
2033 if (_Launched)
2034 OnEventStartPostAutoEvent();
2035 if (_Event.GameMode == GameMode.Battlefield && !_TimerStarted)
2036 StartTimer(configData.Options.Battlefield_Timer);
2037 if (configData.Messaging.AnnounceEvent_During)
2038 EventTimers.Add(timer.Repeat(configData.Messaging.AnnounceEvent_Interval, 0, () => AnnounceDuringEvent()));
2039 }
2040 void OnEventStartPostAutoEvent()
2041 {
2042 if (EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List[_AutoEventNum].TimeLimit != 0)
2043 StartTimer(EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List[_AutoEventNum].TimeLimit * 60);
2044 }
2045 object CanEventStart()
2046 {
2047 if (_Event.EventType == null) return msg("noEventSet");
2048 if (EventGames[_Event.EventType].RequiresSpawns && string.IsNullOrEmpty(_Event.Spawnfile))
2049 return msg("noSpawnsSet");
2050 if (EventGames[_Event.EventType].RequiresMultipleSpawns && string.IsNullOrEmpty(_Event.Spawnfile2))
2051 return msg("noSpawnsSet");
2052 return _Started ? msg("alreadyStarted") : null;
2053 }
2054 object CanEventJoin(BasePlayer player)
2055 {
2056 if (!_Open) return msg("isClosed");
2057
2058 if (!_Started && _Event.MaximumPlayers != 0)
2059 if (Joiners.Count >= _Event.MaximumPlayers)
2060 return string.Format(msg("reachedMaxPlayers"), _EventName);
2061
2062 if (_Started && _Event.MaximumPlayers != 0)
2063 if (EventPlayers.Count >= _Event.MaximumPlayers)
2064 return string.Format(msg("reachedMaxPlayers"), _EventName);
2065
2066 return null;
2067 }
2068 void OnEventEndPost()
2069 {
2070 if (_Launched)
2071 AutoEventNext();
2072 }
2073 void DestroyGame()
2074 {
2075 Unsubscribe(nameof(OnEntityTakeDamage));
2076 Unsubscribe(nameof(OnPlayerAttack));
2077 Unsubscribe(nameof(OnItemPickup));
2078 Unsubscribe(nameof(CanNetworkTo));
2079 Unsubscribe(nameof(CanLootPlayer));
2080 Unsubscribe(nameof(OnCreateWorldProjectile));
2081
2082 DestroyTimers();
2083 EventPlayers.Clear();
2084 Joiners.Clear();
2085 DisableGod();
2086 SpawnCount.SetSpawnCount(true, -1);
2087 SpawnCount.SetSpawnCount(false, -1);
2088 _Open = false;
2089 _Pending = false;
2090 _Destoyed = true;
2091
2092 var players = UnityEngine.Object.FindObjectsOfType<EventPlayer>();
2093 if (players != null)
2094 foreach (var gameObj in players)
2095 UnityEngine.Object.DestroyImmediate(gameObj);
2096
2097 var corpses = UnityEngine.Object.FindObjectsOfType<EventCorpse>();
2098 if (corpses != null)
2099 foreach (var gameObj in corpses)
2100 UnityEngine.Object.DestroyImmediate(gameObj);
2101 }
2102 void StartTimer(int time)
2103 {
2104 if (_GameTimer != null || _TimerStarted) return;
2105 _GameTimer = new GameObject().AddComponent<GameTimer>();
2106 _GameTimer.StartTimer(time);
2107 }
2108 void DestroyTimers()
2109 {
2110 if (_GameTimer != null)
2111 {
2112 UnityEngine.Object.Destroy(_GameTimer);
2113 _GameTimer = null;
2114 _TimerStarted = false;
2115 }
2116 if (_WaveTimer != null)
2117 {
2118 UnityEngine.Object.Destroy(_WaveTimer);
2119 }
2120
2121 foreach (Timer eventtimer in EventTimers)
2122 eventtimer.Destroy();
2123 EventTimers.Clear();
2124
2125 foreach (var eventtimer in RespawnTimers)
2126 eventtimer.Value.Destroy();
2127 RespawnTimers.Clear();
2128 }
2129 #endregion
2130
2131 #region Player Restoration
2132 private RestoreStorage DataStorage;
2133 class RestoreStorage
2134 {
2135 public Dictionary<ulong, RestoreInfo> playerData = new Dictionary<ulong, RestoreInfo>();
2136 }
2137 class RestorationManager
2138 {
2139 public Dictionary<ulong, RestoreInfo> playerData = new Dictionary<ulong, RestoreInfo>();
2140 private int restoreNum;
2141 private int restoreCycles;
2142 private bool restoreStarted;
2143
2144 #region Storage
2145 public void StorePlayer(BasePlayer player)
2146 {
2147 RestoreInfo info = new RestoreInfo
2148 {
2149 inventory = new Dictionary<InventoryType, List<EventInvItem>>
2150 {
2151 {InventoryType.Belt, GetItems(player.inventory.containerBelt).ToList() },
2152 {InventoryType.Main, GetItems(player.inventory.containerMain).ToList() },
2153 {InventoryType.Wear, GetItems(player.inventory.containerWear).ToList() }
2154 },
2155 health = player.Health(),
2156 calories = player.metabolism.calories.value,
2157 hydration = player.metabolism.hydration.value,
2158 x = player.transform.position.x,
2159 y = player.transform.position.y,
2160 z = player.transform.position.z
2161 };
2162 if (!playerData.ContainsKey(player.userID))
2163 playerData.Add(player.userID, info);
2164 else playerData[player.userID] = info;
2165 }
2166 public RestoreInfo GetPlayerData(ulong playerId)
2167 {
2168 RestoreInfo returnData;
2169 if (playerData.TryGetValue(playerId, out returnData))
2170 return returnData;
2171 return null;
2172 }
2173 public void RemovePlayer(ulong playerId)
2174 {
2175 //LogInfo("- Removing player restore data");
2176 if (playerData.ContainsKey(playerId))
2177 playerData.Remove(playerId);
2178 }
2179 public bool HasPendingRestore(ulong playerId) => playerData.ContainsKey(playerId);
2180 private IEnumerable<EventInvItem> GetItems(ItemContainer container)
2181 {
2182 return container.itemList.Select(item => new EventInvItem
2183 {
2184 itemid = item.info.itemid,
2185 amount = item.amount,
2186 ammo = (item.GetHeldEntity() as BaseProjectile)?.primaryMagazine.contents ?? 0,
2187 ammotype = (item.GetHeldEntity() as BaseProjectile)?.primaryMagazine.ammoType.shortname ?? null,
2188 skin = item.skin,
2189 condition = item.condition,
2190 instanceData = item.instanceData ?? null,
2191 contents = item.contents?.itemList.Select(item1 => new EventInvItem
2192 {
2193 itemid = item1.info.itemid,
2194 amount = item1.amount,
2195 condition = item1.condition
2196 }).ToArray()
2197 });
2198 }
2199 #endregion
2200
2201 #region Restoration
2202 public void StartRestoration()
2203 {
2204 if (restoreStarted)
2205 return;
2206 restoreStarted = true;
2207
2208 //LogInfo("--------------------------");
2209 //LogInfo("Initiating restore loop");
2210 restoreNum = 0;
2211 restoreCycles = 0;
2212 RestoreLoop();
2213 }
2214 public void LeaveLoop(BasePlayer player, int attempts = 0)
2215 {
2216 if (player != null)
2217 {
2218 if (attempts > 3)
2219 {
2220 player.DieInstantly();
2221 var eventPlayer = ins.GetUser(player);
2222 if (eventPlayer != null)
2223 {
2224 ins.EventPlayers.Remove(eventPlayer);
2225 UnityEngine.Object.DestroyImmediate(eventPlayer);
2226 }
2227 Interface.CallHook("OnEventLeavePost", player);
2228 return;
2229 }
2230 if (ReadyToRestore(player))
2231 {
2232 if (RestorePlayer(player))
2233 {
2234 Interface.CallHook("OnEventLeavePost", player);
2235 return;
2236 }
2237 }
2238 ins.timer.In(5, ()=> LeaveLoop(player, ++attempts));
2239 return;
2240 }
2241 }
2242 private void RestoreLoop()
2243 {
2244 //LogInfo("Running restore loop cycle");
2245 if (ins.EventPlayers.Count > 0)
2246 {
2247 //LogInfo($"- There are still {ins.EventPlayers.Count} players awaiting restoration");
2248 if (restoreNum > ins.EventPlayers.Count - 1)
2249 {
2250 //LogInfo("- Cycle complete, restarting loop");
2251 restoreCycles++;
2252 if (restoreCycles > 4)
2253 {
2254 //LogInfo($"- Cycle limit reached, destroying {ins.EventPlayers.Count} players without restoration");
2255 foreach (var eventPlayer in ins.EventPlayers)
2256 {
2257 var eplayer = eventPlayer?.GetPlayer();
2258 if (eplayer != null)
2259 {
2260 eplayer?.DieInstantly();
2261 ins.SendReply(eplayer, msg("failedRestore", eplayer));
2262 }
2263 }
2264 restoreStarted = false;
2265 ins.FinalizeGameEnd();
2266 return;
2267 }
2268 restoreNum = 0;
2269 --restoreCycles;
2270 ins.timer.In(5, () => RestoreLoop());
2271 return;
2272 }
2273 //LogInfo("- Finding next player to restore");
2274 var player = ins.EventPlayers[restoreNum]?.GetPlayer();
2275 if (player != null)
2276 {
2277 //LogInfo("-- Player found");
2278 if (ReadyToRestore(player))
2279 {
2280 //LogInfo("-- Player is ready to restore");
2281 if (RestorePlayer(player))
2282 {
2283 //LogInfo("-- Player restored successfully");
2284 RestoreLoop();
2285 return;
2286 }
2287 //LogInfo("-- Player restoration failed");
2288 }
2289 }
2290 else
2291 {
2292 ins.EventPlayers.RemoveAt(restoreNum);
2293 //LogInfo("-- Invalid player found, removing from restore list");
2294 }
2295 restoreNum++;
2296 RestoreLoop();
2297 return;
2298 }
2299 else
2300 {
2301 restoreStarted = false;
2302 ins.FinalizeGameEnd();
2303 }
2304 }
2305 public bool ReadyToRestore(BasePlayer player)
2306 {
2307 //LogInfo($"Checking conditions for restoration: {player.displayName}");
2308 player.inventory.Strip();
2309 UnlockInventory(player);
2310 DestroyUI(player);
2311
2312 var eventPlayer = ins.GetUser(player);
2313 if (eventPlayer != null)
2314 {
2315 //LogInfo("- EventPlayer component found");
2316 eventPlayer.AddToNetwork();
2317 if (eventPlayer.isRespawning)
2318 {
2319 //LogInfo("- EPlayer is respawning");
2320 ins.EndRespawnScreen(player);
2321 return false;
2322 }
2323 if (eventPlayer.isDead)
2324 {
2325 //LogInfo("- EPlayer is dead");
2326 ins.ResetPlayer(player);
2327 return false;
2328 }
2329 if (eventPlayer.isSpectating)
2330 {
2331 //LogInfo("- EPlayer is spectating");
2332 if (ins.configData.Options.UseSpectateMode)
2333 ins.EndSpectating(player);
2334 else ins.NetworkPlayer(player);
2335 return false;
2336 }
2337 }
2338
2339 if (player.IsSleeping())
2340 {
2341 //LogInfo("- Player is sleeping");
2342 player.EndSleeping();
2343 return false;
2344 }
2345
2346 if (player.HasPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot))
2347 {
2348 //LogInfo("- Player is receiving snapshot");
2349 return false;
2350 }
2351
2352 if (player.IsDead() || !player.IsAlive())
2353 {
2354 //LogInfo("- Player is dead");
2355 var spawnPos = ins.GetSpawnPosition(player);
2356 if (spawnPos is Vector3)
2357 player.RespawnAt((Vector3)spawnPos, new Quaternion());
2358 else player.Respawn();
2359 return false;
2360 }
2361
2362 if (player.IsWounded() || player.health < 1)
2363 {
2364 //LogInfo("- Player is wounded");
2365 player.metabolism.Reset();
2366 player.SetPlayerFlag(BasePlayer.PlayerFlags.Wounded, false);
2367 return false;
2368 }
2369 //LogInfo("* Player is ready for restoration *");
2370 return true;
2371 }
2372 public bool RestorePlayer(BasePlayer player)
2373 {
2374 if (player == null) return true;
2375 //LogInfo($"Attempting to restore player: {player.displayName}");
2376 var restoreData = GetPlayerData(player.userID);
2377 if (restoreData == null)
2378 {
2379 //LogInfo("- No restoration data found");
2380 return true;
2381 }
2382 var eventPlayer = ins.GetUser(player);
2383 if (eventPlayer != null)
2384 {
2385 //LogInfo("- Found EventPlayer component");
2386 eventPlayer.restoreAttempts++;
2387 if (eventPlayer.restoreAttempts > 3)
2388 {
2389 //LogInfo("- Player has reached maximum restore attempts. Destroying player");
2390 ins.EventPlayers.Remove(eventPlayer);
2391 UnityEngine.Object.DestroyImmediate(eventPlayer);
2392 player.RespawnAt(new Vector3(restoreData.x, restoreData.y, restoreData.z), new Quaternion());
2393 return true;
2394 }
2395
2396 if (eventPlayer.GetCorpse() != null)
2397 UnityEngine.Object.Destroy(eventPlayer.GetCorpse());
2398 }
2399
2400 player.RemoveFromTriggers();
2401 RemoveFromZone(player);
2402 RestorePlayerStats(player, restoreData.health, restoreData.hydration, restoreData.calories);
2403
2404 if (!RestoreInventory(player, restoreData))
2405 return false;
2406
2407 SendPlayerHome(player, new Vector3(restoreData.x, restoreData.y, restoreData.z));
2408
2409 if (eventPlayer != null)
2410 DestroyEventPlayer(eventPlayer);
2411
2412 RemovePlayer(player.userID);
2413 return true;
2414 }
2415 private void UnlockInventory(BasePlayer player)
2416 {
2417 LogInfo("- Unlocking inventory");
2418 if (player.inventory.containerWear.HasFlag(ItemContainer.Flag.IsLocked))
2419 player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, false);
2420
2421 if (player.inventory.containerBelt.HasFlag(ItemContainer.Flag.IsLocked))
2422 player.inventory.containerBelt.SetFlag(ItemContainer.Flag.IsLocked, false);
2423
2424 if (player.inventory.containerMain.HasFlag(ItemContainer.Flag.IsLocked))
2425 player.inventory.containerMain.SetFlag(ItemContainer.Flag.IsLocked, false);
2426
2427 player.inventory.SendSnapshot();
2428 }
2429 private void DestroyUI(BasePlayer player)
2430 {
2431 LogInfo("- Destroying game UI");
2432 ins.DestroyScoreboard(player);
2433 ins.DestroyPopupUI(player);
2434 CuiHelper.DestroyUi(player, ClockUI);
2435 CuiHelper.DestroyUi(player, DeathUI);
2436 CuiHelper.DestroyUi(player, TimerUI);
2437 CuiHelper.DestroyUi(player, TimerUI);
2438 CuiHelper.DestroyUi(player, "EMUI_Panel");
2439 }
2440 private void RemoveFromZone(BasePlayer player)
2441 {
2442 LogInfo("- Removing player from zone lists");
2443 string zoneId = ins._Event.ZoneID;
2444 if (!string.IsNullOrEmpty(zoneId))
2445 ins.ZoneManager?.Call("RemovePlayerFromZoneWhitelist", zoneId, player);
2446
2447 Interface.Oxide.CallHook("DisableBypass", player.userID);
2448 }
2449 private void RestorePlayerStats(BasePlayer player, float health, float hydration, float calories)
2450 {
2451 LogInfo("- Restoring player statistics");
2452 player.metabolism.Reset();
2453 player.health = health;
2454 player.metabolism.calories.value = calories;
2455 player.metabolism.hydration.value = hydration;
2456 player.metabolism.bleeding.value = 0;
2457 player.metabolism.SendChangesToClient();
2458 }
2459 private void SendPlayerHome(BasePlayer player, Vector3 position)
2460 {
2461 //LogInfo("- Sending player home");
2462 ins.MovePosition(player, position);
2463 }
2464 private bool RestoreInventory(BasePlayer player, RestoreInfo info)
2465 {
2466 LogInfo("- Attempting to restore player inventory");
2467 if (RestoreItems(player, info, InventoryType.Belt) && RestoreItems(player, info, InventoryType.Main) && RestoreItems(player, info, InventoryType.Wear))
2468 return true;
2469 else
2470 {
2471 LogInfo("- Inventory restoration failed");
2472 player.inventory.Strip();
2473 return false;
2474 }
2475 }
2476 private bool RestoreItems(BasePlayer player, RestoreInfo info, InventoryType type)
2477 {
2478 ItemContainer container = type == InventoryType.Belt ? player.inventory.containerBelt : type == InventoryType.Wear ? player.inventory.containerWear : player.inventory.containerMain;
2479
2480 for (int i = 0; i < container.capacity; i++)
2481 {
2482 var existingItem = container.GetSlot(i);
2483 if (existingItem != null)
2484 {
2485 existingItem.RemoveFromContainer();
2486 existingItem.Remove(0f);
2487 }
2488 if (info.inventory[type].Count > i)
2489 {
2490 var itemData = info.inventory[type][i];
2491 var item = ItemManager.CreateByItemID(itemData.itemid, itemData.amount, itemData.skin);
2492 item.condition = itemData.condition;
2493 if (itemData.instanceData != null)
2494 item.instanceData = itemData.instanceData;
2495
2496 var weapon = item.GetHeldEntity() as BaseProjectile;
2497 if (weapon != null)
2498 {
2499 if (!string.IsNullOrEmpty(itemData.ammotype))
2500 weapon.primaryMagazine.ammoType = ItemManager.FindItemDefinition(itemData.ammotype);
2501 weapon.primaryMagazine.contents = itemData.ammo;
2502 }
2503 if (itemData.contents != null)
2504 {
2505 foreach (var contentData in itemData.contents)
2506 {
2507 var newContent = ItemManager.CreateByItemID(contentData.itemid, contentData.amount);
2508 if (newContent != null)
2509 {
2510 newContent.condition = contentData.condition;
2511 newContent.MoveToContainer(item.contents);
2512 }
2513 }
2514 }
2515 item.position = i;
2516 item.SetParent(container);
2517 }
2518 }
2519 if (container.itemList.Count == info.inventory[type].Count)
2520 return true;
2521 return false;
2522 }
2523 private void DestroyEventPlayer(EventPlayer eventPlayer)
2524 {
2525 //LogInfo("- Destroying event player");
2526 eventPlayer.inEvent = false;
2527 eventPlayer.enabled = false;
2528 ins.EventPlayers.Remove(eventPlayer);
2529 UnityEngine.Object.DestroyImmediate(eventPlayer);
2530 }
2531 #endregion
2532 }
2533
2534 static void LogInfo(string info)
2535 {
2536 if (ins.debugEnabled)
2537 ins.LogToFile("debug", info, ins);
2538 }
2539
2540 [ChatCommand("restoreme")]
2541 void cmdRestoreMe(BasePlayer player, string command, string[] args)
2542 {
2543 if (Restoration.HasPendingRestore(player.userID))
2544 {
2545 if (Restoration.ReadyToRestore(player))
2546 {
2547 if (Restoration.RestorePlayer(player))
2548 {
2549 return;
2550 }
2551 }
2552 SendMsg(player, "restoreFailed");
2553 }
2554 else SendMsg(player, "noRestoreSaved");
2555 }
2556 #endregion
2557
2558 #region AutoEvent Management
2559 public object LaunchEvent()
2560 {
2561 _Launched = true;
2562 if (!_Started)
2563 {
2564 if (!_Open)
2565 {
2566 object success = AutoEventNext();
2567 if (success is string)
2568 return (string)success;
2569
2570 //success = OpenEvent();
2571 //if (success is string)
2572 // return (string)success;
2573 }
2574 else OnEventOpenPostAutoEvent();
2575 }
2576 else OnEventStartPostAutoEvent();
2577 return null;
2578 }
2579 void OnEventOpenPost()
2580 {
2581 if (configData.Messaging.AnnounceEvent)
2582 EventTimers.Add(timer.Repeat(configData.Messaging.AnnounceEvent_Interval, 0, ()=> AnnounceEvent()));
2583
2584 if (_Launched && EMInterface.Event_Config.AutoEvent_Config.AutoCancel)
2585 EventTimers.Add(timer.Once(EMInterface.Event_Config.AutoEvent_Config.AutoCancel_Timer * 60, () => { CloseEvent(); AutoEventNext(); }));
2586 }
2587 void OnEventOpenPostAutoEvent()
2588 {
2589 if (!_Launched) return;
2590 DestroyTimers();
2591 var autocfg = EMInterface.Event_Config.AutoEvent_Config;
2592 if (autocfg.AutoCancel_Timer != 0)
2593 EventTimers.Add(timer.Once(autocfg.AutoCancel_Timer, () => CancelEvent(msg("notEnoughPlayers"))));
2594 if (configData.Messaging.AnnounceEvent)
2595 EventTimers.Add(timer.Repeat(configData.Messaging.AnnounceEvent_Interval, 0, () => AnnounceEvent()));
2596 }
2597 object AutoEventNext()
2598 {
2599 if (ValidAutoEvents.Count == 0)
2600 {
2601 _Launched = false;
2602 return msg("noAuto");
2603 }
2604 var nextAutoNum = -1;
2605 if (_ForceNextConfig)
2606 {
2607 nextAutoNum = _NextEventNum;
2608 _ForceNextConfig = false;
2609 _NextConfigName = string.Empty;
2610 }
2611 else
2612 {
2613 if (_RandomizeAuto)
2614 nextAutoNum = UnityEngine.Random.Range(0, ValidAutoEvents.Count - 1);
2615 else
2616 {
2617 nextAutoNum = _AutoEventNum + 1;
2618 if (nextAutoNum > ValidAutoEvents.Count - 1)
2619 nextAutoNum = 0;
2620 }
2621 }
2622
2623 var autocfg = ValidAutoEvents[nextAutoNum];
2624 if (autocfg != null)
2625 {
2626 if (EMInterface.Event_Config.Event_List.ContainsKey(autocfg.EventConfig))
2627 {
2628 _Event = EMInterface.Event_Config.Event_List[autocfg.EventConfig];
2629 _CurrentEventConfig = autocfg.EventConfig;
2630 _AutoEventNum = nextAutoNum;
2631 }
2632 else return $"{msg("errorAutoFind")} {autocfg.EventConfig}";
2633 }
2634 EMInterface.EventVoting.OpenEventVoting(true);
2635 EventTimers.Add(timer.Once(EMInterface.Event_Config.AutoEvent_Config.GameInterval * 60, () => OpenEvent()));
2636 return null;
2637 }
2638
2639 public void ValidateAllEvents()
2640 {
2641 PrintWarning("--- Validating all event configs and auto events ---");
2642 ValidEvents.Clear();
2643 if (EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List.Count == 0)
2644 {
2645 PrintError("No auto events found!");
2646 return;
2647 }
2648 for (int i = 0; i < EMInterface.Event_Config.Event_List.Count; i++)
2649 {
2650 var eventcfg = EMInterface.Event_Config.Event_List.Keys.ToList()[i];
2651 if (EMInterface.Event_Config.Event_List.ContainsKey(eventcfg))
2652 {
2653 var success = ValidateEvent(EMInterface.Event_Config.Event_List[eventcfg]);
2654 if (success is string)
2655 {
2656 PrintError((string)success);
2657 }
2658 else ValidEvents.Add(eventcfg, EMInterface.Event_Config.Event_List[eventcfg]);
2659 }
2660 }
2661 for (int i = 0; i < EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List.Count; i++)
2662 {
2663 var autocfg = EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List[i];
2664 var success = ValidateAutoEvent(autocfg, i);
2665 if (success is string)
2666 PrintError((string)success);
2667 else
2668 {
2669 if (ValidEvents.ContainsKey(autocfg.EventConfig))
2670 ValidAutoEvents.Add(autocfg);
2671 }
2672 }
2673 PrintWarning("--- Finished event validation ---");
2674 if (ValidAutoEvents.Count > 0 && configData.Options.LaunchAutoEventsOnStartup)
2675 LaunchEvent();
2676 }
2677 private object ValidateAutoEvent(EMInterface.AEConfig autocfg, int number)
2678 {
2679 var errorList = new List<string>();
2680 if (autocfg != null)
2681 {
2682 if (string.IsNullOrEmpty(autocfg.EventConfig) || !ValidEvents.ContainsKey(autocfg.EventConfig))
2683 {
2684 EMInterface.Event_Config.AutoEvent_Config.AutoEvent_List.Remove(autocfg);
2685 return $"No valid event config selected for autoevent : #{number}";
2686 }
2687 else return null;
2688 }
2689 return $"AutoEvent config is null: #{number}";
2690 }
2691 public object ValidateEvent(Events eventcfg)
2692 {
2693 var errorList = new List<string>();
2694 if (eventcfg != null)
2695 {
2696 if (!EventGames.ContainsKey(eventcfg.EventType))
2697 errorList.Add($"Event game not registered: {eventcfg.EventType}");
2698 else
2699 {
2700 if (EventGames[eventcfg.EventType].RequiresSpawns)
2701 {
2702 if (string.IsNullOrEmpty(eventcfg.Spawnfile))
2703 errorList.Add("No spawnfile selected");
2704 else if (ValidateSpawnFile(eventcfg.Spawnfile) != null)
2705 errorList.Add("Invalid spawnfile selected");
2706 }
2707
2708 if (EventGames[eventcfg.EventType].RequiresMultipleSpawns)
2709 {
2710 if (string.IsNullOrEmpty(eventcfg.Spawnfile2))
2711 errorList.Add("No secondary spawnfile selected");
2712 else if (ValidateSpawnFile(eventcfg.Spawnfile2) != null)
2713 errorList.Add("Invalid spawnfile selected");
2714 }
2715
2716 if (string.IsNullOrEmpty(eventcfg.ZoneID))
2717 errorList.Add("No Zone ID selected");
2718 else if (ValidateZoneID(eventcfg.ZoneID) != null)
2719 errorList.Add("Invalid Zone ID");
2720
2721 if (EventGames[eventcfg.EventType].RequiresKit)
2722 {
2723 if (!eventcfg.UseClassSelector)
2724 {
2725 if (string.IsNullOrEmpty(eventcfg.Kit))
2726 errorList.Add("No kit selected");
2727 else if (ValidateKit(eventcfg.Kit) != null)
2728 errorList.Add("Invalid kit selected");
2729 }
2730 }
2731
2732 if (eventcfg.MinimumPlayers <= 0)
2733 errorList.Add("Minimum Players must be greater than 0");
2734 }
2735 }
2736 else errorList.Add("Invalid event config selected");
2737
2738 if (errorList.Count > 0)
2739 return errorList.ToSentence();
2740 else return null;
2741 }
2742 #endregion
2743
2744 #region File Validation
2745 public object ValidateSpawnFile(string name)
2746 {
2747 var success = Spawns?.Call("GetSpawnsCount", name);
2748 if (success is string)
2749 return (string)success;
2750 else return null;
2751 }
2752 public object ValidateZoneID(string name)
2753 {
2754 var success = ZoneManager?.Call("CheckZoneID", name);
2755 if (name is string && !string.IsNullOrEmpty((string)name))
2756 return null;
2757 else return msg("zoneNotExist");
2758 }
2759 public object ValidateKit(string name)
2760 {
2761 object success = Kits?.Call("isKit", name);
2762 if ((success is bool))
2763 if (!(bool)success)
2764 return string.Format(msg("kitNotExist"), name);
2765 return null;
2766 }
2767 #endregion
2768
2769 #region Prizes
2770 [HookMethod("AddTokens")]
2771 public void AddTokens(ulong userid, int amount, bool winner = false)
2772 {
2773 if (amount == 0) return;
2774 string tokentype = "";
2775 if (configData.Options.UseEconomicsAsTokens)
2776 {
2777 if (Economics)
2778 {
2779 Economics?.Call("Deposit", userid.ToString(), amount);
2780 tokentype = msg("rewardCoins");
2781 }
2782 }
2783 else if (ServerRewards)
2784 {
2785 ServerRewards?.Call("AddPoints", userid.ToString(), amount);
2786 tokentype = msg("rewardRP");
2787 }
2788 if (winner)
2789 {
2790 if (GameStatistics.Stats.ContainsKey(userid))
2791 GameStatistics.Stats[userid].GamesWon++;
2792 }
2793 BasePlayer player = BasePlayer.FindByID(userid);
2794 if (player != null && !string.IsNullOrEmpty(tokentype))
2795 {
2796 SendReply(player, $"<color={configData.Messaging.MainColor}>{Title}:</color><color={configData.Messaging.MsgColor}> {msg("rewardText")} </color><color={configData.Messaging.MainColor}>{amount} {tokentype}</color>");
2797 }
2798 }
2799 #endregion
2800
2801 #region Statistics
2802 public enum StatType
2803 {
2804 Kills, Deaths, Played, Won, Lost, Flags, Shots, Choppers, Rank
2805 }
2806 public class PlayerStatistics
2807 {
2808 public string Name;
2809 public int Kills;
2810 public int Deaths;
2811 public int GamesPlayed;
2812 public int GamesWon;
2813 public int GamesLost;
2814 public double Score;
2815 public int Rank;
2816 public int FlagsCaptured;
2817 public int ShotsFired;
2818 public int ChoppersKilled;
2819
2820 public PlayerStatistics(string Name)
2821 {
2822 this.Name = Name;
2823 }
2824 }
2825 public class Statistics
2826 {
2827 public Dictionary<ulong, PlayerStatistics> Stats = new Dictionary<ulong, PlayerStatistics>();
2828 public Dictionary<string, int> GamesPlayed = new Dictionary<string, int>();
2829
2830 public string GetTotalKills()
2831 {
2832 int Kills = 0;
2833 foreach (var player in Stats)
2834 Kills += player.Value.Kills;
2835 return Kills.ToString();
2836 }
2837 public string GetTotalDeaths()
2838 {
2839 int Deaths = 0;
2840 foreach (var player in Stats)
2841 Deaths += player.Value.Deaths;
2842 return Deaths.ToString();
2843 }
2844 public string GetTotalGamesPlayed()
2845 {
2846 int GamesPlayed = 0;
2847 foreach (var player in Stats)
2848 if (player.Value.GamesPlayed > GamesPlayed)
2849 GamesPlayed += (player.Value.GamesPlayed - GamesPlayed);
2850 return GamesPlayed.ToString();
2851 }
2852 public string GetTotalShotsFired()
2853 {
2854 int ShotsFired = 0;
2855 foreach (var player in Stats)
2856 ShotsFired += player.Value.ShotsFired;
2857 return ShotsFired.ToString();
2858 }
2859 public string GetFlagsCaptured()
2860 {
2861 int FlagsCaptured = 0;
2862 foreach (var player in Stats)
2863 FlagsCaptured += player.Value.FlagsCaptured;
2864 return FlagsCaptured.ToString();
2865 }
2866 public string GetChoppersKilled()
2867 {
2868 int ChoppersKilled = 0;
2869 foreach (var player in Stats)
2870 ChoppersKilled += player.Value.ChoppersKilled;
2871 return ChoppersKilled.ToString();
2872 }
2873 public string GetTotalPlayers() => Stats.Count.ToString();
2874
2875 public Dictionary<string, int> GetGamesPlayed() => GamesPlayed;
2876 }
2877 private void HasStats(BasePlayer player)
2878 {
2879 if (!StatsCache.ContainsKey(player.userID))
2880 StatsCache.Add(player.userID, new PlayerStatistics(player.displayName));
2881 }
2882 private void UpdateName(BasePlayer player)
2883 {
2884 HasStats(player);
2885 if (StatsCache[player.userID].Name != player.displayName)
2886 StatsCache[player.userID].Name = player.displayName;
2887 }
2888 public void AddStats(BasePlayer player, StatType type, int amount = 1)
2889 {
2890 HasStats(player);
2891 switch (type)
2892 {
2893 case StatType.Kills:
2894 StatsCache[player.userID].Kills += amount;
2895 return;
2896 case StatType.Deaths:
2897 StatsCache[player.userID].Deaths += amount;
2898 return;
2899 case StatType.Played:
2900 StatsCache[player.userID].GamesPlayed += amount;
2901 return;
2902 case StatType.Won:
2903 StatsCache[player.userID].GamesWon += amount;
2904 return;
2905 case StatType.Lost:
2906 StatsCache[player.userID].GamesLost += amount;
2907 return;
2908 case StatType.Flags:
2909 StatsCache[player.userID].FlagsCaptured += amount;
2910 return;
2911 case StatType.Shots:
2912 StatsCache[player.userID].ShotsFired += amount;
2913 return;
2914 case StatType.Choppers:
2915 StatsCache[player.userID].ChoppersKilled += amount;
2916 return;
2917 case StatType.Rank:
2918 StatsCache[player.userID].Rank = amount;
2919 return;
2920 }
2921 }
2922 public int GetStats(BasePlayer player, StatType type)
2923 {
2924 HasStats(player);
2925 switch (type)
2926 {
2927 case StatType.Kills:
2928 return StatsCache[player.userID].Kills;
2929 case StatType.Deaths:
2930 return StatsCache[player.userID].Deaths;
2931 case StatType.Played:
2932 return StatsCache[player.userID].GamesPlayed;
2933 case StatType.Won:
2934 return StatsCache[player.userID].GamesWon;
2935 case StatType.Lost:
2936 return StatsCache[player.userID].GamesLost;
2937 case StatType.Flags:
2938 return StatsCache[player.userID].FlagsCaptured;
2939 case StatType.Shots:
2940 return StatsCache[player.userID].ShotsFired;
2941 case StatType.Choppers:
2942 return StatsCache[player.userID].ChoppersKilled;
2943 case StatType.Rank:
2944 return StatsCache[player.userID].Rank;
2945 }
2946 return 0;
2947 }
2948 void CalculateRanks()
2949 {
2950 foreach (var eplayer in StatsCache)
2951 {
2952 int score = 0;
2953 if (eplayer.Value.Kills > 0) score += eplayer.Value.Kills * 2;
2954 if (eplayer.Value.GamesWon > 0) score += eplayer.Value.GamesWon * 2;
2955 score -= eplayer.Value.Deaths;
2956 score += eplayer.Value.GamesPlayed;
2957 score -= eplayer.Value.GamesLost;
2958 eplayer.Value.Score = score;
2959 }
2960 var sortedPlayers = StatsCache.OrderByDescending(x => x.Value.Score).ToList();
2961 foreach (var eplayer in sortedPlayers)
2962 {
2963 StatsCache[eplayer.Key].Rank = sortedPlayers.IndexOf(eplayer) + 1;
2964 }
2965 SaveStatistics();
2966 }
2967 void SaveStatistics()
2968 {
2969 GameStatistics.Stats = StatsCache;
2970 P_Stats.WriteObject(GameStatistics);
2971 Puts("Saved player statistics");
2972 }
2973 void SaveRestoreInfo()
2974 {
2975 DataStorage.playerData = Restoration.playerData;
2976 RestoreData.WriteObject(DataStorage);
2977 }
2978 void LoadData()
2979 {
2980 try
2981 {
2982 GameStatistics = P_Stats.ReadObject<Statistics>();
2983 StatsCache = GameStatistics.Stats;
2984 }
2985 catch
2986 {
2987 Puts("Couldn't load player statistics, creating new datafile");
2988 GameStatistics = new Statistics();
2989 }
2990 try
2991 {
2992 DataStorage = RestoreData.ReadObject<RestoreStorage>();
2993 Restoration.playerData = DataStorage.playerData;
2994 }
2995 catch
2996 {
2997 DataStorage = new RestoreStorage();
2998 }
2999 }
3000 #endregion
3001
3002 #region API
3003 [HookMethod("isPlaying")]
3004 public bool isPlaying(BasePlayer player)
3005 {
3006 if (GetUser(player) != null) return true;
3007 if (Joiners.Contains(player)) return true;
3008 return false;
3009 }
3010 [HookMethod("GetUserClass")]
3011 public string GetUserClass(BasePlayer player)
3012 {
3013 var eventPlayer = GetUser(player);
3014 if (eventPlayer != null)
3015 return eventPlayer.currentClass;
3016 return null;
3017 }
3018
3019 [HookMethod("GetUserStats")]
3020 public JObject GetUserStats(string userId)
3021 {
3022 ulong playerId;
3023 if (ulong.TryParse(userId, out playerId))
3024 {
3025 if (!StatsCache.ContainsKey(playerId)) return null;
3026 var obj = new JObject();
3027 var stats = StatsCache[playerId];
3028 obj["ChoppersKilled"] = stats.ChoppersKilled;
3029 obj["Deaths"] = stats.Deaths;
3030 obj["FlagsCaptured"] = stats.FlagsCaptured;
3031 obj["GamesLost"] = stats.GamesLost;
3032 obj["GamesPlayed"] = stats.GamesPlayed;
3033 obj["GamesWon"] = stats.GamesWon;
3034 obj["Kills"] = stats.Kills;
3035 obj["Name"] = stats.Name;
3036 obj["Rank"] = stats.Rank;
3037 obj["Score"] = stats.Score;
3038 obj["ShotsFired"] = stats.ShotsFired;
3039 return obj;
3040 }
3041 else return null;
3042 }
3043
3044 [HookMethod("GetAllStats")]
3045 public JObject GetAllStats()
3046 {
3047 var obj = new JObject();
3048 foreach(var player in StatsCache)
3049 {
3050 var stats = new JObject();
3051 stats["ChoppersKilled"] = player.Value.ChoppersKilled;
3052 stats["Deaths"] = player.Value.Deaths;
3053 stats["FlagsCaptured"] = player.Value.FlagsCaptured;
3054 stats["GamesLost"] = player.Value.GamesLost;
3055 stats["GamesPlayed"] = player.Value.GamesPlayed;
3056 stats["GamesWon"] = player.Value.GamesWon;
3057 stats["Kills"] = player.Value.Kills;
3058 stats["Rank"] = player.Value.Rank;
3059 stats["Name"] = player.Value.Name;
3060 stats["Score"] = player.Value.Score;
3061 stats["ShotsFired"] = player.Value.ShotsFired;
3062 obj[player.Key] = stats;
3063 }
3064 return obj;
3065 }
3066
3067 [HookMethod("GetGamesPlayed")]
3068 public JObject GetGamesPlayed()
3069 {
3070 var obj = new JObject();
3071 foreach (var game in GameStatistics.GamesPlayed)
3072 {
3073 obj[game.Key] = game.Value;
3074 }
3075 return obj;
3076 }
3077
3078 [HookMethod("GetGameStats")]
3079 public JObject GetGameStats()
3080 {
3081 var obj = new JObject();
3082 obj["ChoppersKilled"] = GameStatistics.GetChoppersKilled();
3083 obj["FlagsCaptured"] = GameStatistics.GetFlagsCaptured();
3084 obj["TotalDeaths"] = GameStatistics.GetTotalDeaths();
3085 obj["TotalGamesPlayed"] = GameStatistics.GetTotalGamesPlayed();
3086 obj["TotalKills"] = GameStatistics.GetTotalKills();
3087 obj["TotalPlayers"] = GameStatistics.GetTotalPlayers();
3088 obj["TotalShotsFired"] = GameStatistics.GetTotalShotsFired();
3089 return obj;
3090 }
3091
3092 void HasJoinedEvent(BasePlayer player) => Interface.CallHook("JoinedEvent", player);
3093 void HasLeftEvent(BasePlayer player) => Interface.CallHook("LeftEvent", player);
3094 #endregion
3095
3096 #region External Hooks
3097 private object canRedeemKit(BasePlayer player)
3098 {
3099 if (GetUser(player) != null && _Started) { return msg("noKits"); }
3100 return null;
3101 }
3102 private object CanTeleport(BasePlayer player)
3103 {
3104 if (GetUser(player) != null && _Started) { return msg("noTP"); }
3105 return null;
3106 }
3107 private object canRemove(BasePlayer player)
3108 {
3109 if (GetUser(player) != null && _Started) { return msg("noRemove"); }
3110 return null;
3111 }
3112 private object canShop(BasePlayer player)
3113 {
3114 if (GetUser(player) != null && _Started) { return msg("noShop"); }
3115 return null;
3116 }
3117 private object CanTrade(BasePlayer player)
3118 {
3119 if (GetUser(player) != null && _Started) { return msg("noTrade"); }
3120 return null;
3121 }
3122 #endregion
3123
3124 public Events DefaultConfig = new Events
3125 {
3126 CloseOnStart = false,
3127 DisableItemPickup = false,
3128 UseClassSelector = false,
3129 EventType = string.Empty,
3130 GameMode = GameMode.Normal,
3131 Kit = string.Empty,
3132 MaximumPlayers = 0,
3133 MinimumPlayers = 2,
3134 Spawnfile = string.Empty,
3135 SpawnType = SpawnType.Consecutive,
3136 ZoneID = string.Empty,
3137 RespawnType = RespawnType.None,
3138 RespawnTimer = 10,
3139 EnemiesToSpawn = 0,
3140 GameRounds = 1,
3141 ScoreLimit = 10,
3142 Spawnfile2 = string.Empty,
3143 WeaponSet = string.Empty
3144 };
3145
3146 #region Localization
3147 Dictionary<string, string> Messages = new Dictionary<string, string>
3148 {
3149 { "Title", "Event Manager: "},
3150 { "reachedMinPlayers", "The event {0} has reached min players and will start in {1} seconds"},
3151 { "reachedMaxPlayers", "The event {0} has reached max players. You may not join for the moment"},
3152 { "eventBegin", "{0} is about to begin!"},
3153 { "leftEvent", "{0} has left the Event! (Total Players: {1})"},
3154 { "successJoined", "{0} has joined the Event! (Total Players: {1})"},
3155 { "alreadyJoined", "You are already in the Event."},
3156 { "restoringPlayers", "{0} is now over, restoring players and sending them home!"},
3157 { "MessagesEventEnd", "All players respawned, {0} has ended!"},
3158 { "noGamePlaying", "An event game is not underway."},
3159 { "eventCancel", "The event was cancelled!"},
3160 { "eventClose", "The event entrance is now closed!"},
3161 { "noEventSet", "An event config must first be chosen."},
3162 { "noSpawnsSet", "You must select spawnfiles for this event"},
3163 { "eventAlreadyClosed", "The event is already closed."},
3164 { "alreadyStarted", "An event game has already started."},
3165 { "notEnoughPlayers", "Not enough players" },
3166 { "noAuto", "No automatic events configured" },
3167 { "noAutoInit", "No events were successfully initialized, check that your events are correctly configured" },
3168 { "TimeLimit", "Time limit reached" },
3169 { "EventCancelled", "Event {0} was cancelled because: {1}" },
3170 { "eventOpen", "{0} is now open, you can join it by typing /event join" },
3171 { "stillOpen", "{0} is still open for contestants! You can join it by typing /event join" },
3172 { "isClosed", "The event is currently closed." },
3173 { "notInEvent", "You are not currently in the event." },
3174 { "kitNotExist", "The kit {0} doesn't exist" },
3175 {"zoneNotExist", "Invalid Zone ID" },
3176 { "CancelAuto", "Auto events have been cancelled" },
3177 {"ItemPickup", "Item pickup has been disabled during this event!" },
3178 {"NoLooting", "Looting has been disabled during this event!" },
3179 {"noEvent", "Unable to find a event called: {0}" },
3180 {"isAlreadyStarted", "An event is already underway" },
3181 {"noneSelected", "No event has been selected" },
3182 {"noTypeSelected", "No event type has been selected" },
3183 {"isAlreadyOpen","{0} is already open" },
3184 {"cantOpen", "This game type can not be opened once it has started" },
3185 {"Battlefield", "Battlefield" },
3186 {"tpError", "There was a error sending you to the event. Please try again" },
3187 {"a Helicopter","a Helicopter" },
3188 {"a AutoTurret","a AutoTurret" },
3189 {"suicide", "You killed yourself..." },
3190 {"deathBy", "You were killed by {0}" },
3191 {"respawnWait","Waiting to respawn" },
3192 {"respawnTime", "Respawning in {0} seconds..." },
3193 {"respawnWave", "The next wave spawns in {0} seconds..." },
3194 {"oobMsg", "<color={MsgColor}>You have</color> <color={MainColor}>10</color><color={MsgColor}> seconds to return to the arena</color>" },
3195 {"oobMsg2", "<color={MainColor}>{time}</color><color={MsgColor}> seconds</color>" },
3196 {"oobMsg3", "<color={MainColor}>{playerName}</color><color={MsgColor}> tried to run away...</color>" },
3197 {"errorAutoFind", "Error finding event config:" },
3198 {"rewardText", "You have been awarded" },
3199 {"rewardCoins", "Coins" },
3200 {"rewardRP", "RP" },
3201 {"noKits", "You may not redeem a kit in the arena" },
3202 {"noTP", "You may not teleport in the arena" },
3203 {"noRemove", "You may not use the remover tool in the arena" },
3204 {"noShop", "You can not use the store in the arena" },
3205 {"restoreSuccess", "You have successfully been restored" },
3206 {"noTrade", "You can not trade in the arena" },
3207 {"failedRestore", "An attempt to restore your previous state was unsuccessful. You can opt to manually restore at anytime by typing \"/restoreme\"" },
3208 {"noRestoreSaved", "You do not have any pending restore data" },
3209 {"restoreFailed", "Unable to restore you at this time as all requirements have not been met. Please try again shortly" },
3210 {"eventBeginIn", "The event will start in {0} seconds!" }
3211 };
3212 #endregion
3213 }
3214}