· 5 years ago · Nov 22, 2020, 10:42 PM
1#define USE_HTN_HOOK
2//#define DEBUG
3
4using Facepunch;
5using Facepunch.Math;
6using Network;
7using Newtonsoft.Json;
8using Oxide.Core;
9using Oxide.Core.Libraries.Covalence;
10using Oxide.Core.Plugins;
11using Oxide.Game.Rust;
12using Oxide.Game.Rust.Cui;
13using Oxide.Game.Rust.Libraries;
14using Rust;
15using Rust.Ai;
16using System;
17using System.Collections;
18using System.Collections.Generic;
19using System.Diagnostics;
20using System.Globalization;
21using System.Linq;
22using System.Text;
23using System.Text.RegularExpressions;
24using UnityEngine;
25using UnityEngine.AI;
26using UnityEngine.SceneManagement;
27using static NPCPlayerApex;
28
29namespace Oxide.Plugins
30{
31 [Info("Raidable Bases", "nivex", "1.5.21")]
32 [Description("Create fully automated raidable bases with npcs.")]
33 class RaidableBases : RustPlugin
34 {
35 [PluginReference]
36 private Plugin DangerousTreasures, Vanish, LustyMap, ZoneManager, Economics, ServerRewards, Map, GUIAnnouncements, CopyPaste, Friends, Clans, Kits, TruePVE, Spawns, NightLantern, Wizardry, NextGenPVE;
37
38 protected static SingletonBackbone Backbone { get; set; }
39 protected RotationCycle Cycle { get; set; } = new RotationCycle();
40 public Dictionary<int, List<BaseEntity>> Bases { get; } = new Dictionary<int, List<BaseEntity>>();
41 public Dictionary<int, RaidableBase> Raids { get; } = new Dictionary<int, RaidableBase>();
42 public Dictionary<BaseEntity, RaidableBase> RaidEntities { get; } = new Dictionary<BaseEntity, RaidableBase>();
43 public Dictionary<int, RaidableBase> Indices { get; set; } = new Dictionary<int, RaidableBase>();
44 public Dictionary<ulong, RaidableBase> Npcs { get; set; } = new Dictionary<ulong, RaidableBase>();
45 protected Dictionary<ulong, DelaySettings> PvpDelay { get; } = new Dictionary<ulong, DelaySettings>();
46 private Dictionary<string, List<ulong>> Skins { get; } = new Dictionary<string, List<ulong>>();
47 private Dictionary<string, HashSet<ulong>> WorkshopSkins { get; } = new Dictionary<string, HashSet<ulong>>();
48 private Dictionary<MonumentInfo, float> monuments { get; set; } = new Dictionary<MonumentInfo, float>();
49 private Dictionary<Vector3, float> managedZones { get; set; } = new Dictionary<Vector3, float>();
50 private Dictionary<int, MapInfo> mapMarkers { get; set; } = new Dictionary<int, MapInfo>();
51 private Dictionary<int, string> lustyMarkers { get; set; } = new Dictionary<int, string>();
52 protected Dictionary<RaidableType, RaidableSpawns> raidSpawns { get; set; } = new Dictionary<RaidableType, RaidableSpawns>();
53 protected Dictionary<string, float> buyCooldowns { get; set; } = new Dictionary<string, float>();
54 public StoredData storedData { get; set; } = new StoredData();
55 protected Coroutine despawnCoroutine { get; set; }
56 protected Coroutine maintainCoroutine { get; set; }
57 protected Coroutine scheduleCoroutine { get; set; }
58 protected Coroutine gridCoroutine { get; set; }
59 private Stopwatch gridStopwatch { get; } = new Stopwatch();
60 private StringBuilder _sb { get; } = new StringBuilder();
61 protected const float Radius = 25f;
62 private bool wiped { get; set; }
63 private float lastSpawnRequestTime { get; set; }
64 private float gridTime { get; set; }
65 private bool IsUnloading { get; set; }
66 private int _maxOnce { get; set; }
67 private List<string> tryBuyCooldowns { get; set; } = new List<string>();
68 private ItemComparerer itemComparer { get; } = new ItemComparerer();
69
70 public class BuildingProfile
71 {
72 public BuildingOptions Options;
73 public string Name;
74 }
75
76 public class DelaySettings
77 {
78 public Timer Timer;
79 public bool AllowPVP;
80 }
81
82 private enum HandledResult
83 {
84 Allowed,
85 Blocked,
86 None
87 }
88
89 public enum RaidableType
90 {
91 None,
92 Manual,
93 Scheduled,
94 Purchased,
95 Maintained,
96 Grid
97 }
98
99 public enum RaidableMode
100 {
101 Disabled = -1,
102 Easy = 0,
103 Medium = 1,
104 Hard = 2,
105 Expert = 3,
106 Nightmare = 4,
107 Random = 9999
108 }
109
110 public class ResourcePath
111 {
112 public List<uint> Blocks { get; }
113 public List<uint> TrueDamage { get; }
114 public ItemDefinition BoxDefinition { get; }
115 public string ExplosionMarker { get; }
116 public string CodeLock { get; }
117 public string FireballSmall { get; }
118 public string Fireball { get; }
119 public string HighExternalWoodenWall { get; }
120 public string HighExternalStoneWall { get; }
121 public string Ladder { get; }
122 public string Murderer { get; }
123 public string RadiusMarker { get; }
124 public string Sphere { get; }
125 public string Scientist { get; }
126 public string VendingMarker { get; }
127
128 public ResourcePath()
129 {
130 Blocks = new List<uint> { 803699375, 2194854973, 919059809, 3531096400, 310235277, 2326657495, 3234260181, 72949757, 1745077396, 1585379529 };
131 TrueDamage = new List<uint> { 976279966, 3824663394, 1202834203, 4254045167, 1745077396, 1585379529 };
132 BoxDefinition = ItemManager.FindItemDefinition(StringPool.Get(2735448871));
133 CodeLock = StringPool.Get(3518824735);
134 ExplosionMarker = StringPool.Get(4060989661);
135 FireballSmall = StringPool.Get(2086405370);
136 Fireball = StringPool.Get(3369311876);
137 HighExternalWoodenWall = StringPool.Get(1745077396);
138 HighExternalStoneWall = StringPool.Get(1585379529);
139 Ladder = StringPool.Get(2150203378);
140 Murderer = StringPool.Get(3879041546);
141 RadiusMarker = StringPool.Get(2849728229);
142 Sphere = StringPool.Get(3211242734);
143 Scientist = StringPool.Get(4223875851);
144 VendingMarker = StringPool.Get(3459945130);
145 }
146 }
147
148 public class SingletonBackbone : SingletonComponent<SingletonBackbone>
149 {
150 public RaidableBases Plugin { get; private set; }
151 public ResourcePath Path { get; private set; }
152 public Oxide.Core.Libraries.Lang lang => Plugin.lang;
153 private StringBuilder sb => Plugin._sb;
154 public StoredData Data => Plugin.storedData;
155 public List<FinalDestination> Destinations { get; set; }
156 public string Easy { get; set; }
157 public string Medium { get; set; }
158 public string Hard { get; set; }
159 public string Expert { get; set; }
160 public string Nightmare { get; set; }
161
162 public SingletonBackbone(RaidableBases plugin)
163 {
164 Plugin = plugin;
165 Path = new ResourcePath();
166 Destinations = new List<FinalDestination>();
167 Easy = RemoveFormatting(GetMessage("ModeEasy")).ToLower();
168 Medium = RemoveFormatting(GetMessage("ModeMedium")).ToLower();
169 Hard = RemoveFormatting(GetMessage("ModeHard")).ToLower();
170 Expert = RemoveFormatting(GetMessage("ModeExpert")).ToLower();
171 Nightmare = RemoveFormatting(GetMessage("ModeNightmare")).ToLower();
172 }
173
174 public void Destroy()
175 {
176 Path = null;
177 Plugin = null;
178 DestroyImmediate(Instance);
179 }
180
181 public void Message(BasePlayer player, string key, string id, params object[] args)
182 {
183 if (player.IsValid())
184 {
185 Plugin.Player.Message(player, GetMessage(key, id, args), _config.Settings.ChatID);
186 }
187 }
188
189 public string GetMessage(string key, string id = null, params object[] args)
190 {
191 sb.Length = 0;
192
193 if (_config.EventMessages.Prefix && id != null && id != "server_console" && !key.EndsWith("Flag"))
194 {
195 sb.Append(lang.GetMessage("Prefix", Plugin, id));
196 }
197
198 sb.Append(id == "server_console" || id == null ? RemoveFormatting(lang.GetMessage(key, Plugin, id)) : lang.GetMessage(key, Plugin, id));
199
200 return args.Length > 0 ? string.Format(sb.ToString(), args) : sb.ToString();
201 }
202
203 public string RemoveFormatting(string source) => source.Contains(">") ? Regex.Replace(source, "<.*?>", string.Empty) : source;
204
205 public Timer Timer(float seconds, Action action) => Plugin.timer.Once(seconds, action);
206
207 public bool HasPermission(string id, string perm) => Plugin.permission.UserHasPermission(id, perm);
208 }
209
210 public class Elevation
211 {
212 public float Min { get; set; }
213 public float Max { get; set; }
214 }
215
216 public class RaidableSpawnLocation
217 {
218 public Elevation Elevation = new Elevation();
219 public Vector3 Location = Vector3.zero;
220 }
221
222 public class RaidableSpawns
223 {
224 private readonly List<RaidableSpawnLocation> Spawns = new List<RaidableSpawnLocation>();
225 private readonly List<RaidableSpawnLocation> Cache = new List<RaidableSpawnLocation>();
226
227 public void Add(RaidableSpawnLocation rsl)
228 {
229 Spawns.Add(rsl);
230 }
231
232 public void TryAddRange()
233 {
234 if (Cache.Count > 0)
235 {
236 Spawns.AddRange(new List<RaidableSpawnLocation>(Cache));
237 Cache.Clear();
238 }
239 }
240
241 public IEnumerable<RaidableSpawnLocation> All
242 {
243 get
244 {
245 if (Cache.Count > 0)
246 {
247 var list = new List<RaidableSpawnLocation>();
248
249 list.AddRange(Spawns);
250 list.AddRange(Cache);
251
252 return list;
253 }
254 else return Spawns;
255 }
256 }
257
258 public void Check()
259 {
260 if (Spawns.Count == 0)
261 {
262 TryAddRange();
263 }
264 }
265
266 public void Clear()
267 {
268 Spawns.Clear();
269 Cache.Clear();
270 }
271
272 public int Count
273 {
274 get
275 {
276 return Spawns.Count;
277 }
278 }
279
280 public RaidableSpawnLocation GetRandom()
281 {
282 var rsl = Spawns.GetRandom();
283
284 Remove(rsl);
285
286 return rsl;
287 }
288
289 private void Remove(RaidableSpawnLocation a)
290 {
291 Spawns.Remove(a);
292 Cache.Add(a);
293 }
294
295 public void RemoveNear(RaidableSpawnLocation a, float radius)
296 {
297 var list = new List<RaidableSpawnLocation>(Spawns);
298
299 foreach (var b in list)
300 {
301 if (InRange(a.Location, b.Location, radius))
302 {
303 Remove(b);
304 }
305 }
306
307 list.Clear();
308 }
309
310 public RaidableSpawns(List<RaidableSpawnLocation> spawns)
311 {
312 Spawns = spawns;
313 }
314
315 public RaidableSpawns()
316 {
317
318 }
319 }
320
321 private class MapInfo
322 {
323 public string Url;
324 public string IconName;
325 public Vector3 Position;
326 }
327
328 public class PlayerInfo
329 {
330 public int TotalRaids { get; set; }
331 public int Raids { get; set; }
332 public PlayerInfo() { }
333 }
334
335 public class Lockout
336 {
337 public double Easy { get; set; }
338 public double Medium { get; set; }
339 public double Hard { get; set; }
340 public double Expert { get; set; }
341 public double Nightmare { get; set; }
342
343 public bool Any() => Easy > 0 || Medium > 0 || Hard > 0;
344 }
345
346 public class RotationCycle
347 {
348 private Dictionary<RaidableMode, List<string>> Buildings = new Dictionary<RaidableMode, List<string>>();
349
350 public void Add(RaidableType type, RaidableMode mode, string key)
351 {
352 if (!_config.Settings.Management.RequireAllSpawned || type == RaidableType.Grid || type == RaidableType.Manual)
353 {
354 return;
355 }
356
357 List<string> keyList;
358 if (!Buildings.TryGetValue(mode, out keyList))
359 {
360 Buildings[mode] = keyList = new List<string>();
361 }
362
363 if (!keyList.Contains(key))
364 {
365 keyList.Add(key);
366 }
367 }
368
369 public bool CanSpawn(RaidableType type, RaidableMode mode, string key)
370 {
371 if (mode == RaidableMode.Disabled)
372 {
373 return false;
374 }
375
376 if (!_config.Settings.Management.RequireAllSpawned || mode == RaidableMode.Random || type == RaidableType.Grid || type == RaidableType.Manual || TryClear(type, mode))
377 {
378 return true;
379 }
380
381 List<string> keyList;
382 if (!Buildings.TryGetValue(mode, out keyList))
383 {
384 return true;
385 }
386
387 return !keyList.Contains(key);
388 }
389
390 public bool TryClear(RaidableType type, RaidableMode mode)
391 {
392 List<string> keyList;
393 if (!Buildings.TryGetValue(mode, out keyList))
394 {
395 return false;
396 }
397
398 foreach (var building in _config.RaidableBases.Buildings)
399 {
400 if (building.Value.Mode != mode || !CanSpawnDifficultyToday(mode))
401 {
402 continue;
403 }
404
405 if (!CanSpawnDifficultyToday(mode) || MustExclude(type, building.Value.AllowPVP))
406 {
407 continue;
408 }
409
410 foreach (var kvp in building.Value.AdditionalBases)
411 {
412 if (!keyList.Contains(kvp.Key) && FileExists(kvp.Key))
413 {
414 return false;
415 }
416 }
417
418 if (!keyList.Contains(building.Key) && FileExists(building.Key))
419 {
420 return false;
421 }
422 }
423
424 keyList.Clear();
425 return true;
426 }
427 }
428
429 public class StoredData
430 {
431 public Dictionary<string, Lockout> Lockouts { get; } = new Dictionary<string, Lockout>();
432 public Dictionary<string, PlayerInfo> Players { get; set; } = new Dictionary<string, PlayerInfo>();
433 public Dictionary<string, UI.Info> UI { get; set; } = new Dictionary<string, UI.Info>();
434 public string RaidTime { get; set; } = DateTime.MinValue.ToString();
435 public int TotalEvents { get; set; }
436 public StoredData() { }
437 }
438
439 private class PlayWithFire : FacepunchBehaviour
440 {
441 private FireBall fireball { get; set; }
442 private BaseEntity target { get; set; }
443 private bool fireFlung { get; set; }
444 private Coroutine mcCoroutine { get; set; }
445
446 public BaseEntity Target
447 {
448 get
449 {
450 return target;
451 }
452 set
453 {
454 target = value;
455 enabled = true;
456 }
457 }
458
459 private void Awake()
460 {
461 fireball = GetComponent<FireBall>();
462 enabled = false;
463 }
464
465 private void FixedUpdate()
466 {
467 if (!IsValid(target) || target.Health() <= 0)
468 {
469 fireball.Extinguish();
470 Destroy(this);
471 return;
472 }
473
474 fireball.transform.RotateAround(target.transform.position, Vector3.up, 5f);
475 fireball.transform.hasChanged = true;
476 }
477
478 public void FlingFire(BaseEntity attacker)
479 {
480 if (fireFlung) return;
481 fireFlung = true;
482 mcCoroutine = StartCoroutine(MakeContact(attacker));
483 }
484
485 private IEnumerator MakeContact(BaseEntity attacker)
486 {
487 float distance = Vector3.Distance(fireball.ServerPosition, attacker.transform.position);
488
489 while (!Backbone.Plugin.IsUnloading && attacker != null && fireball != null && !fireball.IsDestroyed && !InRange(fireball.ServerPosition, attacker.transform.position, 2.5f))
490 {
491 fireball.ServerPosition = Vector3.MoveTowards(fireball.ServerPosition, attacker.transform.position, distance * 0.1f);
492 yield return Coroutines.WaitForSeconds(0.3f);
493 }
494 }
495
496 private void OnDestroy()
497 {
498 if (mcCoroutine != null)
499 {
500 StopCoroutine(mcCoroutine);
501 mcCoroutine = null;
502 }
503
504 Destroy(this);
505 }
506 }
507
508 public class PlayerInputEx : FacepunchBehaviour
509 {
510 public BasePlayer player { get; set; }
511 private InputState input { get; set; }
512 private RaidableBase raid { get; set; }
513
514 public void Setup(BasePlayer player, RaidableBase raid)
515 {
516 this.player = player;
517 this.raid = raid;
518 raid.Inputs[player] = this;
519 input = player.serverInput;
520 InvokeRepeating(Repeater, 0f, 0.1f);
521 }
522
523 private void Repeater()
524 {
525 if (raid == null)
526 {
527 Destroy(this);
528 return;
529 }
530
531 if (!player || !player.IsConnected)
532 {
533 raid?.TryInvokeResetPayLock();
534 Destroy(this);
535 return;
536 }
537
538 if (player.svActiveItemID == 0)
539 {
540 return;
541 }
542
543 if (!input.WasJustReleased(BUTTON.FIRE_PRIMARY) && !input.IsDown(BUTTON.FIRE_PRIMARY))
544 {
545 return;
546 }
547
548 Item item = player.GetActiveItem();
549
550 if (item?.info.shortname != "ladder.wooden.wall")
551 {
552 return;
553 }
554
555 RaycastHit hit;
556 if (!Physics.Raycast(player.eyes.HeadRay(), out hit, 4f, Layers.Mask.Construction, QueryTriggerInteraction.Ignore))
557 {
558 return;
559 }
560
561 var block = hit.GetEntity();
562
563 if (!IsValid(block) || block.OwnerID != 0 || !Backbone.Path.Blocks.Contains(block.prefabID)) // || !RaidableBase.IsEntity(block, raid.BaseIndex)) // walls and foundations
564 {
565 return;
566 }
567
568 int amount = item.amount;
569 var action = new Action(() =>
570 {
571 if (raid == null || item == null || item.amount != amount || IsLadderNear(hit.point))
572 {
573 return;
574 }
575
576 var rot = Quaternion.LookRotation(hit.normal, Vector3.up); // * Quaternion.Euler(0f, 0f, player.eyes.rotation.eulerAngles.z);
577 var e = GameManager.server.CreateEntity(Backbone.Path.Ladder, hit.point, rot, true); // as BaseLadder;
578
579 if (e == null)
580 {
581 Destroy(this);
582 return;
583 }
584
585 var planner = item.GetHeldEntity() as Planner;
586 //var euler = planner.transform.eulerAngles;
587
588 //e.transform.Rotate(euler);
589 e.gameObject.SendMessage("SetDeployedBy", player, SendMessageOptions.DontRequireReceiver);
590 e.OwnerID = 0;
591 e.Spawn();
592 item.UseItem(1);
593
594 if (planner != null)
595 {
596 var deployable = planner.GetDeployable();
597
598 if (deployable != null && deployable.setSocketParent && block.SupportsChildDeployables())
599 {
600 e.SetParent(block, true, false);
601 }
602 }
603
604 raid.BuiltList.Add(e.net.ID);
605 });
606
607 player.Invoke(action, 0.1f);
608 }
609
610 /*public bool IsLadderNear(Vector3 target)
611 {
612 var list = new List<BaseLadder>();
613
614 Vis.Entities<BaseLadder>(target, 0.3f, list, Layers.Mask.Deployed, QueryTriggerInteraction.Ignore);
615
616 return list.Count > 0;
617 }*/
618
619 private bool IsLadderNear(Vector3 target)
620 {
621 int hits = Physics.OverlapSphereNonAlloc(target, 0.3f, Vis.colBuffer, Layers.Mask.Deployed, QueryTriggerInteraction.Ignore);
622 bool flag = false;
623
624 for (int i = 0; i < hits; i++)
625 {
626 if (Vis.colBuffer[i].name == Backbone.Path.Ladder)
627 {
628 flag = true;
629 }
630
631 Vis.colBuffer[i] = null;
632 }
633
634 return flag;
635 }
636
637 private void OnDestroy()
638 {
639 CancelInvoke();
640 UI.DestroyStatusUI(player);
641 raid?.Inputs?.Remove(player);
642 Destroy(this);
643 }
644 }
645
646 public class FinalDestination : FacepunchBehaviour
647 {
648 public NPCPlayerApex npc;
649 private List<Vector3> positions;
650 public AttackOperator.AttackType attackType;
651 private NpcSettings settings;
652 private bool isRanged;
653 private BasePlayer target;
654
655 private void OnDestroy()
656 {
657 Backbone?.Destinations?.Remove(this);
658 CancelInvoke();
659 Destroy(this);
660 }
661
662 public void Set(NPCPlayerApex npc, List<Vector3> positions, NpcSettings settings)
663 {
664 this.npc = npc;
665 this.positions = positions;
666 this.settings = settings;
667 attackType = IsMelee(npc) ? AttackOperator.AttackType.CloseRange : AttackOperator.AttackType.LongRange;
668 isRanged = attackType != AttackOperator.AttackType.CloseRange;
669
670 Backbone.Destinations.Add(this);
671 InvokeRepeating(Go, 0f, 7.5f);
672 }
673
674 public void Attack(BasePlayer player, bool converge = true)
675 {
676 if (target == player)
677 {
678 return;
679 }
680
681 if (!IsInvoking(Attack))
682 {
683 InvokeRepeating(Attack, 0f, 1f);
684 }
685
686 if (npc.Stats.AggressionRange < 150f)
687 {
688 npc.Stats.AggressionRange += 150f;
689 npc.Stats.DeaggroRange += 100f;
690 }
691
692 npc.AttackTarget = player;
693 npc.lastAttacker = player;
694 npc.AiContext.LastAttacker = player;
695 target = player;
696
697 if (converge)
698 {
699 Converge(player);
700 }
701 }
702
703 private void Attack()
704 {
705 if (npc.AttackTarget == null || !(npc.AttackTarget is BasePlayer))
706 {
707 return;
708 }
709
710 var attacker = npc.AttackTarget as BasePlayer;
711
712 if (attacker.IsDead())
713 {
714 Forget();
715 CancelInvoke(Attack);
716 InvokeRepeating(Go, 0f, 7.5f);
717 return;
718 }
719
720 npc.NeverMove = false;
721 npc.IsStopped = false;
722 npc.RandomMove();
723
724 if (attacker.IsVisible(npc.eyes.position, attacker.eyes.position))
725 {
726 HumanAttackOperator.AttackEnemy(npc.AiContext, attackType);
727 }
728 }
729
730 private void Forget()
731 {
732 npc.Stats.AggressionRange = settings.AggressionRange;
733 npc.Stats.DeaggroRange = settings.AggressionRange * 1.125f;
734 npc.lastDealtDamageTime = Time.time - 21f;
735 npc.SetFact(Facts.HasEnemy, 0, true, true);
736 npc.SetFact(Facts.EnemyRange, 3, true, true);
737 npc.SetFact(Facts.AfraidRange, 1, true, true);
738 npc.AttackTarget = null;
739 npc.lastAttacker = null;
740 npc.lastAttackedTime = Time.time - 31f;
741 npc.LastAttackedDir = Vector3.zero;
742 }
743
744 public void Warp()
745 {
746 var position = positions.GetRandom();
747
748 npc.Pause();
749 npc.ServerPosition = position;
750 npc.GetNavAgent.Warp(position);
751 npc.stuckDuration = 0f;
752 npc.IsStuck = false;
753 npc.Resume();
754 }
755
756 private void Go()
757 {
758 if (npc.IsHeadUnderwater())
759 {
760 npc.Kill();
761 Destroy(this);
762 return;
763 }
764
765 if (npc.AttackTarget == null)
766 {
767 npc.NeverMove = true;
768
769 if (npc.IsStuck)
770 {
771 Warp();
772 }
773
774 var position = positions.GetRandom();
775
776 if (npc.GetNavAgent == null || !npc.GetNavAgent.isOnNavMesh)
777 {
778 npc.finalDestination = position;
779 }
780 else npc.GetNavAgent.SetDestination(position);
781
782 npc.IsStopped = false;
783 npc.Destination = position;
784 }
785 }
786
787 private void Converge(BasePlayer player)
788 {
789 foreach (var fd in Backbone.Destinations)
790 {
791 if (fd != this && fd.npc.IsValid() && fd.npc.AttackTarget == null && fd.npc.IsAlive() && fd.npc.Distance(npc) < 25f)
792 {
793 if (isRanged && fd.attackType == AttackOperator.AttackType.CloseRange) continue;
794 fd.npc.SetFact(Facts.AllyAttackedRecently, 1, true, true);
795 fd.npc.SetFact(Facts.AttackedRecently, 1, true, true);
796 fd.Attack(player, false);
797 fd.Attack();
798 }
799 }
800 }
801
802 private bool IsMelee(BasePlayer player)
803 {
804 var attackEntity = player.GetHeldEntity() as AttackEntity;
805
806 if (attackEntity == null)
807 {
808 return false;
809 }
810
811 return attackEntity is BaseMelee;
812 }
813 }
814
815 public class ItemComparerer : IEqualityComparer<TreasureItem>
816 {
817 public bool Equals(TreasureItem x, TreasureItem y)
818 {
819 return x != null && y != null && x.GetHashCode() == y.GetHashCode();
820 }
821
822 public int GetHashCode(TreasureItem obj)
823 {
824 return obj.GetHashCode();
825 }
826 }
827
828 public class RaidableBase : FacepunchBehaviour
829 {
830 public Hash<uint, float> conditions;
831 private Dictionary<string, List<string>> _clans;
832 private Dictionary<string, List<string>> _friends;
833 public List<StorageContainer> _containers;
834 public List<StorageContainer> _allcontainers;
835 private List<ulong> BoxSkins;
836 public Dictionary<BasePlayer, PlayerInputEx> Inputs;
837 public List<NPCPlayerApex> npcs;
838 public List<string> raiders;
839 public List<BasePlayer> friends;
840 public List<BasePlayer> intruders;
841 private Dictionary<PlayerCorpse, BasePlayer> corpses;
842 private Dictionary<FireBall, PlayWithFire> fireballs;
843 private List<Vector3> foundations;
844 private List<SphereEntity> spheres;
845 private List<BaseEntity> lights;
846 private List<BaseOven> ovens;
847 public List<AutoTurret> turrets;
848 private List<Door> doors;
849 private List<CustomDoorManipulator> doorControllers;
850 public List<uint> BuiltList;
851 public List<uint> blocks;
852 public Dictionary<string, float> lastActive;
853 public List<string> ids;
854 public BuildingPrivlidge priv { get; set; }
855 private Dictionary<string, List<string>> npcKits { get; set; }
856 private MapMarkerExplosion explosionMarker { get; set; }
857 private MapMarkerGenericRadius genericMarker { get; set; }
858 private VendingMachineMapMarker vendingMarker { get; set; }
859 private Coroutine setupRoutine { get; set; } = null;
860 private bool IsInvokingCanFinish { get; set; }
861 public bool selfDestruct { get; set; }
862 public Vector3 PastedLocation { get; set; }
863 public Vector3 Location { get; set; }
864 public string BaseName { get; set; }
865 public int BaseIndex { get; set; } = -1;
866 public uint BuildingID { get; set; }
867 public uint NetworkID { get; set; } = uint.MaxValue;
868 public Color NoneColor { get; set; }
869 public BasePlayer owner { get; set; }
870 public bool ownerFlag { get; set; }
871 public string ID { get; set; } = "0";
872 public string ownerId { get; set; } = "0";
873 public float spawnTime { get; set; }
874 public float despawnTime { get; set; }
875 private ulong skinId { get; set; }
876 public bool AllowPVP { get; set; }
877 public BuildingOptions Options { get; set; }
878 public bool IsAuthed { get; set; }
879 public bool IsOpened { get; set; } = true;
880 public bool IsUnloading { get; set; }
881 public int uid { get; set; }
882 public bool IsPayLocked { get; set; }
883 public int npcSpawnedAmount { get; set; }
884 public RaidableType Type { get; set; }
885 public string DifficultyMode { get; set; }
886 public bool IsLooted => CanUndo();
887 public bool IsLoading => setupRoutine != null;
888 private bool markerCreated { get; set; }
889 private bool lightsOn { get; set; }
890 private bool killed { get; set; }
891 private int itemAmountSpawned { get; set; }
892 private bool privSpawned { get; set; }
893 public string markerName { get; set; }
894 public string NoMode { get; set; }
895 public bool isAuthorized { get; set; }
896 public bool Engaged { get; set; }
897 private TurretState _turretsState { get; set; } = TurretState.Unknown;
898 private ItemDefinition lowgradefuel { get; set; } = ItemManager.FindItemDefinition("lowgradefuel");
899
900 private enum TurretState
901 {
902 Online,
903 Offline,
904 Unknown
905 }
906
907 private void CreatePool()
908 {
909 conditions = Pool.Get<Hash<uint, float>>();
910 _clans = Pool.Get<Dictionary<string, List<string>>>();
911 _friends = Pool.Get<Dictionary<string, List<string>>>();
912 _containers = Pool.GetList<StorageContainer>();
913 _allcontainers = Pool.GetList<StorageContainer>();
914 BoxSkins = Pool.GetList<ulong>();
915 Inputs = Pool.Get<Dictionary<BasePlayer, PlayerInputEx>>();
916 npcs = Pool.GetList<NPCPlayerApex>();
917 raiders = Pool.GetList<string>();
918 friends = Pool.GetList<BasePlayer>();
919 intruders = Pool.GetList<BasePlayer>();
920 corpses = Pool.Get<Dictionary<PlayerCorpse, BasePlayer>>();
921 fireballs = Pool.Get<Dictionary<FireBall, PlayWithFire>>();
922 foundations = Pool.GetList<Vector3>();
923 spheres = Pool.GetList<SphereEntity>();
924 lights = Pool.GetList<BaseEntity>();
925 ovens = Pool.GetList<BaseOven>();
926 turrets = Pool.GetList<AutoTurret>();
927 doors = Pool.GetList<Door>();
928 doorControllers = Pool.GetList<CustomDoorManipulator>();
929 BuiltList = Pool.GetList<uint>();
930 blocks = Pool.GetList<uint>();
931 lastActive = Pool.Get<Dictionary<string, float>>();
932 ids = Pool.Get<List<string>>();
933 }
934
935 public void FreePool()
936 {
937 conditions.Clear();
938 _clans.Clear();
939 _friends.Clear();
940 _containers.Clear();
941 _allcontainers.Clear();
942 BoxSkins.Clear();
943 Inputs.Clear();
944 npcs.Clear();
945 raiders.Clear();
946 friends.Clear();
947 intruders.Clear();
948 corpses.Clear();
949 fireballs.Clear();
950 foundations.Clear();
951 spheres.Clear();
952 lights.Clear();
953 ovens.Clear();
954 turrets.Clear();
955 doors.Clear();
956 doorControllers.Clear();
957 BuiltList.Clear();
958 blocks.Clear();
959 lastActive.Clear();
960 ids.Clear();
961
962 Pool.Free(ref conditions);
963 Pool.Free(ref _clans);
964 Pool.Free(ref _friends);
965 Pool.Free(ref _containers);
966 Pool.Free(ref _allcontainers);
967 Pool.Free(ref BoxSkins);
968 Pool.Free(ref Inputs);
969 Pool.Free(ref npcs);
970 Pool.Free(ref raiders);
971 Pool.Free(ref friends);
972 Pool.Free(ref intruders);
973 Pool.Free(ref corpses);
974 Pool.Free(ref fireballs);
975 Pool.Free(ref foundations);
976 Pool.Free(ref spheres);
977 Pool.Free(ref lights);
978 Pool.Free(ref ovens);
979 Pool.Free(ref turrets);
980 Pool.Free(ref doors);
981 Pool.Free(ref doorControllers);
982 Pool.Free(ref BuiltList);
983 Pool.Free(ref blocks);
984 Pool.Free(ref lastActive);
985 Pool.Free(ref ids);
986 }
987
988 private void Awake()
989 {
990 CreatePool();
991 markerName = _config.Settings.Markers.MarkerName;
992 spawnTime = Time.realtimeSinceStartup;
993 }
994
995 private void OnDestroy()
996 {
997 Interface.CallHook("OnRaidableBaseEnded", Location, (int)Options.Mode);
998 Despawn();
999 FreePool();
1000 Destroy(this);
1001 }
1002
1003 private void OnTriggerEnter(Collider col)
1004 {
1005 var m = col?.ToBaseEntity() as BaseMountable;
1006
1007 if (m.IsValid() && Type != RaidableType.None && TryRemoveMountable(m))
1008 {
1009 return;
1010 }
1011
1012 var player = col?.ToBaseEntity() as BasePlayer;
1013 var players = new List<BasePlayer>();
1014
1015 if (m.IsValid())
1016 {
1017 players = GetMountedPlayers(m);
1018 }
1019
1020 if (player.IsValid() && !player.IsNpc && !players.Contains(player))
1021 {
1022 players.Add(player);
1023 }
1024
1025 foreach (var p in players)
1026 {
1027 if (!p.IsConnected && Time.time - p.sleepStartTime < 2f)
1028 {
1029 continue;
1030 }
1031
1032 OnEnterRaid(p);
1033 }
1034 }
1035
1036 private bool JustRespawned(BasePlayer player)
1037 {
1038 return player.lifeStory?.timeBorn - (uint)Epoch.Current < 1f;
1039 }
1040
1041 public void OnEnterRaid(BasePlayer p)
1042 {
1043 if (Type != RaidableType.None)
1044 {
1045 if (!_config.Settings.Management.AllowTeleport && p.IsConnected && !CanBypass(p) && InRange(p.transform.position, Location, Radius, false))
1046 {
1047 if (CanMessage(p))
1048 {
1049 Backbone.Message(p, "CannotTeleport", p.UserIDString);
1050 }
1051
1052 RemovePlayer(p);
1053 return;
1054 }
1055
1056 if (HasLockout(p) || JustRespawned(p))
1057 {
1058 RemovePlayer(p);
1059 return;
1060 }
1061
1062 if (Backbone.HasPermission(p.UserIDString, banPermission))
1063 {
1064 Backbone.Message(p, "Banned", p.UserIDString);
1065 RemovePlayer(p);
1066 return;
1067 }
1068 }
1069
1070 if (!intruders.Contains(p))
1071 {
1072 intruders.Add(p);
1073 }
1074
1075 Protector();
1076
1077 if (!intruders.Contains(p))
1078 {
1079 return;
1080 }
1081
1082 if (!Inputs.ContainsKey(p))
1083 {
1084 p.gameObject.AddComponent<PlayerInputEx>().Setup(p, this);
1085 }
1086
1087 StopUsingWand(p);
1088
1089 if (_config.EventMessages.AnnounceEnterExit)
1090 {
1091 Backbone.Message(p, AllowPVP ? "OnPlayerEntered" : "OnPlayerEnteredPVE", p.UserIDString);
1092 }
1093
1094 if (intruders.Count > 0 && _turretsState != TurretState.Online)
1095 {
1096 CancelInvoke(SetTurretsState);
1097 SetTurretsState();
1098 }
1099
1100 UI.Update(this, p);
1101 Interface.CallHook("OnPlayerEnteredRaidableBase", p, Location, AllowPVP);
1102 }
1103
1104 private void OnTriggerExit(Collider col)
1105 {
1106 var player = col?.ToBaseEntity() as BasePlayer;
1107
1108 /*if (player.IsValid() && IsUnderTerrain(player.transform.position))
1109 {
1110 return;
1111 }*/
1112
1113 var m = col?.ToBaseEntity() as BaseMountable;
1114 var players = new List<BasePlayer>();
1115
1116 if (m.IsValid())
1117 {
1118 players = GetMountedPlayers(m);
1119 }
1120
1121 if (player.IsValid() && !player.IsNpc && !players.Contains(player))
1122 {
1123 players.Add(player);
1124 }
1125
1126 if (players.Count == 0)
1127 {
1128 return;
1129 }
1130
1131 foreach (var p in players)
1132 {
1133 OnPlayerExit(p);
1134 }
1135 }
1136
1137 public void OnPlayerExit(BasePlayer p)
1138 {
1139 UI.DestroyStatusUI(p);
1140
1141 if (Inputs.ContainsKey(p))
1142 {
1143 Destroy(Inputs[p]);
1144 }
1145
1146 if (!intruders.Contains(p))
1147 {
1148 return;
1149 }
1150
1151 intruders.Remove(p);
1152 Interface.CallHook("OnPlayerExitedRaidableBase", p, Location, AllowPVP);
1153
1154 if (_config.Settings.Management.PVPDelay > 0)
1155 {
1156 if (!IsPVE() || !AllowPVP)
1157 {
1158 goto enterExit;
1159 }
1160
1161 if (_config.EventMessages.AnnounceEnterExit)
1162 {
1163 string arg = Backbone.GetMessage("PVPFlag", p.UserIDString).Replace("[", string.Empty).Replace("] ", string.Empty);
1164 Backbone.Message(p, "DoomAndGloom", p.UserIDString, arg, _config.Settings.Management.PVPDelay);
1165 }
1166
1167 ulong id = p.userID;
1168 DelaySettings ds;
1169 if (!Backbone.Plugin.PvpDelay.TryGetValue(id, out ds))
1170 {
1171 Backbone.Plugin.PvpDelay[id] = ds = new DelaySettings
1172 {
1173 Timer = Backbone.Timer(_config.Settings.Management.PVPDelay, () => Backbone.Plugin.PvpDelay.Remove(id)),
1174 AllowPVP = AllowPVP
1175 };
1176
1177 goto exit;
1178 }
1179
1180 ds.Timer.Reset();
1181 goto exit;
1182 }
1183
1184 enterExit:
1185 if (_config.EventMessages.AnnounceEnterExit)
1186 {
1187 Backbone.Message(p, AllowPVP ? "OnPlayerExit" : "OnPlayerExitPVE", p.UserIDString);
1188 }
1189
1190 exit:
1191 if (intruders.Count == 0)
1192 {
1193 Invoke(SetTurretsState, 10f);
1194 }
1195 }
1196
1197 private bool IsHogging(BasePlayer player)
1198 {
1199 if (!_config.Settings.Management.PreventHogging || CanBypass(player))
1200 {
1201 return false;
1202 }
1203
1204 foreach (var raid in Backbone.Plugin.Raids.Values)
1205 {
1206 if (!raid.IsPayLocked && raid.IsOpened && raid.BaseIndex != BaseIndex && raid.Any(player.userID, false))
1207 {
1208 if (CanMessage(player))
1209 {
1210 Backbone.Message(player, "HoggingFinishYourRaid", player.UserIDString, PositionToGrid(raid.Location));
1211 }
1212
1213 return true;
1214 }
1215 }
1216
1217 if (!_config.Settings.Management.Lockout.IsBlocking() || Backbone.HasPermission(player.UserIDString, bypassBlockPermission))
1218 {
1219 return false;
1220 }
1221
1222 foreach (var raid in Backbone.Plugin.Raids.Values)
1223 {
1224 if (raid.BaseIndex != BaseIndex && !raid.IsPayLocked && raid.IsOpened && IsHogging(player, raid))
1225 {
1226 return true;
1227 }
1228 }
1229
1230 return false;
1231 }
1232
1233 private bool IsHogging(BasePlayer player, RaidableBase raid)
1234 {
1235 foreach (var intruder in raid.intruders)
1236 {
1237 if (!intruder.IsValid())
1238 {
1239 continue;
1240 }
1241
1242 if (_config.Settings.Management.Lockout.BlockTeams && raid.IsOnSameTeam(player.userID, intruder.userID))
1243 {
1244 if (CanMessage(player))
1245 {
1246 Backbone.Message(player, "HoggingFinishYourRaidTeam", player.UserIDString, intruder.displayName, PositionToGrid(raid.Location));
1247 }
1248
1249 return true;
1250 }
1251 else if (_config.Settings.Management.Lockout.BlockFriends && raid.IsFriends(player.UserIDString, intruder.UserIDString))
1252 {
1253 if (CanMessage(player))
1254 {
1255 Backbone.Message(player, "HoggingFinishYourRaidFriend", player.UserIDString, intruder.displayName, PositionToGrid(raid.Location));
1256 }
1257
1258 return true;
1259 }
1260 else if (_config.Settings.Management.Lockout.BlockClans && raid.IsInSameClan(player.UserIDString, intruder.UserIDString))
1261 {
1262 if (CanMessage(player))
1263 {
1264 Backbone.Message(player, "HoggingFinishYourRaidClan", player.UserIDString, intruder.displayName, PositionToGrid(raid.Location));
1265 }
1266
1267 return true;
1268 }
1269 }
1270
1271 return false;
1272 }
1273
1274 private void SetTurretsState()
1275 {
1276 bool online = intruders.Count > 0;
1277
1278 if (online && _turretsState == TurretState.Online || !online && _turretsState == TurretState.Offline)
1279 {
1280 return;
1281 }
1282
1283 int count = turrets.Count;
1284
1285 if (count == 0 || count - turrets.RemoveAll(x => x == null || x.IsDestroyed) == 0)
1286 {
1287 return;
1288 }
1289
1290 foreach (var turret in turrets)
1291 {
1292 if (online) // && !turret.booting && !turret.IsOnline())
1293 {
1294 turret.booting = true;
1295 Effect.server.Run(turret.onlineSound.resourcePath, turret, 0u, Vector3.zero, Vector3.zero, null, false);
1296 turret.SetIsOnline(true);
1297 }
1298 else //if (!online && (turret.booting || turret.IsOnline()))
1299 {
1300 turret.CancelInvoke(turret.SetOnline);
1301 turret.booting = false;
1302 Effect.server.Run(turret.offlineSound.resourcePath, turret, 0u, Vector3.zero, Vector3.zero, null, false);
1303 turret.SetIsOnline(false);
1304 }
1305 }
1306
1307 _turretsState = online ? TurretState.Online : TurretState.Offline;
1308 }
1309
1310 private void CheckCorpses()
1311 {
1312 var dict = new Dictionary<PlayerCorpse, BasePlayer>(corpses);
1313
1314 foreach (var corpse in dict)
1315 {
1316 if (corpse.Key == null || corpse.Key.IsDestroyed)
1317 {
1318 corpses.Remove(corpse.Key);
1319 }
1320 else if (ownerId.IsSteamId() && !Any(corpse.Value))
1321 {
1322 EjectCorpse(corpse.Value, corpse.Key);
1323 }
1324 }
1325
1326 dict.Clear();
1327 }
1328
1329 private void Protector()
1330 {
1331 if (corpses.Count > 0)
1332 {
1333 CheckCorpses();
1334 }
1335
1336 if (Type == RaidableType.None || intruders.Count == 0)
1337 {
1338 return;
1339 }
1340
1341 var targets = new List<BasePlayer>(intruders);
1342
1343 foreach (var target in targets)
1344 {
1345 if (target == owner || friends.Contains(target) || CanBypass(target))
1346 {
1347 continue;
1348 }
1349
1350 if (CanEject(target) || _config.Settings.Management.EjectSleepers && target.IsSleeping())
1351 {
1352 intruders.Remove(target);
1353 RemovePlayer(target);
1354 }
1355 else if (owner.IsValid())
1356 {
1357 friends.Add(target);
1358 }
1359 }
1360
1361 targets.Clear();
1362 }
1363
1364 public void DestroyUI()
1365 {
1366 var list = Pool.GetList<BasePlayer>();
1367
1368 foreach (var kvp in UI.Players)
1369 {
1370 if (kvp.Value == this)
1371 {
1372 list.Add(kvp.Key);
1373 }
1374 }
1375
1376 foreach (var player in list)
1377 {
1378 UI.DestroyStatusUI(player);
1379 }
1380
1381 list.Clear();
1382 Pool.Free(ref list);
1383 }
1384
1385 public bool IsUnderTerrain(Vector3 pos)
1386 {
1387 float height = TerrainMeta.HeightMap.GetHeight(pos);
1388
1389 if (pos.y - 1f > height || pos.y + 1f < height)
1390 {
1391 return false;
1392 }
1393
1394 return true;
1395 }
1396
1397 public static void Unload()
1398 {
1399 foreach (var raid in Backbone.Plugin.Raids.Values)
1400 {
1401 raid.IsUnloading = true;
1402 }
1403 }
1404
1405 public void Despawn()
1406 {
1407 IsOpened = false;
1408
1409 if (killed)
1410 {
1411 return;
1412 }
1413
1414 killed = true;
1415
1416 Interface.CallHook("OnRaidableBaseDespawn", Location, spawnTime, ID);
1417
1418 SetNoDrops();
1419 CancelInvoke();
1420 DestroyFire();
1421 DestroyInputs();
1422 RemoveSpheres();
1423 KillNpc();
1424 StopAllCoroutines();
1425 RemoveMapMarkers();
1426 DestroyUI();
1427
1428 if (!IsUnloading)
1429 {
1430 ServerMgr.Instance.StartCoroutine(Backbone.Plugin.UndoRoutine(BaseIndex));
1431 }
1432
1433 foreach (var raider in raiders)
1434 {
1435 TrySetLockout(raider);
1436 }
1437
1438 Backbone.Plugin.Raids.Remove(uid);
1439
1440 if (Backbone.Plugin.Raids.Count == 0)
1441 {
1442 if (IsUnloading)
1443 {
1444 UnsetStatics();
1445 }
1446 else Backbone.Plugin.UnsubscribeHooks();
1447 }
1448
1449 Destroy(this);
1450 }
1451
1452 public bool AddLooter(BasePlayer looter)
1453 {
1454 if (!looter.IsValid() || !IsAlly(looter) || looter.IsFlying || Backbone.Plugin.IsInvisible(looter))
1455 {
1456 return false;
1457 }
1458
1459 UpdateStatus(looter);
1460
1461 if (!raiders.Contains(looter.UserIDString))
1462 {
1463 raiders.Add(looter.UserIDString);
1464 return true;
1465 }
1466
1467 return false;
1468 }
1469
1470 private void FillAmmoTurret(AutoTurret turret)
1471 {
1472 if (isAuthorized)
1473 {
1474 return;
1475 }
1476
1477 foreach (var id in turret.authorizedPlayers)
1478 {
1479 if (id.userid.IsSteamId())
1480 {
1481 isAuthorized = true;
1482 return;
1483 }
1484 }
1485
1486 var attachedWeapon = turret.GetAttachedWeapon();
1487
1488 if (!attachedWeapon.IsValid())
1489 {
1490 return;
1491 }
1492
1493 turret.inventory.AddItem(attachedWeapon.primaryMagazine.ammoType, _config.Weapons.Ammo.AutoTurret);
1494 attachedWeapon.primaryMagazine.contents = attachedWeapon.primaryMagazine.capacity;
1495 attachedWeapon.SendNetworkUpdateImmediate();
1496 turret.Invoke(turret.UpdateTotalAmmo, 0.1f);
1497 }
1498
1499 private void FillAmmoGunTrap(GunTrap gt)
1500 {
1501 if (gt.ammoType == null)
1502 {
1503 return;
1504 }
1505
1506 gt.inventory.AddItem(gt.ammoType, _config.Weapons.Ammo.GunTrap);
1507 }
1508
1509 private void FillAmmoFogMachine(FogMachine fm)
1510 {
1511 if (lowgradefuel == null)
1512 {
1513 return;
1514 }
1515
1516 fm.inventory.AddItem(lowgradefuel, _config.Weapons.Ammo.FogMachine);
1517 }
1518
1519 private void FillAmmoFlameTurret(FlameTurret ft)
1520 {
1521 if (lowgradefuel == null)
1522 {
1523 return;
1524 }
1525
1526 ft.inventory.AddItem(lowgradefuel, _config.Weapons.Ammo.FlameTurret);
1527 }
1528
1529 private void FillAmmoSamSite(SamSite ss)
1530 {
1531 if (!ss.HasAmmo())
1532 {
1533 Item item = ItemManager.Create(ss.ammoType, _config.Weapons.Ammo.SamSite);
1534
1535 if (!item.MoveToContainer(ss.inventory))
1536 {
1537 item.Remove();
1538 }
1539 else ss.ammoItem = item;
1540 }
1541 else if (ss.ammoItem != null && ss.ammoItem.amount < _config.Weapons.Ammo.SamSite)
1542 {
1543 ss.ammoItem.amount = _config.Weapons.Ammo.SamSite;
1544 }
1545 }
1546
1547 private void OnWeaponItemPreRemove(Item item)
1548 {
1549 if (isAuthorized)
1550 {
1551 return;
1552 }
1553
1554 if (priv != null && !priv.IsDestroyed)
1555 {
1556 foreach (var id in priv.authorizedPlayers)
1557 {
1558 if (id.userid.IsSteamId())
1559 {
1560 isAuthorized = true;
1561 return;
1562 }
1563 }
1564 }
1565
1566 var weapon = item.parent?.entityOwner;
1567
1568 if (weapon is AutoTurret)
1569 {
1570 weapon.Invoke(() => FillAmmoTurret(weapon as AutoTurret), 0.1f);
1571 }
1572 else if (weapon is GunTrap)
1573 {
1574 weapon.Invoke(() => FillAmmoGunTrap(weapon as GunTrap), 0.1f);
1575 }
1576 else if (weapon is SamSite)
1577 {
1578 weapon.Invoke(() => FillAmmoSamSite(weapon as SamSite), 0.1f);
1579 }
1580 }
1581
1582 private void SetupContainers()
1583 {
1584 _containers.RemoveAll(x => !x.IsValid() || x.IsDestroyed);
1585
1586 foreach (var container in _containers)
1587 {
1588 if (container.inventory == null) continue;
1589 container.inventory.onItemAddedRemoved += new Action<Item, bool>(OnItemAddedRemoved);
1590 }
1591 }
1592
1593 private void OnItemAddedRemoved(Item item, bool bAdded)
1594 {
1595 if (!bAdded)
1596 {
1597 StartTryToEnd();
1598 }
1599 }
1600
1601 public void StartTryToEnd()
1602 {
1603 if (!IsInvokingCanFinish)
1604 {
1605 IsInvokingCanFinish = true;
1606 InvokeRepeating(TryToEnd, 0f, 1f);
1607 }
1608 }
1609
1610 public void TryToEnd()
1611 {
1612 if (IsOpened && IsLooted)
1613 {
1614 CancelInvoke(TryToEnd);
1615 AwardRaiders();
1616 Undo();
1617 }
1618 }
1619
1620 public void AwardRaiders()
1621 {
1622 var players = new List<BasePlayer>();
1623 var sb = new StringBuilder();
1624 BasePlayer player;
1625
1626 foreach (var playerId in raiders)
1627 {
1628 TrySetLockout(playerId);
1629
1630 player = BasePlayer.FindAwakeOrSleeping(playerId);
1631
1632 if (player == null || player.IsFlying || !IsPlayerActive(player.UserIDString))
1633 {
1634 continue;
1635 }
1636
1637 if (_config.Settings.RemoveAdminRaiders && player.IsAdmin && Type != RaidableType.None)
1638 {
1639 continue;
1640 }
1641
1642 sb.Append(player.displayName).Append(", ");
1643 players.Add(player);
1644 }
1645
1646 if (players.Count == 0)
1647 {
1648 return;
1649 }
1650
1651 if (Options.Levels.Level2)
1652 {
1653 SpawnNpcs();
1654 }
1655
1656 HandleAwards(players);
1657
1658 sb.Length -= 2;
1659 string thieves = sb.ToString();
1660 string posStr = FormatGridReference(Location);
1661
1662 Backbone.Plugin.Puts(Backbone.GetMessage("Thief", null, posStr, thieves));
1663
1664 if (_config.EventMessages.AnnounceThief)
1665 {
1666 foreach (var target in BasePlayer.activePlayerList)
1667 {
1668 Backbone.Message(target, "Thief", target.UserIDString, posStr, thieves);
1669 }
1670 }
1671
1672 Backbone.Plugin.SaveData();
1673 }
1674
1675 private void HandleAwards(List<BasePlayer> players)
1676 {
1677 foreach (var raider in players)
1678 {
1679 if (_config.RankedLadder.Enabled)
1680 {
1681 PlayerInfo playerInfo;
1682 if (!Backbone.Data.Players.TryGetValue(raider.UserIDString, out playerInfo))
1683 {
1684 Backbone.Data.Players[raider.UserIDString] = playerInfo = new PlayerInfo();
1685 }
1686
1687 playerInfo.TotalRaids++;
1688 playerInfo.Raids++;
1689 }
1690
1691 if (Options.Rewards.Money > 0 && Backbone.Plugin.Economics != null && Backbone.Plugin.Economics.IsLoaded)
1692 {
1693 double money = _config.Settings.Management.DivideRewards ? Options.Rewards.Money / players.Count : Options.Rewards.Money;
1694 Backbone.Plugin.Economics?.Call("Deposit", raider.UserIDString, money);
1695 Backbone.Message(raider, "EconomicsDeposit", raider.UserIDString, money);
1696 }
1697
1698 if (Options.Rewards.Points > 0 && Backbone.Plugin.ServerRewards != null && Backbone.Plugin.ServerRewards.IsLoaded)
1699 {
1700 int points = _config.Settings.Management.DivideRewards ? Options.Rewards.Points / players.Count : Options.Rewards.Points;
1701 Backbone.Plugin.ServerRewards?.Call("AddPoints", raider.userID, points);
1702 Backbone.Message(raider, "ServerRewardPoints", raider.UserIDString, points);
1703 }
1704 }
1705 }
1706
1707 private List<string> messagesSent = new List<string>();
1708
1709 private bool CanMessage(BasePlayer player)
1710 {
1711 if (messagesSent.Contains(player.UserIDString))
1712 {
1713 return false;
1714 }
1715
1716 string uid = player.UserIDString;
1717
1718 messagesSent.Add(uid);
1719 Backbone.Timer(10f, () => messagesSent.Remove(uid));
1720
1721 return true;
1722 }
1723
1724 private bool CanBypass(BasePlayer player)
1725 {
1726 return Backbone.HasPermission(player.UserIDString, canBypassPermission) || player.IsFlying;
1727 }
1728
1729 public bool HasLockout(BasePlayer player)
1730 {
1731 if (!_config.Settings.Management.Lockout.Any() || !player.IsValid() || CanBypass(player))
1732 {
1733 return false;
1734 }
1735
1736 if (!IsOpened && Any(player.userID))
1737 {
1738 return false;
1739 }
1740
1741 if (owner.IsValid() && player == owner)
1742 {
1743 return false;
1744 }
1745
1746 Lockout lo;
1747 if (Backbone.Data.Lockouts.TryGetValue(player.UserIDString, out lo))
1748 {
1749 double time = GetLockoutTime(Options.Mode, lo, player.UserIDString);
1750
1751 if (time > 0f)
1752 {
1753 if (CanMessage(player))
1754 {
1755 Backbone.Message(player, "LockedOut", player.UserIDString, DifficultyMode, FormatTime(time));
1756 }
1757
1758 return true;
1759 }
1760 }
1761
1762 return false;
1763 }
1764
1765 private void TrySetLockout(string playerId)
1766 {
1767 if (IsUnloading || Type == RaidableType.None || Backbone.HasPermission(playerId, canBypassPermission))
1768 {
1769 return;
1770 }
1771
1772 var player = BasePlayer.FindAwakeOrSleeping(playerId);
1773
1774 if (player.IsValid() && player.IsFlying)
1775 {
1776 return;
1777 }
1778
1779 double time = GetLockoutTime();
1780
1781 if (time <= 0)
1782 {
1783 return;
1784 }
1785
1786 Lockout lo;
1787 if (!Backbone.Data.Lockouts.TryGetValue(playerId, out lo))
1788 {
1789 Backbone.Data.Lockouts[playerId] = lo = new Lockout();
1790 }
1791
1792 switch (Options.Mode)
1793 {
1794 case RaidableMode.Easy:
1795 {
1796 if (lo.Easy <= 0)
1797 {
1798 lo.Easy = Epoch.Current + time;
1799 }
1800 return;
1801 }
1802 case RaidableMode.Medium:
1803 {
1804 if (lo.Medium <= 0)
1805 {
1806 lo.Medium = Epoch.Current + time;
1807 }
1808 return;
1809 }
1810 case RaidableMode.Hard:
1811 {
1812 if (lo.Hard <= 0)
1813 {
1814 lo.Hard = Epoch.Current + time;
1815 }
1816 return;
1817 }
1818 case RaidableMode.Expert:
1819 {
1820 if (lo.Expert <= 0)
1821 {
1822 lo.Expert = Epoch.Current + time;
1823 }
1824 return;
1825 }
1826 default:
1827 {
1828 if (lo.Nightmare <= 0)
1829 {
1830 lo.Nightmare = Epoch.Current + time;
1831 }
1832 return;
1833 }
1834 }
1835 }
1836
1837 private double GetLockoutTime()
1838 {
1839 switch (Options.Mode)
1840 {
1841 case RaidableMode.Easy:
1842 {
1843 return _config.Settings.Management.Lockout.Easy * 60;
1844 }
1845 case RaidableMode.Medium:
1846 {
1847 return _config.Settings.Management.Lockout.Medium * 60;
1848 }
1849 case RaidableMode.Hard:
1850 {
1851 return _config.Settings.Management.Lockout.Hard * 60;
1852 }
1853 case RaidableMode.Expert:
1854 {
1855 return _config.Settings.Management.Lockout.Expert * 60;
1856 }
1857 default:
1858 {
1859 return _config.Settings.Management.Lockout.Nightmare * 60;
1860 }
1861 }
1862 }
1863
1864 public static double GetLockoutTime(RaidableMode mode, Lockout lo, string playerId)
1865 {
1866 double time;
1867
1868 switch (mode)
1869 {
1870 case RaidableMode.Easy:
1871 {
1872 time = lo.Easy;
1873
1874 if (time <= 0 || (time -= Epoch.Current) <= 0)
1875 {
1876 lo.Easy = 0;
1877 }
1878
1879 break;
1880 }
1881 case RaidableMode.Medium:
1882 {
1883 time = lo.Medium;
1884
1885 if (time <= 0 || (time -= Epoch.Current) <= 0)
1886 {
1887 lo.Medium = 0;
1888 }
1889
1890 break;
1891 }
1892 case RaidableMode.Hard:
1893 {
1894 time = lo.Hard;
1895
1896 if (time <= 0 || (time -= Epoch.Current) <= 0)
1897 {
1898 lo.Hard = 0;
1899 }
1900
1901 break;
1902 }
1903 case RaidableMode.Expert:
1904 {
1905 time = lo.Expert;
1906
1907 if (time <= 0 || (time -= Epoch.Current) <= 0)
1908 {
1909 lo.Expert = 0;
1910 }
1911
1912 break;
1913 }
1914 default:
1915 {
1916 time = lo.Nightmare;
1917
1918 if (time <= 0 || (time -= Epoch.Current) <= 0)
1919 {
1920 lo.Nightmare = 0;
1921 }
1922
1923 break;
1924 }
1925 }
1926
1927 if (!lo.Any())
1928 {
1929 Backbone.Data.Lockouts.Remove(playerId);
1930 }
1931
1932 return time < 0 ? 0 : time;
1933 }
1934
1935 public string Mode()
1936 {
1937 if (owner.IsValid())
1938 {
1939 return string.Format("{0} {1}", owner.displayName, DifficultyMode.SentenceCase());
1940 }
1941
1942 return DifficultyMode.SentenceCase();
1943 }
1944
1945 public void TrySetPayLock(BasePlayer player)
1946 {
1947 if (!IsOpened)
1948 {
1949 return;
1950 }
1951
1952 if (player != null)
1953 {
1954 IsPayLocked = true;
1955 owner = player;
1956 ownerId = player.UserIDString;
1957 friends.Add(player);
1958 ClearEnemies();
1959 }
1960 else if (!IsPlayerActive(ownerId))
1961 {
1962 IsPayLocked = false;
1963 owner = null;
1964 ownerId = "0";
1965 friends.Clear();
1966 raiders.Clear();
1967 }
1968
1969 UpdateMarker();
1970 }
1971
1972 private bool IsPlayerActive(string playerId)
1973 {
1974 if (_config.Settings.Management.LockTime <= 0f)
1975 {
1976 return true;
1977 }
1978
1979 float time;
1980 if (!lastActive.TryGetValue(playerId, out time))
1981 {
1982 return true;
1983 }
1984
1985 return Time.realtimeSinceStartup - time <= _config.Settings.Management.LockTime * 60f;
1986 }
1987
1988 public void TrySetOwner(BasePlayer attacker, BaseEntity entity, HitInfo hitInfo)
1989 {
1990 UpdateStatus(attacker);
1991
1992 if (!_config.Settings.Management.UseOwners || !IsOpened || owner.IsValid())
1993 {
1994 return;
1995 }
1996
1997 if (_config.Settings.Management.BypassUseOwnersForPVP && AllowPVP)
1998 {
1999 return;
2000 }
2001
2002 if (_config.Settings.Management.BypassUseOwnersForPVE && !AllowPVP)
2003 {
2004 return;
2005 }
2006
2007 if (HasLockout(attacker) || IsHogging(attacker))
2008 {
2009 NullifyDamage(hitInfo);
2010 return;
2011 }
2012
2013 if (RaidableBase.Any(attacker))
2014 {
2015 return;
2016 }
2017
2018 if (entity is NPCPlayerApex)
2019 {
2020 SetOwner(attacker);
2021 return;
2022 }
2023
2024 if (hitInfo == null || hitInfo.damageTypes == null || (!(entity is BuildingBlock) && !(entity is Door) && !(entity is SimpleBuildingBlock)))
2025 {
2026 return;
2027 }
2028
2029 if (IsLootingWeapon(hitInfo) || InRange(attacker.transform.position, entity.transform.position, Options.ProtectionRadius))
2030 {
2031 SetOwner(attacker);
2032 }
2033 }
2034
2035 private void SetOwner(BasePlayer player)
2036 {
2037 if (_config.Settings.Management.LockTime > 0f)
2038 {
2039 if (IsInvoking(ResetOwner)) CancelInvoke(ResetOwner);
2040 Invoke(ResetOwner, _config.Settings.Management.LockTime * 60f);
2041 }
2042
2043 UpdateStatus(player);
2044 owner = player;
2045 ownerId = player.UserIDString;
2046 UpdateMarker();
2047 ClearEnemies();
2048 }
2049
2050 private void ClearEnemies()
2051 {
2052 foreach (string targetId in new List<string>(raiders))
2053 {
2054 ulong userId;
2055
2056 if (ulong.TryParse(targetId, out userId))
2057 {
2058 var target = BasePlayer.FindByID(userId);
2059
2060 if (target == null || !IsAlly(target))
2061 {
2062 raiders.Remove(targetId);
2063 }
2064 }
2065 }
2066 }
2067
2068 public void CheckDespawn()
2069 {
2070 if (_config.Settings.Management.DespawnMinutesInactive <= 0 || !_config.Settings.Management.DespawnMinutesInactiveReset || selfDestruct)
2071 {
2072 return;
2073 }
2074
2075 if (_config.Settings.Management.Engaged && !Engaged)
2076 {
2077 return;
2078 }
2079
2080 if (IsInvoking(Despawn))
2081 {
2082 CancelInvoke(Despawn);
2083 }
2084
2085 float time = _config.Settings.Management.DespawnMinutesInactive * 60f;
2086 despawnTime = Time.realtimeSinceStartup + time;
2087 Invoke(Despawn, time);
2088 }
2089
2090 public bool EndWhenCupboardIsDestroyed()
2091 {
2092 if (_config.Settings.Management.EndWhenCupboardIsDestroyed && privSpawned)
2093 {
2094 return priv == null || priv.IsDestroyed;
2095 }
2096
2097 return false;
2098 }
2099
2100 public bool CanUndo()
2101 {
2102 if (EndWhenCupboardIsDestroyed())
2103 {
2104 return true;
2105 }
2106
2107 if (_config.Settings.Management.RequireCupboardLooted && privSpawned)
2108 {
2109 return priv == null || priv.IsDestroyed || priv?.inventory?.itemList?.Count == 0;
2110 }
2111
2112 foreach (var container in _containers)
2113 {
2114 if (container?.inventory?.itemList?.Count > 0)
2115 {
2116 return false;
2117 }
2118 }
2119
2120 return true;
2121 }
2122
2123 private bool CanPlayerBeLooted()
2124 {
2125 if (!_config.Settings.Management.PlayersLootableInPVE && !AllowPVP || !_config.Settings.Management.PlayersLootableInPVP && AllowPVP)
2126 {
2127 return false;
2128 }
2129
2130 return true;
2131 }
2132
2133 private bool CanBeLooted(BasePlayer player, BaseEntity e)
2134 {
2135 if (IsProtectedWeapon(e))
2136 {
2137 return false;
2138 }
2139
2140 if (e is NPCPlayerCorpse)
2141 {
2142 return true;
2143 }
2144
2145 if (e is LootableCorpse)
2146 {
2147 if (CanBypass(player))
2148 {
2149 return true;
2150 }
2151
2152 var corpse = e as LootableCorpse;
2153
2154 if (!corpse.playerSteamID.IsSteamId() || corpse.playerSteamID == player.userID || corpse.playerName == player.displayName)
2155 {
2156 return true;
2157 }
2158
2159 return CanPlayerBeLooted();
2160 }
2161 else if (e is DroppedItemContainer)
2162 {
2163 if (CanBypass(player))
2164 {
2165 return true;
2166 }
2167
2168 var container = e as DroppedItemContainer;
2169
2170 if (!container.playerSteamID.IsSteamId() || container.playerSteamID == player.userID || container.playerName == player.displayName)
2171 {
2172 return true;
2173 }
2174
2175 return CanPlayerBeLooted();
2176 }
2177
2178 return true;
2179 }
2180
2181 public static bool IsProtectedWeapon(BaseEntity e)
2182 {
2183 return e is GunTrap || e is FlameTurret || e is FogMachine || e is SamSite || e is AutoTurret;
2184 }
2185
2186 private void UpdateStatus(BasePlayer player)
2187 {
2188 if (IsOpened)
2189 {
2190 lastActive[player.UserIDString] = Time.realtimeSinceStartup;
2191 }
2192
2193 if (ownerId == player.UserIDString && _config.Settings.Management.LockTime > 0f)
2194 {
2195 if (IsInvoking(ResetOwner)) CancelInvoke(ResetOwner);
2196 Invoke(ResetOwner, _config.Settings.Management.LockTime * 60f);
2197 }
2198 }
2199
2200 public void OnLootEntityInternal(BasePlayer player, BaseEntity e)
2201 {
2202 UpdateStatus(player);
2203
2204 if (e.OwnerID == player.userID || e is BaseMountable)
2205 {
2206 return;
2207 }
2208
2209 if (_config.Settings.Management.BlacklistedPickupItems.Contains(e.ShortPrefabName))
2210 {
2211 player.Invoke(player.EndLooting, 0.1f);
2212 return;
2213 }
2214
2215 if (e.HasParent() && e.GetParentEntity() is BaseMountable)
2216 {
2217 return;
2218 }
2219
2220 if (!CanBeLooted(player, e))
2221 {
2222 player.Invoke(player.EndLooting, 0.1f);
2223 return;
2224 }
2225
2226 if (e is LootableCorpse || e is DroppedItemContainer)
2227 {
2228 return;
2229 }
2230
2231 if (player.isMounted)
2232 {
2233 Backbone.Message(player, "CannotBeMounted", player.UserIDString);
2234 player.Invoke(player.EndLooting, 0.1f);
2235 return;
2236 }
2237
2238 if (Options.RequiresCupboardAccess && !player.CanBuild()) //player.IsBuildingBlocked())
2239 {
2240 Backbone.Message(player, "MustBeAuthorized", player.UserIDString);
2241 player.Invoke(player.EndLooting, 0.1f);
2242 return;
2243 }
2244
2245 if (!IsAlly(player))
2246 {
2247 Backbone.Message(player, "OwnerLocked", player.UserIDString);
2248 player.Invoke(player.EndLooting, 0.1f);
2249 return;
2250 }
2251
2252 if (raiders.Count > 0 && Type != RaidableType.None)
2253 {
2254 CheckDespawn();
2255 }
2256
2257 AddLooter(player);
2258
2259 if (e is BoxStorage || e is BuildingPrivlidge)
2260 {
2261 StartTryToEnd();
2262 }
2263 }
2264
2265 private void StartPlayingWithFire()
2266 {
2267 if (npcs.Count == 0)
2268 {
2269 return;
2270 }
2271
2272 var dict = Pool.Get<Dictionary<FireBall, PlayWithFire>>();
2273
2274 foreach (var entry in fireballs)
2275 {
2276 dict.Add(entry.Key, entry.Value);
2277 }
2278
2279 foreach (var entry in dict)
2280 {
2281 if (entry.Key == null || entry.Key.IsDestroyed)
2282 {
2283 Destroy(entry.Value);
2284 fireballs.Remove(entry.Key);
2285 }
2286 }
2287
2288 dict.Clear();
2289 Pool.Free(ref dict);
2290
2291 if (fireballs.Count >= Options.Levels.Level1.Amount || UnityEngine.Random.value > Options.Levels.Level1.Chance)
2292 {
2293 return;
2294 }
2295
2296 var npc = npcs.GetRandom();
2297
2298 if (!IsValid(npc))
2299 {
2300 return;
2301 }
2302
2303 var fireball = GameManager.server.CreateEntity(Backbone.Path.FireballSmall, npc.transform.position + new Vector3(0f, 3f, 0f), Quaternion.identity, true) as FireBall;
2304
2305 if (fireball == null)
2306 {
2307 return;
2308 }
2309
2310 var rb = fireball.GetComponent<Rigidbody>();
2311
2312 if (rb != null)
2313 {
2314 rb.isKinematic = false;
2315 rb.useGravity = false;
2316 rb.drag = 0f;
2317 }
2318
2319 fireball.lifeTimeMax = 15f;
2320 fireball.lifeTimeMin = 15f;
2321 fireball.canMerge = false;
2322 fireball.Spawn();
2323 fireball.CancelInvoke(fireball.TryToSpread);
2324
2325 var component = fireball.gameObject.AddComponent<PlayWithFire>();
2326
2327 component.Target = npc;
2328
2329 fireballs.Add(fireball, component);
2330 }
2331
2332 private void SetNoDrops()
2333 {
2334 foreach (var container in _allcontainers)
2335 {
2336 if (!container.IsValid()) continue;
2337 container.dropChance = 0f;
2338 }
2339 }
2340
2341 public void DestroyInputs()
2342 {
2343 foreach (var input in Inputs.ToList())
2344 {
2345 Destroy(input.Value);
2346 }
2347
2348 Inputs.Clear();
2349 }
2350
2351 public void DestroyFire()
2352 {
2353 if (fireballs.Count == 0)
2354 {
2355 return;
2356 }
2357
2358 foreach (var entry in fireballs)
2359 {
2360 Destroy(entry.Value);
2361
2362 if (entry.Key == null)
2363 {
2364 continue;
2365 }
2366
2367 entry.Key.Extinguish();
2368 }
2369
2370 fireballs.Clear();
2371 }
2372
2373 private void SetContainers()
2374 {
2375 List<BaseEntity> entities;
2376 if (!Backbone.Plugin.Bases.TryGetValue(BaseIndex, out entities))
2377 {
2378 return;
2379 }
2380
2381 Collider collider;
2382 StorageContainer container;
2383 int hits = Physics.OverlapSphereNonAlloc(PastedLocation, Radius, Vis.colBuffer, Layers.Mask.Deployed, QueryTriggerInteraction.Ignore);
2384
2385 for (int i = 0; i < hits; i++)
2386 {
2387 collider = Vis.colBuffer[i];
2388
2389 if (collider == null || collider.transform == null)
2390 {
2391 goto next;
2392 }
2393
2394 container = collider.ToBaseEntity() as StorageContainer;
2395
2396 if (container == null || container.IsDestroyed || entities.Contains(container))
2397 {
2398 goto next;
2399 }
2400
2401 entities.Add(container);
2402
2403 next:
2404 Vis.colBuffer[i] = null;
2405 }
2406 }
2407
2408 public void SetEntities(int baseIndex)
2409 {
2410 if (!IsLoading)
2411 {
2412 BaseIndex = baseIndex;
2413 SetContainers();
2414 setupRoutine = StartCoroutine(EntitySetup());
2415 }
2416 }
2417
2418 private Vector3 GetCenterFromMultiplePoints()
2419 {
2420 if (foundations.Count <= 1)
2421 {
2422 return PastedLocation;
2423 }
2424
2425 float x = 0f;
2426 float z = 0f;
2427
2428 foreach (var position in foundations)
2429 {
2430 x += position.x;
2431 z += position.z;
2432 }
2433
2434 var vector = new Vector3(x / foundations.Count, 0f, z / foundations.Count);
2435
2436 vector.y = GetSpawnHeight(vector);
2437
2438 return vector;
2439 }
2440
2441 private void CreateSpheres()
2442 {
2443 if (Options.SphereAmount <= 0)
2444 {
2445 return;
2446 }
2447
2448 for (int i = 0; i < Options.SphereAmount; i++)
2449 {
2450 var sphere = GameManager.server.CreateEntity(Backbone.Path.Sphere, Location, default(Quaternion), true) as SphereEntity;
2451
2452 if (sphere == null)
2453 {
2454 break;
2455 }
2456
2457 sphere.currentRadius = 1f;
2458 sphere.Spawn();
2459 sphere.LerpRadiusTo(Options.ProtectionRadius * 2f, Options.ProtectionRadius * 0.75f);
2460 spheres.Add(sphere);
2461 }
2462 }
2463
2464 private bool CreateZoneWalls()
2465 {
2466 var center = new Vector3(Location.x, Location.y, Location.z);
2467 string prefab = Options.ArenaWalls.Stone ? Backbone.Path.HighExternalStoneWall : Backbone.Path.HighExternalWoodenWall;
2468 float maxHeight = -200f;
2469 float minHeight = 200f;
2470 int raycasts = Mathf.CeilToInt(360 / Options.ArenaWalls.Radius * 0.1375f);
2471
2472 foreach (var position in GetCircumferencePositions(center, Options.ArenaWalls.Radius, raycasts, false))
2473 {
2474 maxHeight = Mathf.Max(position.y, maxHeight, TerrainMeta.WaterMap.GetHeight(position));
2475 minHeight = Mathf.Min(position.y, minHeight);
2476 center.y = minHeight;
2477 }
2478
2479 float gap = prefab == Backbone.Path.HighExternalStoneWall ? 0.3f : 0.5f;
2480 int stacks = Mathf.CeilToInt((maxHeight - minHeight) / 6f) + Options.ArenaWalls.Stacks;
2481 float next = 360 / Options.ArenaWalls.Radius - gap;
2482 float j = Options.ArenaWalls.Stacks * 6f + 6f;
2483 float groundHeight = 0f;
2484 BaseEntity e;
2485
2486 for (int i = 0; i < stacks; i++)
2487 {
2488 foreach (var position in GetCircumferencePositions(center, Options.ArenaWalls.Radius, next, false, center.y))
2489 {
2490 if (Location.y - position.y > 48f)
2491 {
2492 continue;
2493 }
2494
2495 groundHeight = TerrainMeta.HeightMap.GetHeight(new Vector3(position.x, position.y + 6f, position.z));
2496
2497 if (groundHeight > position.y + 9f)
2498 {
2499 continue;
2500 }
2501
2502 if (Options.ArenaWalls.LeastAmount)
2503 {
2504 float h = TerrainMeta.HeightMap.GetHeight(position);
2505
2506 if (position.y - groundHeight > j && position.y < h)
2507 {
2508 continue;
2509 }
2510 }
2511
2512 e = GameManager.server.CreateEntity(prefab, position, default(Quaternion), false);
2513
2514 if (e == null)
2515 {
2516 return false;
2517 }
2518
2519 e.OwnerID = 0;
2520 e.transform.LookAt(center, Vector3.up);
2521
2522 if (Options.ArenaWalls.UseUFOWalls)
2523 {
2524 e.transform.Rotate(-67.5f, 0f, 0f);
2525 }
2526
2527 e.enableSaving = false;
2528 e.Spawn();
2529 e.gameObject.SetActive(true);
2530
2531 if (CanSetupEntity(e) && Backbone.Plugin.Bases.ContainsKey(BaseIndex))
2532 {
2533 Backbone.Plugin.Bases[BaseIndex].Add(e);
2534 }
2535
2536 if (stacks == i - 1)
2537 {
2538 RaycastHit hit;
2539 if (Physics.Raycast(new Vector3(position.x, position.y + 6f, position.z), Vector3.down, out hit, 12f, Layers.Mask.World))
2540 {
2541 stacks++;
2542 }
2543 }
2544 }
2545
2546 center.y += 6f;
2547 }
2548
2549 return true;
2550 }
2551
2552 private void KillTrees()
2553 {
2554 BaseEntity e;
2555 int hits = Physics.OverlapSphereNonAlloc(Location, Radius, Vis.colBuffer, Layers.Mask.Tree, QueryTriggerInteraction.Ignore);
2556 for (int i = 0; i < hits; i++)
2557 {
2558 e = Vis.colBuffer[i].ToBaseEntity();
2559
2560 if (e != null && !e.IsDestroyed)
2561 {
2562 e.Kill();
2563 }
2564
2565 Vis.colBuffer[i] = null;
2566 }
2567 }
2568
2569 private IEnumerator EntitySetup()
2570 {
2571 if (!Backbone.Plugin.Bases.ContainsKey(BaseIndex))
2572 {
2573 yield break;
2574 }
2575
2576 var list = new List<BaseEntity>(Backbone.Plugin.Bases[BaseIndex]);
2577
2578 foreach (var e in list)
2579 {
2580 if (!CanSetupEntity(e))
2581 {
2582 yield return null;
2583 continue;
2584 }
2585
2586 Backbone.Plugin.RaidEntities[e] = this;
2587
2588 if (e.net.ID < NetworkID)
2589 {
2590 NetworkID = e.net.ID;
2591 }
2592
2593 e.OwnerID = 0;
2594
2595 if (!Options.AllowPickup && e is BaseCombatEntity)
2596 {
2597 SetupPickup(e as BaseCombatEntity);
2598 }
2599
2600 if (e is IOEntity)
2601 {
2602 if (e is ContainerIOEntity)
2603 {
2604 SetupIO(e as ContainerIOEntity);
2605 }
2606
2607 if (e is AutoTurret)
2608 {
2609 SetupTurret(e as AutoTurret);
2610 }
2611 else if (e is Igniter)
2612 {
2613 SetupIgniter(e as Igniter);
2614 }
2615 else if (e is SamSite)
2616 {
2617 SetupSamSite(e as SamSite);
2618 }
2619 else if (e is TeslaCoil)
2620 {
2621 SetupTeslaCoil(e as TeslaCoil);
2622 }
2623 else if (e is SearchLight)
2624 {
2625 SetupSearchLight(e as SearchLight);
2626 }
2627 else if (e is CustomDoorManipulator)
2628 {
2629 doorControllers.Add(e as CustomDoorManipulator);
2630 }
2631
2632 SetupIO(e as IOEntity);
2633 }
2634 else if (e is StorageContainer)
2635 {
2636 if (e is BaseOven)
2637 {
2638 ovens.Add(e as BaseOven);
2639 }
2640 else if (e is GunTrap)
2641 {
2642 SetupGunTrap(e as GunTrap);
2643 }
2644 else if (e is FogMachine)
2645 {
2646 SetupFogMachine(e as FogMachine);
2647 }
2648 else if (e is FlameTurret)
2649 {
2650 SetupFlameTurret(e as FlameTurret);
2651 }
2652 else if (e is VendingMachine)
2653 {
2654 SetupVendingMachine(e as VendingMachine);
2655 }
2656 else if (e is BuildingPrivlidge)
2657 {
2658 SetupBuildingPriviledge(e as BuildingPrivlidge);
2659 }
2660
2661 SetupContainer(e as StorageContainer);
2662 }
2663 else if (e is BaseLock)
2664 {
2665 SetupLock(e);
2666 }
2667 else if (e is BuildingBlock)
2668 {
2669 SetupBuildingBlock(e as BuildingBlock);
2670 }
2671 else if (e is Door)
2672 {
2673 doors.Add(e as Door);
2674 }
2675 else if (e is SleepingBag)
2676 {
2677 SetupSleepingBag(e as SleepingBag);
2678 }
2679
2680 if (e is DecayEntity)
2681 {
2682 SetupDecayEntity(e as DecayEntity);
2683 }
2684
2685 yield return null;
2686 }
2687
2688 yield return Coroutines.WaitForSeconds(2f);
2689
2690 setupRoutine = null;
2691 list.Clear();
2692 FinishSetup();
2693 }
2694
2695 private void SetupPickup(BaseCombatEntity e)
2696 {
2697 e.pickup.enabled = false;
2698 }
2699
2700 private void SetupContainer(StorageContainer container)
2701 {
2702 if (container.inventory == null)
2703 {
2704 container.inventory = new ItemContainer();
2705 container.inventory.ServerInitialize(null, 30);
2706 container.inventory.GiveUID();
2707 container.inventory.entityOwner = container;
2708 }
2709
2710 if (container is BoxStorage)
2711 {
2712 if (skinId == 0uL)
2713 {
2714 if (_config.Skins.PresetSkin != 0uL)
2715 {
2716 skinId = _config.Skins.PresetSkin;
2717 }
2718 else
2719 {
2720 BoxSkins.AddRange(GetItemSkins(Backbone.Path.BoxDefinition));
2721 skinId = BoxSkins.GetRandom();
2722 }
2723 }
2724
2725 if (_config.Skins.PresetSkin != 0uL || Options.SetSkins)
2726 {
2727 container.skinID = skinId;
2728 }
2729 else if (_config.Skins.RandomSkins && BoxSkins.Count > 0)
2730 {
2731 container.skinID = BoxSkins.GetRandom();
2732 }
2733
2734 _containers.Add(container);
2735 }
2736
2737 if (Options.SkipTreasureLoot && (container is BoxStorage || container is BuildingPrivlidge) && !_containers.Contains(container))
2738 {
2739 _containers.Add(container);
2740 }
2741
2742 _allcontainers.Add(container);
2743
2744 if (Type == RaidableType.None && container.inventory.itemList.Count > 0)
2745 {
2746 return;
2747 }
2748
2749 container.dropChance = 0f;
2750
2751 if (container is BuildingPrivlidge)
2752 {
2753 container.dropChance = _config.Settings.Management.AllowCupboardLoot ? 1f : 0f;
2754 }
2755 else if (!IsProtectedWeapon(container) && !(container is VendingMachine))
2756 {
2757 container.dropChance = 1f;
2758 }
2759
2760 container.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true);
2761 }
2762
2763 private void SetupIO(ContainerIOEntity io)
2764 {
2765 io.dropChance = IsProtectedWeapon(io) ? 0f : 1f;
2766 io.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true);
2767 }
2768
2769 private void SetupIO(IOEntity io)
2770 {
2771 if (io.IsPowered())
2772 {
2773 return;
2774 }
2775
2776 io.SetFlag(BaseEntity.Flags.Reserved8, true, false, true);
2777 io.SendNetworkUpdateImmediate();
2778 }
2779
2780 private void SetupLock(BaseEntity e, bool justCreated = false)
2781 {
2782 if (Backbone.Plugin.Bases.ContainsKey(BaseIndex) && !Backbone.Plugin.Bases[BaseIndex].Contains(e))
2783 {
2784 Backbone.Plugin.Bases[BaseIndex].Add(e);
2785 }
2786
2787 if (Type == RaidableType.None)
2788 {
2789 return;
2790 }
2791
2792 if (e is CodeLock)
2793 {
2794 var codeLock = e as CodeLock;
2795
2796 if (_config.Settings.Management.RandomCodes || justCreated)
2797 {
2798 codeLock.code = UnityEngine.Random.Range(1000, 9999).ToString();
2799 codeLock.hasCode = true;
2800 }
2801
2802 codeLock.OwnerID = 0;
2803 codeLock.guestCode = string.Empty;
2804 codeLock.hasGuestCode = false;
2805 codeLock.guestPlayers.Clear();
2806 codeLock.whitelistPlayers.Clear();
2807 codeLock.SetFlag(BaseEntity.Flags.Locked, true);
2808 }
2809 else if (e is KeyLock)
2810 {
2811 var keyLock = e as KeyLock;
2812
2813 if (_config.Settings.Management.RandomCodes)
2814 {
2815 keyLock.keyCode = UnityEngine.Random.Range(1, 100000);
2816 }
2817
2818 keyLock.OwnerID = 0;
2819 keyLock.firstKeyCreated = true;
2820 keyLock.SetFlag(BaseEntity.Flags.Locked, true);
2821 }
2822 }
2823
2824 private void SetupVendingMachine(VendingMachine vm)
2825 {
2826 if (_config.Settings.Management.AllowBroadcasting)
2827 {
2828 return;
2829 }
2830
2831 vm.SetFlag(BaseEntity.Flags.Reserved4, false, false, true);
2832 vm.UpdateMapMarker();
2833 }
2834
2835 private void SetupSearchLight(SearchLight light)
2836 {
2837 if (!_config.Settings.Management.Lights && !_config.Settings.Management.AlwaysLights)
2838 {
2839 return;
2840 }
2841
2842 lights.Add(light);
2843
2844 light.enabled = false;
2845 }
2846
2847 private void SetupBuildingBlock(BuildingBlock block)
2848 {
2849 if (Options.Tiers.Any())
2850 {
2851 ChangeTier(block);
2852 }
2853
2854 block.StopBeingDemolishable();
2855 block.StopBeingRotatable();
2856
2857 if (block.transform == null)
2858 {
2859 return;
2860 }
2861
2862 if (block.prefabID == 3234260181 || block.prefabID == 72949757) // triangle and square foundations
2863 {
2864 foundations.Add(block.transform.position);
2865 }
2866 }
2867
2868 private void ChangeTier(BuildingBlock block)
2869 {
2870 if (Options.Tiers.HQM && block.grade != BuildingGrade.Enum.TopTier)
2871 {
2872 SetGrade(block, BuildingGrade.Enum.TopTier);
2873 }
2874 else if (Options.Tiers.Metal && block.grade != BuildingGrade.Enum.Metal)
2875 {
2876 SetGrade(block, BuildingGrade.Enum.Metal);
2877 }
2878 else if (Options.Tiers.Stone && block.grade != BuildingGrade.Enum.Stone)
2879 {
2880 SetGrade(block, BuildingGrade.Enum.Stone);
2881 }
2882 else if (Options.Tiers.Wooden && block.grade != BuildingGrade.Enum.Wood)
2883 {
2884 SetGrade(block, BuildingGrade.Enum.Wood);
2885 }
2886 }
2887
2888 private void SetGrade(BuildingBlock block, BuildingGrade.Enum grade)
2889 {
2890 block.SetGrade(grade);
2891 block.SetHealthToMax();
2892 block.SendNetworkUpdate();
2893 block.UpdateSkin();
2894 }
2895
2896 private void SetupTeslaCoil(TeslaCoil tc)
2897 {
2898 tc.maxDischargeSelfDamageSeconds = 0f;
2899 }
2900
2901 private void SetupIgniter(Igniter igniter)
2902 {
2903 igniter.SelfDamagePerIgnite = 0f;
2904 }
2905
2906 private void SetupTurret(AutoTurret turret)
2907 {
2908 if (Type != RaidableType.None)
2909 {
2910 turret.authorizedPlayers.Clear();
2911 }
2912
2913 turret.InitializeHealth(Options.AutoTurretHealth, Options.AutoTurretHealth);
2914 turret.sightRange = Options.AutoTurretSightRange;
2915 turret.aimCone = Options.AutoTurretAimCone;
2916 turrets.Add(turret);
2917
2918 if (Options.RemoveTurretWeapon)
2919 {
2920 turret.AttachedWeapon = null;
2921 Item slot = turret.inventory.GetSlot(0);
2922
2923 if (slot != null && (slot.info.category == ItemCategory.Weapon || slot.info.category == ItemCategory.Fun))
2924 {
2925 slot.RemoveFromContainer();
2926 slot.Remove();
2927 }
2928 }
2929
2930 if (turret.AttachedWeapon == null)
2931 {
2932 var itemToCreate = ItemManager.FindItemDefinition(Options.AutoTurretShortname);
2933
2934 if (itemToCreate != null)
2935 {
2936 Item item = ItemManager.Create(itemToCreate, 1, (ulong)itemToCreate.skins.GetRandom().id);
2937
2938 if (!item.MoveToContainer(turret.inventory, 0, false))
2939 {
2940 item.Remove();
2941 }
2942 }
2943 }
2944
2945 turret.Invoke(turret.UpdateAttachedWeapon, 0.1f);
2946 turret.Invoke(() => FillAmmoTurret(turret), 0.2f);
2947 //turret.Invoke(turret.InitiateStartup, 0.3f);
2948 turret.SetPeacekeepermode(false);
2949
2950 if (_config.Weapons.InfiniteAmmo.AutoTurret)
2951 {
2952 turret.inventory.onPreItemRemove += new Action<Item>(OnWeaponItemPreRemove);
2953 }
2954 }
2955
2956 private void SetupGunTrap(GunTrap gt)
2957 {
2958 if (_config.Weapons.Ammo.GunTrap > 0)
2959 {
2960 FillAmmoGunTrap(gt);
2961 }
2962
2963 if (_config.Weapons.InfiniteAmmo.GunTrap)
2964 {
2965 gt.inventory.onPreItemRemove += new Action<Item>(OnWeaponItemPreRemove);
2966 }
2967 }
2968
2969 private void SetupFogMachine(FogMachine fm)
2970 {
2971 if (_config.Weapons.Ammo.FogMachine > 0)
2972 {
2973 FillAmmoFogMachine(fm);
2974 }
2975
2976 if (_config.Weapons.InfiniteAmmo.FogMachine)
2977 {
2978 fm.fuelPerSec = 0f;
2979 }
2980
2981 if (_config.Weapons.FogMotion)
2982 {
2983 fm.SetFlag(BaseEntity.Flags.Reserved7, true, false, true);
2984 }
2985
2986 fm.CancelInvoke(fm.CheckTrigger);
2987 fm.SetFlag(BaseEntity.Flags.Reserved6, true, false, true);
2988 fm.SetFlag(BaseEntity.Flags.Reserved8, true, false, true);
2989 fm.SetFlag(BaseEntity.Flags.On, true, false, true);
2990 }
2991
2992 private void SetupFlameTurret(FlameTurret ft)
2993 {
2994 ft.InitializeHealth(Options.FlameTurretHealth, Options.FlameTurretHealth);
2995
2996 if (_config.Weapons.Ammo.FlameTurret > 0)
2997 {
2998 FillAmmoFlameTurret(ft);
2999 }
3000
3001 if (_config.Weapons.InfiniteAmmo.FlameTurret)
3002 {
3003 ft.fuelPerSec = 0f;
3004 }
3005 }
3006
3007 private void SetupSamSite(SamSite ss)
3008 {
3009 SetupIO(ss);
3010
3011 if (_config.Weapons.SamSiteRepair > 0f)
3012 {
3013 ss.staticRespawn = true;
3014 ss.InvokeRepeating(ss.SelfHeal, _config.Weapons.SamSiteRepair * 60f, _config.Weapons.SamSiteRepair * 60f);
3015 }
3016
3017 if (_config.Weapons.SamSiteRange > 0f)
3018 {
3019 ss.scanRadius = _config.Weapons.SamSiteRange;
3020 }
3021
3022 if (_config.Weapons.Ammo.SamSite > 0)
3023 {
3024 FillAmmoSamSite(ss);
3025 }
3026
3027 if (_config.Weapons.InfiniteAmmo.SamSite)
3028 {
3029 ss.inventory.onPreItemRemove += new Action<Item>(OnWeaponItemPreRemove);
3030 }
3031 }
3032
3033 private void SetupDoor(Door door)
3034 {
3035 if (Options.DoorLock)
3036 {
3037 CreateLock(door);
3038 }
3039
3040 if (!Options.CloseOpenDoors)
3041 {
3042 return;
3043 }
3044
3045 door.SetOpen(false);
3046 }
3047
3048 private void SetupDoors()
3049 {
3050 foreach (var door in doors)
3051 {
3052 if (door == null || door.IsDestroyed)
3053 {
3054 continue;
3055 }
3056
3057 SetupDoor(door);
3058 }
3059
3060 doors.Clear();
3061 }
3062
3063 private void SetupDoorControllers()
3064 {
3065 Door door;
3066
3067 foreach (var cdm in doorControllers)
3068 {
3069 SetupIO(cdm);
3070
3071 if (cdm.IsPaired())
3072 {
3073 doors.Remove(cdm.targetDoor);
3074 continue;
3075 }
3076
3077 door = cdm.FindDoor(true);
3078
3079 if (door.IsValid())
3080 {
3081 cdm.SetTargetDoor(door);
3082 doors.Remove(door);
3083
3084 if (Options.DoorLock)
3085 {
3086 CreateLock(door);
3087 }
3088 }
3089 }
3090
3091 doorControllers.Clear();
3092 }
3093
3094 private void CreateLock(Door door)
3095 {
3096 var slot = door.GetSlot(BaseEntity.Slot.Lock) as BaseLock;
3097
3098 if (slot == null)
3099 {
3100 CreateCodeLock(door);
3101 return;
3102 }
3103
3104 var keyLock = slot.GetComponent<KeyLock>();
3105
3106 if (keyLock.IsValid() && !keyLock.IsDestroyed)
3107 {
3108 keyLock.SetParent(null);
3109 keyLock.Kill();
3110 }
3111
3112 CreateCodeLock(door);
3113 }
3114
3115 private void CreateCodeLock(Door door)
3116 {
3117 var codeLock = GameManager.server.CreateEntity(Backbone.Path.CodeLock, default(Vector3), default(Quaternion), true) as CodeLock;
3118
3119 codeLock.gameObject.Identity();
3120 codeLock.SetParent(door, BaseEntity.Slot.Lock.ToString().ToLower());
3121 //codeLock.OnDeployed(door);
3122 codeLock.enableSaving = false;
3123 codeLock.OwnerID = 0;
3124 codeLock.Spawn();
3125 door.SetSlot(BaseEntity.Slot.Lock, codeLock);
3126 Backbone.Plugin.AddEntity(codeLock, this);
3127
3128 SetupLock(codeLock, true);
3129 }
3130
3131 private void SetupBuildingPriviledge(BuildingPrivlidge priv)
3132 {
3133 if (Type != RaidableType.None)
3134 {
3135 priv.authorizedPlayers.Clear();
3136 priv.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
3137 }
3138
3139 this.priv = priv;
3140 privSpawned = true;
3141 }
3142
3143 private void SetupSleepingBag(SleepingBag bag)
3144 {
3145 if (Type == RaidableType.None) return;
3146 bag.deployerUserID = 0uL;
3147 }
3148
3149 private void SetupDecayEntity(DecayEntity decayEntity)
3150 {
3151 if (BuildingID == 0)
3152 {
3153 BuildingID = BuildingManager.server.NewBuildingID();
3154 }
3155
3156 decayEntity.AttachToBuilding(BuildingID);
3157 }
3158
3159 private void FinishSetup()
3160 {
3161 if (IsUnloading)
3162 {
3163 return;
3164 }
3165
3166 transform.position = Location = GetCenterFromMultiplePoints();
3167
3168 var collider = gameObject.GetComponent<SphereCollider>() ?? gameObject.AddComponent<SphereCollider>();
3169 collider.radius = Options.ProtectionRadius;
3170 collider.isTrigger = true;
3171 collider.center = Vector3.zero;
3172 gameObject.layer = (int)Layer.Trigger;
3173
3174 if (Options.ArenaWalls.Enabled)
3175 {
3176 CreateZoneWalls();
3177 }
3178
3179 KillTrees();
3180 CreateGenericMarker();
3181 CreateSpheres();
3182 CheckDespawn();
3183 SetupContainers();
3184 Subscribe();
3185 EjectSleepers();
3186 InvokeRepeating(Protector, 1f, 1f);
3187 UpdateMarker();
3188
3189 if (Options.Levels.Level1.Amount > 0)
3190 {
3191 InvokeRepeating(StartPlayingWithFire, 2f, 2f);
3192 }
3193
3194 if (Backbone.Plugin.NightLantern == null)
3195 {
3196 if (_config.Settings.Management.Lights)
3197 {
3198 InvokeRepeating(Lights, 1f, 1f);
3199 }
3200 else if (_config.Settings.Management.AlwaysLights)
3201 {
3202 Lights();
3203 }
3204 }
3205
3206 Interface.CallHook("OnRaidableBaseStarted", Location, (int)Options.Mode);
3207
3208 SetupDoorControllers();
3209 SetupDoors();
3210 SetupLoot();
3211 }
3212
3213 private void Subscribe()
3214 {
3215 if (IsUnloading)
3216 {
3217 return;
3218 }
3219
3220 if (Options.EnforceDurability)
3221 {
3222 Subscribe(nameof(OnLoseCondition));
3223 }
3224
3225 Subscribe(nameof(CanPickupEntity));
3226
3227 if (Options.NPC.Enabled)
3228 {
3229 if (Options.NPC.SpawnAmount < 1)
3230 {
3231 Options.NPC.Enabled = false;
3232 }
3233
3234 if (Options.NPC.SpawnAmount > 25)
3235 {
3236 Options.NPC.SpawnAmount = 25;
3237 }
3238
3239 if (Options.NPC.SpawnMinAmount < 1 || Options.NPC.SpawnMinAmount > Options.NPC.SpawnAmount)
3240 {
3241 Options.NPC.SpawnMinAmount = 1;
3242 }
3243
3244 if (Options.NPC.ScientistHealth < 100)
3245 {
3246 Options.NPC.ScientistHealth = 100f;
3247 }
3248
3249 if (Options.NPC.ScientistHealth > 5000)
3250 {
3251 Options.NPC.ScientistHealth = 5000f;
3252 }
3253
3254 if (Options.NPC.MurdererHealth < 100)
3255 {
3256 Options.NPC.MurdererHealth = 100f;
3257 }
3258
3259 if (Options.NPC.MurdererHealth > 5000)
3260 {
3261 Options.NPC.MurdererHealth = 5000f;
3262 }
3263
3264 if (_config.Settings.Management.FlameTurrets)
3265 {
3266 Subscribe(nameof(CanBeTargeted));
3267 }
3268
3269 Subscribe(nameof(OnNpcKits));
3270 Subscribe(nameof(OnNpcTarget));
3271 SetupNpcKits();
3272 Invoke(SpawnNpcs, 1f);
3273 }
3274
3275 Subscribe(nameof(OnPlayerDeath));
3276 Subscribe(nameof(OnPlayerDropActiveItem));
3277 Subscribe(nameof(OnEntityDeath));
3278 Subscribe(nameof(OnEntityKill));
3279
3280 if (!_config.Settings.Management.AllowTeleport)
3281 {
3282 Subscribe(nameof(CanTeleport));
3283 Subscribe(nameof(canTeleport));
3284 }
3285
3286 if (_config.Settings.Management.BlockRestorePVP && AllowPVP || _config.Settings.Management.BlockRestorePVE && !AllowPVP)
3287 {
3288 Subscribe(nameof(OnRestoreUponDeath));
3289 }
3290
3291 if (_config.Settings.Management.UseOwners || _config.Settings.Buyable.UsePayLock)
3292 {
3293 Subscribe(nameof(OnFriendAdded));
3294 Subscribe(nameof(OnFriendRemoved));
3295 Subscribe(nameof(OnClanUpdate));
3296 Subscribe(nameof(OnClanDestroy));
3297 }
3298
3299 if (Options.DropTimeAfterLooting > 0)
3300 {
3301 Subscribe(nameof(OnLootEntityEnd));
3302 }
3303
3304 if (!_config.Settings.Management.BackpacksOpenPVP || !_config.Settings.Management.BackpacksOpenPVE)
3305 {
3306 Subscribe(nameof(CanOpenBackpack));
3307 }
3308
3309 if (_config.Settings.Management.PreventFireFromSpreading)
3310 {
3311 Subscribe(nameof(OnFireBallSpread));
3312 }
3313
3314 if (!_config.Settings.Management.AllowBuilding)
3315 {
3316 Subscribe(nameof(CanBuild));
3317 }
3318
3319 Subscribe(nameof(CanDropBackpack));
3320 Subscribe(nameof(OnEntityGroundMissing));
3321 Subscribe(nameof(CanEntityBeTargeted));
3322 Subscribe(nameof(CanEntityTrapTrigger));
3323 Subscribe(nameof(OnLootEntity));
3324 Subscribe(nameof(CanBuildRevertBlock));
3325 Subscribe(nameof(OnEntityBuilt));
3326 Subscribe(nameof(OnCupboardAuthorize));
3327 Subscribe(nameof(OnEntityMounted));
3328 }
3329
3330 private void Subscribe(string hook) => Backbone.Plugin.Subscribe(hook);
3331
3332 private void MakeAnnouncements()
3333 {
3334 if (Type == RaidableType.None || itemAmountSpawned == 0)
3335 {
3336 itemAmountSpawned = 0;
3337
3338 foreach (var x in _allcontainers)
3339 {
3340 if (x == null || x.IsDestroyed)
3341 {
3342 continue;
3343 }
3344
3345 itemAmountSpawned += x.inventory.itemList.Count;
3346 }
3347 }
3348
3349 var posStr = FormatGridReference(Location);
3350
3351 Backbone.Plugin.Puts("{0} @ {1} : {2} items", BaseName, posStr, itemAmountSpawned);
3352
3353 foreach (var target in BasePlayer.activePlayerList)
3354 {
3355 float distance = Mathf.Floor((target.transform.position - Location).magnitude);
3356 string flag = Backbone.GetMessage(AllowPVP ? "PVPFlag" : "PVEFlag", target.UserIDString).Replace("[", string.Empty).Replace("] ", string.Empty);
3357 string api = Backbone.GetMessage("RaidOpenMessage", target.UserIDString, DifficultyMode, posStr, distance, flag);
3358 if (Type == RaidableType.None) api = api.Replace(DifficultyMode, NoMode);
3359 string message = owner.IsValid() ? string.Format("{0}[Owner: {1}]", api, owner.displayName) : api;
3360
3361 if ((!IsPayLocked && _config.EventMessages.Opened) || (IsPayLocked && _config.EventMessages.OpenedAndPaid))
3362 {
3363 target.SendConsoleCommand("chat.add", 2, _config.Settings.ChatID, message);
3364 }
3365
3366 if (_config.GUIAnnouncement.Enabled && Backbone.Plugin.GUIAnnouncements != null && Backbone.Plugin.GUIAnnouncements.IsLoaded && distance <= _config.GUIAnnouncement.Distance)
3367 {
3368 Backbone.Plugin.GUIAnnouncements?.Call("CreateAnnouncement", message, _config.GUIAnnouncement.TintColor, _config.GUIAnnouncement.TextColor, target);
3369 }
3370 }
3371 }
3372
3373 public void ResetOwner()
3374 {
3375 if (!IsOpened || IsPayLocked || IsPlayerActive(ownerId))
3376 {
3377 return;
3378 }
3379
3380 owner = null;
3381 ownerId = "0";
3382 friends.Clear();
3383 UpdateMarker();
3384 }
3385
3386 public void TryInvokeResetPayLock()
3387 {
3388 if (_config.Settings.Buyable.ResetDuration > 0 && IsPayLocked && IsOpened)
3389 {
3390 CancelInvoke(ResetPayLock);
3391 Invoke(ResetPayLock, _config.Settings.Buyable.ResetDuration * 60f);
3392 }
3393 }
3394
3395 private void ResetPayLock()
3396 {
3397 if (!IsOpened || IsPlayerActive(ownerId) || owner.IsValid() && owner.IsConnected)
3398 {
3399 return;
3400 }
3401
3402 IsPayLocked = false;
3403 owner = null;
3404 ownerId = "0";
3405 friends.Clear();
3406 UpdateMarker();
3407 }
3408
3409 private void TakeLootFrom(List<TreasureItem> source)
3410 {
3411 if (source.Count == 0)
3412 {
3413 return;
3414 }
3415
3416 var from = new List<TreasureItem>(source);
3417
3418 from.RemoveAll(ti => ti == null || ti.amount <= 0);
3419
3420 if (from.Count == 0)
3421 {
3422 return;
3423 }
3424
3425 Shuffle(from);
3426
3427 if (Options.Prioritize)
3428 {
3429 int difference = Math.Abs(Options.TreasureAmount - Loot.Count);
3430 int amount = Math.Min(difference, from.Count);
3431 Loot.AddRange(from.Take(amount));
3432 }
3433 else Loot.AddRange(from);
3434
3435 from.Clear();
3436 }
3437
3438 private List<TreasureItem> Loot { get; set; } = new List<TreasureItem>();
3439
3440 private void SetupLoot()
3441 {
3442 var list = new List<StorageContainer>(_containers);
3443
3444 foreach (var container in list)
3445 {
3446 if (container == null || container.IsDestroyed)
3447 {
3448 _containers.Remove(container);
3449 }
3450 else if (container.transform == null)
3451 {
3452 container.Kill();
3453 _containers.Remove(container);
3454 }
3455 else if (container.net == null)
3456 {
3457 container.net = Network.Net.sv.CreateNetworkable();
3458 }
3459 }
3460
3461 if (_containers.Count == 0)
3462 {
3463 Backbone.Plugin.Puts(Backbone.GetMessage("NoContainersFound", null, BaseName, PositionToGrid(Location)));
3464 return;
3465 }
3466
3467 CheckExpansionSettings();
3468
3469 if (Options.SkipTreasureLoot)
3470 {
3471 MakeAnnouncements();
3472 return;
3473 }
3474
3475 TakeLootFrom(Options.Loot);
3476
3477 if (Loot.Count < Options.TreasureAmount)
3478 {
3479 switch (Options.Mode)
3480 {
3481 case RaidableMode.Easy:
3482 {
3483 TakeLootFrom(_config.Treasure.LootEasy);
3484 break;
3485 }
3486 case RaidableMode.Medium:
3487 {
3488 TakeLootFrom(_config.Treasure.LootMedium);
3489 break;
3490 }
3491 case RaidableMode.Hard:
3492 {
3493 TakeLootFrom(_config.Treasure.LootHard);
3494 break;
3495 }
3496 case RaidableMode.Expert:
3497 {
3498 TakeLootFrom(_config.Treasure.LootExpert);
3499 break;
3500 }
3501 case RaidableMode.Nightmare:
3502 {
3503 TakeLootFrom(_config.Treasure.LootNightmare);
3504 break;
3505 }
3506 }
3507 }
3508
3509 if (Loot.Count < Options.TreasureAmount)
3510 {
3511 TakeLootFrom(TreasureLoot);
3512 }
3513
3514 Loot.RemoveAll(ti => ti.amount <= 0);
3515
3516 if (Loot.Count == 0 || Options.TreasureAmount <= 0)
3517 {
3518 Backbone.Plugin.Puts(Backbone.GetMessage("NoConfiguredLoot"));
3519 return;
3520 }
3521
3522 if (Options.EmptyAll)
3523 {
3524 foreach (var container in _containers)
3525 {
3526 container.inventory.Clear();
3527 }
3528
3529 ItemManager.DoRemoves();
3530 }
3531
3532 if (_config.Settings.Management.IgnoreContainedLoot)
3533 {
3534 for (int i = 0; i < _containers.Count; i++)
3535 {
3536 if (_containers[i].inventory.itemList.Count == 0)
3537 {
3538 _containers.RemoveAll(x => x.inventory.itemList.Count != 0);
3539 break;
3540 }
3541 }
3542 }
3543
3544 Shuffle(Loot);
3545
3546 if (Options.AllowDuplicates)
3547 {
3548 if (Loot.Count < Options.TreasureAmount)
3549 {
3550 do
3551 {
3552 Loot.Add(Loot.GetRandom());
3553 } while (Loot.Count < Options.TreasureAmount);
3554 }
3555 }
3556 else Loot = new List<TreasureItem>(Loot.Distinct(Backbone.Plugin.itemComparer));
3557
3558 if (Loot.Count > Options.TreasureAmount)
3559 {
3560 Loot = new List<TreasureItem>(Loot.Take(Options.TreasureAmount));
3561 }
3562
3563 var containers = new List<StorageContainer>();
3564
3565 if (Options.DivideLoot)
3566 {
3567 foreach (var container in _containers)
3568 {
3569 if (container.inventory.itemList.Count == 0)
3570 {
3571 containers.Add(container);
3572 }
3573 }
3574
3575 if (containers.Count == 0)
3576 {
3577 containers.AddRange(_containers);
3578 }
3579
3580 if (containers.Count > 0)
3581 {
3582 int num = Options.TreasureAmount / containers.Count;
3583
3584 foreach (var container in containers)
3585 {
3586 SpawnLoot(container, Loot, num);
3587 }
3588
3589 FinishDivision(containers, Loot, num);
3590 }
3591 }
3592 else
3593 {
3594 foreach (var x in _containers)
3595 {
3596 if (x.inventory.itemList.Count == 0)
3597 {
3598 containers.Add(x);
3599 }
3600 }
3601
3602 if (containers.Count > 0)
3603 {
3604 int capacity = 0;
3605
3606 foreach (var container in containers)
3607 {
3608 capacity = Math.Max(container.inventory.capacity, capacity);
3609 }
3610
3611 containers.RemoveAll(x => x.inventory.capacity != capacity);
3612 SpawnLoot(containers.GetRandom(), Loot, Options.TreasureAmount);
3613 }
3614 }
3615
3616 if (itemAmountSpawned == 0 && containers.Count > 0)
3617 {
3618 Backbone.Plugin.Puts(Backbone.GetMessage("NoLootSpawned"));
3619 }
3620
3621 foreach (var container in _containers)
3622 {
3623 if (!(container is BoxStorage)) continue;
3624 container.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
3625 }
3626
3627 MakeAnnouncements();
3628 }
3629
3630 private void SpawnLoot(StorageContainer container, List<TreasureItem> loot, int total)
3631 {
3632 if (total > container.inventory.capacity)
3633 {
3634 total = container.inventory.capacity;
3635 }
3636
3637 for (int j = 0; j < total; j++)
3638 {
3639 if (loot.Count == 0)
3640 {
3641 break;
3642 }
3643
3644 var lootItem = loot.GetRandom();
3645
3646 loot.Remove(lootItem);
3647
3648 if (lootItem.amount <= 0)
3649 {
3650 continue;
3651 }
3652
3653 bool isBlueprint = lootItem.shortname.EndsWith(".bp");
3654 string shortname = isBlueprint ? lootItem.shortname.Replace(".bp", string.Empty) : lootItem.shortname;
3655 var def = ItemManager.FindItemDefinition(shortname);
3656
3657 if (def == null)
3658 {
3659 Backbone.Plugin.PrintError("Invalid shortname in config: {0}", lootItem.shortname);
3660 continue;
3661 }
3662
3663 ulong skin = lootItem.skin;
3664
3665 if (_config.Treasure.RandomSkins && skin == 0 && def.stackable <= 1)
3666 {
3667 var skins = GetItemSkins(def);
3668
3669 if (skins.Count > 0)
3670 {
3671 skin = skins.GetRandom();
3672 }
3673 }
3674
3675 int amount = lootItem.amount;
3676
3677 if (lootItem.amountMin < lootItem.amount)
3678 {
3679 amount = UnityEngine.Random.Range(lootItem.amountMin, lootItem.amount + 1);
3680 }
3681
3682 if (amount <= 0)
3683 {
3684 j--;
3685 continue;
3686 }
3687
3688 amount = GetPercentAmount(amount);
3689
3690 if (amount <= 0)
3691 {
3692 amount = Math.Max(1, lootItem.amount);
3693 }
3694
3695 Item item;
3696
3697 if (isBlueprint)
3698 {
3699 item = ItemManager.CreateByItemID(-996920608, 1, 0);
3700 item.blueprintTarget = def.itemid;
3701 item.amount = amount;
3702 }
3703 else item = ItemManager.Create(def, amount, skin);
3704
3705 if (MoveToFridge(item) || MoveToBBQ(item) || MoveToCupboard(item) || MoveToOven(item) || item.MoveToContainer(container.inventory, -1, false))
3706 {
3707 itemAmountSpawned++;
3708 }
3709 else
3710 {
3711 item.Remove();
3712 }
3713 }
3714 }
3715
3716 private void FinishDivision(List<StorageContainer> containers, List<TreasureItem> loot, int num)
3717 {
3718 if (itemAmountSpawned < Options.TreasureAmount && containers.Count > 0)
3719 {
3720 int amountLeft = Options.TreasureAmount - itemAmountSpawned;
3721
3722 containers.RemoveAll(container => container.inventory.itemList.Count + (amountLeft / containers.Count) > container.inventory.capacity);
3723
3724 if (containers.Count == 0)
3725 {
3726 return;
3727 }
3728
3729 num = amountLeft / containers.Count;
3730
3731 foreach (var container in containers)
3732 {
3733 if (containers.Count % 1 == 0)
3734 {
3735 num++;
3736 }
3737
3738 if (num + itemAmountSpawned > Options.TreasureAmount)
3739 {
3740 num = Options.TreasureAmount - itemAmountSpawned;
3741 }
3742
3743 SpawnLoot(container, loot, num);
3744
3745 if (itemAmountSpawned == Options.TreasureAmount)
3746 {
3747 break;
3748 }
3749 }
3750 }
3751
3752 containers.Clear();
3753 }
3754
3755 private bool MoveToFridge(Item item)
3756 {
3757 if (!_config.Settings.Management.Food || _allcontainers.Count == 0 || item.info.category != ItemCategory.Food || !IsCooked(item.info))
3758 {
3759 return false;
3760 }
3761
3762 StorageContainer container = null;
3763
3764 if (_allcontainers.Count > 1)
3765 {
3766 Shuffle(_allcontainers);
3767 }
3768
3769 foreach (var x in _allcontainers)
3770 {
3771 if (x.inventory.itemList.Count < x.inventory.capacity && (x.prefabID == 378293714 || x.prefabID == 1844023509))
3772 {
3773 container = x;
3774 break;
3775 }
3776 }
3777
3778 if (container.IsValid() && item.MoveToContainer(container.inventory, -1, true))
3779 {
3780 return true;
3781 }
3782
3783 return false;
3784 }
3785
3786 private bool MoveToBBQ(Item item)
3787 {
3788 if (!_config.Settings.Management.Food || ovens.Count == 0 || item.info.category != ItemCategory.Food || IsCooked(item.info))
3789 {
3790 return false;
3791 }
3792
3793 BaseOven oven = null;
3794
3795 if (ovens.Count > 1)
3796 {
3797 Shuffle(ovens);
3798 }
3799
3800 foreach (var x in ovens)
3801 {
3802 if (x.inventory.itemList.Count < x.inventory.capacity && (x.prefabID == 2162666837 || x.prefabID == 2409469892))
3803 {
3804 oven = x;
3805 break;
3806 }
3807 }
3808
3809 if (oven.IsValid() && item.MoveToContainer(oven.inventory, -1, true))
3810 {
3811 return true;
3812 }
3813
3814 return false;
3815 }
3816
3817 private bool MoveToCupboard(Item item)
3818 {
3819 if (!_config.Settings.Management.Cupboard || !privSpawned || item.info.category != ItemCategory.Resources || IsCooked(item.info))
3820 {
3821 return false;
3822 }
3823
3824 if (priv != null && !priv.IsDestroyed && priv.inventory != null)
3825 {
3826 return item.MoveToContainer(priv.inventory, -1, true);
3827 }
3828
3829 return false;
3830 }
3831
3832 private bool IsCooked(ItemDefinition def)
3833 {
3834 return def.shortname.EndsWith(".cooked") || def.shortname.EndsWith(".burned") || def.GetComponent<ItemModConsumable>();
3835 }
3836
3837 private bool IsCookable(ItemDefinition def)
3838 {
3839 if (IsCooked(def) || def.shortname == "lowgradefuel")
3840 {
3841 return false;
3842 }
3843
3844 return def.GetComponent<ItemModCookable>() || def.GetComponent<ItemModBurnable>();
3845 }
3846
3847 private bool MoveToOven(Item item)
3848 {
3849 if (!_config.Settings.Management.Cook || ovens.Count == 0 || !IsCookable(item.info))
3850 {
3851 return false;
3852 }
3853
3854 BaseOven oven = null;
3855
3856 if (ovens.Count > 1)
3857 {
3858 Shuffle(ovens);
3859 }
3860
3861 foreach (var x in ovens)
3862 {
3863 if (x.inventory.itemList.Count < x.inventory.capacity)
3864 {
3865 oven = x;
3866 break;
3867 }
3868 }
3869
3870 if (oven.IsValid() && item.MoveToContainer(oven.inventory, -1, true))
3871 {
3872 if (!oven.IsOn())
3873 {
3874 oven.SetFlag(BaseEntity.Flags.On, true, false, true);
3875 }
3876
3877 if (!item.HasFlag(global::Item.Flag.OnFire))
3878 {
3879 item.SetFlag(global::Item.Flag.OnFire, true);
3880 item.MarkDirty();
3881 }
3882
3883 return true;
3884 }
3885
3886 return false;
3887 }
3888
3889 private void CheckExpansionSettings()
3890 {
3891 if (!_config.Settings.ExpansionMode || Backbone.Plugin.DangerousTreasures == null || !Backbone.Plugin.DangerousTreasures.IsLoaded || Backbone.Plugin.DangerousTreasures.Version < new VersionNumber(2, 0, 0))
3892 {
3893 return;
3894 }
3895
3896 var boxes = Pool.GetList<StorageContainer>();
3897
3898 foreach (var x in _containers)
3899 {
3900 if (x is BoxStorage)
3901 {
3902 boxes.Add(x);
3903 }
3904 }
3905
3906 if (boxes.Count > 0)
3907 {
3908 Backbone.Plugin.DangerousTreasures?.Call("API_SetContainer", boxes.GetRandom(), Radius, !Options.NPC.Enabled || Options.NPC.UseExpansionNpcs);
3909 boxes.Clear();
3910 }
3911
3912 Pool.Free(ref boxes);
3913 }
3914
3915 private void ToggleNpcMinerHat(NPCPlayerApex npc, bool state)
3916 {
3917 if (npc == null || npc.inventory == null || npc.IsDead())
3918 {
3919 return;
3920 }
3921
3922 var slot = npc.inventory.FindItemID("hat.miner");
3923
3924 if (slot == null)
3925 {
3926 return;
3927 }
3928
3929 if (state && slot.contents != null)
3930 {
3931 slot.contents.AddItem(ItemManager.FindItemDefinition("lowgradefuel"), 50);
3932 }
3933
3934 slot.SwitchOnOff(state);
3935 npc.inventory.ServerUpdate(0f);
3936 }
3937
3938 private void Lights()
3939 {
3940 if (lights.Count == 0 && ovens.Count == 0 && npcSpawnedAmount == 0)
3941 {
3942 CancelInvoke(Lights);
3943 return;
3944 }
3945
3946 if (_config.Settings.Management.AlwaysLights || (!lightsOn && !IsDayTime()))
3947 {
3948 lights.RemoveAll(e => e == null);
3949 ovens.RemoveAll(e => e == null);
3950
3951 var list = new List<BaseEntity>();
3952
3953 list.AddRange(lights);
3954 list.AddRange(ovens);
3955
3956 BaseOven furnace;
3957
3958 foreach (var e in list)
3959 {
3960 if (!e.IsOn())
3961 {
3962 if (e.prefabID == 2931042549)
3963 {
3964 furnace = e as BaseOven;
3965
3966 if (furnace.inventory.itemList.Count == 0)
3967 {
3968 continue;
3969 }
3970 }
3971
3972 e.SetFlag(BaseEntity.Flags.On, true, false, true);
3973 }
3974 }
3975
3976 foreach (var npc in npcs)
3977 {
3978 ToggleNpcMinerHat(npc, true);
3979 }
3980
3981 lightsOn = true;
3982 }
3983 else if (lightsOn && IsDayTime())
3984 {
3985 lights.RemoveAll(e => e == null);
3986 ovens.RemoveAll(e => e == null);
3987
3988 var list = new List<BaseEntity>();
3989
3990 list.AddRange(lights);
3991 list.AddRange(ovens);
3992
3993 foreach (var e in list)
3994 {
3995 if (e.prefabID == 2931042549 || e.prefabID == 4160694184 || e.prefabID == 1374462671 || e.prefabID == 2162666837 || e.prefabID == 2409469892)
3996 {
3997 continue;
3998 }
3999
4000 if (e.IsOn())
4001 {
4002 e.SetFlag(BaseEntity.Flags.On, false);
4003 }
4004 }
4005
4006 foreach (var npc in npcs)
4007 {
4008 ToggleNpcMinerHat(npc, false);
4009 }
4010
4011 lightsOn = false;
4012 }
4013 }
4014
4015 public bool IsDayTime() => TOD_Sky.Instance?.Cycle.DateTime.Hour >= 8 && TOD_Sky.Instance?.Cycle.DateTime.Hour < 20;
4016
4017 public void Undo()
4018 {
4019 if (IsOpened)
4020 {
4021 IsOpened = false;
4022 CancelInvoke(ResetOwner);
4023 Backbone.Plugin.UndoPaste(Location, this, BaseIndex);
4024 }
4025 }
4026
4027 public bool Any(ulong targetId, bool checkAll = true)
4028 {
4029 string targetIdString = targetId.ToString();
4030
4031 if (ownerId == targetIdString)
4032 {
4033 return true;
4034 }
4035
4036 foreach (var x in raiders)
4037 {
4038 if (x == targetIdString)
4039 {
4040 return true;
4041 }
4042 }
4043
4044 if (checkAll)
4045 {
4046 foreach (var x in intruders)
4047 {
4048 if (x?.userID == targetId)
4049 {
4050 return true;
4051 }
4052 }
4053
4054 foreach (var x in friends)
4055 {
4056 if (x?.userID == targetId)
4057 {
4058 return true;
4059 }
4060 }
4061 }
4062
4063 return false;
4064 }
4065
4066 public static bool Any(BasePlayer player)
4067 {
4068 foreach (var raid in Backbone.Plugin.Raids.Values)
4069 {
4070 if (raid.owner.IsValid() && raid.owner == player)
4071 {
4072 return true;
4073 }
4074 }
4075
4076 return false;
4077 }
4078
4079 public static bool Has(ulong userID)
4080 {
4081 return Backbone.Plugin.Npcs.ContainsKey(userID);
4082 }
4083
4084 public static bool Has(BaseEntity entity)
4085 {
4086 return Backbone.Plugin.RaidEntities.ContainsKey(entity);
4087 }
4088
4089 public static bool Has(NPCPlayerApex npc)
4090 {
4091 return npc.IsValid() && Has(npc.userID);
4092 }
4093
4094 public static int Get(RaidableType type)
4095 {
4096 int amount = 0;
4097
4098 foreach (var raid in Backbone.Plugin.Raids.Values)
4099 {
4100 if (raid.Type == type)
4101 {
4102 amount++;
4103 }
4104 }
4105
4106 return amount;
4107 }
4108
4109 public static int Get(RaidableMode mode)
4110 {
4111 int amount = 0;
4112
4113 foreach (var raid in Backbone.Plugin.Raids.Values)
4114 {
4115 if (raid.Options.Mode == mode)
4116 {
4117 amount++;
4118 }
4119 }
4120
4121 return amount;
4122 }
4123
4124 public static RaidableBase Get(ulong userID)
4125 {
4126 if (Backbone.Plugin.Npcs.ContainsKey(userID))
4127 {
4128 return Backbone.Plugin.Npcs[userID];
4129 }
4130
4131 return null;
4132 }
4133
4134 public static RaidableBase Get(Vector3 target)
4135 {
4136 foreach (var raid in Backbone.Plugin.Raids.Values)
4137 {
4138 if (InRange(raid.Location, target, raid.Options.ProtectionRadius))
4139 {
4140 return raid;
4141 }
4142 }
4143
4144 return null;
4145 }
4146
4147 public static RaidableBase Get(int baseIndex)
4148 {
4149 if (Backbone.Plugin.Indices.ContainsKey(baseIndex))
4150 {
4151 return Backbone.Plugin.Indices[baseIndex];
4152 }
4153
4154 return null;
4155 }
4156
4157 public static RaidableBase Get(BaseEntity entity)
4158 {
4159 if (Backbone.Plugin.RaidEntities.ContainsKey(entity))
4160 {
4161 return Backbone.Plugin.RaidEntities[entity];
4162 }
4163
4164 return null;
4165 }
4166
4167 public static RaidableBase Get(List<BaseEntity> entities)
4168 {
4169 entities.RemoveAll(e => e == null || e.IsDestroyed || e.transform == null);
4170
4171 foreach (var raid in Backbone.Plugin.Raids.Values)
4172 {
4173 foreach (var e in entities)
4174 {
4175 if (InRange(raid.PastedLocation, e.transform.position, Radius))
4176 {
4177 return raid;
4178 }
4179 }
4180 }
4181
4182 return null;
4183 }
4184
4185 public static bool IsTooClose(Vector3 target, float radius)
4186 {
4187 foreach (var raid in Backbone.Plugin.Raids.Values)
4188 {
4189 if (InRange(raid.Location, target, radius))
4190 {
4191 return true;
4192 }
4193 }
4194
4195 return false;
4196 }
4197
4198 private List<ulong> GetItemSkins(ItemDefinition def)
4199 {
4200 if (def == null)
4201 {
4202 return new List<ulong> { 0 };
4203 }
4204
4205 List<ulong> skins;
4206 if (!Backbone.Plugin.Skins.TryGetValue(def.shortname, out skins))
4207 {
4208 Backbone.Plugin.Skins[def.shortname] = skins = ExtractItemSkins(def, skins);
4209 }
4210
4211 return skins;
4212 }
4213
4214 private List<ulong> ExtractItemSkins(ItemDefinition def, List<ulong> skins)
4215 {
4216 skins = new List<ulong>();
4217
4218 foreach (var skin in def.skins)
4219 {
4220 skins.Add(Convert.ToUInt64(skin.id));
4221 }
4222
4223 if (Backbone.Plugin.WorkshopSkins.ContainsKey(def.shortname))
4224 {
4225 skins.AddRange(Backbone.Plugin.WorkshopSkins[def.shortname]);
4226 Backbone.Plugin.WorkshopSkins.Remove(def.shortname);
4227 }
4228
4229 return skins;
4230 }
4231
4232 private void AuthorizePlayer(NPCPlayerApex npc)
4233 {
4234 turrets.RemoveAll(x => !x.IsValid() || x.IsDestroyed);
4235
4236 foreach (var turret in turrets)
4237 {
4238 turret.authorizedPlayers.Add(new ProtoBuf.PlayerNameID
4239 {
4240 userid = npc.userID,
4241 username = npc.displayName
4242 });
4243
4244 turret.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
4245 }
4246
4247 if (priv == null || priv.IsDestroyed)
4248 {
4249 return;
4250 }
4251
4252 priv.authorizedPlayers.Add(new ProtoBuf.PlayerNameID
4253 {
4254 userid = npc.userID,
4255 username = npc.displayName
4256 });
4257
4258 priv.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
4259 }
4260
4261 public bool IsInSameClan(string playerId, string targetId)
4262 {
4263 if (playerId == targetId)
4264 {
4265 return false;
4266 }
4267
4268 if (Backbone.Plugin.Clans == null || !Backbone.Plugin.Clans.IsLoaded)
4269 {
4270 return false;
4271 }
4272
4273 var clan = new List<string>();
4274
4275 foreach (var x in _clans.Values)
4276 {
4277 if (x.Contains(playerId))
4278 {
4279 if (x.Contains(targetId))
4280 {
4281 return true;
4282 }
4283
4284 clan = x;
4285 break;
4286 }
4287 }
4288
4289 string playerClan = Backbone.Plugin.Clans?.Call("GetClanOf", playerId) as string;
4290
4291 if (string.IsNullOrEmpty(playerClan))
4292 {
4293 return false;
4294 }
4295
4296 string targetClan = Backbone.Plugin.Clans?.Call("GetClanOf", targetId) as string;
4297
4298 if (string.IsNullOrEmpty(targetClan))
4299 {
4300 return false;
4301 }
4302
4303 if (playerClan == targetClan)
4304 {
4305 if (!_clans.ContainsKey(playerClan))
4306 {
4307 _clans[playerClan] = clan;
4308 }
4309
4310 clan.Add(playerId);
4311 clan.Add(targetId);
4312 return true;
4313 }
4314
4315 return false;
4316 }
4317
4318 public void UpdateClans(string clan)
4319 {
4320 _clans.Remove(clan);
4321 }
4322
4323 public void UpdateFriends(string playerId, string targetId, bool added)
4324 {
4325 List<string> playerList;
4326 if (_friends.TryGetValue(playerId, out playerList))
4327 {
4328 if (added)
4329 {
4330 playerList.Add(targetId);
4331 }
4332 else playerList.Remove(targetId);
4333 }
4334 }
4335
4336 public bool IsFriends(string playerId, string targetId)
4337 {
4338 if (playerId == targetId)
4339 {
4340 return false;
4341 }
4342
4343 if (Backbone.Plugin.Friends == null || !Backbone.Plugin.Friends.IsLoaded)
4344 {
4345 return false;
4346 }
4347
4348 List<string> targetList;
4349 if (!_friends.TryGetValue(targetId, out targetList))
4350 {
4351 _friends[targetId] = targetList = new List<string>();
4352 }
4353
4354 if (targetList.Contains(playerId))
4355 {
4356 return true;
4357 }
4358
4359 var success = Backbone.Plugin.Friends?.Call("AreFriends", playerId, targetId);
4360
4361 if (success is bool && (bool)success)
4362 {
4363 targetList.Add(playerId);
4364 return true;
4365 }
4366
4367 return false;
4368 }
4369
4370 public bool IsOnSameTeam(ulong playerId, ulong targetId)
4371 {
4372 if (playerId == targetId)
4373 {
4374 return false;
4375 }
4376
4377 RelationshipManager.PlayerTeam team1;
4378 if (!RelationshipManager.Instance.playerToTeam.TryGetValue(playerId, out team1))
4379 {
4380 return false;
4381 }
4382
4383 RelationshipManager.PlayerTeam team2;
4384 if (!RelationshipManager.Instance.playerToTeam.TryGetValue(targetId, out team2))
4385 {
4386 return false;
4387 }
4388
4389 return team1.teamID == team2.teamID;
4390 }
4391
4392 public bool IsAlly(ulong playerId, ulong targetId)
4393 {
4394 return playerId == targetId || IsOnSameTeam(playerId, targetId) || IsInSameClan(playerId.ToString(), targetId.ToString()) || IsFriends(playerId.ToString(), targetId.ToString());
4395 }
4396
4397 public bool IsAlly(BasePlayer player)
4398 {
4399 if (!owner.IsValid() || !owner.IsConnected || CanBypass(player) || player.UserIDString == ownerId || friends.Contains(player))
4400 {
4401 return true;
4402 }
4403
4404 return IsOwnerAlly(player);
4405 }
4406
4407 private bool IsOwnerAlly(BasePlayer player)
4408 {
4409 if (IsOnSameTeam(player.userID, owner.userID) || IsInSameClan(player.UserIDString, owner.UserIDString) || IsFriends(player.UserIDString, owner.UserIDString))
4410 {
4411 friends.Add(player);
4412 return true;
4413 }
4414
4415 return false;
4416 }
4417
4418 public static void StopUsingWand(BasePlayer player)
4419 {
4420 if (!_config.Settings.NoWizardry || Backbone.Plugin.Wizardry == null || !Backbone.Plugin.Wizardry.IsLoaded)
4421 {
4422 return;
4423 }
4424
4425 if (player.svActiveItemID == 0)
4426 {
4427 return;
4428 }
4429
4430 Item item = player.GetActiveItem();
4431
4432 if (item?.info.shortname != "knife.bone")
4433 {
4434 return;
4435 }
4436
4437 if (!item.MoveToContainer(player.inventory.containerMain))
4438 {
4439 item.DropAndTossUpwards(player.GetDropPosition() + player.transform.forward, 2f);
4440 Backbone.Message(player, "TooPowerfulDrop", player.UserIDString);
4441 }
4442 else Backbone.Message(player, "TooPowerful", player.UserIDString);
4443 }
4444
4445 private int targetLayer { get; set; } = ~(Layers.Mask.Invisible | Layers.Mask.Trigger | Layers.Mask.Prevent_Movement | Layers.Mask.Prevent_Building); // credits ZoneManager
4446
4447 public Vector3 GetEjectLocation(Vector3 a, float distance)
4448 {
4449 var position = ((a.XZ3D() - Location.XZ3D()).normalized * (Options.ProtectionRadius + distance)) + Location; // credits ZoneManager
4450 float y = TerrainMeta.HighestPoint.y + 250f;
4451
4452 RaycastHit hit;
4453 if (Physics.Raycast(position + new Vector3(0f, y, 0f), Vector3.down, out hit, Mathf.Infinity, targetLayer, QueryTriggerInteraction.Ignore))
4454 {
4455 position.y = hit.point.y + 0.5f;
4456 }
4457 else position.y = TerrainMeta.HeightMap.GetHeight(position) + 0.5f;
4458
4459 return position;
4460 }
4461
4462 public void AddCorpse(PlayerCorpse corpse, BasePlayer player)
4463 {
4464 if (!Options.EjectCorpses || corpses.ContainsKey(corpse))
4465 {
4466 return;
4467 }
4468
4469 corpses.Add(corpse, player);
4470 }
4471
4472 public void EjectCorpse(BasePlayer player, PlayerCorpse corpse)
4473 {
4474 if (/*_config.Settings.Management.BypassUseOwnersForPVP &&*/ AllowPVP && !owner.IsValid())
4475 {
4476 return;
4477 }
4478
4479 var position = GetEjectLocation(corpse.transform.position, 5f);
4480 float w = TerrainMeta.WaterMap.GetHeight(position);
4481
4482 if (position.y < w) position.y = w;
4483
4484 var container = ItemContainer.Drop(StringPool.Get(1519640547), position, Quaternion.identity, corpse.containers);
4485
4486 if (container == null)
4487 {
4488 return;
4489 }
4490
4491 container.playerName = player.displayName;
4492 container.playerSteamID = player.userID;
4493
4494 if (!corpse.IsDestroyed)
4495 {
4496 corpse.Kill();
4497 }
4498
4499 if (player.IsValid() && player.IsConnected)
4500 {
4501 if (_config.Settings.Management.DrawTime <= 0)
4502 {
4503 Backbone.Message(player, "YourCorpse", player.UserIDString);
4504 corpses.Remove(corpse);
4505 return;
4506 }
4507
4508 bool isAdmin = player.IsAdmin;
4509
4510 try
4511 {
4512 if (!isAdmin)
4513 {
4514 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
4515 player.SendNetworkUpdateImmediate();
4516 }
4517
4518 player.SendConsoleCommand("ddraw.text", _config.Settings.Management.DrawTime, Color.red, container.transform.position, Backbone.GetMessage("YourCorpse", player.UserIDString));
4519 }
4520 catch (Exception ex)
4521 {
4522 Backbone.Plugin.Puts(ex.StackTrace);
4523 Backbone.Plugin.Puts(ex.Message);
4524 }
4525 finally
4526 {
4527 if (!isAdmin)
4528 {
4529 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
4530 player.SendNetworkUpdateImmediate();
4531 }
4532 }
4533 }
4534
4535 corpses.Remove(corpse);
4536 }
4537
4538 private void EjectSleepers()
4539 {
4540 if (!_config.Settings.Management.EjectSleepers || Type == RaidableType.None)
4541 {
4542 return;
4543 }
4544
4545 int hits = Physics.OverlapSphereNonAlloc(Location, Options.ProtectionRadius, Vis.colBuffer, Layers.Mask.Player_Server, QueryTriggerInteraction.Ignore);
4546 Collider collider;
4547 BasePlayer e;
4548
4549 for (int i = 0; i < hits; i++)
4550 {
4551 collider = Vis.colBuffer[i];
4552
4553 if (collider == null || collider.transform == null)
4554 {
4555 goto next;
4556 }
4557
4558 e = collider.ToBaseEntity() as BasePlayer;
4559
4560 if (e?.transform != null && !e.IsNpc && e.IsSleeping() && !e.CanBuild())
4561 {
4562 RemovePlayer(e);
4563 }
4564
4565 next:
4566 Vis.colBuffer[i] = null;
4567 }
4568 }
4569
4570 public void RemovePlayer(BasePlayer player)
4571 {
4572 if ((Type == RaidableType.None && !player.IsSleeping()) || JustDied(player))
4573 {
4574 return;
4575 }
4576
4577 if (player.isMounted)
4578 {
4579 RemoveMountable(player.GetMounted());
4580 return;
4581 }
4582
4583 var position = GetEjectLocation(player.transform.position, 10f);
4584
4585 if (player.IsFlying)
4586 {
4587 position.y = player.transform.position.y;
4588 }
4589
4590 player.EnsureDismounted();
4591 player.Teleport(position);
4592 player.SendNetworkUpdateImmediate();
4593 }
4594
4595 private bool JustDied(BasePlayer target)
4596 {
4597 return target?.previousLifeStory != null && target.lifeStory != null && (uint)Epoch.Current - target.lifeStory.timeBorn <= 6;
4598 }
4599
4600 private bool CanEject(BaseMountable m)
4601 {
4602 var player = BasePlayer.FindAwakeOrSleeping(m.OwnerID.ToString());
4603
4604 return CanEject(player);
4605 }
4606
4607 private bool CanEject(BasePlayer target)
4608 {
4609 if (Type == RaidableType.None || target == null || target == owner)
4610 {
4611 return false;
4612 }
4613
4614 if (Backbone.HasPermission(target.UserIDString, banPermission))
4615 {
4616 if (CanMessage(target))
4617 {
4618 Backbone.Message(target, "Banned", target.UserIDString);
4619 }
4620
4621 return true;
4622 }
4623 else if (HasLockout(target) || IsHogging(target))
4624 {
4625 return true;
4626 }
4627 else if (CanEject() && !IsAlly(target))
4628 {
4629 return true;
4630 }
4631 else if (_config.Settings.Management.EjectSleepers && target.IsSleeping() && !target.IsConnected && Type != RaidableType.None)
4632 {
4633 return true;
4634 }
4635
4636 return false;
4637 }
4638
4639 public bool CanEject()
4640 {
4641 if (IsPayLocked && AllowPVP && Options.EjectPurchasedPVP)
4642 {
4643 return true;
4644 }
4645
4646 if (IsPayLocked && !AllowPVP && Options.EjectPurchasedPVE)
4647 {
4648 return true;
4649 }
4650
4651 if (AllowPVP && Options.EjectLockedPVP && owner.IsValid())
4652 {
4653 return true;
4654 }
4655
4656 if (!AllowPVP && Options.EjectLockedPVE && owner.IsValid())
4657 {
4658 return true;
4659 }
4660
4661 return false;
4662 }
4663
4664 private bool TryRemoveMountable(BaseMountable m)
4665 {
4666 if (CanEject(m))
4667 {
4668 return RemoveMountable(m);
4669 }
4670
4671 if (_config.Settings.Management.Mounts.Owner && !m.OwnerID.IsSteamId())
4672 {
4673 return RemoveMountable(m);
4674 }
4675
4676 if (_config.Settings.Management.Mounts.Boats && m is BaseBoat)
4677 {
4678 return RemoveMountable(m);
4679 }
4680 else if (_config.Settings.Management.Mounts.BasicCars && m is BasicCar)
4681 {
4682 return RemoveMountable(m);
4683 }
4684 else if (_config.Settings.Management.Mounts.ModularCars && m is ModularCar)
4685 {
4686 return RemoveMountable(m);
4687 }
4688 else if (_config.Settings.Management.Mounts.CH47 && m is CH47Helicopter)
4689 {
4690 return RemoveMountable(m);
4691 }
4692 else if (_config.Settings.Management.Mounts.Horses && m is RidableHorse)
4693 {
4694 return RemoveMountable(m);
4695 }
4696 else if (_config.Settings.Management.Mounts.Scrap && m is ScrapTransportHelicopter)
4697 {
4698 return RemoveMountable(m);
4699 }
4700 else if (_config.Settings.Management.Mounts.MiniCopters && m is MiniCopter && !(m is ScrapTransportHelicopter))
4701 {
4702 return RemoveMountable(m);
4703 }
4704 else if (_config.Settings.Management.Mounts.Pianos && m is StaticInstrument)
4705 {
4706 return RemoveMountable(m);
4707 }
4708
4709 var players = GetMountedPlayers(m);
4710
4711 foreach (var player in players)
4712 {
4713 if (CanEject(player))
4714 {
4715 return RemoveMountable(m);
4716 }
4717 }
4718
4719 return false;
4720 }
4721
4722 private bool RemoveMountable(BaseMountable m)
4723 {
4724 bool flag = m is MiniCopter || m is CH47Helicopter;
4725
4726 if (InRange(m.transform.position, Location, Radius, false))
4727 {
4728 EjectMountable(m, flag);
4729 return true;
4730 }
4731
4732 var e = m.transform.eulerAngles; // credits k1lly0u
4733
4734 if (m is RidableHorse)
4735 {
4736 m.transform.rotation = m.mountAnchor.transform.rotation = Quaternion.Euler(e.x, e.y - 180f, e.z);
4737 }
4738 else m.transform.rotation = Quaternion.Euler(e.x, e.y - 180f, e.z);
4739
4740 var rigidBody = m.GetComponent<Rigidbody>();
4741
4742 if (rigidBody)
4743 {
4744 rigidBody.velocity *= -1f;
4745 }
4746
4747 if (flag || m is BaseBoat || m is StaticInstrument)
4748 {
4749 EjectMountable(m, flag);
4750 }
4751
4752 return true;
4753 }
4754
4755 private void EjectMountable(BaseMountable m, bool flag)
4756 {
4757 var position = ((m.transform.position.XZ3D() - Location.XZ3D()).normalized * (Options.ProtectionRadius + 10f)) + Location; // credits k1lly0u
4758 float y = TerrainMeta.HighestPoint.y + 250f;
4759
4760 if (!flag)
4761 {
4762 RaycastHit hit;
4763 if (Physics.Raycast(position + new Vector3(0f, y, 0f), Vector3.down, out hit, position.y + y + 1f, targetLayer, QueryTriggerInteraction.Ignore))
4764 {
4765 position.y = hit.point.y;
4766 }
4767 else position.y = GetSpawnHeight(position);
4768 }
4769
4770 if (m.transform.position.y > position.y)
4771 {
4772 position.y = m.transform.position.y;
4773 }
4774
4775 if (flag)
4776 {
4777 m.transform.position = position;
4778 }
4779 else m.transform.position = m.mountAnchor.transform.position = position;
4780
4781 m.TransformChanged();
4782 }
4783
4784 public bool CanSetupEntity(BaseEntity e)
4785 {
4786 BaseEntity.saveList.Remove(e);
4787
4788 if (e == null || e.IsDestroyed)
4789 {
4790 if (e != null)
4791 {
4792 e.enableSaving = false;
4793 }
4794
4795 if (Backbone.Plugin.Bases.ContainsKey(BaseIndex))
4796 {
4797 Backbone.Plugin.Bases[BaseIndex].Remove(e);
4798 }
4799
4800 return false;
4801 }
4802
4803 if (e.net == null)
4804 {
4805 e.net = Net.sv.CreateNetworkable();
4806 }
4807
4808 e.enableSaving = false;
4809
4810 return true;
4811 }
4812
4813 public void RespawnNpc()
4814 {
4815 if (IsInvoking(RespawnNpcNow) || !IsOpened && !Options.Levels.Level2)
4816 {
4817 return;
4818 }
4819
4820 Invoke(RespawnNpcNow, Mathf.Max(1f, Options.RespawnRate));
4821 }
4822
4823 public void RespawnNpcNow()
4824 {
4825 if (!IsOpened && !Options.Levels.Level2)
4826 {
4827 return;
4828 }
4829
4830 SpawnNPC(Options.NPC.SpawnScientistsOnly ? false : Options.NPC.SpawnBoth ? UnityEngine.Random.value > 0.5f : Options.NPC.SpawnMurderers);
4831
4832 if (npcs.Count < npcSpawnedAmount)
4833 {
4834 RespawnNpc();
4835 }
4836 }
4837
4838 public void SpawnNpcs()
4839 {
4840 if (!Options.NPC.Enabled || (Options.NPC.UseExpansionNpcs && _config.Settings.ExpansionMode && Backbone.Plugin.DangerousTreasures != null && Backbone.Plugin.DangerousTreasures.IsLoaded))
4841 {
4842 return;
4843 }
4844
4845 int amount = Options.NPC.SpawnRandomAmount && Options.NPC.SpawnAmount > 1 ? UnityEngine.Random.Range(Options.NPC.SpawnMinAmount, Options.NPC.SpawnAmount) : Options.NPC.SpawnAmount;
4846
4847 for (int i = 0; i < amount; i++)
4848 {
4849 SpawnNPC(Options.NPC.SpawnScientistsOnly ? false : Options.NPC.SpawnBoth ? UnityEngine.Random.value > 0.5f : Options.NPC.SpawnMurderers);
4850 }
4851
4852 npcSpawnedAmount = npcs.Count;
4853 }
4854
4855 private Vector3 FindPointOnNavmesh(Vector3 target, float radius)
4856 {
4857 int tries = 0;
4858 NavMeshHit navHit;
4859
4860 while (++tries < 100)
4861 {
4862 if (NavMesh.SamplePosition(target, out navHit, radius, 1))
4863 {
4864 float y = TerrainMeta.HeightMap.GetHeight(navHit.position);
4865 bool flag = false;
4866
4867 foreach (var a in foundations)
4868 {
4869 if (InRange(a, navHit.position, 1f))
4870 {
4871 flag = true;
4872 break;
4873 }
4874 }
4875
4876 if (flag)
4877 {
4878 continue;
4879 }
4880
4881 if (IsInOrOnRock(navHit.position, "rock_") || navHit.position.y < y)
4882 {
4883 continue;
4884 }
4885
4886 if (TerrainMeta.WaterMap.GetHeight(navHit.position) - y > 1f)
4887 {
4888 continue;
4889 }
4890
4891 if ((navHit.position - Location).magnitude > Mathf.Max(radius * 2f, Options.ProtectionRadius) - 2.5f)
4892 {
4893 continue;
4894 }
4895
4896 return navHit.position;
4897 }
4898 }
4899
4900 return Vector3.zero;
4901 }
4902
4903 private bool IsRockTooLarge(Bounds bounds, float extents = 1.5f)
4904 {
4905 return bounds.extents.Max() > extents;
4906 }
4907
4908 private bool IsInOrOnRock(Vector3 position, string meshName, float radius = 2f)
4909 {
4910 bool flag = false;
4911 int hits = Physics.OverlapSphereNonAlloc(position, radius, Vis.colBuffer, Layers.Mask.World, QueryTriggerInteraction.Ignore);
4912 for (int i = 0; i < hits; i++)
4913 {
4914 if (Vis.colBuffer[i].name.StartsWith(meshName) && IsRockTooLarge(Vis.colBuffer[i].bounds))
4915 {
4916 flag = true;
4917 }
4918
4919 Vis.colBuffer[i] = null;
4920 }
4921 if (!flag)
4922 {
4923 float y = TerrainMeta.HighestPoint.y + 250f;
4924 RaycastHit hit;
4925 if (Physics.Raycast(position, Vector3.up, out hit, y, Layers.Mask.World, QueryTriggerInteraction.Ignore))
4926 {
4927 if (hit.collider.name.StartsWith(meshName) && IsRockTooLarge(hit.collider.bounds)) flag = true;
4928 }
4929 if (!flag && Physics.Raycast(position, Vector3.down, out hit, y, Layers.Mask.World, QueryTriggerInteraction.Ignore))
4930 {
4931 if (hit.collider.name.StartsWith(meshName) && IsRockTooLarge(hit.collider.bounds)) flag = true;
4932 }
4933 if (!flag && Physics.Raycast(position + new Vector3(0f, y, 0f), Vector3.down, out hit, y + 1f, Layers.Mask.World, QueryTriggerInteraction.Ignore))
4934 {
4935 if (hit.collider.name.StartsWith(meshName) && IsRockTooLarge(hit.collider.bounds)) flag = true;
4936 }
4937 }
4938 return flag;
4939 }
4940
4941 private static NPCPlayerApex InstantiateEntity(Vector3 position, bool murd)
4942 {
4943 var prefabName = murd ? Backbone.Path.Murderer : Backbone.Path.Scientist;
4944 var prefab = GameManager.server.FindPrefab(prefabName);
4945 var go = Facepunch.Instantiate.GameObject(prefab, position, default(Quaternion));
4946
4947 go.name = prefabName;
4948 SceneManager.MoveGameObjectToScene(go, Rust.Server.EntityScene);
4949
4950 if (go.GetComponent<Spawnable>())
4951 {
4952 Destroy(go.GetComponent<Spawnable>());
4953 }
4954
4955 if (!go.activeSelf)
4956 {
4957 go.SetActive(true);
4958 }
4959
4960 return go.GetComponent<NPCPlayerApex>();
4961 }
4962
4963 private List<Vector3> RandomWanderPositions
4964 {
4965 get
4966 {
4967 var list = new List<Vector3>();
4968 float maxRoamRange = Options.ArenaWalls.Enabled ? 20f : Mathf.Max(Radius * 2f, Options.ProtectionRadius);
4969
4970 for (int i = 0; i < 10; i++)
4971 {
4972 var vector = FindPointOnNavmesh(GetRandomPoint(maxRoamRange), 15f);
4973
4974 if (vector != Vector3.zero)
4975 {
4976 list.Add(vector);
4977 }
4978 }
4979
4980 return list;
4981 }
4982 }
4983
4984 private Vector3 GetRandomPoint(float radius)
4985 {
4986 var vector = Location + UnityEngine.Random.onUnitSphere * radius;
4987
4988 vector.y = TerrainMeta.HeightMap.GetHeight(vector);
4989
4990 return vector;
4991 }
4992
4993 private NPCPlayerApex SpawnNPC(bool murd)
4994 {
4995 var list = RandomWanderPositions;
4996
4997 if (list.Count == 0)
4998 return null;
4999
5000 var npc = InstantiateEntity(GetRandomPoint(Radius * 0.85f), murd); //var npc = InstantiateEntity(GetRandomPoint(Mathf.Max(Options.ArenaWalls.Radius, Options.ProtectionRadius) * 0.85f), murd);
5001
5002 if (npc == null)
5003 return null;
5004
5005 npc.Spawn();
5006 npc.IsInvinsible = false;
5007 npc.startHealth = murd ? Options.NPC.MurdererHealth : Options.NPC.ScientistHealth;
5008 npc.InitializeHealth(npc.startHealth, npc.startHealth);
5009 npc.CommunicationRadius = 0;
5010 npc.RadioEffect.guid = null;
5011 npc.displayName = Options.NPC.RandomNames.Count > 0 ? Options.NPC.RandomNames.GetRandom() : RandomUsernames.Get(npc.userID);
5012 npc.Stats.AggressionRange = Options.NPC.AggressionRange;
5013 npc.Stats.DeaggroRange = Options.NPC.AggressionRange * 1.125f;
5014 npc.NeverMove = true;
5015
5016 npc.Invoke(() => EquipNpc(npc, murd), 1f);
5017
5018 npc.Invoke(() =>
5019 {
5020 Item projectileItem = null;
5021
5022 foreach (var item in npc.inventory.containerBelt.itemList)
5023 {
5024 if (item.GetHeldEntity() is BaseProjectile)
5025 {
5026 projectileItem = item;
5027 break;
5028 }
5029 }
5030
5031 if (projectileItem != null)
5032 {
5033 npc.UpdateActiveItem(projectileItem.uid);
5034 }
5035 else npc.EquipWeapon();
5036 }, 2f);
5037
5038 if (Options.NPC.DespawnInventory)
5039 {
5040 if (murd)
5041 {
5042 (npc as NPCMurderer).LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
5043 }
5044 else (npc as Scientist).LootSpawnSlots = new LootContainer.LootSpawnSlot[0];
5045 }
5046
5047 if (!murd)
5048 {
5049 (npc as Scientist).LootPanelName = npc.displayName;
5050 }
5051
5052 npcs.Add(npc);
5053 AuthorizePlayer(npc);
5054 npc.Invoke(() => UpdateDestination(npc, list), 0.25f);
5055 Backbone.Plugin.Npcs[npc.userID] = this;
5056
5057 return npc;
5058 }
5059
5060 private void SetupNpcKits()
5061 {
5062 var murdererKits = new List<string>();
5063 var scientistKits = new List<string>();
5064
5065 foreach (string kit in Options.NPC.MurdererKits)
5066 {
5067 if (IsKit(kit))
5068 {
5069 murdererKits.Add(kit);
5070 }
5071 }
5072
5073 foreach (string kit in Options.NPC.ScientistKits)
5074 {
5075 if (IsKit(kit))
5076 {
5077 scientistKits.Add(kit);
5078 }
5079 }
5080
5081 npcKits = new Dictionary<string, List<string>>
5082 {
5083 { "murderer", murdererKits },
5084 { "scientist", scientistKits }
5085 };
5086 }
5087
5088 private bool IsKit(string kit)
5089 {
5090 var success = Backbone.Plugin.Kits?.Call("isKit", kit);
5091
5092 if (success == null || !(success is bool))
5093 {
5094 return false;
5095 }
5096
5097 return (bool)success;
5098 }
5099
5100 private void EquipNpc(NPCPlayerApex npc, bool murd)
5101 {
5102 List<string> kits;
5103 if (npcKits.TryGetValue(murd ? "murderer" : "scientist", out kits) && kits.Count > 0)
5104 {
5105 npc.inventory.Strip();
5106
5107 object success = Backbone.Plugin.Kits?.Call("GiveKit", npc, kits.GetRandom());
5108
5109 if (success is bool && (bool)success)
5110 {
5111 goto done;
5112 }
5113 }
5114
5115 var items = murd ? Options.NPC.MurdererItems : Options.NPC.ScientistItems;
5116
5117 if (items.Count == 0)
5118 {
5119 goto done;
5120 }
5121
5122 npc.inventory.Strip();
5123 List<ulong> skins;
5124 BaseProjectile weapon;
5125
5126 foreach (string shortname in items)
5127 {
5128 Item item = ItemManager.CreateByName(shortname, 1, 0);
5129
5130 if (item == null)
5131 {
5132 Backbone.Plugin.PrintError("Invalid shortname in config: {0}", shortname);
5133 continue;
5134 }
5135
5136 skins = GetItemSkins(item.info);
5137
5138 if (skins.Count > 0 && item.info.stackable <= 1)
5139 {
5140 ulong skin = skins.GetRandom();
5141 item.skin = skin;
5142 }
5143
5144 if (item.skin != 0 && item.GetHeldEntity())
5145 {
5146 item.GetHeldEntity().skinID = item.skin;
5147 }
5148
5149 weapon = item.GetHeldEntity() as BaseProjectile;
5150
5151 if (weapon != null)
5152 {
5153 weapon.primaryMagazine.contents = weapon.primaryMagazine.capacity;
5154 weapon.SendNetworkUpdateImmediate();
5155 }
5156
5157 item.MarkDirty();
5158
5159 if (!item.MoveToContainer(npc.inventory.containerWear, -1, false) && !item.MoveToContainer(npc.inventory.containerBelt, -1, false) && !item.MoveToContainer(npc.inventory.containerMain, -1, true))
5160 {
5161 item.Remove();
5162 }
5163 }
5164
5165 done:
5166 ToggleNpcMinerHat(npc, !IsDayTime());
5167 }
5168
5169 private void UpdateDestination(NPCPlayerApex npc, List<Vector3> list)
5170 {
5171 npc.gameObject.AddComponent<FinalDestination>().Set(npc, list, Options.NPC);
5172 }
5173
5174 public static void UpdateAllMarkers()
5175 {
5176 foreach (var raid in Backbone.Plugin.Raids.Values)
5177 {
5178 raid.UpdateMarker();
5179 }
5180 }
5181
5182 public void UpdateMarker()
5183 {
5184 if (IsLoading)
5185 {
5186 Invoke(UpdateMarker, 1f);
5187 return;
5188 }
5189
5190 if (genericMarker != null && !genericMarker.IsDestroyed)
5191 {
5192 genericMarker.SendUpdate();
5193 }
5194
5195 if (vendingMarker != null && !vendingMarker.IsDestroyed)
5196 {
5197 vendingMarker.transform.position = Location;
5198 float seconds = despawnTime - Time.realtimeSinceStartup;
5199 string despawnText = _config.Settings.Management.DespawnMinutesInactive > 0 && seconds > 0 ? Math.Floor(TimeSpan.FromSeconds(seconds).TotalMinutes).ToString() : null;
5200 string flag = Backbone.GetMessage(AllowPVP ? "PVPFlag" : "PVEFlag");
5201 string markerShopName = markerName == _config.Settings.Markers.MarkerName ? string.Format("{0}{1} {2}", flag, Mode(), markerName) : string.Format("{0} {1}", flag, markerName).TrimStart();
5202 vendingMarker.markerShopName = string.IsNullOrEmpty(despawnText) ? markerShopName : string.Format("{0} [{1}m]", markerShopName, despawnText);
5203 vendingMarker.SendNetworkUpdate();
5204 }
5205
5206 if (markerCreated || !IsMarkerAllowed())
5207 {
5208 return;
5209 }
5210
5211 if (_config.Settings.Markers.UseExplosionMarker)
5212 {
5213 explosionMarker = GameManager.server.CreateEntity(Backbone.Path.ExplosionMarker, Location) as MapMarkerExplosion;
5214
5215 if (explosionMarker != null)
5216 {
5217
5218 explosionMarker.Spawn();
5219 explosionMarker.SendMessage("SetDuration", 60, SendMessageOptions.DontRequireReceiver);
5220
5221 }
5222 }
5223 else if (_config.Settings.Markers.UseVendingMarker)
5224 {
5225 vendingMarker = GameManager.server.CreateEntity(Backbone.Path.VendingMarker, Location) as VendingMachineMapMarker;
5226
5227 if (vendingMarker != null)
5228 {
5229 string flag = Backbone.GetMessage(AllowPVP ? "PVPFlag" : "PVEFlag");
5230 string markerShopName = markerName == _config.Settings.Markers.MarkerName ? string.Format("{0}{1} {2}", flag, Mode(), markerName) : string.Format("{0}{1}", flag, markerName);
5231
5232 vendingMarker.enabled = false;
5233 vendingMarker.markerShopName = markerShopName;
5234 vendingMarker.Spawn();
5235 }
5236 }
5237
5238 markerCreated = true;
5239 }
5240
5241 private void CreateGenericMarker()
5242 {
5243 if (_config.Settings.Markers.UseExplosionMarker || _config.Settings.Markers.UseVendingMarker)
5244 {
5245 if (!IsMarkerAllowed())
5246 {
5247 return;
5248 }
5249
5250 genericMarker = GameManager.server.CreateEntity(Backbone.Path.RadiusMarker, Location) as MapMarkerGenericRadius;
5251
5252 if (genericMarker != null)
5253 {
5254 genericMarker.alpha = 0.75f;
5255 genericMarker.color1 = GetMarkerColor1();
5256 genericMarker.color2 = GetMarkerColor2();
5257 genericMarker.radius = Mathf.Min(2.5f, _config.Settings.Markers.Radius);
5258 genericMarker.Spawn();
5259 genericMarker.SendUpdate();
5260 }
5261 }
5262 }
5263
5264 private Color GetMarkerColor1()
5265 {
5266 if (Type == RaidableType.None)
5267 {
5268 return Color.clear;
5269 }
5270
5271 Color color;
5272
5273 switch (Options.Mode)
5274 {
5275 case RaidableMode.Easy:
5276 {
5277 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors1.Easy, out color))
5278 {
5279 return color;
5280 }
5281 }
5282
5283 return Color.green;
5284 case RaidableMode.Medium:
5285 {
5286 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors1.Medium, out color))
5287 {
5288 return color;
5289 }
5290 }
5291
5292 return Color.yellow;
5293 case RaidableMode.Hard:
5294 {
5295 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors1.Hard, out color))
5296 {
5297 return color;
5298 }
5299 }
5300
5301 return Color.red;
5302 case RaidableMode.Expert:
5303 {
5304 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors1.Expert, out color))
5305 {
5306 return color;
5307 }
5308 }
5309
5310 return Color.blue;
5311 case RaidableMode.Nightmare:
5312 default:
5313 {
5314 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors1.Nightmare, out color))
5315 {
5316 return color;
5317 }
5318 }
5319
5320 return Color.black;
5321 }
5322 }
5323
5324 private Color GetMarkerColor2()
5325 {
5326 if (Type == RaidableType.None)
5327 {
5328 return NoneColor;
5329 }
5330
5331 Color color;
5332
5333 switch (Options.Mode)
5334 {
5335 case RaidableMode.Easy:
5336 {
5337 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors2.Easy, out color))
5338 {
5339 return color;
5340 }
5341 }
5342
5343 return Color.green;
5344 case RaidableMode.Medium:
5345 {
5346 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors2.Medium, out color))
5347 {
5348 return color;
5349 }
5350 }
5351
5352 return Color.yellow;
5353 case RaidableMode.Hard:
5354 {
5355 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors2.Hard, out color))
5356 {
5357 return color;
5358 }
5359 }
5360
5361 return Color.red;
5362 case RaidableMode.Expert:
5363 {
5364 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors2.Expert, out color))
5365 {
5366 return color;
5367 }
5368 }
5369
5370 return Color.blue;
5371 case RaidableMode.Nightmare:
5372 default:
5373 {
5374 if (ColorUtility.TryParseHtmlString(_config.Settings.Management.Colors2.Nightmare, out color))
5375 {
5376 return color;
5377 }
5378 }
5379
5380 return Color.black;
5381 }
5382 }
5383
5384 private bool IsMarkerAllowed()
5385 {
5386 switch (Type)
5387 {
5388 case RaidableType.Grid:
5389 case RaidableType.Manual:
5390 case RaidableType.None:
5391 {
5392 if (!_config.Settings.Markers.Manual)
5393 {
5394 return false;
5395 }
5396
5397 break;
5398 }
5399 case RaidableType.Maintained:
5400 {
5401 if (!_config.Settings.Markers.Maintained)
5402 {
5403 return false;
5404 }
5405
5406 break;
5407 }
5408 case RaidableType.Purchased:
5409 {
5410 if (!_config.Settings.Markers.Buyables)
5411 {
5412 return false;
5413 }
5414
5415 break;
5416 }
5417 case RaidableType.Scheduled:
5418 {
5419 if (!_config.Settings.Markers.Scheduled)
5420 {
5421 return false;
5422 }
5423
5424 break;
5425 }
5426 }
5427
5428 return true;
5429 }
5430
5431 private void KillNpc()
5432 {
5433 var list = new List<BaseEntity>(npcs);
5434
5435 foreach (var npc in list)
5436 {
5437 if (npc != null && !npc.IsDestroyed)
5438 {
5439 npc.Kill();
5440 }
5441 }
5442
5443 npcs.Clear();
5444 list.Clear();
5445 }
5446
5447 private void RemoveSpheres()
5448 {
5449 if (spheres.Count > 0)
5450 {
5451 foreach (var sphere in spheres)
5452 {
5453 if (sphere != null && !sphere.IsDestroyed)
5454 {
5455 sphere.Kill();
5456 }
5457 }
5458
5459 spheres.Clear();
5460 }
5461 }
5462
5463 public void RemoveMapMarkers()
5464 {
5465 Interface.CallHook("RemoveTemporaryLustyMarker", uid);
5466 Interface.CallHook("RemoveMapPrivatePluginMarker", uid);
5467
5468 if (explosionMarker != null && !explosionMarker.IsDestroyed)
5469 {
5470 explosionMarker.CancelInvoke(explosionMarker.DelayedDestroy);
5471 explosionMarker.Kill();
5472 }
5473
5474 if (genericMarker != null && !genericMarker.IsDestroyed)
5475 {
5476 genericMarker.Kill();
5477 }
5478
5479 if (vendingMarker != null && !vendingMarker.IsDestroyed)
5480 {
5481 vendingMarker.Kill();
5482 }
5483 }
5484 }
5485
5486 public static class Coroutines // Credits to Jake Rich
5487 {
5488 private static Dictionary<float, YieldInstruction> _waitForSecondDict;
5489
5490 public static YieldInstruction WaitForSeconds(float delay)
5491 {
5492 if (_waitForSecondDict == null)
5493 {
5494 _waitForSecondDict = new Dictionary<float, YieldInstruction>();
5495 }
5496
5497 YieldInstruction yield;
5498 if (!_waitForSecondDict.TryGetValue(delay, out yield))
5499 {
5500 //Cache the yield instruction for later
5501 yield = new WaitForSeconds(delay);
5502 _waitForSecondDict.Add(delay, yield);
5503 }
5504
5505 return yield;
5506 }
5507
5508 public static void Clear()
5509 {
5510 if (_waitForSecondDict != null)
5511 {
5512 _waitForSecondDict.Clear();
5513 _waitForSecondDict = null;
5514 }
5515 }
5516 }
5517
5518 #region Hooks
5519
5520 private void UnsubscribeHooks()
5521 {
5522 if (IsUnloading)
5523 {
5524 return;
5525 }
5526
5527 Unsubscribe(nameof(CanBuildRevertBlock));
5528 Unsubscribe(nameof(OnRestoreUponDeath));
5529 Unsubscribe(nameof(OnNpcKits));
5530 Unsubscribe(nameof(CanTeleport));
5531 Unsubscribe(nameof(canTeleport));
5532 Unsubscribe(nameof(CanEntityBeTargeted));
5533 Unsubscribe(nameof(CanEntityTrapTrigger));
5534 Unsubscribe(nameof(CanEntityTakeDamage));
5535 Unsubscribe(nameof(OnFriendAdded));
5536 Unsubscribe(nameof(OnFriendRemoved));
5537 Unsubscribe(nameof(OnClanUpdate));
5538 Unsubscribe(nameof(OnClanDestroy));
5539 Unsubscribe(nameof(CanDropBackpack));
5540 Unsubscribe(nameof(CanOpenBackpack));
5541
5542 Unsubscribe(nameof(CanBeTargeted));
5543 Unsubscribe(nameof(OnEntityMounted));
5544 Unsubscribe(nameof(OnEntityBuilt));
5545 Unsubscribe(nameof(OnEntityGroundMissing));
5546 Unsubscribe(nameof(OnEntityKill));
5547 Unsubscribe(nameof(OnEntityTakeDamage));
5548 Unsubscribe(nameof(OnLootEntity));
5549 Unsubscribe(nameof(OnLootEntityEnd));
5550 Unsubscribe(nameof(OnEntityDeath));
5551 Unsubscribe(nameof(CanPickupEntity));
5552 Unsubscribe(nameof(OnPlayerDeath));
5553 Unsubscribe(nameof(OnPlayerDropActiveItem));
5554 Unsubscribe(nameof(OnNpcTarget));
5555 Unsubscribe(nameof(OnEntitySpawned));
5556 Unsubscribe(nameof(OnCupboardAuthorize));
5557 Unsubscribe(nameof(OnActiveItemChanged));
5558 Unsubscribe(nameof(OnLoseCondition));
5559 Unsubscribe(nameof(OnFireBallSpread));
5560 Unsubscribe(nameof(CanBuild));
5561 }
5562
5563 private void OnMapMarkerAdded(BasePlayer player, ProtoBuf.MapNote note)
5564 {
5565 if (player.IsValid() && player.IsConnected && note != null && permission.UserHasPermission(player.UserIDString, mapPermission))
5566 {
5567 player.Teleport(new Vector3(note.worldPosition.x, GetSpawnHeight(note.worldPosition), note.worldPosition.z));
5568 }
5569 }
5570
5571 private void OnNewSave(string filename) => wiped = true;
5572
5573 private void Init()
5574 {
5575 permission.CreateGroup(rankLadderGroup, rankLadderGroup, 0);
5576 permission.GrantGroupPermission(rankLadderGroup, rankLadderPermission, this);
5577 permission.RegisterPermission(adminPermission, this);
5578 permission.RegisterPermission(rankLadderPermission, this);
5579 permission.RegisterPermission(drawPermission, this);
5580 permission.RegisterPermission(mapPermission, this);
5581 permission.RegisterPermission(canBypassPermission, this);
5582 permission.RegisterPermission(bypassBlockPermission, this);
5583 permission.RegisterPermission(banPermission, this);
5584 lastSpawnRequestTime = Time.realtimeSinceStartup;
5585 Backbone = new SingletonBackbone(this);
5586 Unsubscribe(nameof(OnMapMarkerAdded));
5587 Unsubscribe(nameof(OnPlayerSleepEnded));
5588 UnsubscribeHooks();
5589 }
5590
5591 private void OnServerInitialized(bool isStartup)
5592 {
5593 if (!configLoaded)
5594 {
5595 return;
5596 }
5597
5598 LoadData();
5599 Reinitialize();
5600 BlockZoneManagerZones();
5601 SetupMonuments();
5602 LoadSpawns();
5603 SetupGrid();
5604 RegisterCommands();
5605 RemoveAllThirdPartyMarkers();
5606 CheckForWipe();
5607 UpdateUI();
5608 CheckConfig();
5609
5610 timer.Repeat(30f, 0, () => RaidableBase.UpdateAllMarkers());
5611 }
5612
5613 private void Unload()
5614 {
5615 IsUnloading = true;
5616
5617 if (!configLoaded)
5618 {
5619 UnsetStatics();
5620 return;
5621 }
5622
5623 SaveData();
5624 RaidableBase.Unload();
5625 StopScheduleCoroutine();
5626 StopMaintainCoroutine();
5627 StopGridCoroutine();
5628 StopDespawnCoroutine();
5629 DestroyComponents();
5630 RemoveAllThirdPartyMarkers();
5631
5632 if (Raids.Count > 0 || Bases.Count > 0)
5633 {
5634 DespawnAllBasesNow();
5635 return;
5636 }
5637
5638 UnsetStatics();
5639 }
5640
5641 private static void UnsetStatics()
5642 {
5643 UI.DestroyAllLockoutUI();
5644 UI.Players.Clear();
5645 UI.Lockouts.Clear();
5646 Coroutines.Clear();
5647 Backbone.Destroy();
5648 Backbone = null;
5649 _config = null;
5650 }
5651
5652 private void RegisterCommands()
5653 {
5654 AddCovalenceCommand(_config.Settings.BuyCommand, nameof(CommandBuyRaid));
5655 AddCovalenceCommand(_config.Settings.EventCommand, nameof(CommandRaidBase));
5656 AddCovalenceCommand(_config.Settings.HunterCommand, nameof(CommandRaidHunter));
5657 AddCovalenceCommand(_config.Settings.ConsoleCommand, nameof(CommandRaidBase));
5658 AddCovalenceCommand("rb.reloadconfig", nameof(CommandReloadConfig));
5659 AddCovalenceCommand("rb.config", nameof(CommandConfig), "raidablebases.config");
5660 AddCovalenceCommand("rb.populate", nameof(CommandPopulate), "raidablebases.config");
5661 }
5662
5663 private void CheckConfig()
5664 {
5665 foreach (var entry in _config.RaidableBases.Buildings)
5666 {
5667 if (entry.Value.Mode == RaidableMode.Disabled)
5668 {
5669 if (entry.Key.Contains("easy", CompareOptions.OrdinalIgnoreCase))
5670 {
5671 entry.Value.Mode = RaidableMode.Easy;
5672 PrintWarning("Updated profile {0} with {1} difficulty setting", entry.Key, RaidableMode.Easy.ToString());
5673 SaveConfig();
5674 }
5675 else if (entry.Key.Contains("med", CompareOptions.OrdinalIgnoreCase))
5676 {
5677 entry.Value.Mode = RaidableMode.Medium;
5678 PrintWarning("Updated profile {0} with {1} difficulty setting", entry.Key, RaidableMode.Medium.ToString());
5679 SaveConfig();
5680 }
5681 else if (entry.Key.Contains("hard", CompareOptions.OrdinalIgnoreCase))
5682 {
5683 entry.Value.Mode = RaidableMode.Hard;
5684 PrintWarning("Updated profile {0} with {1} difficulty setting", entry.Key, RaidableMode.Hard.ToString());
5685 SaveConfig();
5686 }
5687 else if (entry.Key.Contains("nightmare", CompareOptions.OrdinalIgnoreCase))
5688 {
5689 entry.Value.Mode = RaidableMode.Nightmare;
5690 PrintWarning("Updated profile {0} with {1} difficulty setting", entry.Key, RaidableMode.Nightmare.ToString());
5691 SaveConfig();
5692 }
5693 else if (entry.Key.Contains("expert", CompareOptions.OrdinalIgnoreCase))
5694 {
5695 entry.Value.Mode = RaidableMode.Expert;
5696 PrintWarning("Updated profile {0} with {1} difficulty setting", entry.Key, RaidableMode.Expert.ToString());
5697 SaveConfig();
5698 }
5699 else
5700 {
5701 PrintError("Profile `{0}` does not have a difficulty configured!", entry.Key);
5702 PrintError("See config:");
5703 PrintError(string.Empty);
5704 PrintError("Difficulty (0 = easy, 1 = medium, 2 = hard, 3 = expert, 4 = nightmare)");
5705 PrintError(string.Empty);
5706 }
5707 }
5708 }
5709 }
5710
5711 private void LoadData()
5712 {
5713 try
5714 {
5715 storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
5716 }
5717 catch
5718 {
5719 }
5720
5721 if (storedData?.Players == null)
5722 {
5723 storedData = new StoredData();
5724 SaveData();
5725 }
5726 }
5727
5728 private void CheckForWipe()
5729 {
5730 if (!wiped && storedData.Players.Count >= _config.RankedLadder.Amount && BuildingManager.server.buildingDictionary.Count == 0)
5731 {
5732 foreach (var pi in storedData.Players.Values)
5733 {
5734 if (pi.Raids > 0)
5735 {
5736 wiped = true;
5737 break;
5738 }
5739 }
5740 }
5741
5742 if (wiped)
5743 {
5744 var raids = new List<int>();
5745 var dict = new Dictionary<string, PlayerInfo>(storedData.Players);
5746
5747 if (storedData.Players.Count > 0)
5748 {
5749 if (AssignTreasureHunters())
5750 {
5751 foreach (var entry in dict)
5752 {
5753 if (entry.Value.Raids > 0)
5754 {
5755 raids.Add(entry.Value.Raids);
5756 }
5757
5758 storedData.Players[entry.Key].Raids = 0;
5759 }
5760 }
5761 }
5762
5763 if (raids.Count > 0)
5764 {
5765 var average = raids.Average();
5766
5767 foreach (var entry in dict)
5768 {
5769 if (entry.Value.TotalRaids < average)
5770 {
5771 storedData.Players.Remove(entry.Key);
5772 }
5773 }
5774 }
5775
5776 storedData.Lockouts.Clear();
5777 wiped = false;
5778 SaveData();
5779 }
5780 }
5781
5782 private void SetupMonuments()
5783 {
5784 foreach (var monument in TerrainMeta.Path?.Monuments?.ToArray() ?? UnityEngine.Object.FindObjectsOfType<MonumentInfo>())
5785 {
5786 if (string.IsNullOrEmpty(monument.displayPhrase.translated))
5787 {
5788 float size = monument.name.Contains("power_sub") ? 35f : Mathf.Max(monument.Bounds.size.Max(), 75f);
5789 monuments[monument] = monument.name.Contains("cave") ? 75f : monument.name.Contains("OilrigAI") ? 150f : size;
5790 }
5791 else
5792 {
5793 monuments[monument] = GetMonumentFloat(monument.displayPhrase.translated.TrimEnd());
5794 }
5795
5796 monuments[monument] += _config.Settings.Management.MonumentDistance;
5797 }
5798 }
5799
5800 private void BlockZoneManagerZones()
5801 {
5802 if (ZoneManager == null || !ZoneManager.IsLoaded)
5803 {
5804 return;
5805 }
5806
5807 var zoneIds = ZoneManager?.Call("GetZoneIDs") as string[];
5808
5809 if (zoneIds == null)
5810 {
5811 return;
5812 }
5813
5814 managedZones.Clear();
5815
5816 foreach (string zoneId in zoneIds)
5817 {
5818 var zoneLoc = ZoneManager.Call("GetZoneLocation", zoneId);
5819
5820 if (!(zoneLoc is Vector3))
5821 {
5822 continue;
5823 }
5824
5825 var position = (Vector3)zoneLoc;
5826
5827 if (position == Vector3.zero)
5828 {
5829 continue;
5830 }
5831
5832 var zoneName = Convert.ToString(ZoneManager.Call("GetZoneName", zoneId));
5833
5834 if (_config.Settings.Inclusions.Any(zone => zone == zoneId || !string.IsNullOrEmpty(zoneName) && zoneName.Contains(zone, CompareOptions.OrdinalIgnoreCase)))
5835 {
5836 continue;
5837 }
5838
5839 var zoneRadius = ZoneManager.Call("GetZoneRadius", zoneId);
5840 float distance = 0f;
5841
5842 if (zoneRadius is float)
5843 {
5844 distance = (float)zoneRadius;
5845 }
5846
5847 if (distance == 0f)
5848 {
5849 var zoneSize = ZoneManager.Call("GetZoneSize", zoneId);
5850
5851 if (zoneSize is Vector3)
5852 {
5853 distance = ((Vector3)zoneSize).Max();
5854 }
5855 }
5856
5857 if (distance > 0f)
5858 {
5859 distance += Radius + 5f;
5860 managedZones[position] = distance;
5861 }
5862 }
5863
5864 if (managedZones.Count > 0)
5865 {
5866 Puts(Backbone.GetMessage("BlockedZones", null, managedZones.Count));
5867 }
5868 }
5869
5870 private void Reinitialize()
5871 {
5872 Backbone.Plugin.Skins.Clear();
5873
5874 if (_config.Skins.RandomWorkshopSkins || _config.Treasure.RandomWorkshopSkins)
5875 {
5876 SetWorkshopIDs(); // webrequest.Enqueue("http://s3.amazonaws.com/s3.playrust.com/icons/inventory/rust/schema.json", null, GetWorkshopIDs, this, Core.Libraries.RequestMethod.GET);
5877 }
5878
5879 if (_config.Settings.ExpansionMode && DangerousTreasures != null && DangerousTreasures.IsLoaded && DangerousTreasures.Version < new VersionNumber(1, 4, 1))
5880 {
5881 Puts(Backbone.GetMessage("NotCompatible", null, DangerousTreasures.Version));
5882 DangerousTreasures = null;
5883 }
5884
5885 if (_config.Settings.TeleportMarker)
5886 {
5887 Subscribe(nameof(OnMapMarkerAdded));
5888 }
5889
5890 if (_config.UI.Enabled)
5891 {
5892 Subscribe(nameof(OnPlayerSleepEnded));
5893 }
5894 }
5895
5896 private void OnClanUpdate(string tag)
5897 {
5898 foreach (var raid in Raids.Values)
5899 {
5900 raid.UpdateClans(tag);
5901 }
5902 }
5903
5904 private void OnClanDestroy(string tag)
5905 {
5906 OnClanUpdate(tag);
5907 }
5908
5909 private void OnFriendAdded(string playerId, string targetId)
5910 {
5911 foreach (var raid in Raids.Values)
5912 {
5913 raid.UpdateFriends(playerId, targetId, true);
5914 }
5915 }
5916
5917 private void OnFriendRemoved(string playerId, string targetId)
5918 {
5919 foreach (var raid in Raids.Values)
5920 {
5921 raid.UpdateFriends(playerId, targetId, false);
5922 }
5923 }
5924
5925 private object CanBuildRevertBlock(BasePlayer player, Vector3 target)
5926 {
5927 var raid = RaidableBase.Get(target);
5928
5929 if (raid == null || raid.Options.AllowBuildRevertLadders)
5930 {
5931 return null;
5932 }
5933
5934 return false;
5935 }
5936
5937 private object OnRestoreUponDeath(BasePlayer player)
5938 {
5939 var raid = RaidableBase.Get(player.transform.position);
5940
5941 if (raid == null)
5942 {
5943 return null;
5944 }
5945
5946 return _config.Settings.Management.BlockRestorePVE && !raid.AllowPVP || _config.Settings.Management.BlockRestorePVP && raid.AllowPVP ? true : (object)null;
5947 }
5948
5949 private object OnNpcKits(ulong targetId)
5950 {
5951 var raid = RaidableBase.Get(targetId);
5952
5953 if (raid == null || !raid.Options.NPC.DespawnInventory)
5954 {
5955 return null;
5956 }
5957
5958 return true;
5959 }
5960
5961 private object canTeleport(BasePlayer player)
5962 {
5963 return CanTeleport(player);
5964 }
5965
5966 private object CanTeleport(BasePlayer player)
5967 {
5968 return !player.IsFlying && EventTerritory(player.transform.position) ? Backbone.GetMessage("CannotTeleport", player.UserIDString) : null;
5969 }
5970
5971 private void OnEntityMounted(BaseMountable m, BasePlayer player)
5972 {
5973 var raid = RaidableBase.Get(player.transform.position);
5974
5975 if (raid == null || raid.intruders.Contains(player))
5976 {
5977 return;
5978 }
5979
5980 player.EnsureDismounted();
5981 raid.RemovePlayer(player);
5982 }
5983
5984 private object OnLoseCondition(Item item, float amount)
5985 {
5986 var player = item.GetOwnerPlayer();
5987
5988 if (!IsValid(player))
5989 {
5990 return null;
5991 }
5992
5993 var raid = RaidableBase.Get(player.transform.position);
5994
5995 if (raid == null || !raid.Options.EnforceDurability)
5996 {
5997 return null;
5998 }
5999
6000 uint uid = item.uid;
6001 float condition;
6002 if (!raid.conditions.TryGetValue(uid, out condition))
6003 {
6004 raid.conditions[uid] = condition = item.condition;
6005 }
6006
6007 NextTick(() =>
6008 {
6009 if (raid == null)
6010 {
6011 return;
6012 }
6013
6014 if (!IsValid(item))
6015 {
6016 raid.conditions.Remove(uid);
6017 return;
6018 }
6019
6020 item.condition = condition - amount;
6021
6022 if (item.condition <= 0f && item.condition < condition)
6023 {
6024 item.OnBroken();
6025 raid.conditions.Remove(uid);
6026 }
6027 else raid.conditions[uid] = item.condition;
6028 });
6029
6030 return true;
6031 }
6032
6033 private void OnEntityBuilt(Planner planner, GameObject go)
6034 {
6035 var e = go.ToBaseEntity();
6036
6037 if (e == null)
6038 {
6039 return;
6040 }
6041
6042 var raid = RaidableBase.Get(e.transform.position);
6043
6044 if (raid == null)
6045 {
6046 return;
6047 }
6048
6049 if (!raid.Options.AllowBuildingPriviledges && e is BuildingPrivlidge)
6050 {
6051 var player = planner.GetOwnerPlayer();
6052 var item = player.inventory.FindItemID("cupboard.tool");
6053
6054 if (item != null)
6055 {
6056 item.amount++;
6057 item.MarkDirty();
6058 }
6059 else player.GiveItem(ItemManager.CreateByName("cupboard.tool"));
6060
6061 e.Invoke(e.KillMessage, 0.1f);
6062 return;
6063 }
6064
6065 var priv = e.GetBuildingPrivilege();
6066 bool flag = priv.IsValid();
6067
6068 if (flag && (e.prefabID == 3234260181 || e.prefabID == 72949757) && IsExploitingBuildingParts(priv, raid))
6069 {
6070 e.Invoke(e.KillMessage, 0.1f);
6071 return;
6072 }
6073
6074 if (InRange(raid.Location, e.transform.position, Radius))
6075 {
6076 AddEntity(e, raid);
6077 return;
6078 }
6079
6080 if (flag)
6081 {
6082 if (priv.net.ID < raid.NetworkID)
6083 {
6084 return;
6085 }
6086
6087 var building = priv.GetBuilding();
6088
6089 if (building != null && building.ID != raid.BuildingID)
6090 {
6091 return;
6092 }
6093 }
6094
6095 AddEntity(e, raid);
6096 }
6097
6098 private bool IsExploitingBuildingParts(BuildingPrivlidge priv, RaidableBase raid)
6099 {
6100 if (!EventTerritory(priv.transform.position)) // placing priv outside dome
6101 {
6102 return true;
6103 }
6104
6105 var building = priv.GetBuilding();
6106
6107 if (building != null && building.HasBuildingBlocks()) // placing blocks from a priv outside dome
6108 {
6109 foreach (var block in building.buildingBlocks)
6110 {
6111 if (raid.blocks.Contains(block.net.ID))
6112 {
6113 continue;
6114 }
6115
6116 if (!EventTerritory(block.transform.position))
6117 {
6118 return true;
6119 }
6120 else raid.blocks.Add(block.net.ID);
6121 }
6122 }
6123
6124 return false;
6125 }
6126
6127 private void AddEntity(BaseEntity e, RaidableBase raid)
6128 {
6129 raid.BuiltList.Add(e.net.ID);
6130 RaidEntities[e] = raid;
6131
6132 if (_config.Settings.Management.DoNotDestroyDeployables && e.name.Contains("assets/prefabs/deployable/"))
6133 {
6134 UnityEngine.Object.Destroy(e.GetComponent<DestroyOnGroundMissing>());
6135 UnityEngine.Object.Destroy(e.GetComponent<GroundWatch>());
6136 return;
6137 }
6138
6139 if (Bases.ContainsKey(raid.BaseIndex))
6140 {
6141 Bases[raid.BaseIndex].Add(e);
6142 }
6143 }
6144
6145 private object CanBeTargeted(NPCPlayerApex npc, StorageContainer container) // guntrap and flameturret. containerioentity for autoturrets which is already covered by priv
6146 {
6147 return RaidableBase.Has(npc) ? false : (object)null;
6148 }
6149
6150#if USE_HTN_HOOK
6151 private object OnNpcTarget(NPCPlayerApex npc, BaseEntity entity)
6152 {
6153 return entity.IsNpc && RaidableBase.Has(npc) ? true : (object)null;
6154 }
6155
6156 private object OnNpcTarget(BaseEntity entity, NPCPlayerApex npc)
6157 {
6158 return entity.IsNpc && RaidableBase.Has(npc) ? true : (object)null;
6159 }
6160#else
6161 private object OnNpcTarget(NPCPlayerApex npc, NPCPlayerApex npc2)
6162 {
6163 return RaidableBase.Has(npc) ? true : (object)null;
6164 }
6165
6166 private object OnNpcTarget(BaseNpc entity, NPCPlayerApex npc)
6167 {
6168 return RaidableBase.Has(npc) ? true : (object)null;
6169 }
6170#endif
6171
6172 private void OnActiveItemChanged(BasePlayer player, Item oldItem, Item newItem)
6173 {
6174 if (player.IsNpc || !EventTerritory(player.transform.position))
6175 {
6176 return;
6177 }
6178
6179 RaidableBase.StopUsingWand(player);
6180 }
6181
6182 private void OnPlayerSleepEnded(BasePlayer player)
6183 {
6184 UI.Update(player);
6185
6186 NextTick(() =>
6187 {
6188 if (player == null || player.transform == null)
6189 {
6190 return;
6191 }
6192
6193 var raid = RaidableBase.Get(player.transform.position);
6194
6195 if (raid == null || raid.intruders.Contains(player))
6196 {
6197 return;
6198 }
6199
6200 raid.OnEnterRaid(player); // 1.5.1 sleeping bag exploit fix
6201 });
6202 }
6203
6204 private void OnPlayerDeath(BasePlayer player)
6205 {
6206 if (!player.IsNpc)
6207 {
6208 if (EventTerritory(player.transform.position))
6209 {
6210 UI.DestroyStatusUI(player);
6211 UnityEngine.Object.Destroy(player.GetComponent<PlayerInputEx>());
6212 }
6213
6214 return;
6215 }
6216
6217 var raid = RaidableBase.Get(player.userID);
6218
6219 if (raid == null)
6220 {
6221 return;
6222 }
6223
6224 raid.CheckDespawn();
6225
6226 if (_config.Settings.Management.UseOwners)
6227 {
6228 var attacker = player.lastAttacker as BasePlayer;
6229
6230 if (attacker.IsValid() && !attacker.IsNpc)
6231 {
6232 raid.TrySetOwner(attacker, player, null);
6233 }
6234 }
6235
6236 if (raid.Options.NPC.DespawnInventory)
6237 {
6238 player.inventory.Strip();
6239 }
6240 }
6241
6242 private object OnPlayerDropActiveItem(BasePlayer player, Item item)
6243 {
6244 if (EventTerritory(player.transform.position))
6245 {
6246 return true;
6247 }
6248
6249 return null;
6250 }
6251
6252 private void OnEntityDeath(BuildingPrivlidge priv, HitInfo hitInfo)
6253 {
6254 var raid = RaidableBase.Get(priv);
6255
6256 if (raid == null)
6257 {
6258 return;
6259 }
6260
6261 if (hitInfo?.Initiator == null && !raid.IsOpened)
6262 {
6263 priv.inventory.Clear();
6264 }
6265
6266 if (raid.Options.RequiresCupboardAccess)
6267 {
6268 OnCupboardAuthorize(priv, null);
6269 }
6270
6271 if (raid.IsOpened && raid.EndWhenCupboardIsDestroyed())
6272 {
6273 raid.CancelInvoke(raid.TryToEnd);
6274 raid.AwardRaiders();
6275 raid.Undo();
6276 }
6277 }
6278
6279 private void OnEntityKill(StorageContainer container)
6280 {
6281 if (container is BuildingPrivlidge)
6282 {
6283 OnEntityDeath(container as BuildingPrivlidge, null);
6284 }
6285
6286 EntityHandler(container, null);
6287 }
6288
6289 private void OnEntityDeath(StorageContainer container, HitInfo hitInfo) => EntityHandler(container, hitInfo);
6290
6291 private void OnEntityDeath(Door door, HitInfo hitInfo) => BlockHandler(door, hitInfo);
6292
6293 private void OnEntityDeath(BuildingBlock block, HitInfo hitInfo) => BlockHandler(block, hitInfo);
6294
6295 private void OnEntityDeath(SimpleBuildingBlock block, HitInfo hitInfo) => BlockHandler(block, hitInfo);
6296
6297 private void BlockHandler(BaseEntity entity, HitInfo hitInfo)
6298 {
6299 var player = hitInfo?.Initiator as BasePlayer;
6300
6301 if (!player.IsValid())
6302 {
6303 return;
6304 }
6305
6306 var raid = RaidableBase.Get(entity.transform.position);
6307
6308 if (raid == null)
6309 {
6310 return;
6311 }
6312
6313 raid.TrySetOwner(player, entity, hitInfo);
6314 raid.CheckDespawn();
6315 }
6316
6317 private object OnEntityGroundMissing(StorageContainer container)
6318 {
6319 if (_config.Settings.Management.Invulnerable && RaidableBase.Has(container))
6320 {
6321 return true;
6322 }
6323
6324 EntityHandler(container, null);
6325 return null;
6326 }
6327
6328 private void EntityHandler(StorageContainer container, HitInfo hitInfo)
6329 {
6330 var raid = RaidableBase.Get(container);
6331
6332 if (raid == null || !raid.IsOpened)
6333 {
6334 return;
6335 }
6336
6337 if (hitInfo?.Initiator == null)
6338 {
6339 DropOrRemoveItems(container);
6340 }
6341 else if (IsLootingWeapon(hitInfo))
6342 {
6343 var player = hitInfo?.Initiator as BasePlayer;
6344
6345 if (player.IsValid())
6346 {
6347 raid.AddLooter(player);
6348 }
6349 }
6350
6351 raid._containers.Remove(container);
6352 raid.StartTryToEnd();
6353 UI.Update(raid);
6354
6355 foreach (var x in Raids.Values)
6356 {
6357 if (x._containers.Count > 0)
6358 {
6359 return;
6360 }
6361 }
6362
6363 Unsubscribe(nameof(OnEntityKill));
6364 Unsubscribe(nameof(OnEntityGroundMissing));
6365 }
6366
6367 private static bool IsLootingWeapon(HitInfo hitInfo) => hitInfo.damageTypes.Has(DamageType.Explosion) || hitInfo.damageTypes.IsMeleeType();
6368
6369 private void OnCupboardAuthorize(BuildingPrivlidge priv, BasePlayer player)
6370 {
6371 foreach (var raid in Raids.Values)
6372 {
6373 if (raid.priv == priv && raid.Options.RequiresCupboardAccess && !raid.IsAuthed)
6374 {
6375 raid.IsAuthed = true;
6376
6377 if (raid.Options.RequiresCupboardAccess && _config.EventMessages.AnnounceRaidUnlock)
6378 {
6379 foreach (var p in BasePlayer.activePlayerList)
6380 {
6381 Backbone.Message(p, "OnRaidFinished", p.UserIDString, FormatGridReference(raid.Location));
6382 }
6383 }
6384
6385 break;
6386 }
6387 }
6388
6389 foreach (var raid in Raids.Values)
6390 {
6391 if (!raid.IsAuthed)
6392 {
6393 return;
6394 }
6395 }
6396
6397 Unsubscribe(nameof(OnCupboardAuthorize));
6398 }
6399
6400 private object CanPickupEntity(BasePlayer player, BaseEntity entity)
6401 {
6402 var raid = RaidableBase.Get(entity);
6403
6404 if (raid == null)
6405 {
6406 return null;
6407 }
6408
6409 raid.AddLooter(player);
6410
6411 if (_config.Settings.Management.BlacklistedPickupItems.Contains(entity.ShortPrefabName))
6412 {
6413 return false;
6414 }
6415
6416 return !raid.Options.AllowPickup && entity.OwnerID == 0 ? false : (object)null;
6417 }
6418
6419 private void OnFireBallSpread(FireBall ball, BaseEntity fire)
6420 {
6421 if (EventTerritory(fire.transform.position))
6422 {
6423 NextTick(() =>
6424 {
6425 if (fire == null || fire.IsDestroyed)
6426 {
6427 return;
6428 }
6429
6430 fire.Kill();
6431 });
6432 }
6433 }
6434
6435 private void OnEntitySpawned(DroppedItemContainer backpack)
6436 {
6437 if (!backpack.playerSteamID.IsSteamId())
6438 {
6439 return;
6440 }
6441
6442 var raid = RaidableBase.Get(backpack.transform.position);
6443
6444 if (raid == null)
6445 {
6446 return;
6447 }
6448
6449 if (raid.AllowPVP && _config.Settings.Management.BackpacksPVP || !raid.AllowPVP && _config.Settings.Management.BackpacksPVE)
6450 {
6451 backpack.playerSteamID = 0;
6452 }
6453 }
6454
6455 private void OnEntitySpawned(BaseLock entity)
6456 {
6457 var parent = entity.GetParentEntity();
6458
6459 foreach (var raid in Raids.Values)
6460 {
6461 foreach (var container in raid._containers)
6462 {
6463 if (parent == container)
6464 {
6465 entity.Invoke(entity.KillMessage, 0.1f);
6466 break;
6467 }
6468 }
6469 }
6470 }
6471
6472 private void OnEntitySpawned(PlayerCorpse corpse)
6473 {
6474 var raid = (PvpDelay.ContainsKey(corpse.playerSteamID) ? GetNearestBase(corpse.transform.position) : RaidableBase.Get(corpse.playerSteamID)) ?? RaidableBase.Get(corpse.transform.position);
6475
6476 if (raid == null)
6477 {
6478 return;
6479 }
6480
6481 if (corpse.playerSteamID.IsSteamId())
6482 {
6483 var player = RustCore.FindPlayerById(corpse.playerSteamID);
6484
6485 if (raid.Any(corpse.playerSteamID, false))
6486 {
6487 raid.AddCorpse(corpse, player);
6488 Interface.CallHook("OnRaidablePlayerCorpse", player, corpse);
6489 }
6490 else if (raid.Options.EjectCorpses)
6491 {
6492 raid.EjectCorpse(player, corpse);
6493 }
6494
6495 if (_config.Settings.Management.PlayersLootableInPVE && !raid.AllowPVP || _config.Settings.Management.PlayersLootableInPVP && raid.AllowPVP)
6496 {
6497 corpse.playerSteamID = 0;
6498 }
6499
6500 return;
6501 }
6502
6503 if (raid.Options.NPC.DespawnInventory)
6504 {
6505 corpse.Invoke(corpse.KillMessage, 30f);
6506 }
6507
6508 raid.npcs.RemoveAll(npc => npc == null || npc.userID == corpse.playerSteamID);
6509 Npcs.Remove(corpse.playerSteamID);
6510
6511 if (raid.Options.RespawnRate > 0)
6512 {
6513 raid.RespawnNpc();
6514 return;
6515 }
6516
6517 if (!AnyNpcs())
6518 {
6519 Unsubscribe(nameof(OnNpcTarget));
6520 }
6521 }
6522
6523 private object CanBuild(Planner planner, Construction prefab, Construction.Target target)
6524 {
6525 var buildPos = target.entity && target.entity.transform && target.socket ? target.GetWorldPosition() : target.position;
6526
6527 if (EventTerritory(buildPos))
6528 {
6529 target.player.ChatMessage(lang.GetMessage("Building is blocked!", this, target.player.UserIDString));
6530 return false;
6531 }
6532
6533 return null;
6534 }
6535
6536 private void OnLootEntityEnd(BasePlayer player, StorageContainer container)
6537 {
6538 if (container?.inventory == null || container.OwnerID.IsSteamId() || IsInvisible(player))
6539 {
6540 return;
6541 }
6542
6543 var raid = RaidableBase.Get(container);
6544
6545 if (raid == null || raid.Options.DropTimeAfterLooting <= 0 || (raid.Options.DropOnlyBoxesAndPrivileges && !(container is BoxStorage) && !(container is BuildingPrivlidge)))
6546 {
6547 return;
6548 }
6549
6550 if (container.inventory.itemList.Count == 0 && (container.prefabID == 2206646561 || container.prefabID == 1560881570 || container.prefabID == 4080262419))
6551 {
6552 container.Invoke(container.KillMessage, 0.1f);
6553 }
6554 else container.Invoke(() => DropOrRemoveItems(container), raid.Options.DropTimeAfterLooting);
6555 }
6556
6557 private void OnLootEntity(BasePlayer player, BaseEntity entity)
6558 {
6559 var raid = RaidableBase.Get(entity.transform.position);
6560
6561 if (raid == null)
6562 {
6563 return;
6564 }
6565
6566 raid.OnLootEntityInternal(player, entity);
6567 }
6568
6569 private void CanOpenBackpack(BasePlayer looter, ulong backpackOwnerID)
6570 {
6571 var raid = RaidableBase.Get(looter.transform.position);
6572
6573 if (raid == null)
6574 {
6575 return;
6576 }
6577
6578 if (!raid.AllowPVP && !_config.Settings.Management.BackpacksOpenPVE || raid.AllowPVP && !_config.Settings.Management.BackpacksOpenPVP)
6579 {
6580 looter.Invoke(looter.EndLooting, 0.1f);
6581 Player.Message(looter, lang.GetMessage("NotAllowed", this, looter.UserIDString));
6582 }
6583 }
6584
6585 private object CanDropBackpack(ulong backpackOwnerID, Vector3 position)
6586 {
6587 var player = RustCore.FindPlayerById(backpackOwnerID);
6588
6589 if (player?.transform != null)
6590 {
6591 DelaySettings ds;
6592 if (PvpDelay.TryGetValue(player.userID, out ds) && (ds.AllowPVP && _config.Settings.Management.BackpacksPVP || !ds.AllowPVP && _config.Settings.Management.BackpacksPVE))
6593 {
6594 return true;
6595 }
6596
6597 var nearest = GetNearestBase(player.transform.position);
6598
6599 if (nearest != null && (nearest.AllowPVP && _config.Settings.Management.BackpacksPVP || !nearest.AllowPVP && _config.Settings.Management.BackpacksPVE))
6600 {
6601 return true;
6602 }
6603 }
6604
6605 var raid = RaidableBase.Get(position);
6606
6607 if (raid == null)
6608 {
6609 return null;
6610 }
6611
6612 return raid.AllowPVP && _config.Settings.Management.BackpacksPVP || !raid.AllowPVP && _config.Settings.Management.BackpacksPVE ? true : (object)null;
6613 }
6614
6615 private object CanEntityBeTargeted(BaseMountable m, BaseEntity turret)
6616 {
6617 if (!turret.IsValid() || turret.OwnerID.IsSteamId())
6618 {
6619 return null;
6620 }
6621
6622 if (turret is AutoTurret && EventTerritory(m.transform.position) && EventTerritory(turret.transform.position))
6623 {
6624 return true;
6625 }
6626
6627 var players = GetMountedPlayers(m);
6628
6629 if (players.Count == 0)
6630 {
6631 return null;
6632 }
6633
6634 var player = players[0];
6635
6636 return IsValid(player) && EventTerritory(player.transform.position) && IsTrueDamage(turret) && !IsInvisible(player) ? true : (object)null;
6637 }
6638
6639 private object CanEntityBeTargeted(BasePlayer player, BaseEntity turret)
6640 {
6641 if (!IsValid(player) || !turret.IsValid() || turret.OwnerID.IsSteamId() || IsInvisible(player))
6642 {
6643 return null;
6644 }
6645
6646 if (turret is AutoTurret && RaidableBase.Has(player.userID))
6647 {
6648 return !EventTerritory(turret.transform.position) ? true : (object)null;
6649 }
6650
6651 return !RaidableBase.Has(player.userID) && EventTerritory(player.transform.position) && IsTrueDamage(turret) ? true : (object)null;
6652 }
6653
6654 private object CanEntityTrapTrigger(BaseTrap trap, BasePlayer player)
6655 {
6656 return IsValid(player) && !RaidableBase.Has(player.userID) && trap.IsValid() && EventTerritory(player.transform.position) && !IsInvisible(player) ? true : (object)null;
6657 }
6658
6659 private object CanEntityTakeDamage(BaseEntity entity, HitInfo hitInfo)
6660 {
6661 if (hitInfo == null || !IsValid(entity))
6662 {
6663 return null;
6664 }
6665
6666 if (entity is BasePlayer && HandlePlayerDamage(entity as BasePlayer, hitInfo) == HandledResult.Blocked)
6667 {
6668 return false;
6669 }
6670 else if (ShouldHandleEntity(entity) && HandleEntityDamage(entity, hitInfo) == HandledResult.Blocked)
6671 {
6672 return false;
6673 }
6674
6675 if (entity is NPCPlayerApex && RaidableBase.Has(entity as NPCPlayerApex))
6676 {
6677 return true;
6678 }
6679
6680 var raid = RaidableBase.Get(entity.transform.position);
6681 bool isRaidValid = raid != null;
6682
6683 if (isRaidValid)
6684 {
6685 if (entity.IsNpc)
6686 {
6687 return true;
6688 }
6689 else if (entity is RidableHorse || !_config.Settings.Management.MountDamage && (entity is BaseMountable || entity is BasicCar || entity.name.Contains("modularcar")))
6690 {
6691 hitInfo.damageTypes = new DamageTypeList();
6692 return null;
6693 }
6694 }
6695
6696 var attacker = hitInfo.Initiator as BasePlayer;
6697 bool isAttackerValid = IsValid(attacker);
6698
6699 if (RaidEntities.ContainsKey(entity))
6700 {
6701 if (isAttackerValid && isRaidValid)
6702 {
6703 if (CanBlockOutsideDamage(raid, attacker, raid.Options.BlockOutsideDamageToBaseDistance))
6704 {
6705 return false;
6706 }
6707
6708 if (raid.ID.IsSteamId() && entity is BoxStorage && raid.IsAlly(attacker.userID, Convert.ToUInt64(raid.ID)))
6709 {
6710 return false;
6711 }
6712 }
6713
6714 return true;
6715 }
6716
6717 if (isRaidValid)
6718 {
6719 if (!entity.OwnerID.IsSteamId() && (entity is Door || (isAttackerValid && IsTrueDamage(entity))))
6720 {
6721 return true;
6722 }
6723 else if (IsTrueDamage(hitInfo.Initiator))
6724 {
6725 return !(entity is BuildingBlock);
6726 }
6727 else if (hitInfo.Initiator is FireBall)
6728 {
6729 return entity is BasePlayer;
6730 }
6731 else if (entity is PlayerCorpse)
6732 {
6733 return true;
6734 }
6735 }
6736
6737 if (!isAttackerValid || !(entity is BasePlayer))
6738 {
6739 return null;
6740 }
6741
6742 var victim = entity as BasePlayer;
6743
6744 if (PvpDelay.ContainsKey(victim.userID) || attacker.userID == victim.userID || _config.TruePVE.ServerWidePVP)
6745 {
6746 return true;
6747 }
6748
6749 if (!EventTerritory(attacker.transform.position) || !isRaidValid || !raid.AllowPVP)
6750 {
6751 return null;
6752 }
6753
6754 return true;
6755 }
6756
6757 private void OnEntityTakeDamage(BaseEntity entity, HitInfo hitInfo)
6758 {
6759 if (entity == null || hitInfo == null || hitInfo.damageTypes == null)
6760 {
6761 return;
6762 }
6763
6764 if (entity is BasePlayer)
6765 {
6766 HandlePlayerDamage(entity as BasePlayer, hitInfo);
6767 }
6768 else if (ShouldHandleEntity(entity))
6769 {
6770 HandleEntityDamage(entity, hitInfo);
6771 }
6772 }
6773
6774 private HandledResult HandlePlayerDamage(BasePlayer victim, HitInfo hitInfo)
6775 {
6776 var raid = RaidableBase.Get(victim.transform.position);
6777
6778 if (raid == null)
6779 {
6780 return HandledResult.None;
6781 }
6782
6783 var attacker = hitInfo?.Initiator as BasePlayer;
6784
6785 if (IsValid(attacker))
6786 {
6787 if (victim.IsNpc && !attacker.IsNpc)
6788 {
6789 if (raid.HasLockout(attacker))
6790 {
6791 NullifyDamage(hitInfo);
6792 return HandledResult.Blocked;
6793 }
6794
6795 foreach (var fd in Backbone.Destinations)
6796 {
6797 if (fd.npc == victim)
6798 {
6799 var e = attacker.HasParent() ? attacker.GetParentEntity() : null;
6800
6801 if (e is ScrapTransportHelicopter || e is HotAirBalloon)
6802 {
6803 NullifyDamage(hitInfo);
6804 return HandledResult.Blocked;
6805 }
6806
6807 fd.Attack(attacker);
6808 break;
6809 }
6810 }
6811 }
6812 else if (!victim.IsNpc && !attacker.IsNpc && victim.userID != attacker.userID)
6813 {
6814 if (!raid.Options.AllowFriendlyFire && raid.IsOnSameTeam(victim.userID, attacker.userID))
6815 {
6816 NullifyDamage(hitInfo);
6817 return HandledResult.Blocked;
6818 }
6819
6820 if (PvpDelay.ContainsKey(victim.userID))
6821 {
6822 return HandledResult.Allowed;
6823 }
6824
6825 if (!raid.AllowPVP || (_config.Settings.Management.BlockMounts && attacker.isMounted) || CanBlockOutsideDamage(raid, attacker, raid.Options.BlockOutsideDamageToPlayersDistance))
6826 {
6827 NullifyDamage(hitInfo);
6828 return HandledResult.Blocked;
6829 }
6830 }
6831 else if (RaidableBase.Has(attacker.userID))
6832 {
6833 if (RaidableBase.Has(victim.userID))
6834 {
6835 NullifyDamage(hitInfo);
6836 return HandledResult.Blocked;
6837 }
6838
6839 if (raid.Options.NPC.Accuracy < UnityEngine.Random.Range(0f, 100f))
6840 {
6841 if (victim.GetMounted() is MiniCopter || victim.GetMounted() is ScrapTransportHelicopter)
6842 {
6843 return HandledResult.Allowed;
6844 }
6845
6846 NullifyDamage(hitInfo);
6847 return HandledResult.Blocked;
6848 }
6849 }
6850 }
6851 else if (RaidableBase.Has(victim.userID))
6852 {
6853 NullifyDamage(hitInfo); // make npc's immune to all damage which isn't from a player
6854 return HandledResult.Blocked;
6855 }
6856
6857 return HandledResult.None;
6858 }
6859
6860 private HandledResult HandleEntityDamage(BaseEntity entity, HitInfo hitInfo)
6861 {
6862 var raid = RaidableBase.Get(entity);
6863
6864 if (raid == null)
6865 {
6866 return HandledResult.None;
6867 }
6868
6869 if (_config.Settings.Management.BlocksImmune && entity is BuildingBlock || hitInfo.Initiator == null && hitInfo.damageTypes.Has(DamageType.Decay))
6870 {
6871 NullifyDamage(hitInfo, entity is BuildingBlock ? entity as BaseCombatEntity : null);
6872 return HandledResult.Blocked;
6873 }
6874
6875 if (raid.Type != RaidableType.None)
6876 {
6877 raid.CheckDespawn();
6878 }
6879
6880 if (_config.Settings.Management.Invulnerable && entity is BoxStorage && !raid.BuiltList.Contains(entity.net.ID))
6881 {
6882 NullifyDamage(hitInfo);
6883 }
6884
6885 var attacker = hitInfo?.Initiator as BasePlayer;
6886
6887 if (!IsValid(attacker) || attacker.IsNpc)
6888 {
6889 return HandledResult.None;
6890 }
6891
6892 if (raid.owner.IsValid() && !raid.IsAlly(attacker))
6893 {
6894 NullifyDamage(hitInfo);
6895 return HandledResult.Blocked;
6896 }
6897
6898 if ((_config.Settings.Management.BlockMounts && attacker.isMounted) || CanBlockOutsideDamage(raid, attacker, raid.Options.BlockOutsideDamageToBaseDistance))
6899 {
6900 NullifyDamage(hitInfo);
6901 return HandledResult.Blocked;
6902 }
6903
6904 raid.TrySetOwner(attacker, entity, hitInfo);
6905
6906 if (raid.IsOpened && IsLootingWeapon(hitInfo))
6907 {
6908 raid.AddLooter(attacker);
6909 }
6910
6911 if (!raid.Options.ExplosionModifier.Equals(100) && hitInfo.damageTypes.Has(DamageType.Explosion))
6912 {
6913 float m = Mathf.Clamp(raid.Options.ExplosionModifier, 0f, 999f);
6914
6915 hitInfo.damageTypes.Scale(DamageType.Explosion, m.Equals(0f) ? 0f : m / 100f);
6916 }
6917
6918 if (!raid.Engaged && raid.Type != RaidableType.None)
6919 {
6920 raid.Engaged = true;
6921 raid.CheckDespawn();
6922 }
6923
6924 return HandledResult.Allowed;
6925 }
6926
6927 #endregion Hooks
6928
6929 #region Spawn
6930
6931 private static float GetSpawnHeight(Vector3 target)
6932 {
6933 float y = TerrainMeta.HeightMap.GetHeight(target);
6934 float w = TerrainMeta.WaterMap.GetHeight(target);
6935 float p = TerrainMeta.HighestPoint.y + 250f;
6936 RaycastHit hit;
6937
6938 if (Physics.Raycast(new Vector3(target.x, w, target.z), Vector3.up, out hit, p, Layers.Mask.World))
6939 {
6940 y = Mathf.Max(y, hit.point.y);
6941
6942 if (Physics.Raycast(new Vector3(target.x, hit.point.y + 0.5f, target.z), Vector3.up, out hit, p, Layers.Mask.World))
6943 {
6944 y = Mathf.Max(y, hit.point.y);
6945 }
6946 }
6947
6948 return Mathf.Max(y, w);
6949 }
6950
6951 private bool OnIceSheetOrInDeepWater(Vector3 vector)
6952 {
6953 if (TerrainMeta.WaterMap.GetHeight(vector) - TerrainMeta.HeightMap.GetHeight(vector) > 5f)
6954 {
6955 return true;
6956 }
6957
6958 vector.y += TerrainMeta.HighestPoint.y;
6959
6960 RaycastHit hit;
6961 if (Physics.Raycast(vector, Vector3.down, out hit, vector.y + 1f, Layers.Mask.World, QueryTriggerInteraction.Ignore) && hit.collider.name.StartsWith("ice_sheet"))
6962 {
6963 return true;
6964 }
6965
6966 return false;
6967 }
6968
6969 protected void LoadSpawns()
6970 {
6971 raidSpawns.Clear();
6972 raidSpawns.Add(RaidableType.Grid, new RaidableSpawns());
6973
6974 if (SpawnsFileValid(_config.Settings.Manual.SpawnsFile))
6975 {
6976 var spawns = GetSpawnsLocations(_config.Settings.Manual.SpawnsFile);
6977
6978 if (spawns?.Count > 0)
6979 {
6980 Puts(Backbone.GetMessage("LoadedManual", null, spawns.Count));
6981 raidSpawns[RaidableType.Manual] = new RaidableSpawns(spawns);
6982 }
6983 }
6984
6985 if (SpawnsFileValid(_config.Settings.Schedule.SpawnsFile))
6986 {
6987 var spawns = GetSpawnsLocations(_config.Settings.Schedule.SpawnsFile);
6988
6989 if (spawns?.Count > 0)
6990 {
6991 Puts(Backbone.GetMessage("LoadedScheduled", null, spawns.Count));
6992 raidSpawns[RaidableType.Scheduled] = new RaidableSpawns(spawns);
6993 }
6994 }
6995
6996 if (SpawnsFileValid(_config.Settings.Maintained.SpawnsFile))
6997 {
6998 var spawns = GetSpawnsLocations(_config.Settings.Maintained.SpawnsFile);
6999
7000 if (spawns?.Count > 0)
7001 {
7002 Puts(Backbone.GetMessage("LoadedMaintained", null, spawns.Count));
7003 raidSpawns[RaidableType.Maintained] = new RaidableSpawns(spawns);
7004 }
7005 }
7006
7007 if (SpawnsFileValid(_config.Settings.Buyable.SpawnsFile))
7008 {
7009 var spawns = GetSpawnsLocations(_config.Settings.Buyable.SpawnsFile);
7010
7011 if (spawns?.Count > 0)
7012 {
7013 Puts(Backbone.GetMessage("LoadedBuyable", null, spawns.Count));
7014 raidSpawns[RaidableType.Purchased] = new RaidableSpawns(spawns);
7015 }
7016 }
7017 }
7018
7019 protected void SetupGrid()
7020 {
7021 if (raidSpawns.Count >= 5)
7022 {
7023 StartAutomation();
7024 return;
7025 }
7026
7027 StopGridCoroutine();
7028
7029 NextTick(() =>
7030 {
7031 gridStopwatch.Start();
7032 gridTime = Time.realtimeSinceStartup;
7033 gridCoroutine = ServerMgr.Instance.StartCoroutine(GenerateGrid());
7034 });
7035 }
7036
7037 private bool IsValidLocation(Vector3 vector, float radius)
7038 {
7039 if (IsMonumentPosition(vector))
7040 {
7041 draw(vector, "M");
7042 return false;
7043 }
7044
7045 if (OnIceSheetOrInDeepWater(vector))
7046 {
7047 draw(vector, "D");
7048 return false;
7049 }
7050
7051 if (!IsAreaSafe(ref vector, radius, Layers.Mask.World | Layers.Mask.Water))
7052 {
7053 return false;
7054 }
7055
7056 foreach (var zone in managedZones)
7057 {
7058 if (InRange(zone.Key, vector, zone.Value))
7059 {
7060 draw(vector, "Z");
7061 return false;
7062 }
7063 }
7064
7065 return true;
7066 }
7067
7068 private void StopGridCoroutine()
7069 {
7070 if (gridCoroutine != null)
7071 {
7072 ServerMgr.Instance.StopCoroutine(gridCoroutine);
7073 gridCoroutine = null;
7074 }
7075 }
7076
7077 private IEnumerator GenerateGrid() // Credits to Jake_Rich for creating this for me!
7078 {
7079 RaidableSpawns rs = raidSpawns[RaidableType.Grid] = new RaidableSpawns();
7080 int minPos = (int)(World.Size / -2f);
7081 int maxPos = (int)(World.Size / 2f);
7082 int checks = 0;
7083 float max = GetMaxElevation();
7084
7085 for (float x = minPos; x < maxPos; x += 12.5f)
7086 {
7087 for (float z = minPos; z < maxPos; z += 12.5f)
7088 {
7089 var pos = new Vector3(x, 0f, z);
7090 pos.y = GetSpawnHeight(pos);
7091
7092 draw(pos, "O");
7093 ExtractLocation(rs, max, pos);
7094
7095 if (++checks >= 75)
7096 {
7097 checks = 0;
7098 yield return null;
7099 }
7100 }
7101 }
7102
7103 Puts(Backbone.GetMessage("InitializedGrid", null, gridStopwatch.Elapsed.Seconds, gridStopwatch.Elapsed.Milliseconds, World.Size, rs.Count));
7104 gridCoroutine = null;
7105 gridStopwatch.Stop();
7106 gridStopwatch.Reset();
7107 StartAutomation();
7108 }
7109
7110 private void ExtractLocation(RaidableSpawns rs, float max, Vector3 pos)
7111 {
7112 if (IsValidLocation(pos, Radius))
7113 {
7114 var elevation = GetTerrainElevation(pos);
7115
7116 if (IsFlatTerrain(pos, elevation, max))
7117 {
7118 rs.Add(new RaidableSpawnLocation
7119 {
7120 Location = pos,
7121 Elevation = elevation
7122 });
7123 }
7124 else draw(pos, "Elevation: " + (elevation.Max - elevation.Min).ToString());
7125 }
7126 }
7127
7128 private void draw(Vector3 pos, string text)
7129 {
7130#if DEBUG
7131 foreach (var player in BasePlayer.activePlayerList)
7132 {
7133 if (!player.IsAdmin || !InRange(player.transform.position, pos, 100f))
7134 continue;
7135
7136 player.SendConsoleCommand("ddraw.text", 60f, Color.yellow, pos, text);
7137 }
7138#endif
7139 }
7140
7141 private float GetMaxElevation()
7142 {
7143 float max = 2.5f;
7144
7145 if (_config.RaidableBases.Buildings.Values.Count > 0)
7146 {
7147 foreach (var x in _config.RaidableBases.Buildings.Values)
7148 {
7149 if (x.Elevation > max)
7150 {
7151 max = x.Elevation;
7152 }
7153 }
7154 }
7155
7156 return ++max;
7157 }
7158
7159 private bool SpawnsFileValid(string spawnsFile)
7160 {
7161 if (Spawns == null || !Spawns.IsLoaded)
7162 {
7163 return false;
7164 }
7165
7166 if (!Interface.Oxide.DataFileSystem.ExistsDatafile($"SpawnsDatabase/{spawnsFile}"))
7167 {
7168 return false;
7169 }
7170
7171 return Spawns?.Call("GetSpawnsCount", spawnsFile) is int;
7172 }
7173
7174 private List<RaidableSpawnLocation> GetSpawnsLocations(string spawnsFile)
7175 {
7176 object success = Spawns?.Call("LoadSpawnFile", spawnsFile);
7177
7178 if (success == null)
7179 {
7180 return null;
7181 }
7182
7183 var list = (List<Vector3>)success;
7184 var locations = new List<RaidableSpawnLocation>();
7185
7186 foreach (var pos in list)
7187 {
7188 locations.Add(new RaidableSpawnLocation
7189 {
7190 Location = pos
7191 });
7192 }
7193
7194 list.Clear();
7195
7196 return locations;
7197 }
7198
7199 private void StartAutomation()
7200 {
7201 if (_config.Settings.Schedule.Enabled)
7202 {
7203 if (storedData.RaidTime != DateTime.MinValue.ToString() && GetRaidTime() > _config.Settings.Schedule.IntervalMax) // Allows users to lower max event time
7204 {
7205 storedData.RaidTime = DateTime.MinValue.ToString();
7206 SaveData();
7207 }
7208
7209 StartScheduleCoroutine();
7210 }
7211
7212 StartMaintainCoroutine();
7213 }
7214
7215 public static void Shuffle<T>(IList<T> list) // Fisher-Yates shuffle
7216 {
7217 int count = list.Count;
7218 int n = count;
7219 while (n-- > 0)
7220 {
7221 int k = UnityEngine.Random.Range(0, count);
7222 int j = UnityEngine.Random.Range(0, count);
7223 T value = list[k];
7224 list[k] = list[j];
7225 list[j] = value;
7226 }
7227 }
7228
7229 public Vector3 GetEventPosition(BuildingOptions options, BasePlayer owner, float distanceFrom, bool checkTerrain, RaidableSpawns rs, RaidableType type)
7230 {
7231 rs.Check();
7232
7233 int num1 = 0;
7234 float distance;
7235
7236 switch (type)
7237 {
7238 case RaidableType.Maintained:
7239 {
7240 distance = _config.Settings.Maintained.Distance;
7241 break;
7242 }
7243 case RaidableType.Purchased:
7244 {
7245 distance = _config.Settings.Buyable.Distance;
7246 break;
7247 }
7248 case RaidableType.Scheduled:
7249 {
7250 distance = _config.Settings.Schedule.Distance;
7251 break;
7252 }
7253 case RaidableType.Manual:
7254 {
7255 distance = _config.Settings.Manual.Distance;
7256 break;
7257 }
7258 default:
7259 {
7260 distance = 100f;
7261 break;
7262 }
7263 }
7264
7265 bool isOwner = IsValid(owner);
7266 float safeRadius = Radius * 2f;
7267 int attempts = isOwner ? 1000 : 2000;
7268 int layers = Layers.Mask.Player_Server | Layers.Mask.Construction | Layers.Mask.Deployed;
7269 float buildRadius = Mathf.Max(_config.Settings.Management.CupboardDetectionRadius, options.ArenaWalls.Radius, options.ProtectionRadius) + 5f;
7270
7271 while (rs.Count > 0 && attempts > 0)
7272 {
7273 var rsl = rs.GetRandom();
7274
7275 if (isOwner && distanceFrom > 0 && !InRange(owner.transform.position, rsl.Location, distanceFrom))
7276 {
7277 num1++;
7278 continue;
7279 }
7280
7281 if (RaidableBase.IsTooClose(rsl.Location, distance))
7282 {
7283 continue;
7284 }
7285
7286 attempts--;
7287
7288 if (!IsAreaSafe(ref rsl.Location, safeRadius, layers, type, options.ArenaWalls.Radius))
7289 {
7290 continue;
7291 }
7292
7293 if (checkTerrain && !IsFlatTerrain(rsl.Location, rsl.Elevation, options.Elevation))
7294 {
7295 continue;
7296 }
7297
7298 if (HasActivePlayerBuildingPrivilege(rsl.Location, buildRadius))
7299 {
7300 continue;
7301 }
7302
7303 var position = new Vector3(rsl.Location.x, rsl.Location.y, rsl.Location.z);
7304 float w = TerrainMeta.WaterMap.GetHeight(position);
7305 float h = TerrainMeta.HeightMap.GetHeight(position);
7306
7307 if (w - h > 1f)
7308 {
7309 if (!options.Submerged)
7310 {
7311 continue;
7312 }
7313
7314 position.y = w;
7315 }
7316
7317 if (!raidSpawns.ContainsKey(type) && !options.Submerged && IsSubmerged(position, Mathf.Max(options.ArenaWalls.Radius, options.ProtectionRadius)))
7318 {
7319 continue;
7320 }
7321
7322 if (!checkTerrain)
7323 {
7324 rs.RemoveNear(rsl, options.ProtectionRadius);
7325 }
7326
7327 return position;
7328 }
7329
7330 rs.TryAddRange();
7331
7332 if (rs.Count > 0 && rs.Count < 500 && num1 >= rs.Count / 2 && (distanceFrom += 50f) < World.Size)
7333 {
7334 return GetEventPosition(options, owner, distanceFrom, checkTerrain, rs, type);
7335 }
7336
7337 return Vector3.zero;
7338 }
7339
7340 private bool IsSubmerged(Vector3 position, float radius)
7341 {
7342 foreach (var vector in GetCircumferencePositions(position, radius, 30f, false, 1f)) // 70 to 30 in 1.5.1
7343 {
7344 float w = TerrainMeta.WaterMap.GetHeight(vector);
7345 float h = TerrainMeta.HeightMap.GetHeight(vector);
7346
7347 if (w - h > 1f)
7348 {
7349 return true;
7350 }
7351 }
7352
7353 return false;
7354 }
7355
7356 public bool HasActivePlayerBuildingPrivilege(Vector3 target, float radius)
7357 {
7358 bool flag = false;
7359 var list = Pool.GetList<BuildingPrivlidge>();
7360 Vis.Entities(target, radius, list);
7361 foreach (var tc in list)
7362 {
7363 if (tc.IsValid() && tc.OwnerID.IsSteamId())
7364 {
7365 flag = true;
7366 break;
7367 }
7368 }
7369 Pool.FreeList(ref list);
7370 return flag;
7371 }
7372
7373 private readonly List<string> assets = new List<string>
7374 {
7375 "/props/", "/structures/", "/building/", "train_", "powerline_", "dune"
7376 };
7377
7378 public bool IsAreaSafe(ref Vector3 position, float radius, int layers, RaidableType type = RaidableType.None, float awr = 0f)
7379 {
7380 if (awr > radius)
7381 {
7382 radius = awr + 2.5f;
7383 }
7384
7385 int hits = Physics.OverlapSphereNonAlloc(position, radius, Vis.colBuffer, layers, QueryTriggerInteraction.Ignore);
7386 int count = hits;
7387
7388 for (int i = 0; i < hits; i++)
7389 {
7390 if (count == int.MaxValue)
7391 {
7392 goto next;
7393 }
7394
7395 var collider = Vis.colBuffer[i];
7396
7397 if (collider == null || collider.transform == null)
7398 {
7399 count--;
7400 goto next;
7401 }
7402
7403 var e = collider.ToBaseEntity();
7404
7405 if (e.IsValid())
7406 {
7407 if (e.IsNpc || e is SleepingBag || e is BaseOven) count--;
7408 else if (e is BasePlayer)
7409 {
7410 var player = e as BasePlayer;
7411
7412 if (_config.Settings.Management.EjectSleepers && player.IsSleeping()) count--;
7413 else if ((player.IsAdmin || permission.UserHasPermission(player.UserIDString, adminPermission)) && player.IsFlying) count--;
7414 }
7415 else if (!(e is BuildingBlock) && e.OwnerID == 0) count--;
7416
7417 goto next;
7418 }
7419
7420 if (collider.gameObject?.layer == (int)Layer.Water)
7421 {
7422 if (!_config.Settings.Management.AllowOnRivers && collider.name.StartsWith("River Mesh"))
7423 {
7424 count = int.MaxValue;
7425 draw(position, "W");
7426 goto next;
7427 }
7428
7429 count--;
7430 goto next;
7431 }
7432
7433 if (collider.gameObject?.layer == (int)Layer.World)
7434 {
7435 if (!_config.Settings.Management.AllowOnRoads && collider.name.ToLower().StartsWith("road"))
7436 {
7437 count = int.MaxValue;
7438 draw(position, "road");
7439 goto next;
7440 }
7441
7442 if (collider.name.Contains("dune"))
7443 {
7444 count = int.MaxValue;
7445 draw(position, "dune");
7446 goto next;
7447 }
7448
7449 if (collider.transform != null)
7450 {
7451 var y = TerrainMeta.HeightMap.GetHeight(collider.transform.position);
7452
7453 if (position.y < y)
7454 {
7455 position.y = y + 0.15f;
7456 }
7457
7458 goto next;
7459 }
7460
7461 if (collider.transform == null || Vector3.Distance(collider.transform.position, position) <= Radius)
7462 {
7463 bool flag = false;
7464
7465 foreach (var asset in assets)
7466 {
7467 if (collider.name.Contains(asset))
7468 {
7469 flag = true;
7470 break;
7471 }
7472 }
7473
7474 if (flag)
7475 {
7476 count = int.MaxValue;
7477 goto next;
7478 }
7479 }
7480
7481 count--;
7482 goto next;
7483 }
7484
7485 if (collider.gameObject?.layer == (int)Layer.Prevent_Building)
7486 {
7487 if (!e.IsValid())
7488 {
7489 count--;
7490 goto next;
7491 }
7492
7493 draw(position, "PB");
7494 count = int.MaxValue;
7495 goto next;
7496 }
7497
7498 if (collider.name.StartsWith("rock_"))
7499 {
7500 if (collider.bounds.extents.Max() > 0f && collider.bounds.extents.Max() <= 7f)
7501 {
7502 count--;
7503 goto next;
7504 }
7505 else
7506 {
7507 draw(position, "rock_");
7508 count = int.MaxValue;
7509 goto next;
7510 }
7511 }
7512
7513 if (collider.bounds.size.Max() > radius)
7514 {
7515 count--;
7516 goto next;
7517 }
7518
7519 next:
7520 Vis.colBuffer[i] = null;
7521 }
7522
7523 return count == 0;
7524 }
7525
7526 public bool IsMonumentPosition(Vector3 target)
7527 {
7528 foreach (var monument in monuments)
7529 {
7530 if (InRange(monument.Key.transform.position, target, monument.Value))
7531 {
7532 return true;
7533 }
7534 }
7535
7536 return false;
7537 }
7538
7539 private void SetAllowPVP(RaidableType type, RaidableBase raid, bool flag)
7540 {
7541 if (type == RaidableType.Maintained && _config.Settings.Maintained.ConvertPVP)
7542 {
7543 raid.AllowPVP = false;
7544 }
7545 else if (type == RaidableType.Scheduled && _config.Settings.Schedule.ConvertPVP)
7546 {
7547 raid.AllowPVP = false;
7548 }
7549 else if (type == RaidableType.Manual && _config.Settings.Manual.ConvertPVP)
7550 {
7551 raid.AllowPVP = false;
7552 }
7553 else if (type == RaidableType.Purchased && _config.Settings.Buyable.ConvertPVP)
7554 {
7555 raid.AllowPVP = false;
7556 }
7557 else if (type == RaidableType.Maintained && _config.Settings.Maintained.ConvertPVE)
7558 {
7559 raid.AllowPVP = true;
7560 }
7561 else if (type == RaidableType.Scheduled && _config.Settings.Schedule.ConvertPVE)
7562 {
7563 raid.AllowPVP = true;
7564 }
7565 else if (type == RaidableType.Manual && _config.Settings.Manual.ConvertPVE)
7566 {
7567 raid.AllowPVP = true;
7568 }
7569 else if (type == RaidableType.Purchased && _config.Settings.Buyable.ConvertPVE)
7570 {
7571 raid.AllowPVP = true;
7572 }
7573 else raid.AllowPVP = flag;
7574 }
7575
7576 public bool TryOpenEvent(RaidableType type, Vector3 position, int uid, string BaseName, KeyValuePair<string, BuildingOptions> building, out RaidableBase raid)
7577 {
7578 if (IsUnloading)
7579 {
7580 raid = null;
7581 return false;
7582 }
7583
7584 raid = new GameObject().AddComponent<RaidableBase>();
7585
7586 SetAllowPVP(type, raid, building.Value.AllowPVP);
7587
7588 raid.DifficultyMode = building.Value.Mode == RaidableMode.Easy ? Backbone.Easy : building.Value.Mode == RaidableMode.Medium ? Backbone.Medium : building.Value.Mode == RaidableMode.Hard ? Backbone.Hard : building.Value.Mode == RaidableMode.Expert ? Backbone.Expert : Backbone.Nightmare;
7589
7590 /*if (Options.ExplosionModifier != 100f)
7591 {
7592 if (Options.ExplosionModifier <= 75)
7593 {
7594 raid.DifficultyMode = Backbone.GetMessage("VeryHard");
7595 }
7596 else if (Options.ExplosionModifier >= 150)
7597 {
7598 raid.DifficultyMode = Backbone.GetMessage("VeryEasy");
7599 }
7600 }*/
7601
7602 raid.PastedLocation = position;
7603 raid.Options = building.Value;
7604 raid.BaseName = BaseName;
7605 raid.Type = type;
7606 raid.uid = uid;
7607
7608 if (_config.Settings.NoWizardry && Wizardry != null && Wizardry.IsLoaded)
7609 {
7610 Subscribe(nameof(OnActiveItemChanged));
7611 }
7612
7613 Subscribe(nameof(OnEntitySpawned));
7614
7615 if (IsPVE())
7616 {
7617 Subscribe(nameof(CanEntityTakeDamage));
7618 }
7619 else Subscribe(nameof(OnEntityTakeDamage));
7620
7621 storedData.TotalEvents++;
7622 SaveData();
7623
7624 if (_config.LustyMap.Enabled && LustyMap != null && LustyMap.IsLoaded)
7625 {
7626 AddTemporaryLustyMarker(position, uid);
7627 }
7628
7629 if (Map)
7630 {
7631 AddMapPrivatePluginMarker(position, uid);
7632 }
7633
7634 Raids[uid] = raid;
7635 return true;
7636 }
7637
7638 #endregion
7639
7640 #region Paste
7641
7642 protected bool IsGridLoading
7643 {
7644 get
7645 {
7646 return gridCoroutine != null;
7647 }
7648 }
7649
7650 protected bool IsPasteAvailable
7651 {
7652 get
7653 {
7654 foreach (var raid in Raids.Values)
7655 {
7656 if (raid.IsLoading)
7657 {
7658 return false;
7659 }
7660 }
7661
7662 return true;
7663 }
7664 }
7665
7666 private bool TryBuyRaidServerRewards(BasePlayer buyer, BasePlayer player, RaidableMode mode)
7667 {
7668 if (_config.Settings.ServerRewards.Any && ServerRewards != null && ServerRewards.IsLoaded)
7669 {
7670 int cost = mode == RaidableMode.Easy ? _config.Settings.ServerRewards.Easy : mode == RaidableMode.Medium ? _config.Settings.ServerRewards.Medium : mode == RaidableMode.Hard ? _config.Settings.ServerRewards.Hard : mode == RaidableMode.Expert ? _config.Settings.ServerRewards.Expert : _config.Settings.ServerRewards.Nightmare;
7671 var success = ServerRewards?.Call("CheckPoints", buyer.userID);
7672 int points = success is int ? Convert.ToInt32(success) : 0;
7673
7674 if (points > 0 && points - cost >= 0)
7675 {
7676 if (BuyRaid(player, mode))
7677 {
7678 ServerRewards.Call("TakePoints", buyer.userID, cost);
7679 Backbone.Message(buyer, "ServerRewardPointsTaken", buyer.UserIDString, cost);
7680 if (buyer != player) Backbone.Message(player, "ServerRewardPointsGift", player.UserIDString, buyer.displayName, cost);
7681 return true;
7682 }
7683 }
7684 else Backbone.Message(buyer, "ServerRewardPointsFailed", buyer.UserIDString, cost);
7685 }
7686
7687 return false;
7688 }
7689
7690 private bool TryBuyRaidEconomics(BasePlayer buyer, BasePlayer player, RaidableMode mode)
7691 {
7692 if (_config.Settings.Economics.Any && Economics != null && Economics.IsLoaded)
7693 {
7694 var cost = mode == RaidableMode.Easy ? _config.Settings.Economics.Easy : mode == RaidableMode.Medium ? _config.Settings.Economics.Medium : mode == RaidableMode.Hard ? _config.Settings.Economics.Hard : mode == RaidableMode.Expert ? _config.Settings.Economics.Expert : _config.Settings.Economics.Nightmare;
7695 var success = Economics?.Call("Balance", buyer.UserIDString);
7696 var points = success is double ? Convert.ToDouble(success) : 0;
7697
7698 if (points > 0 && points - cost >= 0)
7699 {
7700 if (BuyRaid(player, mode))
7701 {
7702 Economics.Call("Withdraw", buyer.UserIDString, cost);
7703 Backbone.Message(buyer, "EconomicsWithdraw", buyer.UserIDString, cost);
7704 if (buyer != player) Backbone.Message(player, "EconomicsWithdrawGift", player.UserIDString, buyer.displayName, cost);
7705 return true;
7706 }
7707 }
7708 else Backbone.Message(buyer, "EconomicsWithdrawFailed", buyer.UserIDString, cost);
7709 }
7710
7711 return false;
7712 }
7713
7714 private bool BuyRaid(BasePlayer owner, RaidableMode mode)
7715 {
7716 string message;
7717 var position = SpawnRandomBase(out message, RaidableType.Purchased, mode, null, false, owner);
7718
7719 if (position != Vector3.zero)
7720 {
7721 var grid = FormatGridReference(position);
7722 Backbone.Message(owner, "BuyBaseSpawnedAt", owner.UserIDString, position, grid);
7723
7724 if (_config.EventMessages.AnnounceBuy)
7725 {
7726 foreach (var target in BasePlayer.activePlayerList)
7727 {
7728 var announcement = Backbone.GetMessage("BuyBaseAnnouncement", target.UserIDString, owner.displayName, position, grid);
7729 target.SendConsoleCommand("chat.add", 2, _config.Settings.ChatID, announcement);
7730 }
7731 }
7732
7733 Puts(Backbone.RemoveFormatting(Backbone.GetMessage("BuyBaseAnnouncement", null, owner.displayName, position, grid)));
7734 return true;
7735 }
7736
7737 Backbone.Message(owner, message, owner.UserIDString);
7738 return false;
7739 }
7740
7741 private static bool IsDifficultyAvailable(RaidableMode mode, bool checkAllowPVP)
7742 {
7743 if (!CanSpawnDifficultyToday(mode))
7744 {
7745 return false;
7746 }
7747
7748 foreach (var option in _config.RaidableBases.Buildings.Values)
7749 {
7750 if (option.Mode != mode || (checkAllowPVP && !_config.Settings.Buyable.BuyPVP && option.AllowPVP))
7751 {
7752 continue;
7753 }
7754
7755 return true;
7756 }
7757
7758 return false;
7759 }
7760
7761 private void PasteBuilding(RaidableType type, Vector3 position, KeyValuePair<string, BuildingOptions> building, BasePlayer owner)
7762 {
7763 int uid;
7764
7765 do
7766 {
7767 uid = UnityEngine.Random.Range(1000, 100000);
7768 } while (Raids.ContainsKey(uid));
7769
7770 var callback = new Action(() =>
7771 {
7772 RaidableBase raid;
7773 if (TryOpenEvent(type, position, uid, building.Key, building, out raid))
7774 {
7775 Cycle.Add(type, building.Value.Mode, building.Key);
7776
7777 if (type == RaidableType.Purchased && _config.Settings.Buyable.UsePayLock)
7778 {
7779 raid.TrySetPayLock(owner);
7780 }
7781 }
7782 });
7783
7784 var list = GetListedOptions(building.Value.PasteOptions);
7785 float rotationCorrection = IsValid(owner) ? DegreeToRadian(owner.GetNetworkRotation().eulerAngles.y) : 0f;
7786
7787 CopyPaste.Call("TryPasteFromVector3", position, rotationCorrection, building.Key, list.ToArray(), callback);
7788 }
7789
7790 private List<string> GetListedOptions(List<PasteOption> options)
7791 {
7792 var list = new List<string>();
7793 bool flag1 = false, flag2 = false, flag3 = false, flag4 = false;
7794
7795 for (int i = 0; i < options.Count; i++)
7796 {
7797 string key = options[i].Key.ToLower();
7798 string value = options[i].Value.ToLower();
7799
7800 if (key == "stability") flag1 = true;
7801 if (key == "autoheight") flag2 = true;
7802 if (key == "height") flag3 = true;
7803 if (key == "entityowner") flag4 = true;
7804
7805 list.Add(key);
7806 list.Add(value);
7807 }
7808
7809 if (!flag1)
7810 {
7811 list.Add("stability");
7812 list.Add("false");
7813 }
7814
7815 if (!flag2)
7816 {
7817 list.Add("autoheight");
7818 list.Add("false");
7819 }
7820
7821 if (!flag3)
7822 {
7823 list.Add("height");
7824 list.Add("2.5");
7825 }
7826
7827 if (!flag4)
7828 {
7829 list.Add("entityowner");
7830 list.Add("false");
7831 }
7832
7833 return list;
7834 }
7835
7836 private float DegreeToRadian(float angle)
7837 {
7838 return angle.Equals(0f) ? 0f : (float)(Math.PI * angle / 180.0f);
7839 }
7840
7841 private void OnPasteFinished(List<BaseEntity> pastedEntities)
7842 {
7843 if (pastedEntities == null || pastedEntities.Count == 0)
7844 {
7845 return;
7846 }
7847
7848 Timer t = null;
7849
7850 t = timer.Repeat(1f, 120, () =>
7851 {
7852 if (IsUnloading)
7853 {
7854 if (t != null)
7855 {
7856 t.Destroy();
7857 }
7858
7859 return;
7860 }
7861
7862 var raid = RaidableBase.Get(pastedEntities);
7863
7864 if (raid == null)
7865 {
7866 return;
7867 }
7868
7869 if (t != null)
7870 {
7871 t.Destroy();
7872 }
7873
7874 int baseIndex = 0;
7875
7876 while (Bases.ContainsKey(baseIndex) || Indices.ContainsKey(baseIndex))
7877 {
7878 baseIndex = UnityEngine.Random.Range(1, 9999999);
7879 }
7880
7881 Indices[baseIndex] = raid;
7882 Bases[baseIndex] = pastedEntities;
7883
7884 raid.SetEntities(baseIndex);
7885 });
7886 }
7887
7888 public IEnumerator UndoRoutine(int baseIndex)
7889 {
7890 var raid = RaidableBase.Get(baseIndex);
7891
7892 if (raid != null)
7893 {
7894 UnityEngine.Object.Destroy(raid.gameObject);
7895 }
7896
7897 if (!Bases.ContainsKey(baseIndex))
7898 {
7899 yield break;
7900 }
7901
7902 int total = 0;
7903 OreResourceEntity ore;
7904 var entities = Bases[baseIndex];
7905
7906 while (entities.Count > 0)
7907 {
7908 var e = entities[0];
7909
7910 if (e == null)
7911 {
7912 entities.Remove(e);
7913 continue;
7914 }
7915
7916 ore = e as OreResourceEntity;
7917
7918 if (ore != null)
7919 {
7920 ore.CleanupBonus();
7921 }
7922
7923 if (!e.IsDestroyed)
7924 {
7925 e.Kill();
7926 }
7927
7928 if (_config.Settings.BatchLimit > 0 && ++total % _config.Settings.BatchLimit == 0)
7929 {
7930 yield return null;
7931 }
7932 else yield return new WaitWhile(() => !e.IsDestroyed);
7933
7934 entities.Remove(e);
7935 RaidEntities.Remove(e);
7936 }
7937
7938 Bases.Remove(baseIndex);
7939 Indices.Remove(baseIndex);
7940
7941 if (Bases.Count == 0)
7942 {
7943 UnsubscribeHooks();
7944 }
7945 }
7946
7947 private void UndoPaste(Vector3 position, RaidableBase raid, int baseIndex)
7948 {
7949 if (IsUnloading || !Bases.ContainsKey(baseIndex))
7950 {
7951 return;
7952 }
7953
7954 if (_config.Settings.Management.DespawnMinutes > 0)
7955 {
7956 if (_config.EventMessages.ShowWarning)
7957 {
7958 var grid = FormatGridReference(position);
7959
7960 foreach (var target in BasePlayer.activePlayerList)
7961 {
7962 var message = Backbone.GetMessage("DestroyingBaseAt", target.UserIDString, grid, _config.Settings.Management.DespawnMinutes);
7963 target.SendConsoleCommand("chat.add", 2, _config.Settings.ChatID, message);
7964 }
7965 }
7966
7967 float time = _config.Settings.Management.DespawnMinutes * 60f;
7968
7969 if (raid != null)
7970 {
7971 raid.selfDestruct = true;
7972 raid.despawnTime = Time.realtimeSinceStartup + time;
7973 }
7974
7975 timer.Once(time, () =>
7976 {
7977 if (!IsUnloading)
7978 {
7979 ServerMgr.Instance.StartCoroutine(UndoRoutine(baseIndex));
7980 }
7981 });
7982 }
7983 else ServerMgr.Instance.StartCoroutine(UndoRoutine(baseIndex));
7984 }
7985
7986 private static List<Vector3> GetCircumferencePositions(Vector3 center, float radius, float next, bool spawnHeight, float y = 0f)
7987 {
7988 var positions = new List<Vector3>();
7989
7990 if (next < 1f)
7991 {
7992 next = 1f;
7993 }
7994
7995 float angle = 0f;
7996 float angleInRadians = 2 * (float)Math.PI;
7997
7998 while (angle < 360)
7999 {
8000 float radian = (angleInRadians / 360) * angle;
8001 float x = center.x + radius * (float)Math.Cos(radian);
8002 float z = center.z + radius * (float)Math.Sin(radian);
8003 var a = new Vector3(x, 0f, z);
8004
8005 a.y = y == 0f ? spawnHeight ? GetSpawnHeight(a) : TerrainMeta.HeightMap.GetHeight(a) : y;
8006
8007 if (a.y < -48f)
8008 {
8009 a.y = -48f;
8010 }
8011
8012 positions.Add(a);
8013 angle += next;
8014 }
8015
8016 return positions;
8017 }
8018
8019 private Elevation GetTerrainElevation(Vector3 center)
8020 {
8021 float maxY = -1000;
8022 float minY = 1000;
8023
8024 foreach (var position in GetCircumferencePositions(center, 20f, 30f, true)) // 70 to 30 in 1.5.1
8025 {
8026 if (position.y > maxY) maxY = position.y;
8027 if (position.y < minY) minY = position.y;
8028 }
8029
8030 return new Elevation
8031 {
8032 Min = minY,
8033 Max = maxY
8034 };
8035 }
8036
8037 private bool IsFlatTerrain(Vector3 center, Elevation elevation, float value)
8038 {
8039 return elevation.Max - elevation.Min <= value && elevation.Max - center.y <= value;
8040 }
8041
8042 private float GetMonumentFloat(string monumentName)
8043 {
8044 switch (monumentName)
8045 {
8046 case "Abandoned Cabins":
8047 return 54f;
8048 case "Abandoned Supermarket":
8049 return 50f;
8050 case "Airfield":
8051 return 200f;
8052 case "Bandit Camp":
8053 return 125f;
8054 case "Giant Excavator Pit":
8055 return 225f;
8056 case "Harbor":
8057 return 150f;
8058 case "HQM Quarry":
8059 return 37.5f;
8060 case "Large Oil Rig":
8061 return 200f;
8062 case "Launch Site":
8063 return 300f;
8064 case "Lighthouse":
8065 return 48f;
8066 case "Military Tunnel":
8067 return 100f;
8068 case "Mining Outpost":
8069 return 45f;
8070 case "Oil Rig":
8071 return 100f;
8072 case "Outpost":
8073 return 250f;
8074 case "Oxum's Gas Station":
8075 return 65f;
8076 case "Power Plant":
8077 return 140f;
8078 case "Satellite Dish":
8079 return 90f;
8080 case "Sewer Branch":
8081 return 100f;
8082 case "Stone Quarry":
8083 return 27.5f;
8084 case "Sulfur Quarry":
8085 return 27.5f;
8086 case "The Dome":
8087 return 70f;
8088 case "Train Yard":
8089 return 150f;
8090 case "Water Treatment Plant":
8091 return 185f;
8092 case "Water Well":
8093 return 24f;
8094 case "Wild Swamp":
8095 return 24f;
8096 }
8097
8098 return 300f;
8099 }
8100
8101 private Vector3 SpawnRandomBase(out string message, RaidableType type, RaidableMode mode, string baseName = null, bool isAdmin = false, BasePlayer owner = null)
8102 {
8103 lastSpawnRequestTime = Time.realtimeSinceStartup;
8104
8105 if (type == RaidableType.Purchased && !owner.IsValid())
8106 {
8107 message = "?";
8108 return Vector3.zero;
8109 }
8110
8111 var building = GetBuilding(type, mode, baseName);
8112 bool flag = IsBuildingValid(building);
8113
8114 if (flag)
8115 {
8116 bool checkTerrain;
8117 var spawns = GetSpawns(type, out checkTerrain);
8118
8119 if (spawns != null)
8120 {
8121 var eventPos = GetEventPosition(building.Value, owner, _config.Settings.Buyable.DistanceToSpawnFrom, checkTerrain, spawns, type);
8122
8123 if (eventPos != Vector3.zero)
8124 {
8125 PasteBuilding(type, eventPos, building, owner);
8126 message = "Success";
8127 return eventPos;
8128 }
8129 }
8130 }
8131
8132 if (!flag)
8133 {
8134 if (mode == RaidableMode.Random)
8135 {
8136 message = Backbone.GetMessage("NoValidBuildingsConfigured");
8137 }
8138 else message = Backbone.GetMessage(isAdmin ? "Difficulty Not Available Admin" : "Difficulty Not Available", null, (int)mode);
8139 }
8140 else message = Backbone.GetMessage("CannotFindPosition");
8141
8142 return Vector3.zero;
8143 }
8144
8145 public RaidableSpawns GetSpawns(RaidableType type, out bool checkTerrain)
8146 {
8147 RaidableSpawns spawns;
8148
8149 switch (type)
8150 {
8151 case RaidableType.Maintained:
8152 {
8153 if (raidSpawns.TryGetValue(RaidableType.Maintained, out spawns))
8154 {
8155 checkTerrain = false;
8156 return spawns;
8157 }
8158 break;
8159 }
8160 case RaidableType.Manual:
8161 {
8162 if (raidSpawns.TryGetValue(RaidableType.Manual, out spawns))
8163 {
8164 checkTerrain = false;
8165 return spawns;
8166 }
8167 break;
8168 }
8169 case RaidableType.Purchased:
8170 {
8171 if (raidSpawns.TryGetValue(RaidableType.Purchased, out spawns))
8172 {
8173 checkTerrain = false;
8174 return spawns;
8175 }
8176 break;
8177 }
8178 case RaidableType.Scheduled:
8179 {
8180 if (raidSpawns.TryGetValue(RaidableType.Scheduled, out spawns))
8181 {
8182 checkTerrain = false;
8183 return spawns;
8184 }
8185 break;
8186 }
8187 }
8188
8189 checkTerrain = true;
8190 return raidSpawns.TryGetValue(RaidableType.Grid, out spawns) ? spawns : null;
8191 }
8192
8193 private KeyValuePair<string, BuildingOptions> GetBuilding(RaidableType type, RaidableMode mode, string baseName)
8194 {
8195 var list = new List<KeyValuePair<string, BuildingOptions>>();
8196 bool isBaseNull = string.IsNullOrEmpty(baseName);
8197
8198 foreach (var building in _config.RaidableBases.Buildings)
8199 {
8200 if (building.Value.Mode == RaidableMode.Disabled || MustExclude(type, building.Value.AllowPVP) || !IsBuildingAllowed(type, mode, building.Value.Mode, building.Value.AllowPVP))
8201 {
8202 continue;
8203 }
8204
8205 if (FileExists(building.Key) && Cycle.CanSpawn(type, mode, building.Key))
8206 {
8207 if (isBaseNull)
8208 {
8209 list.Add(building);
8210 }
8211 else if (building.Key.Equals(baseName, StringComparison.OrdinalIgnoreCase))
8212 {
8213 return building;
8214 }
8215 }
8216
8217 foreach (var extra in building.Value.AdditionalBases)
8218 {
8219 if (!FileExists(extra.Key) || !Cycle.CanSpawn(type, mode, extra.Key))
8220 {
8221 continue;
8222 }
8223
8224 var kvp = new KeyValuePair<string, BuildingOptions>(extra.Key, BuildingOptions.Clone(building.Value));
8225 kvp.Value.PasteOptions = new List<PasteOption>(extra.Value);
8226
8227 if (isBaseNull)
8228 {
8229 list.Add(kvp);
8230 }
8231 else if (extra.Key.Equals(baseName, StringComparison.OrdinalIgnoreCase))
8232 {
8233 return kvp;
8234 }
8235 }
8236 }
8237
8238 if (list.Count > 0)
8239 {
8240
8241 var r = list.GetRandom();
8242
8243 list.Clear();
8244
8245 return r;
8246 }
8247
8248 return default(KeyValuePair<string, BuildingOptions>);
8249 }
8250
8251 public static bool IsBuildingValid(KeyValuePair<string, BuildingOptions> building)
8252 {
8253 if (string.IsNullOrEmpty(building.Key) || building.Value == null)
8254 {
8255 return false;
8256 }
8257
8258 return true;
8259 }
8260
8261 public RaidableMode GetRandomDifficulty(RaidableType type)
8262 {
8263 var list = new List<RaidableMode>();
8264 decimal max = 0;
8265
8266 if (_config.Settings.Maintained.Enabled)
8267 {
8268 max += _config.Settings.Maintained.Max;
8269 }
8270
8271 if (_config.Settings.Schedule.Enabled)
8272 {
8273 max += _config.Settings.Schedule.Max;
8274 }
8275
8276 foreach (RaidableMode mode in Enum.GetValues(typeof(RaidableMode)))
8277 {
8278 if (!CanSpawnDifficultyToday(mode) || Exceeds(mode, RaidableBase.Get(mode), max))
8279 {
8280 continue;
8281 }
8282
8283 foreach (var options in _config.RaidableBases.Buildings.Values)
8284 {
8285 if (options.Mode == mode && !MustExclude(type, options.AllowPVP))
8286 {
8287 list.Add(mode);
8288 break;
8289 }
8290 }
8291 }
8292
8293 if (list.Count > 0)
8294 {
8295 return list.GetRandom();
8296 }
8297
8298 return RaidableMode.Random;
8299 }
8300
8301 public bool Exceeds(RaidableMode mode, decimal amount, decimal max)
8302 {
8303 decimal percent;
8304
8305 switch (mode)
8306 {
8307 case RaidableMode.Easy:
8308 {
8309 percent = _config.Settings.Management.Percent.Easy;
8310 break;
8311 }
8312 case RaidableMode.Medium:
8313 {
8314 percent = _config.Settings.Management.Percent.Medium;
8315 break;
8316 }
8317 case RaidableMode.Hard:
8318 {
8319 percent = _config.Settings.Management.Percent.Hard;
8320 break;
8321 }
8322 case RaidableMode.Expert:
8323 {
8324 percent = _config.Settings.Management.Percent.Expert;
8325 break;
8326 }
8327 default:
8328 {
8329 percent = _config.Settings.Management.Percent.Nightmare;
8330 break;
8331 }
8332 }
8333
8334 if (percent < 0)
8335 {
8336 return true;
8337 }
8338
8339 if (percent == 0m || amount <= 0m || max <= 0m)
8340 {
8341 return false;
8342 }
8343
8344 return (amount / max) * 100m >= percent;
8345 }
8346
8347 public static bool FileExists(string file)
8348 {
8349 return Interface.Oxide.DataFileSystem.ExistsDatafile($"copypaste/{file}");
8350 }
8351
8352 public static bool IsBuildingAllowed(RaidableType type, RaidableMode requestedMode, RaidableMode buildingMode, bool allowPVP)
8353 {
8354 if (requestedMode != RaidableMode.Random && buildingMode != requestedMode)
8355 {
8356 return false;
8357 }
8358
8359 switch (type)
8360 {
8361 case RaidableType.Purchased:
8362 {
8363 if (!CanSpawnDifficultyToday(buildingMode) || !_config.Settings.Buyable.BuyPVP && allowPVP)
8364 {
8365 return false;
8366 }
8367 break;
8368 }
8369 case RaidableType.Maintained:
8370 case RaidableType.Scheduled:
8371 {
8372 if (!CanSpawnDifficultyToday(buildingMode))
8373 {
8374 return false;
8375 }
8376 break;
8377 }
8378 }
8379
8380 return true;
8381 }
8382
8383 public static bool CanSpawnDifficultyToday(RaidableMode mode)
8384 {
8385 switch (DateTime.Now.DayOfWeek)
8386 {
8387 case DayOfWeek.Monday:
8388 {
8389 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Monday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Monday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Monday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Monday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Monday : false;
8390 }
8391 case DayOfWeek.Tuesday:
8392 {
8393 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Tuesday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Tuesday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Tuesday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Tuesday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Tuesday : false;
8394 }
8395 case DayOfWeek.Wednesday:
8396 {
8397 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Wednesday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Wednesday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Wednesday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Wednesday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Wednesday : false;
8398 }
8399 case DayOfWeek.Thursday:
8400 {
8401 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Thursday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Thursday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Thursday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Thursday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Thursday : false;
8402 }
8403 case DayOfWeek.Friday:
8404 {
8405 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Friday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Friday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Friday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Friday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Friday : false;
8406 }
8407 case DayOfWeek.Saturday:
8408 {
8409 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Saturday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Saturday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Saturday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Saturday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Saturday : false;
8410 }
8411 default:
8412 {
8413 return mode == RaidableMode.Easy ? _config.Settings.Management.Easy.Sunday : mode == RaidableMode.Medium ? _config.Settings.Management.Medium.Sunday : mode == RaidableMode.Hard ? _config.Settings.Management.Hard.Sunday : mode == RaidableMode.Expert ? _config.Settings.Management.Expert.Sunday : mode == RaidableMode.Nightmare ? _config.Settings.Management.Nightmare.Sunday : false;
8414 }
8415 }
8416 }
8417
8418#endregion
8419
8420#region Commands
8421
8422 private void CommandReloadConfig(IPlayer p, string command, string[] args)
8423 {
8424 if (p.IsServer || ((p.Object as BasePlayer)?.IsAdmin ?? p.IsAdmin))
8425 {
8426 p.Reply(Backbone.GetMessage("ReloadConfig", p.Id));
8427 LoadConfig();
8428
8429 if (maintainCoroutine != null)
8430 {
8431 StopMaintainCoroutine();
8432 p.Reply(Backbone.GetMessage("ReloadMaintainCo", p.Id));
8433 }
8434
8435 if (scheduleCoroutine != null)
8436 {
8437 StopScheduleCoroutine();
8438 p.Reply(Backbone.GetMessage("ReloadScheduleCo", p.Id));
8439 }
8440
8441 p.Reply(Backbone.GetMessage("ReloadInit", p.Id));
8442
8443 LoadData();
8444 Reinitialize();
8445 BlockZoneManagerZones();
8446 LoadSpawns();
8447 SetupGrid();
8448 }
8449 }
8450
8451 private void CommandBuyRaid(IPlayer p, string command, string[] args)
8452 {
8453 if (args.Length == 0)
8454 {
8455 p.Reply(Backbone.GetMessage("BuySyntax", p.Id, _config.Settings.BuyCommand, p.IsServer ? "ID" : p.Id));
8456 return;
8457 }
8458
8459 if (CopyPaste == null || !CopyPaste.IsLoaded)
8460 {
8461 p.Reply(Backbone.GetMessage("LoadCopyPaste", p.Id));
8462 return;
8463 }
8464
8465 if (IsGridLoading)
8466 {
8467 p.Reply(Backbone.GetMessage("GridIsLoading", p.Id));
8468 return;
8469 }
8470
8471 if (RaidableBase.Get(RaidableType.Purchased) >= _config.Settings.Buyable.Max)
8472 {
8473 p.Reply(Backbone.GetMessage("Max Manual Events", p.Id, _config.Settings.Buyable.Max));
8474 return;
8475 }
8476
8477 string value = args[0].ToLower();
8478 RaidableMode mode = IsEasy(value) ? RaidableMode.Easy : IsMedium(value) ? RaidableMode.Medium : IsHard(value) ? RaidableMode.Hard : IsExpert(value) ? RaidableMode.Expert : IsNightmare(value) ? RaidableMode.Nightmare : RaidableMode.Random;
8479
8480 if (!CanSpawnDifficultyToday(mode))
8481 {
8482 p.Reply(Backbone.GetMessage("BuyDifficultyNotAvailableToday", p.Id, value));
8483 return;
8484 }
8485
8486 if (!IsDifficultyAvailable(mode, false))
8487 {
8488 p.Reply(Backbone.GetMessage("BuyAnotherDifficulty", p.Id, value));
8489 return;
8490 }
8491
8492 if (!IsDifficultyAvailable(mode, true))
8493 {
8494 p.Reply(Backbone.GetMessage("BuyPVPRaidsDisabled", p.Id));
8495 return;
8496 }
8497
8498 if (!IsPasteAvailable)
8499 {
8500 p.Reply(Backbone.GetMessage("PasteOnCooldown", p.Id));
8501 return;
8502 }
8503
8504 var player = p.Object as BasePlayer;
8505
8506 if (args.Length > 1 && args[1].IsSteamId())
8507 {
8508 ulong playerId;
8509 if (ulong.TryParse(args[1], out playerId))
8510 {
8511 player = BasePlayer.FindByID(playerId);
8512 }
8513 }
8514
8515 if (!IsValid(player))
8516 {
8517 p.Reply(args.Length > 1 ? Backbone.GetMessage("TargetNotFoundId", p.Id, args[1]) : Backbone.GetMessage("TargetNotFoundNoId", p.Id));
8518 return;
8519 }
8520
8521 var buyer = p.Object as BasePlayer ?? player;
8522 string id = buyer.UserIDString;
8523
8524 if (tryBuyCooldowns.Contains(id))
8525 {
8526 p.Reply(Backbone.GetMessage("BuyableAlreadyRequested", p.Id));
8527 return;
8528 }
8529
8530 if (ServerMgr.Instance.Restarting)
8531 {
8532 p.Reply(Backbone.GetMessage("BuyableServerRestarting", p.Id));
8533 return;
8534 }
8535
8536 if (SaveRestore.IsSaving)
8537 {
8538 p.Reply(Backbone.GetMessage("BuyableServerSaving", p.Id));
8539 return;
8540 }
8541
8542 if (RaidableBase.Any(player))
8543 {
8544 p.Reply(Backbone.GetMessage("BuyableAlreadyOwner", p.Id));
8545 return;
8546 }
8547
8548 tryBuyCooldowns.Add(id);
8549 timer.Once(2f, () => tryBuyCooldowns.Remove(id));
8550
8551 float cooldown;
8552 if (buyCooldowns.TryGetValue(id, out cooldown))
8553 {
8554 Backbone.Message(buyer, "BuyCooldown", id, cooldown - Time.realtimeSinceStartup);
8555 return;
8556 }
8557
8558 bool flag = TryBuyRaidServerRewards(buyer, player, mode) || TryBuyRaidEconomics(buyer, player, mode);
8559
8560 if (flag && _config.Settings.Buyable.Cooldown > 0)
8561 {
8562 buyCooldowns.Add(id, Time.realtimeSinceStartup + _config.Settings.Buyable.Cooldown);
8563 timer.Once(_config.Settings.Buyable.Cooldown, () => buyCooldowns.Remove(id));
8564 }
8565 }
8566
8567 private void CommandRaidHunter(IPlayer p, string command, string[] args)
8568 {
8569 var player = p.Object as BasePlayer;
8570 bool isAdmin = p.IsServer || player.IsAdmin;
8571 string arg = args.Length >= 1 ? args[0].ToLower() : string.Empty;
8572
8573 switch (arg)
8574 {
8575 case "resettotal":
8576 {
8577 if (isAdmin)
8578 {
8579 foreach (var entry in storedData.Players)
8580 {
8581 entry.Value.TotalRaids = 0;
8582 }
8583
8584 SaveData();
8585 }
8586
8587 return;
8588 }
8589 case "resettime":
8590 {
8591 if (isAdmin)
8592 {
8593 storedData.RaidTime = DateTime.MinValue.ToString();
8594 SaveData();
8595 }
8596
8597 return;
8598 }
8599 case "wipe":
8600 {
8601 if (isAdmin)
8602 {
8603 wiped = true;
8604 CheckForWipe();
8605 }
8606
8607 return;
8608 }
8609 case "grid":
8610 {
8611 if (player.IsValid() && (isAdmin || permission.UserHasPermission(player.UserIDString, drawPermission)))
8612 {
8613 ShowGrid(player);
8614 }
8615
8616 return;
8617 }
8618 case "ui":
8619 {
8620 CommandUI(p, command, args.Skip(1).ToArray());
8621 return;
8622 }
8623 case "ladder":
8624 case "lifetime":
8625 {
8626 ShowLadder(p, args);
8627 return;
8628 }
8629 }
8630
8631 if (_config.UI.Enabled)
8632 {
8633 p.Reply(Backbone.GetMessage(_config.UI.Lockout.Enabled ? "UIHelpTextAll" : "UIHelpText", p.Id, command));
8634 }
8635
8636 if (_config.RankedLadder.Enabled)
8637 {
8638 p.Reply(Backbone.GetMessage("Wins", p.Id, storedData.Players.ContainsKey(p.Id) ? storedData.Players[p.Id].Raids : 0, _config.Settings.HunterCommand));
8639 }
8640
8641 if (Raids.Count == 0 && _config.Settings.Schedule.Enabled)
8642 {
8643 ShowNextScheduledEvent(p);
8644 return;
8645 }
8646
8647 if (!player.IsValid())
8648 {
8649 return;
8650 }
8651
8652 DrawRaidLocations(player, isAdmin || permission.UserHasPermission(player.UserIDString, drawPermission));
8653 }
8654
8655 protected void DrawRaidLocations(BasePlayer player, bool hasPerm)
8656 {
8657 if (!hasPerm)
8658 {
8659 foreach (var raid in Raids.Values)
8660 {
8661 if (InRange(raid.Location, player.transform.position, 100f))
8662 {
8663 Player.Message(player, string.Format("{0} @ {1} ({2}", raid.BaseName, raid.Location, PositionToGrid(raid.Location)));
8664 }
8665 }
8666
8667 return;
8668 }
8669
8670 bool isAdmin = player.IsAdmin;
8671
8672 try
8673 {
8674 if (!isAdmin)
8675 {
8676 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
8677 player.SendNetworkUpdateImmediate();
8678 }
8679
8680 foreach (var raid in Raids.Values)
8681 {
8682 int num = 0;
8683
8684 foreach (var t in BasePlayer.activePlayerList)
8685 {
8686 if (t.Distance(raid.Location) <= raid.Options.ProtectionRadius * 3f)
8687 {
8688 num++;
8689 }
8690 }
8691
8692 int distance = Mathf.CeilToInt(Vector3.Distance(player.transform.position, raid.Location));
8693 string message = string.Format(lang.GetMessage("RaidMessage", this, player.UserIDString), distance, num);
8694 string flag = Backbone.GetMessage(raid.AllowPVP ? "PVPFlag" : "PVEFlag", player.UserIDString);
8695
8696 player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, raid.Location, string.Format("{0} : {1}{2} {3}", raid.BaseName, flag, raid.Mode(), message));
8697
8698 foreach (var target in raid.friends)
8699 {
8700 if (!target.IsValid())
8701 {
8702 continue;
8703 }
8704
8705 player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, target.transform.position, "Ally");
8706 }
8707
8708 if (raid.owner.IsValid())
8709 {
8710 player.SendConsoleCommand("ddraw.text", 15f, Color.yellow, raid.owner.transform.position, "Owner");
8711 }
8712 }
8713 }
8714 catch (Exception ex)
8715 {
8716 Puts(ex.StackTrace);
8717 Puts(ex.Message);
8718 }
8719 finally
8720 {
8721 if (!isAdmin)
8722 {
8723 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
8724 player.SendNetworkUpdateImmediate();
8725 }
8726 }
8727 }
8728
8729 protected void ShowNextScheduledEvent(IPlayer p)
8730 {
8731 string message;
8732 double time = GetRaidTime();
8733
8734 if (BasePlayer.activePlayerList.Count < _config.Settings.Schedule.PlayerLimit)
8735 {
8736 message = Backbone.GetMessage("Not Enough Online", p.Id, _config.Settings.Schedule.PlayerLimit);
8737 }
8738 else message = FormatTime(time);
8739
8740 p.Reply(Backbone.GetMessage("Next", p.Id, message));
8741 }
8742
8743 protected void ShowLadder(IPlayer p, string[] args)
8744 {
8745 if (!_config.RankedLadder.Enabled)
8746 {
8747 return;
8748 }
8749
8750 if (storedData.Players.Count == 0)
8751 {
8752 p.Reply(Backbone.GetMessage("Ladder Insufficient Players", p.Id));
8753 return;
8754 }
8755
8756 if (args.Length == 2 && args[1].ToLower() == "resetme" && storedData.Players.ContainsKey(p.Id))
8757 {
8758 storedData.Players[p.Id].Raids = 0;
8759 return;
8760 }
8761
8762 string key = args[0].ToLower();
8763 var ladder = GetLadder(key);
8764 int rank = 0;
8765
8766 ladder.Sort((x, y) => y.Value.CompareTo(x.Value));
8767
8768 p.Reply(Backbone.GetMessage(key == "ladder" ? "Ladder" : "Ladder Total", p.Id));
8769
8770 foreach (var kvp in ladder)
8771 {
8772 if (++rank >= 10)
8773 {
8774 break;
8775 }
8776
8777 NotifyPlayer(p, rank, kvp);
8778 }
8779
8780 ladder.Clear();
8781 }
8782
8783 protected void ShowGrid(BasePlayer player)
8784 {
8785 bool isAdmin = player.IsAdmin;
8786
8787 try
8788 {
8789 if (!isAdmin)
8790 {
8791 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
8792 player.SendNetworkUpdateImmediate();
8793 }
8794
8795 foreach (var rsl in raidSpawns[RaidableType.Grid].All)
8796 {
8797 if (InRange(rsl.Location, player.transform.position, 1000f))
8798 {
8799 player.SendConsoleCommand("ddraw.text", 30f, Color.green, rsl.Location, "X");
8800 }
8801 }
8802
8803 foreach (var monument in monuments)
8804 {
8805 string text = monument.Key.displayPhrase.translated;
8806
8807 if (string.IsNullOrWhiteSpace(text))
8808 {
8809 text = GetMonumentName(monument);
8810 }
8811
8812 player.SendConsoleCommand("ddraw.sphere", 30f, Color.blue, monument.Key.transform.position, monument.Value);
8813 player.SendConsoleCommand("ddraw.text", 30f, Color.cyan, monument.Key.transform.position, text);
8814 }
8815 }
8816 catch (Exception ex)
8817 {
8818 Puts(ex.StackTrace);
8819 Puts(ex.Message);
8820 }
8821 finally
8822 {
8823 if (!isAdmin)
8824 {
8825 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
8826 player.SendNetworkUpdateImmediate();
8827 }
8828 }
8829 }
8830
8831 protected List<KeyValuePair<string, int>> GetLadder(string arg)
8832 {
8833 var ladder = new List<KeyValuePair<string, int>>();
8834 bool isLadder = arg.ToLower() == "ladder";
8835
8836 foreach (var entry in storedData.Players)
8837 {
8838 int value = isLadder ? entry.Value.Raids : entry.Value.TotalRaids;
8839
8840 if (value > 0)
8841 {
8842 ladder.Add(new KeyValuePair<string, int>(entry.Key, value));
8843 }
8844 }
8845
8846 return ladder;
8847 }
8848
8849 private void NotifyPlayer(IPlayer p, int rank, KeyValuePair<string, int> kvp)
8850 {
8851 string name = covalence.Players.FindPlayerById(kvp.Key)?.Name ?? kvp.Key;
8852 string value = kvp.Value.ToString("N0");
8853 string message = lang.GetMessage("NotifyPlayerMessageFormat", this, p.Id);
8854
8855 message = message.Replace("{rank}", rank.ToString());
8856 message = message.Replace("{name}", name);
8857 message = message.Replace("{value}", value);
8858
8859 p.Reply(message);
8860 }
8861
8862 protected string GetMonumentName(KeyValuePair<MonumentInfo, float> monument)
8863 {
8864 string text;
8865 if (monument.Key.name.Contains("Oilrig")) text = "Oil Rig";
8866 else if (monument.Key.name.Contains("cave")) text = "Cave";
8867 else if (monument.Key.name.Contains("power_sub")) text = "Power Sub Station";
8868 else text = "Unknown Monument";
8869 return text;
8870 }
8871
8872 private void CommandRaidBase(IPlayer p, string command, string[] args)
8873 {
8874 var player = p.Object as BasePlayer;
8875 bool isAllowed = p.IsServer || player.IsAdmin || p.HasPermission(adminPermission);
8876
8877 if (!CanCommandContinue(player, p, args, isAllowed))
8878 {
8879 return;
8880 }
8881
8882 if (command == _config.Settings.EventCommand) // rbe
8883 {
8884 ProcessEventCommand(player, p, args, isAllowed);
8885 }
8886 else if (command == _config.Settings.ConsoleCommand) // rbevent
8887 {
8888 ProcessConsoleCommand(p, args, isAllowed);
8889 }
8890 }
8891
8892 protected void ProcessEventCommand(BasePlayer player, IPlayer p, string[] args, bool isAllowed)
8893 {
8894 if (!isAllowed || !player.IsValid())
8895 {
8896 return;
8897 }
8898
8899 RaidableMode mode = RaidableMode.Random;
8900 string baseName = null;
8901
8902 if (args.Length > 0)
8903 {
8904 for (int i = 0; i < args.Length; i++)
8905 {
8906 string value = args[i].ToLower();
8907
8908 if (IsEasy(value)) mode = RaidableMode.Easy;
8909 else if (IsMedium(value)) mode = RaidableMode.Medium;
8910 else if (IsHard(value)) mode = RaidableMode.Hard;
8911 else if (IsExpert(value)) mode = RaidableMode.Expert;
8912 else if (IsNightmare(value)) mode = RaidableMode.Nightmare;
8913 else if ((string.IsNullOrEmpty(baseName) || !FileExists(baseName)) && FileExists(args[i])) baseName = args[i];
8914 }
8915 }
8916
8917 var building = GetBuilding(RaidableType.Manual, mode, baseName);
8918
8919 if (IsBuildingValid(building))
8920 {
8921 RaycastHit hit;
8922 int layers = Layers.Mask.Construction | Layers.Mask.Default | Layers.Mask.Deployed | Layers.Mask.Tree | Layers.Mask.Terrain | Layers.Mask.Water | Layers.Mask.World;
8923 if (Physics.Raycast(player.eyes.HeadRay(), out hit, isAllowed ? Mathf.Infinity : 100f, layers, QueryTriggerInteraction.Ignore))
8924 {
8925 var position = hit.point;
8926 var safe = IsAreaSafe(ref position, Radius * 2f, Layers.Mask.Player_Server | Layers.Mask.Construction | Layers.Mask.Deployed, RaidableType.Manual, building.Value.ArenaWalls.Radius);
8927
8928 if (!safe && !player.IsFlying && InRange(player.transform.position, position, 50f, false))
8929 {
8930 p.Reply(Backbone.GetMessage("PasteIsBlockedStandAway", p.Id));
8931 return;
8932 }
8933
8934 if (safe && (isAllowed || !IsMonumentPosition(hit.point)))
8935 {
8936 PasteBuilding(RaidableType.Manual, hit.point, building, null);
8937 if (player.IsAdmin) player.SendConsoleCommand("ddraw.text", 10f, Color.red, hit.point, "XXX");
8938 }
8939 else p.Reply(Backbone.GetMessage("PasteIsBlocked", p.Id));
8940 }
8941 else p.Reply(Backbone.GetMessage("LookElsewhere", p.Id));
8942 }
8943 else
8944 {
8945 if (!string.IsNullOrEmpty(baseName))
8946 {
8947 p.Reply(Backbone.GetMessage(FileExists(baseName) ? "BuildingIsNotConfigured" : "FileDoesNotExist", p.Id));
8948 }
8949 else p.Reply(Backbone.GetMessage("NoValidBuildingsConfigured", p.Id));
8950 }
8951 }
8952
8953 protected void ProcessConsoleCommand(IPlayer p, string[] args, bool isAdmin)
8954 {
8955 if (IsGridLoading)
8956 {
8957 p.Reply(GridIsLoadingMessage);
8958 return;
8959 }
8960
8961 RaidableMode mode = RaidableMode.Random;
8962 string baseName = null;
8963
8964 if (args.Length > 0)
8965 {
8966 for (int i = 0; i < args.Length; i++)
8967 {
8968 string value = args[i].ToLower();
8969
8970 if (IsEasy(value)) mode = RaidableMode.Easy;
8971 else if (IsMedium(value)) mode = RaidableMode.Medium;
8972 else if (IsHard(value)) mode = RaidableMode.Hard;
8973 else if (IsExpert(value)) mode = RaidableMode.Expert;
8974 else if (IsNightmare(value)) mode = RaidableMode.Nightmare;
8975 else if ((string.IsNullOrEmpty(baseName) || !FileExists(baseName)) && FileExists(args[i])) baseName = args[i];
8976 }
8977 }
8978
8979 string message;
8980 var position = SpawnRandomBase(out message, RaidableType.Manual, mode, baseName, isAdmin);
8981
8982 if (position == Vector3.zero)
8983 {
8984 if (!string.IsNullOrEmpty(baseName))
8985 {
8986 p.Reply(Backbone.GetMessage(FileExists(baseName) ? "BuildingIsNotConfigured" : "FileDoesNotExist", p.Id));
8987 }
8988 else p.Reply(message);
8989 }
8990 else if (isAdmin && p.IsConnected)
8991 {
8992 p.Teleport(position.x, position.y, position.z);
8993 }
8994 }
8995
8996 private bool CanCommandContinue(BasePlayer player, IPlayer p, string[] args, bool isAllowed)
8997 {
8998 if (HandledCommandArguments(player, p, isAllowed, args))
8999 {
9000 return false;
9001 }
9002
9003 if (CopyPaste == null || !CopyPaste.IsLoaded)
9004 {
9005 p.Reply(Backbone.GetMessage("LoadCopyPaste", p.Id));
9006 return false;
9007 }
9008
9009 if (!isAllowed && RaidableBase.Get(RaidableType.Manual) >= _config.Settings.Manual.Max)
9010 {
9011 p.Reply(Backbone.GetMessage("Max Manual Events", p.Id, _config.Settings.Manual.Max));
9012 return false;
9013 }
9014
9015 if (!IsPasteAvailable)
9016 {
9017 p.Reply(Backbone.GetMessage("PasteOnCooldown", p.Id));
9018 return false;
9019 }
9020
9021 if (IsSpawnOnCooldown())
9022 {
9023 p.Reply(Backbone.GetMessage("SpawnOnCooldown", p.Id));
9024 return false;
9025 }
9026
9027 if (!isAllowed && BaseNetworkable.serverEntities.Count > 300000)
9028 {
9029 p.Reply(lang.GetMessage("EntityCountMax", this, p.Id));
9030 return false;
9031 }
9032
9033 return true;
9034 }
9035
9036 private bool HandledCommandArguments(BasePlayer player, IPlayer p, bool isAllowed, string[] args)
9037 {
9038 if (args.Length == 0)
9039 {
9040 return false;
9041 }
9042
9043 if (player.IsValid())
9044 {
9045 if (!permission.UserHasPermission(player.UserIDString, drawPermission) && !isAllowed)
9046 {
9047 return false;
9048 }
9049
9050 if (args[0].ToLower() == "draw")
9051 {
9052 bool isAdmin = player.IsAdmin;
9053
9054 try
9055 {
9056 if (!isAdmin)
9057 {
9058 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
9059 player.SendNetworkUpdateImmediate();
9060 }
9061
9062 foreach (var raid in Raids.Values)
9063 {
9064 player.SendConsoleCommand("ddraw.sphere", 30f, Color.blue, raid.Location, raid.Options.ProtectionRadius);
9065 }
9066 }
9067 finally
9068 {
9069 if (!isAdmin)
9070 {
9071 player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
9072 player.SendNetworkUpdateImmediate();
9073 }
9074 }
9075
9076 return true;
9077 }
9078 }
9079
9080 if (!isAllowed)
9081 {
9082 return false;
9083 }
9084
9085 switch (args[0].ToLower())
9086 {
9087 case "type":
9088 {
9089 List<string> list;
9090
9091 foreach (RaidableType type in Enum.GetValues(typeof(RaidableType)))
9092 {
9093 list = new List<string>();
9094
9095 foreach (var raid in Raids.Values)
9096 {
9097 if (raid.Type != type)
9098 {
9099 continue;
9100 }
9101
9102 list.Add(PositionToGrid(raid.Location));
9103 }
9104
9105 p.Reply("{0} : {1} @ {2}", type.ToString(), RaidableBase.Get(type), string.Join(", ", list.ToArray()));
9106 }
9107
9108 return true;
9109 }
9110 case "resetmarkers":
9111 {
9112 RemoveAllThirdPartyMarkers();
9113
9114 foreach (var raid in Raids.Values)
9115 {
9116 raid.RemoveMapMarkers();
9117 raid.UpdateMarker();
9118 }
9119
9120 return true;
9121 }
9122 case "despawn":
9123 {
9124 if (IsValid(player))
9125 {
9126 bool success = DespawnBase(player.transform.position);
9127 Backbone.Message(player, success ? "DespawnBaseSuccess" : "DespawnBaseNoneAvailable", player.UserIDString);
9128 if (success) Puts(Backbone.GetMessage("DespawnedAt", null, player.displayName, FormatGridReference(player.transform.position)));
9129 return true;
9130 }
9131
9132 break;
9133 }
9134 case "despawnall":
9135 {
9136 if (Raids.Count > 0)
9137 {
9138 DespawnAllBasesNow();
9139 Puts(Backbone.GetMessage("DespawnedAll", null, player?.displayName ?? p.Id));
9140 }
9141
9142 return true;
9143 }
9144 case "expire":
9145 case "resetcooldown":
9146 {
9147 if (args.Length >= 2)
9148 {
9149 var target = RustCore.FindPlayer(args[1]);
9150
9151 if (target.IsValid())
9152 {
9153 storedData.Lockouts.Remove(target.UserIDString);
9154 SaveData();
9155 p.Reply(Backbone.GetMessage("RemovedLockFor", p.Id, target.displayName, target.UserIDString));
9156 }
9157 }
9158
9159 return true;
9160 }
9161 case "expireall":
9162 {
9163 storedData.Lockouts.Clear();
9164 SaveData();
9165 return true;
9166 }
9167 case "setowner":
9168 case "lockraid":
9169 {
9170 if (args.Length >= 2)
9171 {
9172 var target = RustCore.FindPlayer(args[1]);
9173
9174 if (IsValid(target))
9175 {
9176 var raid = GetNearestBase(target.transform.position);
9177
9178 if (raid == null)
9179 {
9180 p.Reply(Backbone.GetMessage("TargetTooFar", p.Id));
9181 }
9182 else
9183 {
9184 raid.TrySetPayLock(target);
9185 p.Reply(Backbone.GetMessage("RaidLockedTo", p.Id, target.displayName));
9186 }
9187 }
9188 else p.Reply(Backbone.GetMessage("TargetNotFoundId", p.Id, args[1]));
9189 }
9190
9191 return true;
9192 }
9193 case "clearowner":
9194 {
9195 if (!player.IsValid()) return true;
9196
9197 var raid = GetNearestBase(player.transform.position);
9198
9199 if (raid == null)
9200 {
9201 p.Reply(Backbone.GetMessage("TooFar", p.Id));
9202 }
9203 else
9204 {
9205 raid.TrySetPayLock(null);
9206 p.Reply(Backbone.GetMessage("RaidOwnerCleared", p.Id));
9207 }
9208
9209 return true;
9210 }
9211 }
9212
9213 return false;
9214 }
9215
9216 private void CommandPopulate(IPlayer p, string command, string[] args)
9217 {
9218 if (args.Length == 0)
9219 {
9220 p.Reply("Valid arguments: easy medium hard expert nightmare loot all");
9221 p.Reply("Valid arguments: 0 1 2 3 4 loot all");
9222 return;
9223 }
9224
9225 string file = $"{Interface.Oxide.ConfigDirectory}/{Name}.backup.{DateTime.Now:yyyy-MM-dd hh-mm-ss}.json";
9226 Config.WriteObject(_config, false, file);
9227 Puts("Created config backup: {0}", file);
9228
9229 var list = new List<TreasureItem>();
9230
9231 foreach (var def in ItemManager.GetItemDefinitions())
9232 {
9233 list.Add(new TreasureItem
9234 {
9235 shortname = def.shortname,
9236 });
9237 }
9238
9239 list.Sort((x, y) => x.shortname.CompareTo(y.shortname));
9240
9241 foreach (var str in args)
9242 {
9243 string arg = str.ToLower();
9244
9245 if (IsEasy(arg) || arg == "all")
9246 {
9247 AddToList(_config.Treasure.LootEasy, list);
9248 p.Reply("Saved to `Loot (Easy Difficulty)`");
9249 }
9250 else if (IsMedium(arg) || arg == "all")
9251 {
9252 AddToList(_config.Treasure.LootMedium, list);
9253 p.Reply("Saved to `Loot (Medium Difficulty)`");
9254 }
9255 else if (IsHard(arg) || arg == "all")
9256 {
9257 AddToList(_config.Treasure.LootHard, list);
9258 p.Reply("Saved to `Loot (Hard Difficulty)`");
9259 }
9260 else if (IsExpert(arg) || arg == "all")
9261 {
9262 AddToList(_config.Treasure.LootExpert, list);
9263 p.Reply("Saved to `Loot (Expert Difficulty)`");
9264 }
9265 else if (IsNightmare(arg) || arg == "all")
9266 {
9267 AddToList(_config.Treasure.LootNightmare, list);
9268 p.Reply("Saved to `Loot (Nightmare Difficulty)`");
9269 }
9270 else if (arg == "loot" || arg == "all")
9271 {
9272 AddToList(_config.Treasure.Loot, list);
9273 p.Reply("Saved to `Loot`");
9274 }
9275 }
9276
9277 SaveConfig();
9278 }
9279
9280 private void AddToList(List<TreasureItem> a, List<TreasureItem> b)
9281 {
9282 foreach (var item in b)
9283 {
9284 if (!a.Contains(item, itemComparer))
9285 {
9286 a.Add(item);
9287 }
9288 }
9289 }
9290
9291 private void CommandConfig(IPlayer p, string command, string[] args)
9292 {
9293 if (!IsValid(args))
9294 {
9295 p.Reply(string.Format(lang.GetMessage("ConfigUseFormat", this, p.Id), string.Join("|", arguments.ToArray())));
9296 return;
9297 }
9298
9299 switch (args[0].ToLower())
9300 {
9301 case "add":
9302 {
9303 ConfigAddBase(p, args);
9304 return;
9305 }
9306 case "remove":
9307 {
9308 ConfigRemoveBase(p, args);
9309 return;
9310 }
9311 case "list":
9312 {
9313 ConfigListBases(p);
9314 return;
9315 }
9316 }
9317 }
9318
9319 #endregion Commands
9320
9321 #region Helpers
9322
9323 private static bool IsPVE() => Backbone.Plugin.NextGenPVE != null || Backbone.Plugin.TruePVE != null;
9324
9325 private static bool IsEasy(string value) => value == "0" || value == "easy" || value == Backbone.Easy;
9326
9327 private static bool IsMedium(string value) => value == "1" || value == "med" || value == "medium" || value == Backbone.Medium;
9328
9329 private static bool IsHard(string value) => value == "2" || value == "hard" || value == Backbone.Hard;
9330
9331 private static bool IsExpert(string value) => value == "3" || value == "expert" || value == Backbone.Expert;
9332
9333 private static bool IsNightmare(string value) => value == "4" || value == "nm" || value == "nightmare" || value == Backbone.Nightmare;
9334
9335 private void UpdateUI()
9336 {
9337 if (!_config.UI.Enabled)
9338 {
9339 return;
9340 }
9341
9342 foreach (var player in BasePlayer.activePlayerList)
9343 {
9344 UI.Update(player);
9345 }
9346 }
9347
9348 private bool IsInvisible(BasePlayer player)
9349 {
9350 if (!player || Vanish == null || !Vanish.IsLoaded)
9351 {
9352 return false;
9353 }
9354
9355 var success = Vanish?.Call("IsInvisible", player);
9356
9357 return success is bool ? (bool)success : false;
9358 }
9359
9360 private static void NullifyDamage(HitInfo hitInfo, BaseCombatEntity entity = null)
9361 {
9362 if (hitInfo == null)
9363 {
9364 return;
9365 }
9366
9367 if (entity.IsValid())
9368 {
9369 var total = hitInfo.damageTypes?.Total() ?? entity.MaxHealth();
9370
9371 entity.Invoke(() =>
9372 {
9373 if (!entity.IsDestroyed)
9374 {
9375 entity.Heal(total);
9376 }
9377 }, 1f);
9378 }
9379
9380 hitInfo.damageTypes = new DamageTypeList();
9381 hitInfo.DidHit = false;
9382 hitInfo.DoHitEffects = false;
9383 hitInfo.HitEntity = null;
9384 }
9385
9386 private bool ShouldHandleEntity(BaseEntity entity)
9387 {
9388 return entity is BuildingBlock || entity is SimpleBuildingBlock || entity.name.Contains("assets/prefabs/deployable/");
9389 }
9390
9391 public static bool MustExclude(RaidableType type, bool allowPVP)
9392 {
9393 if (!_config.Settings.Maintained.IncludePVE && type == RaidableType.Maintained && !allowPVP)
9394 {
9395 return true;
9396 }
9397
9398 if (!_config.Settings.Maintained.IncludePVP && type == RaidableType.Maintained && allowPVP)
9399 {
9400 return true;
9401 }
9402
9403 if (!_config.Settings.Schedule.IncludePVE && type == RaidableType.Scheduled && !allowPVP)
9404 {
9405 return true;
9406 }
9407
9408 if (!_config.Settings.Schedule.IncludePVP && type == RaidableType.Scheduled && allowPVP)
9409 {
9410 return true;
9411 }
9412
9413 return false;
9414 }
9415
9416 private static List<BasePlayer> GetMountedPlayers(BaseMountable m)
9417 {
9418 var players = new List<BasePlayer>();
9419
9420 if (m.GetMounted())
9421 {
9422 players.Add(m.GetMounted());
9423 }
9424
9425 if (m is BaseVehicle)
9426 {
9427 var vehicle = m as BaseVehicle;
9428 BasePlayer player;
9429
9430 foreach (var mp in vehicle.mountPoints)
9431 {
9432 if (mp.mountable.IsValid())
9433 {
9434 player = mp.mountable.GetMounted();
9435
9436 if (player.IsValid() && !players.Contains(player))
9437 {
9438 players.Add(player);
9439 }
9440 }
9441 }
9442 }
9443
9444 return players;
9445 }
9446
9447 private bool AnyNpcs()
9448 {
9449 foreach (var x in Raids.Values)
9450 {
9451 if (x.npcs.Count > 0)
9452 {
9453 return true;
9454 }
9455 }
9456
9457 return false;
9458 }
9459
9460 private void DestroyComponents()
9461 {
9462 foreach (var raid in Raids.Values)
9463 {
9464 raid.DestroyFire();
9465 raid.DestroyInputs();
9466 }
9467 }
9468
9469 private string GridIsLoadingMessage
9470 {
9471 get
9472 {
9473 int count = raidSpawns.ContainsKey(RaidableType.Grid) ? raidSpawns[RaidableType.Grid].Count : 0;
9474 return Backbone.GetMessage("GridIsLoadingFormatted", null, (Time.realtimeSinceStartup - gridTime).ToString("N02"), count);
9475 }
9476 }
9477
9478 private void ConfigAddBase(IPlayer p, string[] args)
9479 {
9480 if (args.Length < 2)
9481 {
9482 p.Reply(lang.GetMessage("ConfigAddBaseSyntax", this, p.Id));
9483 return;
9484 }
9485
9486 _sb.Length = 0;
9487 var values = new List<string>(args);
9488 values.RemoveAt(0);
9489 string value = values[0];
9490 RaidableMode mode = RaidableMode.Random;
9491
9492 if (args.Length > 2)
9493 {
9494 foreach (string s in values)
9495 {
9496 string str = s.ToLower();
9497
9498 if (IsEasy(str))
9499 {
9500 mode = RaidableMode.Easy;
9501 values.Remove(s);
9502 break;
9503 }
9504 else if (IsMedium(str))
9505 {
9506 mode = RaidableMode.Medium;
9507 values.Remove(s);
9508 break;
9509 }
9510 else if (IsHard(str))
9511 {
9512 mode = RaidableMode.Hard;
9513 values.Remove(s);
9514 break;
9515 }
9516 else if (IsExpert(str))
9517 {
9518 mode = RaidableMode.Expert;
9519 values.Remove(s);
9520 break;
9521 }
9522 else if (IsNightmare(str))
9523 {
9524 mode = RaidableMode.Nightmare;
9525 values.Remove(s);
9526 break;
9527 }
9528 }
9529 }
9530
9531 p.Reply(string.Format(lang.GetMessage("Adding", this, p.Id), string.Join(" ", values.ToArray())));
9532
9533 BuildingOptions building;
9534 if (!_config.RaidableBases.Buildings.TryGetValue(value, out building))
9535 {
9536 _config.RaidableBases.Buildings[value] = building = new BuildingOptions();
9537 _sb.AppendLine(string.Format(lang.GetMessage("AddedPrimaryBase", this, p.Id), value));
9538 building.AdditionalBases.Clear();
9539 }
9540
9541 if (IsModeValid(mode) && building.Mode != mode)
9542 {
9543 building.Mode = mode;
9544 _sb.AppendLine(string.Format(lang.GetMessage("DifficultySetTo", this, p.Id), (int)mode));
9545 }
9546
9547 if (args.Length >= 3)
9548 {
9549 values.RemoveAt(0);
9550
9551 foreach (string ab in values)
9552 {
9553 if (!building.AdditionalBases.ContainsKey(ab))
9554 {
9555 building.AdditionalBases.Add(ab, DefaultPasteOptions);
9556 _sb.AppendLine(string.Format(lang.GetMessage("AddedAdditionalBase", this, p.Id), ab));
9557 }
9558 }
9559 }
9560
9561 if (_sb.Length > 0)
9562 {
9563 p.Reply(_sb.ToString());
9564 _sb.Length = 0;
9565 SaveConfig();
9566 }
9567 else p.Reply(lang.GetMessage("EntryAlreadyExists", this, p.Id));
9568
9569 values.Clear();
9570 }
9571
9572 private void ConfigRemoveBase(IPlayer p, string[] args)
9573 {
9574 if (args.Length < 2)
9575 {
9576 p.Reply(lang.GetMessage("RemoveSyntax", this, p.Id));
9577 return;
9578 }
9579
9580 int num = 0;
9581 var dict = new Dictionary<string, BuildingOptions>(_config.RaidableBases.Buildings);
9582 var array = args.Skip(1).ToArray();
9583
9584 _sb.Length = 0;
9585 _sb.AppendLine(string.Format(lang.GetMessage("RemovingAllBasesFor", this, p.Id), string.Join(" ", array)));
9586
9587 foreach (var entry in dict)
9588 {
9589 var list = new List<KeyValuePair<string, List<PasteOption>>>(entry.Value.AdditionalBases);
9590
9591 foreach (string value in array)
9592 {
9593 foreach (var ab in list)
9594 {
9595 if (ab.Key == value || entry.Key == value)
9596 {
9597 _sb.AppendLine(string.Format(lang.GetMessage("RemovedAdditionalBase", this, p.Id), ab.Key, entry.Key));
9598 entry.Value.AdditionalBases.Remove(ab.Key);
9599 num++;
9600 }
9601 }
9602
9603 if (entry.Key == value)
9604 {
9605 _sb.AppendLine(string.Format(lang.GetMessage("RemovedPrimaryBase", this, p.Id), value));
9606 _config.RaidableBases.Buildings.Remove(entry.Key);
9607 num++;
9608 }
9609 }
9610
9611 list.Clear();
9612 }
9613
9614 _sb.AppendLine(string.Format(lang.GetMessage("RemovedEntries", this, p.Id), num));
9615 p.Reply(_sb.ToString());
9616 dict.Clear();
9617 SaveConfig();
9618 _sb.Length = 0;
9619 }
9620
9621 private void ConfigListBases(IPlayer p)
9622 {
9623 _sb.Length = 0;
9624 _sb.Append(lang.GetMessage("ListingAll", this, p.Id));
9625 _sb.AppendLine();
9626
9627 bool buyable = false;
9628 bool validBase = false;
9629
9630 foreach (var entry in _config.RaidableBases.Buildings)
9631 {
9632 if (!entry.Value.AllowPVP)
9633 {
9634 buyable = true;
9635 }
9636
9637 _sb.AppendLine(lang.GetMessage("PrimaryBase", this, p.Id));
9638
9639 if (FileExists(entry.Key))
9640 {
9641 _sb.AppendLine(entry.Key);
9642 validBase = true;
9643 }
9644 else _sb.Append(entry.Key).Append(lang.GetMessage("FileDoesNotExist", this, p.Id));
9645
9646 if (entry.Value.AdditionalBases.Count > 0)
9647 {
9648 _sb.AppendLine(lang.GetMessage("AdditionalBase", this, p.Id));
9649
9650 foreach (var ab in entry.Value.AdditionalBases)
9651 {
9652 if (FileExists(ab.Key))
9653 {
9654 _sb.AppendLine(ab.Key);
9655 validBase = true;
9656 }
9657 else _sb.Append(ab.Key).Append((lang.GetMessage("FileDoesNotExist", this, p.Id)));
9658 }
9659 }
9660 }
9661
9662 if (!buyable && !_config.Settings.Buyable.BuyPVP)
9663 {
9664 _sb.AppendLine(lang.GetMessage("RaidPVEWarning", this, p.Id));
9665 }
9666
9667 if (!validBase)
9668 {
9669 _sb.AppendLine(lang.GetMessage("NoValidBuildingsWarning", this, p.Id));
9670 }
9671
9672 p.Reply(_sb.ToString());
9673 _sb.Length = 0;
9674 }
9675
9676 private readonly List<string> arguments = new List<string>
9677 {
9678 "add", "remove", "list"
9679 };
9680
9681 private static bool IsValid(BaseEntity e)
9682 {
9683 if (e == null || e.net == null || e.IsDestroyed || e.transform == null)
9684 {
9685 return false;
9686 }
9687
9688 return true;
9689 }
9690
9691 private bool IsValid(Item item)
9692 {
9693 if (item == null || !item.IsValid() || item.isBroken)
9694 {
9695 return false;
9696 }
9697
9698 return true;
9699 }
9700
9701 private bool IsValid(string[] args)
9702 {
9703 return args.Length > 0 && arguments.Contains(args[0]);
9704 }
9705
9706 private void DropOrRemoveItems(StorageContainer container, bool isOpened = true)
9707 {
9708 if (!isOpened || RaidableBase.IsProtectedWeapon(container) || (container is BuildingPrivlidge && !_config.Settings.Management.AllowCupboardLoot))
9709 {
9710 container.inventory.Clear();
9711 }
9712 else
9713 {
9714 var dropPos = container.WorldSpaceBounds().ToBounds().center;
9715
9716 RaycastHit hit;
9717 if (Physics.Raycast(container.transform.position, Vector3.up, out hit, 5f, Layers.Mask.World | Layers.Mask.Construction, QueryTriggerInteraction.Ignore))
9718 {
9719 dropPos.y = hit.point.y - 0.3f;
9720 }
9721
9722 container.inventory.Drop(StringPool.Get(545786656), dropPos, container.transform.rotation);
9723 }
9724
9725 container.Invoke(container.KillMessage, 0.1f);
9726 }
9727
9728 private bool IsSpawnOnCooldown()
9729 {
9730 if (Time.realtimeSinceStartup - lastSpawnRequestTime < 2f)
9731 {
9732 return true;
9733 }
9734
9735 lastSpawnRequestTime = Time.realtimeSinceStartup;
9736 return false;
9737 }
9738
9739 private bool DespawnBase(Vector3 target)
9740 {
9741 var raid = GetNearestBase(target);
9742
9743 if (raid == null)
9744 {
9745 return false;
9746 }
9747
9748 raid.Despawn();
9749
9750 return true;
9751 }
9752
9753 private static RaidableBase GetNearestBase(Vector3 target)
9754 {
9755 var values = new List<RaidableBase>();
9756
9757 foreach (var x in Backbone.Plugin.Raids.Values)
9758 {
9759 if (InRange(x.Location, target, 100f))
9760 {
9761 values.Add(x);
9762 }
9763 }
9764
9765 int count = values.Count;
9766
9767 if (count == 0)
9768 {
9769 return null;
9770 }
9771
9772 if (count > 1)
9773 {
9774 values.Sort((a, b) => (a.Location - target).sqrMagnitude.CompareTo((b.Location - target).sqrMagnitude));
9775 }
9776
9777 return values[0];
9778 }
9779
9780 private void DespawnAllBasesNow()
9781 {
9782 if (!IsUnloading)
9783 {
9784 StartDespawnRoutine();
9785 return;
9786 }
9787
9788 if (Interface.Oxide.IsShuttingDown)
9789 {
9790 return;
9791 }
9792
9793 StartDespawnInvokes();
9794 DestroyAll();
9795 }
9796
9797 private void DestroyAll()
9798 {
9799 foreach (var raid in Raids.Values.ToList())
9800 {
9801 Interface.CallHook("OnRaidableBaseDespawn", raid.Location, raid.spawnTime, raid.ID);
9802 Puts(Backbone.GetMessage("Destroyed Raid", null, raid.Location));
9803 if (raid.IsOpened) raid.AwardRaiders();
9804 raid.Despawn();
9805 UnityEngine.Object.Destroy(raid.gameObject);
9806 }
9807 }
9808
9809 private void StartDespawnInvokes()
9810 {
9811 if (Bases.Count == 0)
9812 {
9813 return;
9814 }
9815
9816 float num = 0f;
9817
9818 foreach (var entry in Bases)
9819 {
9820 if (entry.Value == null || entry.Value.Count == 0)
9821 {
9822 continue;
9823 }
9824
9825 foreach (var e in entry.Value)
9826 {
9827 if (e != null && !e.IsDestroyed)
9828 {
9829 if (e is StorageContainer)
9830 {
9831 (e as StorageContainer).dropChance = 0f;
9832 }
9833 else if (e is ContainerIOEntity)
9834 {
9835 (e as ContainerIOEntity).dropChance = 0f;
9836 }
9837
9838 e.Invoke(() =>
9839 {
9840 if (!e.IsDestroyed)
9841 {
9842 e.KillMessage();
9843 }
9844 }, num += 0.002f);
9845 }
9846 }
9847 }
9848 }
9849
9850 private void StopDespawnCoroutine()
9851 {
9852 if (despawnCoroutine != null)
9853 {
9854 ServerMgr.Instance.StopCoroutine(despawnCoroutine);
9855 despawnCoroutine = null;
9856 }
9857 }
9858
9859 private void StartDespawnRoutine()
9860 {
9861 if (Raids.Count == 0)
9862 {
9863 return;
9864 }
9865
9866 if (despawnCoroutine != null)
9867 {
9868 timer.Once(0.1f, () => StartDespawnRoutine());
9869 return;
9870 }
9871
9872 despawnCoroutine = ServerMgr.Instance.StartCoroutine(DespawnCoroutine());
9873 }
9874
9875 private IEnumerator DespawnCoroutine()
9876 {
9877 while (Raids.Count > 0)
9878 {
9879 var raid = Raids.ElementAt(0).Value;
9880 var baseIndex = raid.BaseIndex;
9881 var uid = raid.uid;
9882
9883 raid.Despawn();
9884
9885 do
9886 {
9887 yield return Coroutines.WaitForSeconds(0.1f);
9888 } while (Bases.ContainsKey(baseIndex));
9889
9890 Raids.Remove(uid);
9891 yield return Coroutines.WaitForSeconds(0.1f);
9892 }
9893
9894 despawnCoroutine = null;
9895 }
9896
9897 private bool IsTrueDamage(BaseEntity entity)
9898 {
9899 if (!entity.IsValid())
9900 {
9901 return false;
9902 }
9903
9904 return entity.skinID == 1587601905 || Backbone.Path.TrueDamage.Contains(entity.prefabID) || RaidableBase.IsProtectedWeapon(entity) || entity is TeslaCoil || entity is FireBall || entity is BaseTrap;
9905 }
9906
9907 private bool EventTerritory(Vector3 position)
9908 {
9909 foreach (var raid in Raids.Values)
9910 {
9911 if (InRange(raid.Location, position, raid.Options.ProtectionRadius))
9912 {
9913 return true;
9914 }
9915 }
9916
9917 return false;
9918 }
9919
9920 private bool CanBlockOutsideDamage(RaidableBase raid, BasePlayer attacker, bool isEnabled)
9921 {
9922 if (isEnabled)
9923 {
9924 float radius = Mathf.Max(raid.Options.ProtectionRadius, raid.Options.ArenaWalls.Radius, Radius);
9925
9926 return (attacker.transform.position - raid.Location).sqrMagnitude > radius * radius;
9927 }
9928
9929 return false;
9930 }
9931
9932 private static bool InRange(Vector3 a, Vector3 b, float distance, bool ex = true)
9933 {
9934 if (!ex)
9935 {
9936 return (a - b).sqrMagnitude <= distance * distance;
9937 }
9938
9939 return (new Vector3(a.x, 0f, a.z) - new Vector3(b.x, 0f, b.z)).sqrMagnitude <= distance * distance;
9940 }
9941
9942 private void SetWorkshopIDs()
9943 {
9944 if (Rust.Workshop.Approved.All == null || Rust.Workshop.Approved.All.Count == 0)
9945 {
9946 timer.Once(1f, () => SetWorkshopIDs());
9947 return;
9948 }
9949
9950 List<ulong> skins;
9951 HashSet<ulong> workshopSkins;
9952
9953 foreach (var def in ItemManager.GetItemDefinitions())
9954 {
9955 skins = new List<ulong>();
9956
9957 foreach (var asi in Rust.Workshop.Approved.All)
9958 {
9959 if (asi.Value?.Name == def.shortname)
9960 {
9961 skins.Add(Convert.ToUInt64(asi.Value.WorkshopdId));
9962 }
9963 }
9964
9965 if (skins.Count == 0)
9966 {
9967 continue;
9968 }
9969
9970 if (!WorkshopSkins.TryGetValue(def.shortname, out workshopSkins))
9971 {
9972 WorkshopSkins[def.shortname] = workshopSkins = new HashSet<ulong>();
9973 }
9974
9975 foreach (ulong skin in skins)
9976 {
9977 workshopSkins.Add(skin);
9978 }
9979
9980 skins.Clear();
9981 }
9982 }
9983
9984 private bool AssignTreasureHunters()
9985 {
9986 foreach (var target in covalence.Players.All)
9987 {
9988 if (target == null || string.IsNullOrEmpty(target.Id))
9989 continue;
9990
9991 if (permission.UserHasPermission(target.Id, rankLadderPermission))
9992 permission.RevokeUserPermission(target.Id, rankLadderPermission);
9993
9994 if (permission.UserHasGroup(target.Id, rankLadderGroup))
9995 permission.RemoveUserGroup(target.Id, rankLadderGroup);
9996 }
9997
9998 if (!_config.RankedLadder.Enabled)
9999 return true;
10000
10001 var ladder = new List<KeyValuePair<string, int>>();
10002
10003 foreach (var entry in storedData.Players)
10004 {
10005 if (entry.Value.Raids > 0)
10006 {
10007 ladder.Add(new KeyValuePair<string, int>(entry.Key, entry.Value.Raids));
10008 }
10009 }
10010
10011 ladder.Sort((x, y) => y.Value.CompareTo(x.Value));
10012
10013 int permsGiven = 0;
10014 IPlayer p;
10015
10016 for (int i = 0; i < ladder.Count; i++)
10017 {
10018 p = covalence.Players.FindPlayerById(ladder[i].Key);
10019
10020 if (p == null || p.IsBanned || p.IsAdmin)
10021 continue;
10022
10023 permission.GrantUserPermission(p.Id, rankLadderPermission, this);
10024 permission.AddUserGroup(p.Id, rankLadderGroup);
10025
10026 LogToFile("treasurehunters", DateTime.Now.ToString() + " : " + Backbone.GetMessage("Log Stolen", null, p.Name, p.Id, ladder[i].Value), this, true);
10027 Puts(Backbone.GetMessage("Log Granted", null, p.Name, p.Id, rankLadderPermission, rankLadderGroup));
10028
10029 if (++permsGiven >= _config.RankedLadder.Amount)
10030 break;
10031 }
10032
10033 if (permsGiven > 0)
10034 {
10035 Puts(Backbone.GetMessage("Log Saved", null, "treasurehunters"));
10036 }
10037
10038 return true;
10039 }
10040
10041 private void AddMapPrivatePluginMarker(Vector3 position, int uid)
10042 {
10043 if (Map == null || !Map.IsLoaded)
10044 {
10045 return;
10046 }
10047
10048 mapMarkers[uid] = new MapInfo { IconName = _config.LustyMap.IconName, Position = position, Url = _config.LustyMap.IconFile };
10049 Map?.Call("ApiAddPointUrl", _config.LustyMap.IconFile, _config.LustyMap.IconName, position);
10050 }
10051
10052 private void RemoveMapPrivatePluginMarker(int uid)
10053 {
10054 if (Map == null || !Map.IsLoaded || !mapMarkers.ContainsKey(uid))
10055 {
10056 return;
10057 }
10058
10059 var mapInfo = mapMarkers[uid];
10060 Map?.Call("ApiRemovePointUrl", mapInfo.Url, mapInfo.IconName, mapInfo.Position);
10061 mapMarkers.Remove(uid);
10062 }
10063
10064 private void AddTemporaryLustyMarker(Vector3 pos, int uid)
10065 {
10066 if (LustyMap == null || !LustyMap.IsLoaded)
10067 {
10068 return;
10069 }
10070
10071 string name = string.Format("{0}_{1}", _config.LustyMap.IconName, storedData.TotalEvents).ToLower();
10072 LustyMap?.Call("AddTemporaryMarker", pos.x, pos.z, name, _config.LustyMap.IconFile, _config.LustyMap.IconRotation);
10073 lustyMarkers[uid] = name;
10074 }
10075
10076 private void RemoveTemporaryLustyMarker(int uid)
10077 {
10078 if (LustyMap == null || !LustyMap.IsLoaded || !lustyMarkers.ContainsKey(uid))
10079 {
10080 return;
10081 }
10082
10083 LustyMap?.Call("RemoveTemporaryMarker", lustyMarkers[uid]);
10084 lustyMarkers.Remove(uid);
10085 }
10086
10087 private void RemoveAllThirdPartyMarkers()
10088 {
10089 if (lustyMarkers.Count > 0)
10090 {
10091 var lusty = new Dictionary<int, string>(lustyMarkers);
10092
10093 foreach (var entry in lusty)
10094 {
10095 RemoveTemporaryLustyMarker(entry.Key);
10096 }
10097
10098 lusty.Clear();
10099 }
10100
10101 if (mapMarkers.Count > 0)
10102 {
10103 var maps = new Dictionary<int, MapInfo>(mapMarkers);
10104
10105 foreach (var entry in maps)
10106 {
10107 RemoveMapPrivatePluginMarker(entry.Key);
10108 }
10109
10110 maps.Clear();
10111 }
10112 }
10113
10114 private void StopMaintainCoroutine()
10115 {
10116 if (maintainCoroutine != null)
10117 {
10118 ServerMgr.Instance.StopCoroutine(maintainCoroutine);
10119 maintainCoroutine = null;
10120 }
10121 }
10122
10123 private void StartMaintainCoroutine()
10124 {
10125 if (_config.Settings.Maintained.Enabled)
10126 {
10127 if (IsGridLoading)
10128 {
10129 timer.Once(1f, () => StartMaintainCoroutine());
10130 return;
10131 }
10132
10133 StopMaintainCoroutine();
10134
10135 timer.Once(0.2f, () =>
10136 {
10137 maintainCoroutine = ServerMgr.Instance.StartCoroutine(MaintainCoroutine());
10138 });
10139 }
10140 }
10141
10142 private IEnumerator MaintainCoroutine()
10143 {
10144 string message;
10145 RaidableMode mode;
10146
10147 if (!CanContinueAutomation())
10148 {
10149 Puts(Backbone.GetMessage("MaintainCoroutineFailedToday"));
10150 yield break;
10151 }
10152
10153 while (!IsUnloading)
10154 {
10155 if (SaveRestore.IsSaving)
10156 {
10157 yield return Coroutines.WaitForSeconds(15f);
10158 continue;
10159 }
10160
10161 mode = GetRandomDifficulty(RaidableType.Maintained);
10162
10163 if (!IsModeValid(mode) || !CanMaintainOpenEvent() || CopyPaste == null || !CopyPaste.IsLoaded)
10164 {
10165 yield return Coroutines.WaitForSeconds(1f);
10166 continue;
10167 }
10168
10169 SpawnRandomBase(out message, RaidableType.Maintained, mode);
10170 yield return Coroutines.WaitForSeconds(15f);
10171 }
10172 }
10173
10174 private bool CanMaintainOpenEvent() => IsPasteAvailable && !IsGridLoading && RaidableBase.Get(RaidableType.Maintained) < _config.Settings.Maintained.Max && BasePlayer.activePlayerList.Count >= _config.Settings.Maintained.PlayerLimit;
10175
10176 private void StopScheduleCoroutine()
10177 {
10178 if (scheduleCoroutine != null)
10179 {
10180 ServerMgr.Instance.StopCoroutine(scheduleCoroutine);
10181 scheduleCoroutine = null;
10182 }
10183 }
10184
10185 private void StartScheduleCoroutine()
10186 {
10187 if (_config.Settings.Schedule.Enabled)
10188 {
10189 if (IsGridLoading)
10190 {
10191 timer.Once(1f, () => StartScheduleCoroutine());
10192 return;
10193 }
10194
10195 StopScheduleCoroutine();
10196
10197 timer.Once(0.2f, () =>
10198 {
10199 scheduleCoroutine = ServerMgr.Instance.StartCoroutine(ScheduleCoroutine());
10200 });
10201 }
10202 }
10203
10204 private IEnumerator ScheduleCoroutine()
10205 {
10206 if (!CanContinueAutomation())
10207 {
10208 Puts(Backbone.GetMessage("ScheduleCoroutineFailedToday"));
10209 yield break;
10210 }
10211
10212 double raidInterval = Core.Random.Range(_config.Settings.Schedule.IntervalMin, _config.Settings.Schedule.IntervalMax);
10213 string message;
10214 RaidableMode mode = RaidableMode.Random;
10215
10216 if (storedData.RaidTime == DateTime.MinValue.ToString()) // first time users
10217 {
10218 storedData.RaidTime = DateTime.Now.AddSeconds(raidInterval).ToString();
10219 Puts(Backbone.GetMessage("Next Automated Raid", null, FormatTime(raidInterval), DateTime.UtcNow.AddSeconds(raidInterval).ToString()));
10220 SaveData();
10221 }
10222
10223 while (!IsUnloading)
10224 {
10225 if (CanScheduleOpenEvent() && CopyPaste != null && CopyPaste.IsLoaded)
10226 {
10227 while (RaidableBase.Get(RaidableType.Scheduled) < _config.Settings.Schedule.Max && MaxOnce())
10228 {
10229 if (SaveRestore.IsSaving)
10230 {
10231 yield return Coroutines.WaitForSeconds(15f);
10232 continue;
10233 }
10234
10235 mode = GetRandomDifficulty(RaidableType.Scheduled);
10236
10237 if (IsModeValid(mode) && SpawnRandomBase(out message, RaidableType.Scheduled, mode) != Vector3.zero)
10238 {
10239 _maxOnce++;
10240 yield return Coroutines.WaitForSeconds(15f);
10241 continue;
10242 }
10243
10244 yield return Coroutines.WaitForSeconds(1f);
10245 }
10246
10247 _maxOnce = 0;
10248 raidInterval = Core.Random.Range(_config.Settings.Schedule.IntervalMin, _config.Settings.Schedule.IntervalMax);
10249 storedData.RaidTime = DateTime.Now.AddSeconds(raidInterval).ToString();
10250 Puts(Backbone.GetMessage("Next Automated Raid", null, FormatTime(raidInterval), DateTime.Now.AddSeconds(raidInterval)));
10251 SaveData();
10252 }
10253
10254 yield return Coroutines.WaitForSeconds(1f);
10255 }
10256 }
10257
10258 private bool MaxOnce()
10259 {
10260 return _config.Settings.Schedule.MaxOnce <= 0 || _maxOnce < _config.Settings.Schedule.MaxOnce;
10261 }
10262
10263 private bool CanContinueAutomation()
10264 {
10265 foreach (RaidableMode mode in Enum.GetValues(typeof(RaidableMode)))
10266 {
10267 if (CanSpawnDifficultyToday(mode))
10268 {
10269 return true;
10270 }
10271 }
10272
10273 return false;
10274 }
10275
10276 private static bool IsModeValid(RaidableMode mode) => mode != RaidableMode.Disabled && mode != RaidableMode.Random;
10277
10278 private double GetRaidTime() => DateTime.Parse(storedData.RaidTime).Subtract(DateTime.Now).TotalSeconds;
10279
10280 private bool CanScheduleOpenEvent() => GetRaidTime() <= 0 && RaidableBase.Get(RaidableType.Scheduled) < _config.Settings.Schedule.Max && IsPasteAvailable && !IsGridLoading && BasePlayer.activePlayerList.Count >= _config.Settings.Schedule.PlayerLimit;
10281
10282 private void DoLockoutRemoves()
10283 {
10284 var keys = new List<string>();
10285
10286 foreach (var lockout in storedData.Lockouts)
10287 {
10288 if (lockout.Value.Easy - Epoch.Current <= 0)
10289 {
10290 lockout.Value.Easy = 0;
10291 }
10292
10293 if (lockout.Value.Medium - Epoch.Current <= 0)
10294 {
10295 lockout.Value.Medium = 0;
10296 }
10297
10298 if (lockout.Value.Hard - Epoch.Current <= 0)
10299 {
10300 lockout.Value.Hard = 0;
10301 }
10302
10303 if (lockout.Value.Expert - Epoch.Current <= 0)
10304 {
10305 lockout.Value.Expert = 0;
10306 }
10307
10308 if (lockout.Value.Nightmare - Epoch.Current <= 0)
10309 {
10310 lockout.Value.Nightmare = 0;
10311 }
10312
10313 if (!lockout.Value.Any())
10314 {
10315 keys.Add(lockout.Key);
10316 }
10317 }
10318
10319 foreach (string key in keys)
10320 {
10321 storedData.Lockouts.Remove(key);
10322 }
10323 }
10324
10325 private void SaveData()
10326 {
10327 DoLockoutRemoves();
10328 Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
10329 }
10330
10331 public static string FormatGridReference(Vector3 position)
10332 {
10333 if (_config.Settings.ShowXZ)
10334 {
10335 return string.Format("{0} ({1} {2})", PositionToGrid(position), position.x.ToString("N2"), position.z.ToString("N2"));
10336 }
10337
10338 return PositionToGrid(position);
10339 }
10340
10341 /*public static string PositionToGrid(Vector3 position) // Credit: yetzt/Dana/nivex
10342 {
10343 var r = new Vector2(World.Size / 2 + position.x, World.Size / 2 + position.z);
10344 var f = Mathf.Floor(r.x / 148.4f);
10345 var x = f % 26;
10346 var c = Mathf.Floor(f / 26);
10347 var z = Mathf.Floor(World.Size / 148.4f) - Mathf.Floor(r.y / 148.4f);
10348 var s = c <= 0 ? string.Empty : ((char)('A' + (c - 1))).ToString();
10349
10350 return $"{s}{(char)('A' + x)}{z}";
10351 }*/
10352
10353 private static string PositionToGrid(Vector3 position) // Credit: MagicGridPanel
10354 {
10355 int maxGridSize = Mathf.FloorToInt(World.Size / 146.3f) - 1;
10356 int xGrid = Mathf.Clamp(Mathf.FloorToInt((position.x + (World.Size / 2f)) / 146.3f), 0, maxGridSize);
10357 string extraA = string.Empty;
10358 if (xGrid > 26) extraA = $"{(char)('A' + (xGrid / 26 - 1))}";
10359 return $"{extraA}{(char)('A' + xGrid % 26)}{Mathf.Clamp(maxGridSize - Mathf.FloorToInt((position.z + (World.Size / 2f)) / 146.3f), 0, maxGridSize).ToString()}";
10360 }
10361
10362 private static string FormatTime(double seconds)
10363 {
10364 if (seconds < 0)
10365 {
10366 return "0s";
10367 }
10368
10369 var ts = TimeSpan.FromSeconds(seconds);
10370 string format = Backbone.GetMessage("TimeFormat");
10371
10372 if (format == "TimeFormat")
10373 {
10374 format = "{0:D2}h {1:D2}m {2:D2}s";
10375 }
10376
10377 return string.Format(format, ts.Hours, ts.Minutes, ts.Seconds);
10378 }
10379
10380#endregion
10381
10382#region Config
10383
10384 private Dictionary<string, Dictionary<string, string>> GetMessages()
10385 {
10386 return new Dictionary<string, Dictionary<string, string>>
10387 {
10388 {"No Permission", new Dictionary<string, string>() {
10389 {"en", "You do not have permission to use this command."},
10390 }},
10391 {"Building is blocked!", new Dictionary<string, string>() {
10392 {"en", "<color=#FF0000>Building is blocked near raidable bases!</color>"},
10393 }},
10394 {"Difficulty Not Available", new Dictionary<string, string>() {
10395 {"en", "Difficulty <color=#FF0000>{0}</color> is not available on any of your buildings."},
10396 }},
10397 {"Difficulty Not Available Admin", new Dictionary<string, string>() {
10398 {"en", "Difficulty <color=#FF0000>{0}</color> is not available on any of your buildings. This could indicate that your CopyPaste files are not on this server in the oxide/data/copypaste folder."},
10399 }},
10400 {"Max Manual Events", new Dictionary<string, string>() {
10401 {"en", "Maximum number of manual events <color=#FF0000>{0}</color> has been reached!"},
10402 }},
10403 {"Manual Event Failed", new Dictionary<string, string>() {
10404 {"en", "Event failed to start! Unable to obtain a valid position. Please try again."},
10405 }},
10406 {"Help", new Dictionary<string, string>() {
10407 {"en", "/{0} <tp> - start a manual event, and teleport to the position if TP argument is specified and you are an admin."},
10408 }},
10409 {"RaidOpenMessage", new Dictionary<string, string>() {
10410 {"en", "<color=#C0C0C0>A {0} raidable base event has opened at <color=#FFFF00>{1}</color>! You are <color=#FFA500>{2}m</color> away. [{3}]</color>"},
10411 }},
10412 {"Next", new Dictionary<string, string>() {
10413 {"en", "<color=#C0C0C0>No events are open. Next event in <color=#FFFF00>{0}</color></color>"},
10414 }},
10415 {"Wins", new Dictionary<string, string>()
10416 {
10417 {"en", "<color=#C0C0C0>You have looted <color=#FFFF00>{0}</color> raid bases! View the ladder using <color=#FFA500>/{1} ladder</color> or <color=#FFA500>/{1} lifetime</color></color>"},
10418 }},
10419 {"RaidMessage", new Dictionary<string, string>() {
10420 {"en", "Raidable Base {0}m [{1} players]"},
10421 }},
10422 {"Ladder", new Dictionary<string, string>()
10423 {
10424 {"en", "<color=#FFFF00>[ Top 10 Raid Hunters (This Wipe) ]</color>:"},
10425 }},
10426 {"Ladder Total", new Dictionary<string, string>()
10427 {
10428 {"en", "<color=#FFFF00>[ Top 10 Raid Hunters (Lifetime) ]</color>:"},
10429 }},
10430 {"Ladder Insufficient Players", new Dictionary<string, string>()
10431 {
10432 {"en", "<color=#FFFF00>No players are on the ladder yet!</color>"},
10433 }},
10434 {"Next Automated Raid", new Dictionary<string, string>() {
10435 {"en", "Next automated raid in {0} at {1}"},
10436 }},
10437 {"Not Enough Online", new Dictionary<string, string>() {
10438 {"en", "Not enough players online ({0} minimum)"},
10439 }},
10440 {"Raid Base Distance", new Dictionary<string, string>() {
10441 {"en", "<color=#C0C0C0>Raidable Base <color=#FFA500>{0}m</color>"},
10442 }},
10443 {"Destroyed Raid", new Dictionary<string, string>() {
10444 {"en", "Destroyed a left over raid base at {0}"},
10445 }},
10446 {"Indestructible", new Dictionary<string, string>() {
10447 {"en", "<color=#FF0000>Treasure chests are indestructible!</color>"},
10448 }},
10449 {"View Config", new Dictionary<string, string>() {
10450 {"en", "Please view the config if you haven't already."},
10451 }},
10452 {"Log Stolen", new Dictionary<string, string>() {
10453 {"en", "{0} ({1}) Raids {2}"},
10454 }},
10455 {"Log Granted", new Dictionary<string, string>() {
10456 {"en", "Granted {0} ({1}) permission {2} for group {3}"},
10457 }},
10458 {"Log Saved", new Dictionary<string, string>() {
10459 {"en", "Raid Hunters have been logged to: {0}"},
10460 }},
10461 {"Prefix", new Dictionary<string, string>() {
10462 {"en", "[ <color=#406B35>Raidable Bases</color> ] "},
10463 }},
10464 {"RestartDetected", new Dictionary<string, string>()
10465 {
10466 {"en", "Restart detected. Next event in {0} minutes."},
10467 }},
10468 {"EconomicsDeposit", new Dictionary<string, string>()
10469 {
10470 {"en", "You have received <color=#FFFF00>${0}</color> for stealing the treasure!"},
10471 }},
10472 {"EconomicsWithdraw", new Dictionary<string, string>()
10473 {
10474 {"en", "You have paid <color=#FFFF00>${0}</color> for a raidable base!"},
10475 }},
10476 {"EconomicsWithdrawGift", new Dictionary<string, string>()
10477 {
10478 {"en", "{0} has paid <color=#FFFF00>${1}</color> for your raidable base!"},
10479 }},
10480 {"EconomicsWithdrawFailed", new Dictionary<string, string>()
10481 {
10482 {"en", "You do not have <color=#FFFF00>${0}</color> for a raidable base!"},
10483 }},
10484 {"ServerRewardPoints", new Dictionary<string, string>()
10485 {
10486 {"en", "You have received <color=#FFFF00>{0} RP</color> for stealing the treasure!"},
10487 }},
10488 {"ServerRewardPointsTaken", new Dictionary<string, string>()
10489 {
10490 {"en", "You have paid <color=#FFFF00>{0} RP</color> for a raidable base!"},
10491 }},
10492 {"ServerRewardPointsGift", new Dictionary<string, string>()
10493 {
10494 {"en", "{0} has paid <color=#FFFF00>{1} RP</color> for your raidable base!"},
10495 }},
10496 {"ServerRewardPointsFailed", new Dictionary<string, string>()
10497 {
10498 {"en", "You do not have <color=#FFFF00>{0} RP</color> for a raidable base!"},
10499 }},
10500 {"InvalidItem", new Dictionary<string, string>()
10501 {
10502 {"en", "Invalid item shortname: {0}. Use /{1} additem <shortname> <amount> [skin]"},
10503 }},
10504 {"AddedItem", new Dictionary<string, string>()
10505 {
10506 {"en", "Added item: {0} amount: {1}, skin: {2}"},
10507 }},
10508 {"CustomPositionSet", new Dictionary<string, string>()
10509 {
10510 {"en", "Custom event spawn location set to: {0}"},
10511 }},
10512 {"CustomPositionRemoved", new Dictionary<string, string>()
10513 {
10514 {"en", "Custom event spawn location removed."},
10515 }},
10516 {"OpenedEvents", new Dictionary<string, string>()
10517 {
10518 {"en", "Opened {0}/{1} events."},
10519 }},
10520 {"OnPlayerEntered", new Dictionary<string, string>()
10521 {
10522 {"en", "<color=#FF0000>You have entered a raidable PVP base!</color>"},
10523 }},
10524 {"OnPlayerEnteredPVE", new Dictionary<string, string>()
10525 {
10526 {"en", "<color=#FF0000>You have entered a raidable PVE base!</color>"},
10527 }},
10528 {"OnFirstPlayerEntered", new Dictionary<string, string>()
10529 {
10530 {"en", "<color=#FFFF00>{0}</color> is the first to enter the raidable base at <color=#FFFF00>{1}</color>"},
10531 }},
10532 {"OnChestOpened", new Dictionary<string, string>() {
10533 {"en", "<color=#FFFF00>{0}</color> is the first to see the treasures at <color=#FFFF00>{1}</color>!</color>"},
10534 }},
10535 {"OnRaidFinished", new Dictionary<string, string>() {
10536 {"en", "The raid at <color=#FFFF00>{0}</color> has been unlocked!"},
10537 }},
10538 {"CannotBeMounted", new Dictionary<string, string>() {
10539 {"en", "You cannot loot the treasure while mounted!"},
10540 }},
10541 {"CannotTeleport", new Dictionary<string, string>() {
10542 {"en", "You are not allowed to teleport from this event."},
10543 }},
10544 {"MustBeAuthorized", new Dictionary<string, string>() {
10545 {"en", "You must have building privilege to access this treasure!"},
10546 }},
10547 {"OwnerLocked", new Dictionary<string, string>() {
10548 {"en", "This treasure belongs to someone else!"},
10549 }},
10550 {"CannotFindPosition", new Dictionary<string, string>() {
10551 {"en", "Could not find a random position!"},
10552 }},
10553 {"PasteOnCooldown", new Dictionary<string, string>() {
10554 {"en", "Paste is on cooldown!"},
10555 }},
10556 {"SpawnOnCooldown", new Dictionary<string, string>() {
10557 {"en", "Try again, a manual spawn was already requested."},
10558 }},
10559 {"Thief", new Dictionary<string, string>() {
10560 {"en", "<color=#FFFF00>The base at <color=#FFFF00>{0}</color> has been raided by <color=#FFFF00>{1}</color>!</color>"},
10561 }},
10562 {"BuySyntax", new Dictionary<string, string>() {
10563 {"en", "<color=#FFFF00>Syntax: {0} easy|medium|hard {1}</color>"},
10564 }},
10565 {"TargetNotFoundId", new Dictionary<string, string>() {
10566 {"en", "<color=#FFFF00>Target {0} not found, or not online.</color>"},
10567 }},
10568 {"TargetNotFoundNoId", new Dictionary<string, string>() {
10569 {"en", "<color=#FFFF00>No steamid provided.</color>"},
10570 }},
10571 {"BuyAnotherDifficulty", new Dictionary<string, string>() {
10572 {"en", "Difficulty '<color=#FFFF00>{0}</color>' is not available, please try another difficulty."},
10573 }},
10574 {"BuyDifficultyNotAvailableToday", new Dictionary<string, string>() {
10575 {"en", "Difficulty '<color=#FFFF00>{0}</color>' is not available today, please try another difficulty."},
10576 }},
10577 {"BuyPVPRaidsDisabled", new Dictionary<string, string>() {
10578 {"en", "<color=#FFFF00>No PVE raids can be bought for this difficulty as buying raids that allow PVP is not allowed.</color>"},
10579 }},
10580 {"BuyBaseSpawnedAt", new Dictionary<string, string>() {
10581 {"en", "<color=#FFFF00>Your base has been spawned at {0} in {1} !</color>"},
10582 }},
10583 {"BuyBaseAnnouncement", new Dictionary<string, string>() {
10584 {"en", "<color=#FFFF00>{0} has paid for a base at {1} in {2}!</color>"},
10585 }},
10586 {"DestroyingBaseAt", new Dictionary<string, string>() {
10587 {"en", "<color=#C0C0C0>Destroying raid base at <color=#FFFF00>{0}</color> in <color=#FFFF00>{1}</color> minutes!</color>"},
10588 }},
10589 {"PasteIsBlocked", new Dictionary<string, string>() {
10590 {"en", "You cannot start a raid base event there!"},
10591 }},
10592 {"LookElsewhere", new Dictionary<string, string>() {
10593 {"en", "Unable to find a position; look elsewhere."},
10594 }},
10595 {"BuildingIsNotConfigured", new Dictionary<string, string>() {
10596 {"en", "You cannot spawn a base that is not in the config. Raidable Bases > Building Names in config."},
10597 }},
10598 {"NoValidBuildingsConfigured", new Dictionary<string, string>() {
10599 {"en", "No valid buildings have been configured. Raidable Bases > Building Names in config."},
10600 }},
10601 {"DespawnBaseSuccess", new Dictionary<string, string>() {
10602 {"en", "<color=#C0C0C0>Despawning the nearest raid base to you!</color>"},
10603 }},
10604 {"DespawnedAt", new Dictionary<string, string>() {
10605 {"en", "{0} despawned a base manually at {1}"},
10606 }},
10607 {"DespawnedAll", new Dictionary<string, string>() {
10608 {"en", "{0} despawned all bases manually"},
10609 }},
10610 {"ModeLevel", new Dictionary<string, string>() {
10611 {"en", "level"},
10612 }},
10613 {"ModeEasy", new Dictionary<string, string>() {
10614 {"en", "easy"},
10615 }},
10616 {"ModeMedium", new Dictionary<string, string>() {
10617 {"en", "medium"},
10618 }},
10619 {"ModeHard", new Dictionary<string, string>() {
10620 {"en", "hard"},
10621 }},
10622 {"ModeExpert", new Dictionary<string, string>() {
10623 {"en", "expert"},
10624 }},
10625 {"ModeNightmare", new Dictionary<string, string>() {
10626 {"en", "nightmare"},
10627 }},
10628 {"DespawnBaseNoneAvailable", new Dictionary<string, string>() {
10629 {"en", "<color=#C0C0C0>You must be within 100m of a raid base to despawn it.</color>"},
10630 }},
10631 {"GridIsLoading", new Dictionary<string, string>() {
10632 {"en", "The grid is loading; please wait until it has finished."},
10633 }},
10634 {"GridIsLoadingFormatted", new Dictionary<string, string>() {
10635 {"en", "Grid is loading. The process has taken {0} seconds so far with {1} locations added on the grid."},
10636 }},
10637 {"TooPowerful", new Dictionary<string, string>() {
10638 {"en", "<color=#FF0000>This place is guarded by a powerful spirit. You sheath your wand in fear!</color>"},
10639 }},
10640 {"TooPowerfulDrop", new Dictionary<string, string>() {
10641 {"en", "<color=#FF0000>This place is guarded by a powerful spirit. You drop your wand in fear!</color>"},
10642 }},
10643 {"BuyCooldown", new Dictionary<string, string>() {
10644 {"en", "<color=#FF0000>You must wait {0} seconds to use this command!</color>"},
10645 }},
10646 {"LoadCopyPaste", new Dictionary<string, string>() {
10647 {"en", "CopyPaste is not loaded."},
10648 }},
10649 {"DoomAndGloom", new Dictionary<string, string>() {
10650 {"en", "<color=#FF0000>You have left a {0} zone and can be attacked for another {1} seconds!</color>"},
10651 }},
10652 {"MaintainCoroutineFailedToday", new Dictionary<string, string>() {
10653 {"en", "<color=#FF0000>Failed to start maintain coroutine; no difficulties are available today.</color>"},
10654 }},
10655 {"ScheduleCoroutineFailedToday", new Dictionary<string, string>() {
10656 {"en", "<color=#FF0000>Failed to start scheduled coroutine; no difficulties are available today.</color>"},
10657 }},
10658 {"NoConfiguredLoot", new Dictionary<string, string>() {
10659 {"en", "Error: No loot found in the config!"},
10660 }},
10661 {"NoContainersFound", new Dictionary<string, string>() {
10662 {"en", "Error: No containers found for {0} @ {1}!"},
10663 }},
10664 {"NoLootSpawned", new Dictionary<string, string>() {
10665 {"en", "Error: No loot was spawned!"},
10666 }},
10667 {"NotCompatible", new Dictionary<string, string>() {
10668 {"en", "Expansion Mode is not available for your version of Dangerous Treasures ({0}). Please update it to use this feature."},
10669 }},
10670 {"LoadedManual", new Dictionary<string, string>() {
10671 {"en", "Loaded {0} manual spawns."},
10672 }},
10673 {"LoadedBuyable", new Dictionary<string, string>() {
10674 {"en", "Loaded {0} buyable spawns."},
10675 }},
10676 {"LoadedMaintained", new Dictionary<string, string>() {
10677 {"en", "Loaded {0} maintained spawns."},
10678 }},
10679 {"LoadedScheduled", new Dictionary<string, string>() {
10680 {"en", "Loaded {0} scheduled spawns."},
10681 }},
10682 {"InitializedGrid", new Dictionary<string, string>() {
10683 {"en", "Grid initialization completed in {0} seconds and {1} milliseconds on a {2} size map. {3} locations are on the grid."},
10684 }},
10685 {"EntityCountMax", new Dictionary<string, string>() {
10686 {"en", "Command disabled due to entity count being greater than 300k"},
10687 }},
10688 {"NotifyPlayerMessageFormat", new Dictionary<string, string>() {
10689 {"en", "<color=#ADD8E6>{rank}</color>. <color=#C0C0C0>{name}</color> (<color=#FFFF00>{value}</color>)"},
10690 }},
10691 {"ConfigUseFormat", new Dictionary<string, string>() {
10692 {"en", "Use: rb.config <{0}> [base] [subset]"},
10693 }},
10694 {"ConfigAddBaseSyntax", new Dictionary<string, string>() {
10695 {"en", "Use: rb.config add nivex1 nivex4 nivex5 nivex6"},
10696 }},
10697 {"FileDoesNotExist", new Dictionary<string, string>() {
10698 {"en", " > This file does not exist\n"},
10699 }},
10700 {"ListingAll", new Dictionary<string, string>() {
10701 {"en", "Listing all primary bases and their subsets:"},
10702 }},
10703 {"PrimaryBase", new Dictionary<string, string>() {
10704 {"en", "Primary Base: "},
10705 }},
10706 {"AdditionalBase", new Dictionary<string, string>() {
10707 {"en", "Additional Base: "},
10708 }},
10709 {"RaidPVEWarning", new Dictionary<string, string>() {
10710 {"en", "Configuration is set to block PVP raids from being bought, and no PVE raids are configured. Therefore players cannot buy raids until you add a PVE raid."},
10711 }},
10712 {"NoValidBuilingsWarning", new Dictionary<string, string>() {
10713 {"en", "No valid buildings are configured with a valid file that exists. Did you configure valid files and reload the plugin?"},
10714 }},
10715 {"Adding", new Dictionary<string, string>() {
10716 {"en", "Adding: {0}"},
10717 }},
10718 {"AddedPrimaryBase", new Dictionary<string, string>() {
10719 {"en", "Added Primary Base: {0}"},
10720 }},
10721 {"AddedAdditionalBase", new Dictionary<string, string>() {
10722 {"en", "Added Additional Base: {0}"},
10723 }},
10724 {"DifficultySetTo", new Dictionary<string, string>() {
10725 {"en", "Difficulty set to: {0}"},
10726 }},
10727 {"EntryAlreadyExists", new Dictionary<string, string>() {
10728 {"en", "That entry already exists."},
10729 }},
10730 {"RemoveSyntax", new Dictionary<string, string>() {
10731 {"en", "Use: rb.config remove nivex1"},
10732 }},
10733 {"RemovingAllBasesFor", new Dictionary<string, string>() {
10734 {"en", "\nRemoving all bases for: {0}"},
10735 }},
10736 {"RemovedPrimaryBase", new Dictionary<string, string>() {
10737 {"en", "Removed primary base: {0}"},
10738 }},
10739 {"RemovedAdditionalBase", new Dictionary<string, string>() {
10740 {"en", "Removed additional base {0} from primary base {1}"},
10741 }},
10742 {"RemovedEntries", new Dictionary<string, string>() {
10743 {"en", "Removed {0} entries"},
10744 }},
10745 {"LockedOut", new Dictionary<string, string>() {
10746 {"en", "You are locked out from {0} raids for {1}"},
10747 }},
10748 {"PVPFlag", new Dictionary<string, string>() {
10749 {"en", "[<color=#FF0000>PVP</color>] "},
10750 }},
10751 {"PVEFlag", new Dictionary<string, string>() {
10752 {"en", "[<color=#008000>PVE</color>] "},
10753 }},
10754 {"PVP ZONE", new Dictionary<string, string>() {
10755 {"en", "PVP ZONE"},
10756 }},
10757 {"PVE ZONE", new Dictionary<string, string>() {
10758 {"en", "PVE ZONE"},
10759 }},
10760 {"OnPlayerExit", new Dictionary<string, string>()
10761 {
10762 {"en", "<color=#FF0000>You have left a raidable PVP base!</color>"},
10763 }},
10764 {"OnPlayerExitPVE", new Dictionary<string, string>()
10765 {
10766 {"en", "<color=#FF0000>You have left a raidable PVE base!</color>"},
10767 }},
10768 {"PasteIsBlockedStandAway", new Dictionary<string, string>() {
10769 {"en", "You cannot start a raid base event there because you are too close to the spawn."},
10770 }},
10771 {"ReloadConfig", new Dictionary<string, string>() {
10772 {"en", "Reloading config..."},
10773 }},
10774 {"ReloadMaintainCo", new Dictionary<string, string>() {
10775 {"en", "Stopped maintain coroutine."},
10776 }},
10777 {"ReloadScheduleCo", new Dictionary<string, string>() {
10778 {"en", "Stopped schedule coroutine."},
10779 }},
10780 {"ReloadInit", new Dictionary<string, string>() {
10781 {"en", "Initializing..."},
10782 }},
10783 {"YourCorpse", new Dictionary<string, string>() {
10784 {"en", "Your Corpse"},
10785 }},
10786 {"NotAllowed", new Dictionary<string, string>() {
10787 {"en", "<color=#FF0000>That action is not allowed in this zone.</color>"},
10788 }},
10789 {"BlockedZones", new Dictionary<string, string>() {
10790 {"en", "Blocked spawn points in {0} zones."},
10791 }},
10792 {"UIFormat", new Dictionary<string, string>() {
10793 {"en", "{0} C:{1} [{2}m]"},
10794 }},
10795 {"UIFormatContainers", new Dictionary<string, string>() {
10796 {"en", "{0} C:{1}"},
10797 }},
10798 {"UIFormatMinutes", new Dictionary<string, string>() {
10799 {"en", "{0} [{1}m]"},
10800 }},
10801 {"UIFormatLockoutMinutes", new Dictionary<string, string>() {
10802 {"en", "{0}m"},
10803 }},
10804 {"UIHelpTextAll", new Dictionary<string, string>() {
10805 {"en", "<color=#C0C0C0>You can toggle the UI by using <color=#FFA500>/{0} ui [lockouts]</color></color>"},
10806 }},
10807 {"UIHelpText", new Dictionary<string, string>() {
10808 {"en", "<color=#C0C0C0>You can toggle the UI by using <color=#FFA500>/{0} ui</color></color>"},
10809 }},
10810 {"HoggingFinishYourRaid", new Dictionary<string, string>() {
10811 {"en", "<color=#FF0000>You must finish your last raid at {0} before joining another.</color>"},
10812 }},
10813 {"HoggingFinishYourRaidClan", new Dictionary<string, string>() {
10814 {"en", "<color=#FF0000>Your clan mate `{0}` must finish their last raid at {1}.</color>"},
10815 }},
10816 {"HoggingFinishYourRaidTeam", new Dictionary<string, string>() {
10817 {"en", "<color=#FF0000>Your team mate `{0}` must finish their last raid at {1}.</color>"},
10818 }},
10819 {"HoggingFinishYourRaidFriend", new Dictionary<string, string>() {
10820 {"en", "<color=#FF0000>Your friend `{0}` must finish their last raid at {1}.</color>"},
10821 }},
10822 {"TimeFormat", new Dictionary<string, string>() {
10823 {"en", "{0:D2}h {1:D2}m {2:D2}s"},
10824 }},
10825 {"BuyableAlreadyRequested", new Dictionary<string, string>() {
10826 {"en", "You must wait 2 seconds to try buying again."},
10827 }},
10828 {"BuyableServerRestarting", new Dictionary<string, string>() {
10829 {"en", "You cannot buy a raid when a server restart is pending."},
10830 }},
10831 {"BuyableServerSaving", new Dictionary<string, string>() {
10832 {"en", "You cannot buy a raid while the server is saving."},
10833 }},
10834 {"BuyableAlreadyOwner", new Dictionary<string, string>() {
10835 {"en", "You cannot buy multiple raids."},
10836 }},
10837 {"TargetTooFar", new Dictionary<string, string>() {
10838 {"en", "Your target is not close enough to a raid."},
10839 }},
10840 {"TooFar", new Dictionary<string, string>() {
10841 {"en", "You are not close enough to a raid."},
10842 }},
10843 {"RaidLockedTo", new Dictionary<string, string>() {
10844 {"en", "Raid has been locked to: {0}"},
10845 }},
10846 {"RemovedLockFor", new Dictionary<string, string>() {
10847 {"en", "Removed lockout for {0} ({1})"},
10848 }},
10849 {"RaidOwnerCleared", new Dictionary<string, string>() {
10850 {"en", "Raid owner has been cleared."},
10851 }},
10852 };
10853 }
10854
10855 protected override void LoadDefaultMessages()
10856 {
10857 var compiledLangs = new Dictionary<string, Dictionary<string, string>>();
10858
10859 foreach (var line in GetMessages())
10860 {
10861 foreach (var translate in line.Value)
10862 {
10863 if (!compiledLangs.ContainsKey(translate.Key))
10864 compiledLangs[translate.Key] = new Dictionary<string, string>();
10865
10866 compiledLangs[translate.Key][line.Key] = translate.Value;
10867 }
10868 }
10869
10870 foreach (var cLangs in compiledLangs)
10871 lang.RegisterMessages(cLangs.Value, this, cLangs.Key);
10872 }
10873
10874 private static int GetPercentAmount(int amount)
10875 {
10876 decimal percentLoss = _config.Treasure.PercentLoss;
10877
10878 if (percentLoss > 0m && (percentLoss /= 100m) > 0m)
10879 {
10880 amount = Convert.ToInt32(amount - (amount * percentLoss));
10881
10882 if (_config.Treasure.UseDOWL && !_config.Treasure.Increased)
10883 {
10884 return amount;
10885 }
10886 }
10887
10888 decimal percentIncrease = GetDayPercentIncrease();
10889
10890 if (percentIncrease > 0m && (percentIncrease /= 100m) > 0m)
10891 {
10892 return Convert.ToInt32(amount + (amount * percentIncrease));
10893 }
10894
10895 return amount;
10896 }
10897
10898 private static decimal GetDayPercentIncrease()
10899 {
10900 decimal percentIncrease;
10901
10902 switch (DateTime.Now.DayOfWeek)
10903 {
10904 case DayOfWeek.Monday:
10905 {
10906 percentIncrease = _config.Treasure.PercentIncreaseOnMonday;
10907 break;
10908 }
10909 case DayOfWeek.Tuesday:
10910 {
10911 percentIncrease = _config.Treasure.PercentIncreaseOnTuesday;
10912 break;
10913 }
10914 case DayOfWeek.Wednesday:
10915 {
10916 percentIncrease = _config.Treasure.PercentIncreaseOnWednesday;
10917 break;
10918 }
10919 case DayOfWeek.Thursday:
10920 {
10921 percentIncrease = _config.Treasure.PercentIncreaseOnThursday;
10922 break;
10923 }
10924 case DayOfWeek.Friday:
10925 {
10926 percentIncrease = _config.Treasure.PercentIncreaseOnFriday;
10927 break;
10928 }
10929 case DayOfWeek.Saturday:
10930 {
10931 percentIncrease = _config.Treasure.PercentIncreaseOnSaturday;
10932 break;
10933 }
10934 default:
10935 {
10936 percentIncrease = _config.Treasure.PercentIncreaseOnSunday;
10937 break;
10938 }
10939 }
10940
10941 return percentIncrease;
10942 }
10943
10944#endregion
10945
10946#region Configuration
10947
10948 private static Configuration _config;
10949
10950 private static BuildingOptions DefaultBuilding
10951 {
10952 get
10953 {
10954 return new BuildingOptions
10955 {
10956 PasteOptions = DefaultPasteOptions
10957 };
10958 }
10959 }
10960
10961 private static List<PasteOption> DefaultPasteOptions
10962 {
10963 get
10964 {
10965 return new List<PasteOption>
10966 {
10967 new PasteOption() { Key = "stability", Value = "false" },
10968 new PasteOption() { Key = "autoheight", Value = "false" },
10969 new PasteOption() { Key = "height", Value = "1.0" }
10970 };
10971 }
10972 }
10973
10974 private static Dictionary<string, List<PasteOption>> DefaultPasteOptionsAddition
10975 {
10976 get
10977 {
10978 return new Dictionary<string, List<PasteOption>>
10979 {
10980 ["nivex4"] = DefaultPasteOptions,
10981 ["nivex5"] = DefaultPasteOptions,
10982 ["nivex6"] = DefaultPasteOptions
10983 };
10984 }
10985 }
10986
10987 private static Dictionary<string, BuildingOptions> DefaultBuildingOptions
10988 {
10989 get
10990 {
10991 return new Dictionary<string, BuildingOptions>()
10992 {
10993 ["nivex1"] = DefaultBuilding,
10994 ["nivex2"] = DefaultBuilding,
10995 ["nivex3"] = DefaultBuilding,
10996 };
10997 }
10998 }
10999
11000 private static List<TreasureItem> DefaultLoot
11001 {
11002 get
11003 {
11004 return new List<TreasureItem>
11005 {
11006 new TreasureItem { shortname = "ammo.pistol", amount = 40, skin = 0, amountMin = 40 },
11007 new TreasureItem { shortname = "ammo.pistol.fire", amount = 40, skin = 0, amountMin = 40 },
11008 new TreasureItem { shortname = "ammo.pistol.hv", amount = 40, skin = 0, amountMin = 40 },
11009 new TreasureItem { shortname = "ammo.rifle", amount = 60, skin = 0, amountMin = 60 },
11010 new TreasureItem { shortname = "ammo.rifle.explosive", amount = 60, skin = 0, amountMin = 60 },
11011 new TreasureItem { shortname = "ammo.rifle.hv", amount = 60, skin = 0, amountMin = 60 },
11012 new TreasureItem { shortname = "ammo.rifle.incendiary", amount = 60, skin = 0, amountMin = 60 },
11013 new TreasureItem { shortname = "ammo.shotgun", amount = 24, skin = 0, amountMin = 24 },
11014 new TreasureItem { shortname = "ammo.shotgun.slug", amount = 40, skin = 0, amountMin = 40 },
11015 new TreasureItem { shortname = "surveycharge", amount = 20, skin = 0, amountMin = 20 },
11016 new TreasureItem { shortname = "metal.refined", amount = 150, skin = 0, amountMin = 150 },
11017 new TreasureItem { shortname = "bucket.helmet", amount = 1, skin = 0, amountMin = 1 },
11018 new TreasureItem { shortname = "cctv.camera", amount = 1, skin = 0, amountMin = 1 },
11019 new TreasureItem { shortname = "coffeecan.helmet", amount = 1, skin = 0, amountMin = 1 },
11020 new TreasureItem { shortname = "explosive.timed", amount = 1, skin = 0, amountMin = 1 },
11021 new TreasureItem { shortname = "metal.facemask", amount = 1, skin = 0, amountMin = 1 },
11022 new TreasureItem { shortname = "metal.plate.torso", amount = 1, skin = 0, amountMin = 1 },
11023 new TreasureItem { shortname = "mining.quarry", amount = 1, skin = 0, amountMin = 1 },
11024 new TreasureItem { shortname = "pistol.m92", amount = 1, skin = 0, amountMin = 1 },
11025 new TreasureItem { shortname = "rifle.ak", amount = 1, skin = 0, amountMin = 1 },
11026 new TreasureItem { shortname = "rifle.bolt", amount = 1, skin = 0, amountMin = 1 },
11027 new TreasureItem { shortname = "rifle.lr300", amount = 1, skin = 0, amountMin = 1 },
11028 new TreasureItem { shortname = "smg.2", amount = 1, skin = 0, amountMin = 1 },
11029 new TreasureItem { shortname = "smg.mp5", amount = 1, skin = 0, amountMin = 1 },
11030 new TreasureItem { shortname = "smg.thompson", amount = 1, skin = 0, amountMin = 1 },
11031 new TreasureItem { shortname = "supply.signal", amount = 1, skin = 0, amountMin = 1 },
11032 new TreasureItem { shortname = "targeting.computer", amount = 1, skin = 0, amountMin = 1 },
11033 };
11034 }
11035 }
11036
11037 public class PluginSettingsLimitsDays
11038 {
11039 [JsonProperty(PropertyName = "Monday")]
11040 public bool Monday { get; set; } = true;
11041
11042 [JsonProperty(PropertyName = "Tuesday")]
11043 public bool Tuesday { get; set; } = true;
11044
11045 [JsonProperty(PropertyName = "Wednesday")]
11046 public bool Wednesday { get; set; } = true;
11047
11048 [JsonProperty(PropertyName = "Thursday")]
11049 public bool Thursday { get; set; } = true;
11050
11051 [JsonProperty(PropertyName = "Friday")]
11052 public bool Friday { get; set; } = true;
11053
11054 [JsonProperty(PropertyName = "Saturday")]
11055 public bool Saturday { get; set; } = true;
11056
11057 [JsonProperty(PropertyName = "Sunday")]
11058 public bool Sunday { get; set; } = true;
11059 }
11060
11061 public class PluginSettingsBaseLockout
11062 {
11063 [JsonProperty(PropertyName = "Time Between Raids In Minutes (Easy)")]
11064 public double Easy { get; set; }
11065
11066 [JsonProperty(PropertyName = "Time Between Raids In Minutes (Medium)")]
11067 public double Medium { get; set; }
11068
11069 [JsonProperty(PropertyName = "Time Between Raids In Minutes (Hard)")]
11070 public double Hard { get; set; }
11071
11072 [JsonProperty(PropertyName = "Time Between Raids In Minutes (Expert)")]
11073 public double Expert { get; set; }
11074
11075 [JsonProperty(PropertyName = "Time Between Raids In Minutes (Nightmare)")]
11076 public double Nightmare { get; set; }
11077
11078 [JsonProperty(PropertyName = "Block Clans From Owning More Than One Raid")]
11079 public bool BlockClans { get; set; }
11080
11081 [JsonProperty(PropertyName = "Block Friends From Owning More Than One Raid")]
11082 public bool BlockFriends { get; set; }
11083
11084 [JsonProperty(PropertyName = "Block Teams From Owning More Than One Raid")]
11085 public bool BlockTeams { get; set; }
11086
11087 public bool Any() => Easy > 0 || Medium > 0 || Hard > 0 || Expert > 0 || Nightmare > 0;
11088
11089 public bool IsBlocking() => BlockClans || BlockFriends || BlockTeams;
11090 }
11091
11092 public class PluginSettingsBasePercentages
11093 {
11094 [JsonProperty(PropertyName = "Easy")]
11095 public decimal Easy { get; set; }
11096
11097 [JsonProperty(PropertyName = "Medium")]
11098 public decimal Medium { get; set; }
11099
11100 [JsonProperty(PropertyName = "Hard")]
11101 public decimal Hard { get; set; }
11102
11103 [JsonProperty(PropertyName = "Expert")]
11104 public decimal Expert { get; set; }
11105
11106 [JsonProperty(PropertyName = "Nightmare")]
11107 public decimal Nightmare { get; set; }
11108 }
11109
11110 public class PluginSettingsColors1
11111 {
11112 [JsonProperty(PropertyName = "Easy")]
11113 public string Easy { get; set; } = "000000";
11114
11115 [JsonProperty(PropertyName = "Medium")]
11116 public string Medium { get; set; } = "000000";
11117
11118 [JsonProperty(PropertyName = "Hard")]
11119 public string Hard { get; set; } = "000000";
11120
11121 [JsonProperty(PropertyName = "Expert")]
11122 public string Expert { get; set; } = "000000";
11123
11124 [JsonProperty(PropertyName = "Nightmare")]
11125 public string Nightmare { get; set; } = "000000";
11126 }
11127
11128 public class PluginSettingsColors2
11129 {
11130 [JsonProperty(PropertyName = "Easy")]
11131 public string Easy { get; set; } = "00FF00";
11132
11133 [JsonProperty(PropertyName = "Medium")]
11134 public string Medium { get; set; } = "FFEB04";
11135
11136 [JsonProperty(PropertyName = "Hard")]
11137 public string Hard { get; set; } = "FF0000";
11138
11139 [JsonProperty(PropertyName = "Expert")]
11140 public string Expert { get; set; } = "0000FF";
11141
11142 [JsonProperty(PropertyName = "Nightmare")]
11143 public string Nightmare { get; set; } = "000000";
11144 }
11145
11146 public class PluginSettingsBaseManagementMountables
11147 {
11148 [JsonProperty(PropertyName = "Boats")]
11149 public bool Boats { get; set; }
11150
11151 [JsonProperty(PropertyName = "Cars (Basic)")]
11152 public bool BasicCars { get; set; }
11153
11154 [JsonProperty(PropertyName = "Cars (Modular)")]
11155 public bool ModularCars { get; set; } = true;
11156
11157 [JsonProperty(PropertyName = "Chinook")]
11158 public bool CH47 { get; set; } = true;
11159
11160 [JsonProperty(PropertyName = "Horses")]
11161 public bool Horses { get; set; }
11162
11163 [JsonProperty(PropertyName = "MiniCopters")]
11164 public bool MiniCopters { get; set; } = true;
11165
11166 [JsonProperty(PropertyName = "Pianos")]
11167 public bool Pianos { get; set; } = true;
11168
11169 [JsonProperty(PropertyName = "Scrap Transport Helicopters")]
11170 public bool Scrap { get; set; } = true;
11171
11172 [JsonProperty(PropertyName = "Mounts Without OwnerID")]
11173 public bool Owner { get; set; } = true;
11174 }
11175
11176 public class PluginSettingsBaseManagement
11177 {
11178 [JsonProperty(PropertyName = "Eject Mounts")]
11179 public PluginSettingsBaseManagementMountables Mounts { get; set; } = new PluginSettingsBaseManagementMountables();
11180
11181 [JsonProperty(PropertyName = "Max Percent Allowed To Spawn Per Difficulty (0 = ignore)")]
11182 public PluginSettingsBasePercentages Percent { get; set; } = new PluginSettingsBasePercentages();
11183
11184 [JsonProperty(PropertyName = "Player Lockouts (0 = ignore)")]
11185 public PluginSettingsBaseLockout Lockout { get; set; } = new PluginSettingsBaseLockout();
11186
11187 [JsonProperty(PropertyName = "Easy Raids Can Spawn On")]
11188 public PluginSettingsLimitsDays Easy { get; set; } = new PluginSettingsLimitsDays();
11189
11190 [JsonProperty(PropertyName = "Medium Raids Can Spawn On")]
11191 public PluginSettingsLimitsDays Medium { get; set; } = new PluginSettingsLimitsDays();
11192
11193 [JsonProperty(PropertyName = "Hard Raids Can Spawn On")]
11194 public PluginSettingsLimitsDays Hard { get; set; } = new PluginSettingsLimitsDays();
11195
11196 [JsonProperty(PropertyName = "Expert Raids Can Spawn On")]
11197 public PluginSettingsLimitsDays Expert { get; set; } = new PluginSettingsLimitsDays();
11198
11199 [JsonProperty(PropertyName = "Nightmare Raids Can Spawn On")]
11200 public PluginSettingsLimitsDays Nightmare { get; set; } = new PluginSettingsLimitsDays();
11201
11202 [JsonProperty(PropertyName = "Entities Not Allowed To Be Picked Up", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11203 public List<string> BlacklistedPickupItems { get; set; } = new List<string>();
11204
11205 [JsonProperty(PropertyName = "Difficulty Colors (Border)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11206 public PluginSettingsColors1 Colors1 { get; set; } = new PluginSettingsColors1();
11207
11208 [JsonProperty(PropertyName = "Difficulty Colors (Inner)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11209 public PluginSettingsColors2 Colors2 { get; set; } = new PluginSettingsColors2();
11210
11211 [JsonProperty(PropertyName = "Allow Teleport")]
11212 public bool AllowTeleport { get; set; }
11213
11214 [JsonProperty(PropertyName = "Allow Cupboard Loot To Drop")]
11215 public bool AllowCupboardLoot { get; set; } = true;
11216
11217 [JsonProperty(PropertyName = "Allow Players To Build")]
11218 public bool AllowBuilding { get; set; } = true;
11219
11220 [JsonProperty(PropertyName = "Allow Player Bags To Be Lootable At PVP Bases")]
11221 public bool PlayersLootableInPVP { get; set; } = true;
11222
11223 [JsonProperty(PropertyName = "Allow Player Bags To Be Lootable At PVE Bases")]
11224 public bool PlayersLootableInPVE { get; set; } = true;
11225
11226 [JsonProperty(PropertyName = "Allow Raid Bases On Roads")]
11227 public bool AllowOnRoads { get; set; } = true;
11228
11229 [JsonProperty(PropertyName = "Allow Raid Bases On Rivers")]
11230 public bool AllowOnRivers { get; set; } = true;
11231
11232 [JsonProperty(PropertyName = "Allow Vending Machines To Broadcast")]
11233 public bool AllowBroadcasting { get; set; }
11234
11235 [JsonProperty(PropertyName = "Backpacks Can Be Opened At PVE Bases")]
11236 public bool BackpacksOpenPVE { get; set; } = true;
11237
11238 [JsonProperty(PropertyName = "Backpacks Can Be Opened At PVP Bases")]
11239 public bool BackpacksOpenPVP { get; set; } = true;
11240
11241 [JsonProperty(PropertyName = "Backpacks Can Drop At PVE Bases")]
11242 public bool BackpacksPVE { get; set; } = true;
11243
11244 [JsonProperty(PropertyName = "Backpacks Can Drop In PVP Bases")]
11245 public bool BackpacksPVP { get; set; } = true;
11246
11247 [JsonProperty(PropertyName = "Block Mounted Damage To Bases And Players")]
11248 public bool BlockMounts { get; set; }
11249
11250 [JsonProperty(PropertyName = "Block RestoreUponDeath Plugin For PVP Bases")]
11251 public bool BlockRestorePVP { get; set; }
11252
11253 [JsonProperty(PropertyName = "Block RestoreUponDeath Plugin For PVE Bases")]
11254 public bool BlockRestorePVE { get; set; }
11255
11256 [JsonProperty(PropertyName = "Boxes Are Invulnerable")]
11257 public bool Invulnerable { get; set; }
11258
11259 [JsonProperty(PropertyName = "Bypass Lock Treasure To First Attacker For PVE Bases")]
11260 public bool BypassUseOwnersForPVE { get; set; }
11261
11262 [JsonProperty(PropertyName = "Bypass Lock Treasure To First Attacker For PVP Bases")]
11263 public bool BypassUseOwnersForPVP { get; set; }
11264
11265 [JsonProperty(PropertyName = "Building Blocks Are Immune To Damage")]
11266 public bool BlocksImmune { get; set; }
11267
11268 //[JsonProperty(PropertyName = "Destroy Dropped Container Loot On Despawn")]
11269 //public bool DestroyLoot { get; set; }
11270
11271 [JsonProperty(PropertyName = "Do Not Destroy Player Built Deployables")]
11272 public bool DoNotDestroyDeployables { get; set; }
11273
11274 [JsonProperty(PropertyName = "Divide Rewards Among All Raiders")]
11275 public bool DivideRewards { get; set; } = true;
11276
11277 [JsonProperty(PropertyName = "Draw Corpse Time (Seconds)")]
11278 public float DrawTime { get; set; } = 300f;
11279
11280 [JsonProperty(PropertyName = "Eject Sleepers Before Spawning Base")]
11281 public bool EjectSleepers { get; set; } = true;
11282
11283 [JsonProperty(PropertyName = "Extra Distance To Spawn From Monuments")]
11284 public float MonumentDistance { get; set; }
11285
11286 [JsonProperty(PropertyName = "Flame Turrets Ignore NPCs")]
11287 public bool FlameTurrets { get; set; }
11288
11289 [JsonProperty(PropertyName = "Ignore Containers That Spawn With Loot Already")]
11290 public bool IgnoreContainedLoot { get; set; }
11291
11292 [JsonProperty(PropertyName = "Move Cookables Into Ovens")]
11293 public bool Cook { get; set; } = true;
11294
11295 [JsonProperty(PropertyName = "Move Food Into BBQ Or Fridge")]
11296 public bool Food { get; set; } = true;
11297
11298 [JsonProperty(PropertyName = "Move Resources Into Tool Cupboard")]
11299 public bool Cupboard { get; set; }
11300
11301 [JsonProperty(PropertyName = "Lock Treasure To First Attacker")]
11302 public bool UseOwners { get; set; }
11303
11304 [JsonProperty(PropertyName = "Lock Treasure Max Inactive Time (Minutes)")]
11305 public float LockTime { get; set; } = 10f;
11306
11307 [JsonProperty(PropertyName = "Minutes Until Despawn After Looting (min: 1)")]
11308 public int DespawnMinutes { get; set; } = 15;
11309
11310 [JsonProperty(PropertyName = "Minutes Until Despawn After Inactive (0 = disabled)")]
11311 public int DespawnMinutesInactive { get; set; } = 45;
11312
11313 [JsonProperty(PropertyName = "Minutes Until Despawn After Inactive Resets When Damaged")]
11314 public bool DespawnMinutesInactiveReset { get; set; } = true;
11315
11316 [JsonProperty(PropertyName = "Mounts Can Take Damage")]
11317 public bool MountDamage { get; set; }
11318
11319 [JsonProperty(PropertyName = "Player Cupboard Detection Radius")]
11320 public float CupboardDetectionRadius { get; set; } = 75f;
11321
11322 [JsonProperty(PropertyName = "PVP Delay Between Zone Hopping")]
11323 public float PVPDelay { get; set; } = 10f;
11324
11325 [JsonProperty(PropertyName = "Prevent Fire From Spreading")]
11326 public bool PreventFireFromSpreading { get; set; } = true;
11327
11328 [JsonProperty(PropertyName = "Prevent Players From Hogging Raids")]
11329 public bool PreventHogging { get; set; } = true;
11330
11331 [JsonProperty(PropertyName = "Require Cupboard To Be Looted Before Despawning")]
11332 public bool RequireCupboardLooted { get; set; }
11333
11334 [JsonProperty(PropertyName = "Destroying The Cupboard Completes The Raid")]
11335 public bool EndWhenCupboardIsDestroyed { get; set; }
11336
11337 [JsonProperty(PropertyName = "Require All Bases To Spawn Before Respawning An Existing Base")]
11338 public bool RequireAllSpawned { get; set; }
11339
11340 [JsonProperty(PropertyName = "Turn Lights On At Night")]
11341 public bool Lights { get; set; } = true;
11342
11343 [JsonProperty(PropertyName = "Turn Lights On Indefinitely")]
11344 public bool AlwaysLights { get; set; }
11345
11346 [JsonProperty(PropertyName = "Use Random Codes On Code Locks")]
11347 public bool RandomCodes { get; set; } = true;
11348
11349 [JsonProperty(PropertyName = "Wait To Start Despawn Timer When Base Takes Damage From Player")]
11350 public bool Engaged { get; set; }
11351 }
11352
11353 public class PluginSettingsMapMarkers
11354 {
11355 [JsonProperty(PropertyName = "Marker Name")]
11356 public string MarkerName { get; set; } = "Raidable Base Event";
11357
11358 [JsonProperty(PropertyName = "Radius")]
11359 public float Radius { get; set; } = 0.25f;
11360
11361 [JsonProperty(PropertyName = "Use Vending Map Marker")]
11362 public bool UseVendingMarker { get; set; } = true;
11363
11364 [JsonProperty(PropertyName = "Use Explosion Map Marker")]
11365 public bool UseExplosionMarker { get; set; }
11366
11367 [JsonProperty(PropertyName = "Create Markers For Buyable Events")]
11368 public bool Buyables { get; set; } = true;
11369
11370 [JsonProperty(PropertyName = "Create Markers For Maintained Events")]
11371 public bool Maintained { get; set; } = true;
11372
11373 [JsonProperty(PropertyName = "Create Markers For Scheduled Events")]
11374 public bool Scheduled { get; set; } = true;
11375
11376 [JsonProperty(PropertyName = "Create Markers For Manual Events")]
11377 public bool Manual { get; set; } = true;
11378 }
11379
11380 public class PluginSettings
11381 {
11382 [JsonProperty(PropertyName = "Raid Management")]
11383 public PluginSettingsBaseManagement Management { get; set; } = new PluginSettingsBaseManagement();
11384
11385 [JsonProperty(PropertyName = "Map Markers")]
11386 public PluginSettingsMapMarkers Markers { get; set; } = new PluginSettingsMapMarkers();
11387
11388 [JsonProperty(PropertyName = "Buyable Events")]
11389 public RaidableBaseSettingsBuyable Buyable { get; set; } = new RaidableBaseSettingsBuyable();
11390
11391 [JsonProperty(PropertyName = "Maintained Events")]
11392 public RaidableBaseSettingsMaintained Maintained { get; set; } = new RaidableBaseSettingsMaintained();
11393
11394 [JsonProperty(PropertyName = "Manual Events")]
11395 public RaidableBaseSettingsManual Manual { get; set; } = new RaidableBaseSettingsManual();
11396
11397 [JsonProperty(PropertyName = "Scheduled Events")]
11398 public RaidableBaseSettingsScheduled Schedule { get; set; } = new RaidableBaseSettingsScheduled();
11399
11400 [JsonProperty(PropertyName = "Economics Buy Raid Costs (0 = disabled)")]
11401 public RaidableBaseEconomicsOptions Economics { get; set; } = new RaidableBaseEconomicsOptions();
11402
11403 [JsonProperty(PropertyName = "ServerRewards Buy Raid Costs (0 = disabled)")]
11404 public RaidableBaseServerRewardsOptions ServerRewards { get; set; } = new RaidableBaseServerRewardsOptions();
11405
11406 [JsonProperty(PropertyName = "Allowed Zone Manager Zones", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11407 public List<string> Inclusions { get; set; } = new List<string> { "pvp", "99999999" };
11408
11409 [JsonProperty(PropertyName = "Amount Of Entities To Undo Per Batch (1 = Slowest But Better Performance, 0 = Wait For Each Entity To Be Destroyed)")]
11410 public int BatchLimit { get; set; } = 10;
11411
11412 [JsonProperty(PropertyName = "Automatically Teleport Admins To Their Map Marker Positions")]
11413 public bool TeleportMarker { get; set; }
11414
11415 [JsonProperty(PropertyName = "Block Wizardry Plugin At Events")]
11416 public bool NoWizardry { get; set; }
11417
11418 [JsonProperty(PropertyName = "Chat Steam64ID")]
11419 public ulong ChatID { get; set; }
11420
11421 [JsonProperty(PropertyName = "Expansion Mode (Dangerous Treasures)")]
11422 public bool ExpansionMode { get; set; }
11423
11424 [JsonProperty(PropertyName = "Remove Admins From Raiders List")]
11425 public bool RemoveAdminRaiders { get; set; }
11426
11427 [JsonProperty(PropertyName = "Show X Z Coordinates")]
11428 public bool ShowXZ { get; set; } = false;
11429
11430 [JsonProperty(PropertyName = "Buy Raid Command")]
11431 public string BuyCommand { get; set; } = "buyraid";
11432
11433 [JsonProperty(PropertyName = "Event Command")]
11434 public string EventCommand { get; set; } = "rbe";
11435
11436 [JsonProperty(PropertyName = "Hunter Command")]
11437 public string HunterCommand { get; set; } = "rb";
11438
11439 [JsonProperty(PropertyName = "Server Console Command")]
11440 public string ConsoleCommand { get; set; } = "rbevent";
11441 }
11442
11443 public class EventMessageSettings
11444 {
11445 [JsonProperty(PropertyName = "Announce Raid Unlocked")]
11446 public bool AnnounceRaidUnlock { get; set; }
11447
11448 [JsonProperty(PropertyName = "Announce Buy Base Messages")]
11449 public bool AnnounceBuy { get; set; }
11450
11451 [JsonProperty(PropertyName = "Announce Thief Message")]
11452 public bool AnnounceThief { get; set; } = true;
11453
11454 [JsonProperty(PropertyName = "Announce PVE/PVP Enter/Exit Messages")]
11455 public bool AnnounceEnterExit { get; set; } = true;
11456
11457 [JsonProperty(PropertyName = "Show Destroy Warning")]
11458 public bool ShowWarning { get; set; } = true;
11459
11460 [JsonProperty(PropertyName = "Show Opened Message")]
11461 public bool Opened { get; set; } = true;
11462
11463 [JsonProperty(PropertyName = "Show Opened Message For Paid Bases")]
11464 public bool OpenedAndPaid { get; set; } = true;
11465
11466 [JsonProperty(PropertyName = "Show Prefix")]
11467 public bool Prefix { get; set; } = true;
11468 }
11469
11470 public class GUIAnnouncementSettings
11471 {
11472 [JsonProperty(PropertyName = "Enabled")]
11473 public bool Enabled { get; set; }
11474
11475 [JsonProperty(PropertyName = "Banner Tint Color")]
11476 public string TintColor { get; set; } = "Grey";
11477
11478 [JsonProperty(PropertyName = "Maximum Distance")]
11479 public float Distance { get; set; } = 300f;
11480
11481 [JsonProperty(PropertyName = "Text Color")]
11482 public string TextColor { get; set; } = "White";
11483 }
11484
11485 public class LustyMapSettings
11486 {
11487 [JsonProperty(PropertyName = "Enabled")]
11488 public bool Enabled { get; set; }
11489
11490 [JsonProperty(PropertyName = "Icon File")]
11491 public string IconFile { get; set; } = "http://i.imgur.com/XoEMTJj.png";
11492
11493 [JsonProperty(PropertyName = "Icon Name")]
11494 public string IconName { get; set; } = "rbevent";
11495
11496 [JsonProperty(PropertyName = "Icon Rotation")]
11497 public float IconRotation { get; set; }
11498 }
11499
11500 public class NpcSettings
11501 {
11502 [JsonProperty(PropertyName = "Murderer Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11503 public List<string> MurdererItems { get; set; } = new List<string> { "metal.facemask", "metal.plate.torso", "pants", "tactical.gloves", "boots.frog", "tshirt", "machete" };
11504
11505 [JsonProperty(PropertyName = "Scientist Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11506 public List<string> ScientistItems { get; set; } = new List<string> { "hazmatsuit_scientist", "rifle.ak" };
11507
11508 [JsonProperty(PropertyName = "Murderer Kits", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11509 public List<string> MurdererKits { get; set; } = new List<string> { "murderer_kit_1", "murderer_kit_2" };
11510
11511 [JsonProperty(PropertyName = "Scientist Kits", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11512 public List<string> ScientistKits { get; set; } = new List<string> { "scientist_kit_1", "scientist_kit_2" };
11513
11514 [JsonProperty(PropertyName = "Random Names", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11515 public List<string> RandomNames { get; set; } = new List<string>();
11516
11517 [JsonProperty(PropertyName = "Amount To Spawn")]
11518 public int SpawnAmount { get; set; } = 3;
11519
11520 [JsonProperty(PropertyName = "Aggression Range")]
11521 public float AggressionRange { get; set; } = 70f;
11522
11523 [JsonProperty(PropertyName = "Despawn Inventory On Death")]
11524 public bool DespawnInventory { get; set; } = true;
11525
11526 [JsonProperty(PropertyName = "Enabled")]
11527 public bool Enabled { get; set; } = true;
11528
11529 [JsonProperty(PropertyName = "Health For Murderers (100 min, 5000 max)")]
11530 public float MurdererHealth { get; set; } = 150f;
11531
11532 [JsonProperty(PropertyName = "Health For Scientists (100 min, 5000 max)")]
11533 public float ScientistHealth { get; set; } = 150f;
11534
11535 [JsonProperty(PropertyName = "Minimum Amount To Spawn")]
11536 public int SpawnMinAmount { get; set; } = 1;
11537
11538 [JsonProperty(PropertyName = "Use Dangerous Treasures NPCs")]
11539 public bool UseExpansionNpcs { get; set; }
11540
11541 [JsonProperty(PropertyName = "Spawn Murderers And Scientists")]
11542 public bool SpawnBoth { get; set; } = true;
11543
11544 [JsonProperty(PropertyName = "Scientist Weapon Accuracy (0 - 100)")]
11545 public float Accuracy { get; set; } = 75f;
11546
11547 [JsonProperty(PropertyName = "Spawn Murderers")]
11548 public bool SpawnMurderers { get; set; }
11549
11550 [JsonProperty(PropertyName = "Spawn Random Amount")]
11551 public bool SpawnRandomAmount { get; set; }
11552
11553 [JsonProperty(PropertyName = "Spawn Scientists Only")]
11554 public bool SpawnScientistsOnly { get; set; }
11555 }
11556
11557 public class PasteOption
11558 {
11559 [JsonProperty(PropertyName = "Option")]
11560 public string Key { get; set; }
11561
11562 [JsonProperty(PropertyName = "Value")]
11563 public string Value { get; set; }
11564 }
11565
11566 public class BuildingLevelOne
11567 {
11568 [JsonProperty(PropertyName = "Amount (0 = disabled)")]
11569 public int Amount { get; set; }
11570
11571 [JsonProperty(PropertyName = "Chance To Play")]
11572 public float Chance { get; set; } = 0.5f;
11573 }
11574
11575 public class BuildingLevels
11576 {
11577 [JsonProperty(PropertyName = "Level 1 - Play With Fire")]
11578 public BuildingLevelOne Level1 { get; set; } = new BuildingLevelOne();
11579
11580 [JsonProperty(PropertyName = "Level 2 - Final Death")]
11581 public bool Level2 { get; set; }
11582 }
11583
11584 public class BuildingGradeLevels
11585 {
11586 [JsonProperty(PropertyName = "Wooden")]
11587 public bool Wooden { get; set; }
11588
11589 [JsonProperty(PropertyName = "Stone")]
11590 public bool Stone { get; set; }
11591
11592 [JsonProperty(PropertyName = "Metal")]
11593 public bool Metal { get; set; }
11594
11595 [JsonProperty(PropertyName = "HQM")]
11596 public bool HQM { get; set; }
11597
11598 public bool Any() => Wooden || Stone || Metal || HQM;
11599 }
11600
11601 public class BuildingOptions
11602 {
11603 [JsonProperty(PropertyName = "Difficulty (0 = easy, 1 = medium, 2 = hard, 3 = expert, 4 = nightmare)")]
11604 public RaidableMode Mode { get; set; } = RaidableMode.Disabled;
11605
11606 [JsonProperty(PropertyName = "Additional Bases For This Difficulty", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11607 public Dictionary<string, List<PasteOption>> AdditionalBases { get; set; } = DefaultPasteOptionsAddition;
11608
11609 [JsonProperty(PropertyName = "Paste Options", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11610 public List<PasteOption> PasteOptions { get; set; } = new List<PasteOption>();
11611
11612 [JsonProperty(PropertyName = "Arena Walls")]
11613 public RaidableBaseWallOptions ArenaWalls { get; set; } = new RaidableBaseWallOptions();
11614
11615 [JsonProperty(PropertyName = "NPC Levels")]
11616 public BuildingLevels Levels { get; set; } = new BuildingLevels();
11617
11618 [JsonProperty(PropertyName = "NPCs")]
11619 public NpcSettings NPC { get; set; } = new NpcSettings();
11620
11621 [JsonProperty(PropertyName = "Rewards")]
11622 public RewardSettings Rewards { get; set; } = new RewardSettings();
11623
11624 [JsonProperty(PropertyName = "Change Building Material Tier To")]
11625 public BuildingGradeLevels Tiers { get; set; } = new BuildingGradeLevels();
11626
11627 [JsonProperty(PropertyName = "Loot (Empty List = Use Treasure Loot)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11628 public List<TreasureItem> Loot { get; set; } = new List<TreasureItem>();
11629
11630 [JsonProperty(PropertyName = "Add Code Lock To Unlocked Or KeyLocked Doors")]
11631 public bool DoorLock { get; set; } = true;
11632
11633 [JsonProperty(PropertyName = "Close Open Doors With No Door Controller Installed")]
11634 public bool CloseOpenDoors { get; set; } = true;
11635
11636 [JsonProperty(PropertyName = "Allow Base To Float Above Water")]
11637 public bool Submerged { get; set; }
11638
11639 [JsonProperty(PropertyName = "Allow BuildRevert Ladders")]
11640 public bool AllowBuildRevertLadders { get; set; } = true;
11641
11642 [JsonProperty(PropertyName = "Allow Duplicate Items")]
11643 public bool AllowDuplicates { get; set; }
11644
11645 [JsonProperty(PropertyName = "Allow Players To Pickup Deployables")]
11646 public bool AllowPickup { get; set; }
11647
11648 [JsonProperty(PropertyName = "Allow Players To Deploy A Cupboard")]
11649 public bool AllowBuildingPriviledges { get; set; } = true;
11650
11651 [JsonProperty(PropertyName = "Allow PVP")]
11652 public bool AllowPVP { get; set; } = true;
11653
11654 [JsonProperty(PropertyName = "Allow Friendly Fire (Teams)")]
11655 public bool AllowFriendlyFire { get; set; } = true;
11656
11657 [JsonProperty(PropertyName = "Amount Of Items To Spawn")]
11658 public int TreasureAmount { get; set; } = 6;
11659
11660 [JsonProperty(PropertyName = "Auto Turret Health")]
11661 public float AutoTurretHealth { get; set; } = 1000f;
11662
11663 [JsonProperty(PropertyName = "Auto Turret Aim Cone")]
11664 public float AutoTurretAimCone { get; set; } = 5f;
11665
11666 [JsonProperty(PropertyName = "Auto Turret Sight Range")]
11667 public float AutoTurretSightRange { get; set; } = 30f;
11668
11669 [JsonProperty(PropertyName = "Flame Turret Health")]
11670 public float FlameTurretHealth { get; set; } = 300f;
11671
11672 [JsonProperty(PropertyName = "Block Damage Outside Of The Dome To Players Inside")]
11673 public bool BlockOutsideDamageToPlayersDistance { get; set; }
11674
11675 [JsonProperty(PropertyName = "Block Damage Outside Of The Dome To Bases Inside")]
11676 public bool BlockOutsideDamageToBaseDistance { get; set; }
11677
11678 [JsonProperty(PropertyName = "Divide Loot Into All Containers")]
11679 public bool DivideLoot { get; set; }
11680
11681 [JsonProperty(PropertyName = "Drop Container Loot X Seconds After It Is Looted")]
11682 public float DropTimeAfterLooting { get; set; }
11683
11684 [JsonProperty(PropertyName = "Drop Container Loot Applies Only To Boxes And Cupboards")]
11685 public bool DropOnlyBoxesAndPrivileges { get; set; } = true;
11686
11687 [JsonProperty(PropertyName = "Create Dome Around Event Using Spheres (0 = disabled, recommended = 5)")]
11688 public int SphereAmount { get; set; } = 5;
11689
11690 [JsonProperty(PropertyName = "Empty All Containers Before Spawning Loot")]
11691 public bool EmptyAll { get; set; }
11692
11693 [JsonProperty(PropertyName = "Eject Corpses From Enemy Raids (Advanced Users Only)")]
11694 public bool EjectCorpses { get; set; } = true;
11695
11696 [JsonProperty(PropertyName = "Eject Enemies From Purchased PVE Raids")]
11697 public bool EjectPurchasedPVE { get; set; }
11698
11699 [JsonProperty(PropertyName = "Eject Enemies From Purchased PVP Raids")]
11700 public bool EjectPurchasedPVP { get; set; }
11701
11702 [JsonProperty(PropertyName = "Eject Enemies From Locked PVE Raids")]
11703 public bool EjectLockedPVE { get; set; }
11704
11705 [JsonProperty(PropertyName = "Eject Enemies From Locked PVP Raids")]
11706 public bool EjectLockedPVP { get; set; }
11707
11708 [JsonProperty(PropertyName = "Equip Unequipped AutoTurret With")]
11709 public string AutoTurretShortname { get; set; } = "rifle.ak";
11710
11711 [JsonProperty(PropertyName = "Explosion Damage Modifier (0-999)")]
11712 public float ExplosionModifier { get; set; } = 100f;
11713
11714 [JsonProperty(PropertyName = "Force All Boxes To Have Same Skin")]
11715 public bool SetSkins { get; set; } = true;
11716
11717 [JsonProperty(PropertyName = "Maximum Elevation Level")]
11718 public float Elevation { get; set; } = 2.5f;
11719
11720 [JsonProperty(PropertyName = "Protection Radius")]
11721 public float ProtectionRadius { get; set; } = 50f;
11722
11723 [JsonProperty(PropertyName = "Block Plugins Which Prevent Item Durability Loss")]
11724 public bool EnforceDurability { get; set; } = false;
11725
11726 [JsonProperty(PropertyName = "Remove Equipped AutoTurret Weapon")]
11727 public bool RemoveTurretWeapon { get; set; }
11728
11729 [JsonProperty(PropertyName = "Require Cupboard Access To Loot")]
11730 public bool RequiresCupboardAccess { get; set; }
11731
11732 [JsonProperty(PropertyName = "Respawn Npc X Seconds After Death")]
11733 public float RespawnRate { get; set; }
11734
11735 [JsonProperty(PropertyName = "Skip Treasure Loot And Use Loot In Base Only")]
11736 public bool SkipTreasureLoot { get; set; }
11737
11738 [JsonProperty(PropertyName = "Use Loot Table Priority")]
11739 public bool Prioritize { get; set; }
11740
11741 public static BuildingOptions Clone(BuildingOptions options)
11742 {
11743 return options.MemberwiseClone() as BuildingOptions;
11744 }
11745 }
11746
11747 public class RaidableBaseSettingsScheduled
11748 {
11749 [JsonProperty(PropertyName = "Enabled")]
11750 public bool Enabled { get; set; }
11751
11752 [JsonProperty(PropertyName = "Convert PVE To PVP")]
11753 public bool ConvertPVE { get; set; }
11754
11755 [JsonProperty(PropertyName = "Convert PVP To PVE")]
11756 public bool ConvertPVP { get; set; }
11757
11758 [JsonProperty(PropertyName = "Every Min Seconds")]
11759 public double IntervalMin { get; set; } = 3600f;
11760
11761 [JsonProperty(PropertyName = "Every Max Seconds")]
11762 public double IntervalMax { get; set; } = 7200f;
11763
11764 [JsonProperty(PropertyName = "Include PVE Bases")]
11765 public bool IncludePVE { get; set; } = true;
11766
11767 [JsonProperty(PropertyName = "Include PVP Bases")]
11768 public bool IncludePVP { get; set; } = true;
11769
11770 [JsonProperty(PropertyName = "Max Scheduled Events")]
11771 public int Max { get; set; } = 1;
11772
11773 [JsonProperty(PropertyName = "Max To Spawn At Once (0 = Use Max Scheduled Events Amount)")]
11774 public int MaxOnce { get; set; }
11775
11776 [JsonProperty(PropertyName = "Minimum Required Players Online")]
11777 public int PlayerLimit { get; set; } = 1;
11778
11779 [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
11780 public float Distance { get; set; } = 100f;
11781
11782 [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
11783 public string SpawnsFile { get; set; } = "none";
11784 }
11785
11786 public class RaidableBaseSettingsMaintained
11787 {
11788 [JsonProperty(PropertyName = "Always Maintain Max Events")]
11789 public bool Enabled { get; set; }
11790
11791 [JsonProperty(PropertyName = "Convert PVE To PVP")]
11792 public bool ConvertPVE { get; set; }
11793
11794 [JsonProperty(PropertyName = "Convert PVP To PVE")]
11795 public bool ConvertPVP { get; set; }
11796
11797 [JsonProperty(PropertyName = "Include PVE Bases")]
11798 public bool IncludePVE { get; set; } = true;
11799
11800 [JsonProperty(PropertyName = "Include PVP Bases")]
11801 public bool IncludePVP { get; set; } = true;
11802
11803 [JsonProperty(PropertyName = "Minimum Required Players Online")]
11804 public int PlayerLimit { get; set; } = 1;
11805
11806 [JsonProperty(PropertyName = "Max Maintained Events")]
11807 public int Max { get; set; } = 1;
11808
11809 [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
11810 public float Distance { get; set; } = 100f;
11811
11812 [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
11813 public string SpawnsFile { get; set; } = "none";
11814 }
11815
11816 public class RaidableBaseSettingsBuyable
11817 {
11818 [JsonProperty(PropertyName = "Allow Players To Buy PVP Raids")]
11819 public bool BuyPVP { get; set; }
11820
11821 [JsonProperty(PropertyName = "Cooldown Between Purchases")]
11822 public float Cooldown { get; set; } = 60f;
11823
11824 [JsonProperty(PropertyName = "Convert PVE To PVP")]
11825 public bool ConvertPVE { get; set; }
11826
11827 [JsonProperty(PropertyName = "Convert PVP To PVE")]
11828 public bool ConvertPVP { get; set; }
11829
11830 [JsonProperty(PropertyName = "Distance To Spawn Bought Raids From Player")]
11831 public float DistanceToSpawnFrom { get; set; } = 150f;
11832
11833 [JsonProperty(PropertyName = "Lock Raid To Buyer And Friends")]
11834 public bool UsePayLock { get; set; } = true;
11835
11836 [JsonProperty(PropertyName = "Max Buyable Events")]
11837 public int Max { get; set; } = 1;
11838
11839 [JsonProperty(PropertyName = "Reset Purchased Owner After X Minutes Offline")]
11840 public float ResetDuration { get; set; } = 10f;
11841
11842 [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
11843 public float Distance { get; set; } = 100f;
11844
11845 [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
11846 public string SpawnsFile { get; set; } = "none";
11847 }
11848
11849 public class RaidableBaseSettingsManual
11850 {
11851 [JsonProperty(PropertyName = "Convert PVE To PVP")]
11852 public bool ConvertPVE { get; set; }
11853
11854 [JsonProperty(PropertyName = "Convert PVP To PVE")]
11855 public bool ConvertPVP { get; set; }
11856
11857 [JsonProperty(PropertyName = "Max Manual Events")]
11858 public int Max { get; set; } = 1;
11859
11860 [JsonProperty(PropertyName = "Spawn Bases X Distance Apart")]
11861 public float Distance { get; set; } = 100f;
11862
11863 [JsonProperty(PropertyName = "Spawns Database File (Optional)")]
11864 public string SpawnsFile { get; set; } = "none";
11865 }
11866
11867 public class RaidableBaseSettings
11868 {
11869 [JsonProperty(PropertyName = "Buildings", ObjectCreationHandling = ObjectCreationHandling.Replace)]
11870 public Dictionary<string, BuildingOptions> Buildings { get; set; } = DefaultBuildingOptions;
11871 }
11872
11873 public class RaidableBaseWallOptions
11874 {
11875 [JsonProperty(PropertyName = "Enabled")]
11876 public bool Enabled { get; set; } = true;
11877
11878 [JsonProperty(PropertyName = "Extra Stacks")]
11879 public int Stacks { get; set; } = 1;
11880
11881 [JsonProperty(PropertyName = "Use Stone Walls")]
11882 public bool Stone { get; set; } = true;
11883
11884 [JsonProperty(PropertyName = "Use Least Amount Of Walls")]
11885 public bool LeastAmount { get; set; } = true;
11886
11887 [JsonProperty(PropertyName = "Use UFO Walls")]
11888 public bool UseUFOWalls { get; set; }
11889
11890 [JsonProperty(PropertyName = "Radius")]
11891 public float Radius { get; set; } = 25f;
11892 }
11893
11894 public class RaidableBaseEconomicsOptions
11895 {
11896 [JsonProperty(PropertyName = "Easy")]
11897 public double Easy { get; set; }
11898
11899 [JsonProperty(PropertyName = "Medium")]
11900 public double Medium { get; set; }
11901
11902 [JsonProperty(PropertyName = "Hard")]
11903 public double Hard { get; set; }
11904
11905 [JsonProperty(PropertyName = "Expert")]
11906 public double Expert { get; set; }
11907
11908 [JsonProperty(PropertyName = "Nightmare")]
11909 public double Nightmare { get; set; }
11910
11911 [JsonIgnore]
11912 public bool Any
11913 {
11914 get
11915 {
11916 return Easy > 0 || Medium > 0 || Hard > 0 || Expert > 0 || Nightmare > 0;
11917 }
11918 }
11919 }
11920
11921 public class RaidableBaseServerRewardsOptions
11922 {
11923 [JsonProperty(PropertyName = "Easy")]
11924 public int Easy { get; set; }
11925
11926 [JsonProperty(PropertyName = "Medium")]
11927 public int Medium { get; set; }
11928
11929 [JsonProperty(PropertyName = "Hard")]
11930 public int Hard { get; set; }
11931
11932 [JsonProperty(PropertyName = "Expert")]
11933 public int Expert { get; set; }
11934
11935 [JsonProperty(PropertyName = "Nightmare")]
11936 public int Nightmare { get; set; }
11937
11938 [JsonIgnore]
11939 public bool Any
11940 {
11941 get
11942 {
11943 return Easy > 0 || Medium > 0 || Hard > 0 || Expert > 0 || Nightmare > 0;
11944 }
11945 }
11946 }
11947
11948 public class RankedLadderSettings
11949 {
11950 [JsonProperty(PropertyName = "Award Top X Players On Wipe")]
11951 public int Amount { get; set; } = 3;
11952
11953 [JsonProperty(PropertyName = "Enabled")]
11954 public bool Enabled { get; set; } = true;
11955 }
11956
11957 public class RewardSettings
11958 {
11959 [JsonProperty(PropertyName = "Economics Money")]
11960 public double Money { get; set; }
11961
11962 [JsonProperty(PropertyName = "ServerRewards Points")]
11963 public int Points { get; set; }
11964 }
11965
11966 public class SkinSettings
11967 {
11968 [JsonProperty(PropertyName = "Include Workshop Skins")]
11969 public bool RandomWorkshopSkins { get; set; } = true;
11970
11971 [JsonProperty(PropertyName = "Preset Skin")]
11972 public ulong PresetSkin { get; set; }
11973
11974 [JsonProperty(PropertyName = "Use Random Skin")]
11975 public bool RandomSkins { get; set; } = true;
11976 }
11977
11978 public class TreasureItem
11979 {
11980 public string shortname { get; set; }
11981 public int amount { get; set; }
11982 public ulong skin { get; set; }
11983 public int amountMin { get; set; }
11984 }
11985
11986 public class TreasureSettings
11987 {
11988 [JsonProperty(PropertyName = "Use Random Skins")]
11989 public bool RandomSkins { get; set; }
11990
11991 [JsonProperty(PropertyName = "Include Workshop Skins")]
11992 public bool RandomWorkshopSkins { get; set; }
11993
11994 [JsonProperty(PropertyName = "Use Day Of Week Loot")]
11995 public bool UseDOWL { get; set; }
11996
11997 [JsonProperty(PropertyName = "Percent Minimum Loss")]
11998 public decimal PercentLoss { get; set; }
11999
12000 [JsonProperty(PropertyName = "Percent Increase When Using Day Of Week Loot")]
12001 public bool Increased { get; set; }
12002
12003 [JsonProperty(PropertyName = "Day Of Week Loot Monday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12004 public List<TreasureItem> DOWL_Monday { get; set; } = new List<TreasureItem>();
12005
12006 [JsonProperty(PropertyName = "Day Of Week Loot Tuesday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12007 public List<TreasureItem> DOWL_Tuesday { get; set; } = new List<TreasureItem>();
12008
12009 [JsonProperty(PropertyName = "Day Of Week Loot Wednesday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12010 public List<TreasureItem> DOWL_Wednesday { get; set; } = new List<TreasureItem>();
12011
12012 [JsonProperty(PropertyName = "Day Of Week Loot Thursday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12013 public List<TreasureItem> DOWL_Thursday { get; set; } = new List<TreasureItem>();
12014
12015 [JsonProperty(PropertyName = "Day Of Week Loot Friday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12016 public List<TreasureItem> DOWL_Friday { get; set; } = new List<TreasureItem>();
12017
12018 [JsonProperty(PropertyName = "Day Of Week Loot Saturday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12019 public List<TreasureItem> DOWL_Saturday { get; set; } = new List<TreasureItem>();
12020
12021 [JsonProperty(PropertyName = "Day Of Week Loot Sunday", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12022 public List<TreasureItem> DOWL_Sunday { get; set; } = new List<TreasureItem>();
12023
12024 [JsonProperty(PropertyName = "Percent Increase On Monday")]
12025 public decimal PercentIncreaseOnMonday { get; set; }
12026
12027 [JsonProperty(PropertyName = "Percent Increase On Tuesday")]
12028 public decimal PercentIncreaseOnTuesday { get; set; }
12029
12030 [JsonProperty(PropertyName = "Percent Increase On Wednesday")]
12031 public decimal PercentIncreaseOnWednesday { get; set; }
12032
12033 [JsonProperty(PropertyName = "Percent Increase On Thursday")]
12034 public decimal PercentIncreaseOnThursday { get; set; }
12035
12036 [JsonProperty(PropertyName = "Percent Increase On Friday")]
12037 public decimal PercentIncreaseOnFriday { get; set; }
12038
12039 [JsonProperty(PropertyName = "Percent Increase On Saturday")]
12040 public decimal PercentIncreaseOnSaturday { get; set; }
12041
12042 [JsonProperty(PropertyName = "Percent Increase On Sunday")]
12043 public decimal PercentIncreaseOnSunday { get; set; }
12044
12045 [JsonProperty(PropertyName = "Loot (Easy Difficulty)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12046 public List<TreasureItem> LootEasy { get; set; } = new List<TreasureItem>();
12047
12048 [JsonProperty(PropertyName = "Loot (Medium Difficulty)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12049 public List<TreasureItem> LootMedium { get; set; } = new List<TreasureItem>();
12050
12051 [JsonProperty(PropertyName = "Loot (Hard Difficulty)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12052 public List<TreasureItem> LootHard { get; set; } = new List<TreasureItem>();
12053
12054 [JsonProperty(PropertyName = "Loot (Expert Difficulty)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12055 public List<TreasureItem> LootExpert { get; set; } = new List<TreasureItem>();
12056
12057 [JsonProperty(PropertyName = "Loot (Nightmare Difficulty)", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12058 public List<TreasureItem> LootNightmare { get; set; } = new List<TreasureItem>();
12059
12060 [JsonProperty(PropertyName = "Loot", ObjectCreationHandling = ObjectCreationHandling.Replace)]
12061 public List<TreasureItem> Loot { get; set; } = DefaultLoot;
12062 }
12063
12064 public class TruePVESettings
12065 {
12066 [JsonProperty(PropertyName = "Allow PVP Server-Wide During Events")]
12067 public bool ServerWidePVP { get; set; }
12068 }
12069
12070 public class UILockoutSettings
12071 {
12072 [JsonProperty(PropertyName = "Enabled")]
12073 public bool Enabled { get; set; } = true;
12074
12075 [JsonProperty(PropertyName = "Easy Anchor Min")]
12076 public string EasyMin { get; set; } = "0.838 0.285";
12077
12078 [JsonProperty(PropertyName = "Easy Anchor Max")]
12079 public string EasyMax { get; set; } = "0.883 0.320";
12080
12081 [JsonProperty(PropertyName = "Medium Anchor Min")]
12082 public string MediumMin { get; set; } = "0.893 0.285";
12083
12084 [JsonProperty(PropertyName = "Medium Anchor Max")]
12085 public string MediumMax { get; set; } = "0.936 0.320";
12086
12087 [JsonProperty(PropertyName = "Hard Anchor Min")]
12088 public string HardMin { get; set; } = "0.946 0.285";
12089
12090 [JsonProperty(PropertyName = "Hard Anchor Max")]
12091 public string HardMax { get; set; } = "0.986 0.320";
12092
12093 [JsonProperty(PropertyName = "Panel Alpha")]
12094 public float Alpha { get; set; } = 1f;
12095 }
12096
12097 public class UISettings
12098 {
12099 [JsonProperty(PropertyName = "Lockouts")]
12100 public UILockoutSettings Lockout { get; set; } = new UILockoutSettings();
12101
12102 [JsonProperty(PropertyName = "Enabled")]
12103 public bool Enabled { get; set; } = true;
12104
12105 [JsonProperty(PropertyName = "Anchor Min")]
12106 public string AnchorMin { get; set; } = "0.838 0.249";
12107
12108 [JsonProperty(PropertyName = "Anchor Max")]
12109 public string AnchorMax { get; set; } = "0.986 0.284";
12110
12111 [JsonProperty(PropertyName = "Font Size")]
12112 public int FontSize { get; set; } = 18;
12113
12114 [JsonProperty(PropertyName = "Panel Alpha")]
12115 public float Alpha { get; set; } = 1f;
12116
12117 [JsonProperty(PropertyName = "Panel Color")]
12118 public string PanelColor { get; set; } = "#000000";
12119
12120 [JsonProperty(PropertyName = "PVP Color")]
12121 public string ColorPVP { get; set; } = "#FF0000";
12122
12123 [JsonProperty(PropertyName = "PVE Color")]
12124 public string ColorPVE { get; set; } = "#008000";
12125
12126 [JsonProperty(PropertyName = "Show Containers Left")]
12127 public bool Containers { get; set; }
12128
12129 [JsonProperty(PropertyName = "Show Time Left")]
12130 public bool Time { get; set; } = true;
12131 }
12132
12133 public class WeaponTypeStateSettings
12134 {
12135 [JsonProperty(PropertyName = "AutoTurret")]
12136 public bool AutoTurret { get; set; } = true;
12137
12138 [JsonProperty(PropertyName = "FlameTurret")]
12139 public bool FlameTurret { get; set; } = true;
12140
12141 [JsonProperty(PropertyName = "FogMachine")]
12142 public bool FogMachine { get; set; } = true;
12143
12144 [JsonProperty(PropertyName = "GunTrap")]
12145 public bool GunTrap { get; set; } = true;
12146
12147 [JsonProperty(PropertyName = "SamSite")]
12148 public bool SamSite { get; set; } = true;
12149 }
12150
12151 public class WeaponTypeAmountSettings
12152 {
12153 [JsonProperty(PropertyName = "AutoTurret")]
12154 public int AutoTurret { get; set; } = 256;
12155
12156 [JsonProperty(PropertyName = "FlameTurret")]
12157 public int FlameTurret { get; set; } = 256;
12158
12159 [JsonProperty(PropertyName = "FogMachine")]
12160 public int FogMachine { get; set; } = 5;
12161
12162 [JsonProperty(PropertyName = "GunTrap")]
12163 public int GunTrap { get; set; } = 128;
12164
12165 [JsonProperty(PropertyName = "SamSite")]
12166 public int SamSite { get; set; } = 24;
12167 }
12168
12169 public class WeaponSettings
12170 {
12171 [JsonProperty(PropertyName = "Infinite Ammo")]
12172 public WeaponTypeStateSettings InfiniteAmmo { get; set; } = new WeaponTypeStateSettings();
12173
12174 [JsonProperty(PropertyName = "Ammo")]
12175 public WeaponTypeAmountSettings Ammo { get; set; } = new WeaponTypeAmountSettings();
12176
12177 [JsonProperty(PropertyName = "Fog Machine Allows Motion Toggle")]
12178 public bool FogMotion { get; set; }
12179
12180 [JsonProperty(PropertyName = "SamSite Repairs Every X Minutes (0.0 = disabled)")]
12181 public float SamSiteRepair { get; set; } = 5f;
12182
12183 [JsonProperty(PropertyName = "SamSite Range (350.0 = Rust default)")]
12184 public float SamSiteRange { get; set; } = 75f;
12185 }
12186
12187 public class Configuration
12188 {
12189 [JsonProperty(PropertyName = "Settings")]
12190 public PluginSettings Settings = new PluginSettings();
12191
12192 [JsonProperty(PropertyName = "Event Messages")]
12193 public EventMessageSettings EventMessages = new EventMessageSettings();
12194
12195 [JsonProperty(PropertyName = "GUIAnnouncements")]
12196 public GUIAnnouncementSettings GUIAnnouncement = new GUIAnnouncementSettings();
12197
12198 [JsonProperty(PropertyName = "Lusty Map")]
12199 public LustyMapSettings LustyMap = new LustyMapSettings();
12200
12201 [JsonProperty(PropertyName = "Raidable Bases")]
12202 public RaidableBaseSettings RaidableBases = new RaidableBaseSettings();
12203
12204 [JsonProperty(PropertyName = "Ranked Ladder")]
12205 public RankedLadderSettings RankedLadder = new RankedLadderSettings();
12206
12207 [JsonProperty(PropertyName = "Skins")]
12208 public SkinSettings Skins = new SkinSettings();
12209
12210 [JsonProperty(PropertyName = "Treasure")]
12211 public TreasureSettings Treasure = new TreasureSettings();
12212
12213 [JsonProperty(PropertyName = "TruePVE")]
12214 public TruePVESettings TruePVE = new TruePVESettings();
12215
12216 [JsonProperty(PropertyName = "UI")]
12217 public UISettings UI = new UISettings();
12218
12219 [JsonProperty(PropertyName = "Weapons")]
12220 public WeaponSettings Weapons = new WeaponSettings();
12221 }
12222
12223 private bool configLoaded = false;
12224
12225 protected override void LoadConfig()
12226 {
12227 base.LoadConfig();
12228
12229 try
12230 {
12231 _config = Config.ReadObject<Configuration>();
12232 }
12233 catch (JsonException ex)
12234 {
12235 Puts(ex.Message);
12236 PrintError("Your configuration file contains a json error, shown above. Please fix this.");
12237 return;
12238 }
12239 catch (Exception ex)
12240 {
12241 Puts(ex.Message);
12242 LoadDefaultConfig();
12243 }
12244
12245 if (_config == null)
12246 {
12247 Puts("Config is null");
12248 LoadDefaultConfig();
12249 }
12250
12251 configLoaded = true;
12252
12253 if (string.IsNullOrEmpty(_config.LustyMap.IconFile) || string.IsNullOrEmpty(_config.LustyMap.IconName))
12254 {
12255 _config.LustyMap.Enabled = false;
12256 }
12257
12258 if (_config.GUIAnnouncement.TintColor.ToLower() == "black")
12259 {
12260 _config.GUIAnnouncement.TintColor = "grey";
12261 }
12262
12263 SaveConfig();
12264 Config.WriteObject(_config, false, $"{Interface.Oxide.ConfigDirectory}/{Name}.backup.json");
12265 }
12266
12267 private const string rankLadderPermission = "raidablebases.th";
12268 private const string rankLadderGroup = "raidhunter";
12269 private const string adminPermission = "raidablebases.allow";
12270 private const string drawPermission = "raidablebases.ddraw";
12271 private const string mapPermission = "raidablebases.mapteleport";
12272 private const string canBypassPermission = "raidablebases.canbypass";
12273 private const string bypassBlockPermission = "raidablebases.blockbypass";
12274 private const string banPermission = "raidablebases.banned";
12275
12276 public static List<TreasureItem> TreasureLoot
12277 {
12278 get
12279 {
12280 if (_config.Treasure.UseDOWL)
12281 {
12282 switch (DateTime.Now.DayOfWeek)
12283 {
12284 case DayOfWeek.Monday:
12285 {
12286 if (_config.Treasure.DOWL_Monday.Count > 0)
12287 {
12288 return new List<TreasureItem>(_config.Treasure.DOWL_Monday);
12289 }
12290 break;
12291 }
12292 case DayOfWeek.Tuesday:
12293 {
12294 if (_config.Treasure.DOWL_Tuesday.Count > 0)
12295 {
12296 return new List<TreasureItem>(_config.Treasure.DOWL_Tuesday);
12297 }
12298 break;
12299 }
12300 case DayOfWeek.Wednesday:
12301 {
12302 if (_config.Treasure.DOWL_Wednesday.Count > 0)
12303 {
12304 return new List<TreasureItem>(_config.Treasure.DOWL_Wednesday);
12305 }
12306 break;
12307 }
12308 case DayOfWeek.Thursday:
12309 {
12310 if (_config.Treasure.DOWL_Thursday.Count > 0)
12311 {
12312 return new List<TreasureItem>(_config.Treasure.DOWL_Thursday);
12313 }
12314 break;
12315 }
12316 case DayOfWeek.Friday:
12317 {
12318 if (_config.Treasure.DOWL_Friday.Count > 0)
12319 {
12320 return new List<TreasureItem>(_config.Treasure.DOWL_Friday);
12321 }
12322 break;
12323 }
12324 case DayOfWeek.Saturday:
12325 {
12326 if (_config.Treasure.DOWL_Saturday.Count > 0)
12327 {
12328 return new List<TreasureItem>(_config.Treasure.DOWL_Saturday);
12329 }
12330 break;
12331 }
12332 case DayOfWeek.Sunday:
12333 {
12334 if (_config.Treasure.DOWL_Sunday.Count > 0)
12335 {
12336 return new List<TreasureItem>(_config.Treasure.DOWL_Sunday);
12337 }
12338 break;
12339 }
12340 }
12341 }
12342
12343 return new List<TreasureItem>(_config.Treasure.Loot);
12344 }
12345 }
12346
12347 protected override void SaveConfig() => Config.WriteObject(_config);
12348
12349 protected override void LoadDefaultConfig()
12350 {
12351 _config = new Configuration();
12352 Puts("Loaded default configuration file");
12353 }
12354
12355#endregion
12356
12357#region UI
12358
12359 public class UI // Credits: Absolut & k1lly0u
12360 {
12361 private static CuiElementContainer CreateElementContainer(string panelName, string color, string aMin, string aMax, bool cursor = false, string parent = "Overlay")
12362 {
12363 var NewElement = new CuiElementContainer
12364 {
12365 {
12366 new CuiPanel
12367 {
12368 Image =
12369 {
12370 Color = color
12371 },
12372 RectTransform =
12373 {
12374 AnchorMin = aMin,
12375 AnchorMax = aMax
12376 },
12377 CursorEnabled = cursor
12378 },
12379 new CuiElement().Parent = parent,
12380 panelName
12381 }
12382 };
12383 return NewElement;
12384 }
12385
12386 private static void CreateLabel(ref CuiElementContainer container, string panel, string color, string text, int size, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter)
12387 {
12388 container.Add(new CuiLabel
12389 {
12390 Text =
12391 {
12392 Color = color,
12393 FontSize = size,
12394 Align = align,
12395 FadeIn = 1.0f,
12396 Text = text
12397 },
12398 RectTransform =
12399 {
12400 AnchorMin = aMin,
12401 AnchorMax = aMax
12402 }
12403 },
12404 panel);
12405 }
12406
12407 private static string Color(string hexColor, float a = 1.0f)
12408 {
12409 hexColor = hexColor.TrimStart('#');
12410 int r = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
12411 int g = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
12412 int b = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
12413 return $"{(double)r / 255} {(double)g / 255} {(double)b / 255} {a}";
12414 }
12415
12416 public static void DestroyStatusUI(BasePlayer player)
12417 {
12418 if (player.IsValid() && player.IsConnected && Players.ContainsKey(player))
12419 {
12420 CuiHelper.DestroyUi(player, StatusPanelName);
12421 Players.Remove(player);
12422 }
12423 }
12424
12425 public static void DestroyLockoutUI(BasePlayer player)
12426 {
12427 if (player.IsValid() && player.IsConnected && Lockouts.Contains(player))
12428 {
12429 CuiHelper.DestroyUi(player, EasyPanelName);
12430 CuiHelper.DestroyUi(player, MediumPanelName);
12431 CuiHelper.DestroyUi(player, HardPanelName);
12432 Lockouts.Remove(player);
12433 }
12434 }
12435
12436 public static void DestroyAllLockoutUI()
12437 {
12438 foreach (var player in new List<BasePlayer>(Lockouts))
12439 {
12440 DestroyLockoutUI(player);
12441 }
12442 }
12443
12444 private static void Create(BasePlayer player, RaidableBase raid, string panelName, string text, string color, string panelColor, string aMin, string aMax)
12445 {
12446 var element = CreateElementContainer(panelName, panelColor, aMin, aMax, false, "Hud");
12447
12448 CreateLabel(ref element, panelName, Color(color), text, _config.UI.FontSize, "0 0", "1 1");
12449 CuiHelper.AddUi(player, element);
12450
12451 if (!Players.ContainsKey(player))
12452 {
12453 Players.Add(player, raid);
12454 }
12455 }
12456
12457 private static void Create(BasePlayer player, string panelName, string text, string color, string panelColor, string aMin, string aMax)
12458 {
12459 var element = CreateElementContainer(panelName, panelColor, aMin, aMax, false, "Hud");
12460
12461 CreateLabel(ref element, panelName, Color(color), text, _config.UI.FontSize, "0 0", "1 1");
12462 CuiHelper.AddUi(player, element);
12463
12464 if (!Lockouts.Contains(player))
12465 {
12466 Lockouts.Add(player);
12467 }
12468 }
12469
12470 private static void ShowStatus(RaidableBase raid, BasePlayer player)
12471 {
12472 string zone = raid.AllowPVP ? Backbone.GetMessage("PVP ZONE") : Backbone.GetMessage("PVE ZONE");
12473 int lootAmount = 0;
12474 float seconds = raid.despawnTime - Time.realtimeSinceStartup;
12475 string despawnText = _config.Settings.Management.DespawnMinutesInactive > 0 && seconds > 0 ? Math.Floor(TimeSpan.FromSeconds(seconds).TotalMinutes).ToString() : null;
12476 string text;
12477
12478 foreach (var x in raid._containers)
12479 {
12480 if (x == null || x.IsDestroyed) continue;
12481 lootAmount += x.inventory.itemList.Count;
12482 }
12483
12484 if (_config.UI.Containers && _config.UI.Time && !string.IsNullOrEmpty(despawnText))
12485 {
12486 text = Backbone.GetMessage("UIFormat", null, zone, lootAmount, despawnText);
12487 }
12488 else if (_config.UI.Containers)
12489 {
12490 text = Backbone.GetMessage("UIFormatContainers", null, zone, lootAmount);
12491 }
12492 else if (_config.UI.Time && !string.IsNullOrEmpty(despawnText))
12493 {
12494 text = Backbone.GetMessage("UIFormatMinutes", null, zone, despawnText);
12495 }
12496 else text = zone;
12497
12498 Create(player, raid, StatusPanelName, text, raid.AllowPVP ? _config.UI.ColorPVP : _config.UI.ColorPVE, Color(_config.UI.PanelColor, _config.UI.Alpha), _config.UI.AnchorMin, _config.UI.AnchorMax);
12499 }
12500
12501 private static void ShowLockouts(BasePlayer player)
12502 {
12503 Lockout lo;
12504 if (!Backbone.Data.Lockouts.TryGetValue(player.UserIDString, out lo))
12505 {
12506 Backbone.Data.Lockouts[player.UserIDString] = lo = new Lockout();
12507 }
12508
12509 double easyTime = RaidableBase.GetLockoutTime(RaidableMode.Easy, lo, player.UserIDString);
12510 double mediumTime = RaidableBase.GetLockoutTime(RaidableMode.Medium, lo, player.UserIDString);
12511 double hardTime = RaidableBase.GetLockoutTime(RaidableMode.Hard, lo, player.UserIDString);
12512
12513 if (easyTime <= 0 && mediumTime <= 0 && hardTime <= 0)
12514 {
12515 return;
12516 }
12517
12518 string easy = Math.Floor(TimeSpan.FromSeconds(easyTime).TotalMinutes).ToString();
12519 string medium = Math.Floor(TimeSpan.FromSeconds(mediumTime).TotalMinutes).ToString();
12520 string hard = Math.Floor(TimeSpan.FromSeconds(hardTime).TotalMinutes).ToString();
12521 string green = Color("#008000", _config.UI.Lockout.Alpha);
12522 string yellow = Color("#FFFF00", _config.UI.Lockout.Alpha);
12523 string red = Color("#FF0000", _config.UI.Lockout.Alpha);
12524
12525 Create(player, EasyPanelName, Backbone.GetMessage("UIFormatLockoutMinutes", null, easy), "#000000", green, _config.UI.Lockout.EasyMin, _config.UI.Lockout.EasyMax);
12526 Create(player, MediumPanelName, Backbone.GetMessage("UIFormatLockoutMinutes", null, medium), "#000000", yellow, _config.UI.Lockout.MediumMin, _config.UI.Lockout.MediumMax);
12527 Create(player, HardPanelName, Backbone.GetMessage("UIFormatLockoutMinutes", null, hard), "#000000", red, _config.UI.Lockout.HardMin, _config.UI.Lockout.HardMax);
12528 }
12529
12530 public static void Update(RaidableBase raid, BasePlayer player)
12531 {
12532 if (_config == null || raid == null)
12533 {
12534 return;
12535 }
12536
12537 if (!IsValid(player) || !player.IsConnected)
12538 {
12539 Players.Remove(player);
12540 return;
12541 }
12542
12543 if (!_config.UI.Enabled || raid == null || !raid.intruders.Contains(player))
12544 {
12545 DestroyStatusUI(player);
12546 DestroyLockoutUI(player);
12547 return;
12548 }
12549
12550 var uii = Get(player.UserIDString);
12551
12552 if (!uii.Enabled || (!uii.Status && !uii.Lockouts))
12553 {
12554 return;
12555 }
12556
12557 DestroyStatusUI(player);
12558
12559 if (uii.Status)
12560 {
12561 ShowStatus(raid, player);
12562 }
12563
12564 if (uii.Lockouts)
12565 {
12566 Update(player, false);
12567 }
12568
12569 player.Invoke(() => Update(raid, player), 60f);
12570 }
12571
12572 public static void Update(BasePlayer player, bool invoke = true)
12573 {
12574 if (_config == null || (!_config.UI.Lockout.Enabled && !Lockouts.Contains(player)))
12575 {
12576 return;
12577 }
12578
12579 if (!IsValid(player) || !player.IsConnected)
12580 {
12581 Lockouts.Remove(player);
12582 return;
12583 }
12584
12585 if (!_config.UI.Enabled)
12586 {
12587 DestroyLockoutUI(player);
12588 return;
12589 }
12590
12591 var uii = Get(player.UserIDString);
12592
12593 if (!uii.Enabled || !uii.Lockouts || !_config.UI.Lockout.Enabled)
12594 {
12595 DestroyLockoutUI(player);
12596 return;
12597 }
12598
12599 DestroyLockoutUI(player);
12600 ShowLockouts(player);
12601
12602 if (invoke) player.Invoke(() => Update(player), 60f);
12603 }
12604
12605 public static void Update(RaidableBase raid)
12606 {
12607 var _players = new Dictionary<BasePlayer, RaidableBase>(Players);
12608
12609 foreach (var player in _players)
12610 {
12611 if (!player.Key.IsValid() || !player.Key.IsConnected || player.Value == null)
12612 {
12613 Players.Remove(player.Key);
12614 }
12615 else if (player.Value == raid)
12616 {
12617 Update(raid, player.Key);
12618 }
12619 }
12620
12621 _players.Clear();
12622 }
12623
12624 public static Info Get(string playerId)
12625 {
12626 Info uii;
12627 if (!Backbone.Data.UI.TryGetValue(playerId, out uii))
12628 {
12629 Backbone.Data.UI[playerId] = uii = new UI.Info();
12630 }
12631
12632 return uii;
12633 }
12634
12635 private const string StatusPanelName = "RB_UI_Status";
12636 private const string EasyPanelName = "RB_UI_Easy";
12637 private const string MediumPanelName = "RB_UI_Medium";
12638 private const string HardPanelName = "RB_UI_Hard";
12639
12640 public static Dictionary<BasePlayer, RaidableBase> Players { get; set; } = new Dictionary<BasePlayer, RaidableBase>();
12641 public static List<BasePlayer> Lockouts { get; set; } = new List<BasePlayer>();
12642
12643 public class Info
12644 {
12645 public bool Enabled { get; set; } = true;
12646 public bool Lockouts { get; set; } = true;
12647 public bool Status { get; set; } = true;
12648 }
12649 }
12650
12651 private void CommandUI(IPlayer p, string command, string[] args)
12652 {
12653 if (p.IsServer)
12654 {
12655 return;
12656 }
12657
12658 var uii = UI.Get(p.Id);
12659 var player = p.Object as BasePlayer;
12660 var raid = RaidableBase.Get(player.transform.position);
12661
12662 if (args.Length == 0)
12663 {
12664 uii.Enabled = !uii.Enabled;
12665 if (!uii.Enabled)
12666 {
12667 UI.DestroyStatusUI(player);
12668 UI.DestroyLockoutUI(player);
12669 }
12670 else
12671 {
12672 UI.Update(raid, player);
12673 UI.Update(player, false);
12674 }
12675 return;
12676 }
12677
12678 switch (args[0].ToLower())
12679 {
12680 case "lockouts":
12681 {
12682 uii.Lockouts = !uii.Lockouts;
12683 UI.Update(player, uii.Lockouts);
12684 return;
12685 }
12686 case "status":
12687 {
12688 uii.Status = !uii.Status;
12689 UI.Update(raid, player);
12690 return;
12691 }
12692 }
12693 }
12694
12695#endregion UI
12696 }
12697}
12698