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