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