· 4 years ago · Jun 08, 2021, 02:46 PM
1//Requires: ZoneManager
2
3using System;
4using System.Collections;
5using System.Collections.Generic;
6using System.Globalization;
7using System.Linq;
8using System.Reflection;
9using System.Text;
10using Facepunch;
11using Newtonsoft.Json;
12using Newtonsoft.Json.Converters;
13using Oxide.Core;
14using Oxide.Core.Libraries.Covalence;
15using Oxide.Core.Plugins;
16using UnityEngine;
17
18namespace Oxide.Plugins
19{
20 [Info("Dynamic PVP", "CatMeat/Arainrr", "4.2.7", ResourceId = 2728)]
21 [Description("Creates temporary PvP zones on certain actions/events")]
22 public class DynamicPVP : RustPlugin
23 {
24 #region Fields
25
26 [PluginReference] private readonly Plugin ZoneManager, TruePVE, NextGenPVE, BotSpawn;
27
28 private const string PERMISSION_ADMIN = "dynamicpvp.admin";
29 private const string PREFAB_SPHERE = "assets/prefabs/visualization/sphere.prefab";
30 private const string ZONE_NAME = "DynamicPVP";
31 private static object True = true, False = false;
32
33 private readonly Dictionary<string, Timer> eventTimers = new Dictionary<string, Timer>();
34 private readonly Dictionary<ulong, LeftZone> pvpDelays = new Dictionary<ulong, LeftZone>();
35 private readonly Dictionary<string, string> activeDynamicZones = new Dictionary<string, string>();//ID -> EventName
36
37 private bool dataChanged;
38 private Vector3 oilRigPosition;
39 private Vector3 largeOilRigPosition;
40 private Coroutine createEventsCoroutine;
41
42 private class LeftZone : Pool.IPooled
43 {
44 public string zoneID;
45 public Timer zoneTimer;
46
47 public void EnterPool()
48 {
49 zoneID = null;
50 zoneTimer?.Destroy();
51 zoneTimer = null;
52 }
53
54 public void LeavePool()
55 {
56 }
57 }
58
59 [Flags]
60 [JsonConverter(typeof(StringEnumConverter))]
61 private enum PVPDelayFlags
62 {
63 None = 0,
64 ZonePlayersCanDamageDelayedPlayers = 1,
65 DelayedPlayersCanDamageZonePlayers = 1 << 1,
66 DelayedPlayersCanDamageDelayedPlayers = 1 << 2,
67 }
68
69 private enum GeneralEventType
70 {
71 Bradley,
72 Helicopter,
73 SupplyDrop,
74 SupplySignal,
75 CargoShip,
76 HackableCrate,
77 ExcavatorIgnition,
78 }
79
80 #endregion Fields
81
82 #region Oxide Hooks
83
84 private void Init()
85 {
86 LoadData();
87 permission.RegisterPermission(PERMISSION_ADMIN, this);
88 AddCovalenceCommand(configData.chatS.command, nameof(CmdDynamicPVP));
89 Unsubscribe(nameof(OnEntitySpawned));
90 Unsubscribe(nameof(OnCargoPlaneSignaled));
91 Unsubscribe(nameof(OnCrateHack));
92 Unsubscribe(nameof(OnDieselEngineToggled));
93 Unsubscribe(nameof(OnEntityDeath));
94 Unsubscribe(nameof(OnSupplyDropLanded));
95 Unsubscribe(nameof(OnEntityKill));
96 Unsubscribe(nameof(OnPlayerCommand));
97 Unsubscribe(nameof(OnServerCommand));
98 Unsubscribe(nameof(CanEntityTakeDamage));
99 Unsubscribe(nameof(OnEnterZone));
100 Unsubscribe(nameof(OnExitZone));
101 }
102
103 private void OnServerInitialized()
104 {
105 //Pool.FillBuffer<LeftZone>();
106 DeleteOldDynamicZones();
107 createEventsCoroutine = ServerMgr.Instance.StartCoroutine(CreateMonumentEvents());
108 if (configData.generalEvents.excavatorIgnition.enabled)
109 {
110 Subscribe(nameof(OnDieselEngineToggled));
111 }
112 if (configData.generalEvents.patrolHelicopter.enabled || configData.generalEvents.bradleyAPC.enabled)
113 {
114 Subscribe(nameof(OnEntityDeath));
115 }
116 if (configData.generalEvents.supplySignal.enabled || configData.generalEvents.timedSupply.enabled)
117 {
118 Subscribe(nameof(OnCargoPlaneSignaled));
119 }
120 if (configData.generalEvents.hackableCrate.enabled && !configData.generalEvents.hackableCrate.startWhenSpawned)
121 {
122 Subscribe(nameof(OnCrateHack));
123 }
124 if (configData.generalEvents.timedSupply.enabled && !configData.generalEvents.timedSupply.startWhenSpawned ||
125 configData.generalEvents.supplySignal.enabled && !configData.generalEvents.supplySignal.startWhenSpawned)
126 {
127 Subscribe(nameof(OnSupplyDropLanded));
128 }
129 if (configData.generalEvents.timedSupply.enabled && configData.generalEvents.timedSupply.startWhenSpawned ||
130 configData.generalEvents.supplySignal.enabled && configData.generalEvents.supplySignal.startWhenSpawned ||
131 configData.generalEvents.hackableCrate.enabled && configData.generalEvents.hackableCrate.startWhenSpawned)
132 {
133 Subscribe(nameof(OnEntitySpawned));
134 }
135
136 if (configData.generalEvents.hackableCrate.enabled && configData.generalEvents.hackableCrate.stopWhenKilled ||
137 configData.generalEvents.supplySignal.enabled && configData.generalEvents.supplySignal.stopWhenKilled ||
138 configData.generalEvents.hackableCrate.enabled && configData.generalEvents.hackableCrate.stopWhenKilled)
139 {
140 Subscribe(nameof(OnEntityKill));
141 }
142 if (configData.generalEvents.cargoShip.enabled)
143 {
144 Subscribe(nameof(OnEntitySpawned));
145 Subscribe(nameof(OnEntityKill));
146 foreach (var serverEntity in BaseNetworkable.serverEntities)
147 {
148 var cargoShip = serverEntity as CargoShip;
149 if (cargoShip == null) continue;
150 OnEntitySpawned(cargoShip);
151 }
152 }
153 }
154
155 private void Unload()
156 {
157 if (createEventsCoroutine != null)
158 {
159 ServerMgr.Instance.StopCoroutine(createEventsCoroutine);
160 }
161
162 if (activeDynamicZones.Count > 0)
163 {
164 PrintDebug($"Deleting {activeDynamicZones.Count} active zones.", true);
165 foreach (var entry in activeDynamicZones.ToArray())
166 {
167 DeleteDynamicZone(entry.Key);
168 }
169 }
170
171 var leftZones = pvpDelays.Values.ToArray();
172 for (var i = leftZones.Length - 1; i >= 0; i--)
173 {
174 var value = leftZones[i];
175 Pool.Free(ref value);
176 }
177
178 foreach (var evenTimer in eventTimers.Values)
179 {
180 evenTimer?.Destroy();
181 }
182
183 var spheres = zoneSpheres.Values.ToArray();
184 for (var i = spheres.Length - 1; i >= 0; i--)
185 {
186 var sphereEntities = spheres[i];
187 foreach (var sphereEntity in sphereEntities)
188 {
189 if (sphereEntity != null && !sphereEntity.IsDestroyed)
190 sphereEntity.KillMessage();
191 }
192 Pool.FreeList(ref sphereEntities);
193 }
194
195 SaveData();
196 SaveDebug();
197 Pool.directory.Remove(typeof(LeftZone));
198 True = False = null;
199 }
200
201 private void OnServerSave() => timer.Once(UnityEngine.Random.Range(0f, 60f), () =>
202 {
203 SaveDebug();
204 if (dataChanged)
205 {
206 SaveData();
207 dataChanged = false;
208 }
209 });
210
211 private void OnPlayerRespawned(BasePlayer player)
212 {
213 if (player == null || !player.userID.IsSteamId()) return;
214 TryRemovePVPDelay(player.userID, player.displayName);
215 }
216
217 #endregion Oxide Hooks
218
219 #region Methods
220
221 private void TryRemoveEventTimer(string zoneID)
222 {
223 Timer value;
224 if (eventTimers.TryGetValue(zoneID, out value))
225 {
226 value?.Destroy();
227 eventTimers.Remove(zoneID);
228 }
229 }
230
231 private LeftZone GetOrAddPVPDelay(BasePlayer player)
232 {
233 PrintDebug($"Adding {player.displayName} to pvp delay.");
234 LeftZone leftZone;
235 if (pvpDelays.TryGetValue(player.userID, out leftZone))
236 {
237 leftZone.zoneTimer?.Destroy();
238 }
239 else
240 {
241 leftZone = Pool.Get<LeftZone>();
242 pvpDelays.Add(player.userID, leftZone);
243 CheckHooks(true);
244 }
245
246 return leftZone;
247 }
248
249 private void TryRemovePVPDelay(ulong playerID, string playerName)
250 {
251 PrintDebug($"Removing {playerName} from pvp delay.");
252 LeftZone leftZone;
253 if (pvpDelays.TryGetValue(playerID, out leftZone))
254 {
255 pvpDelays.Remove(playerID);
256 Interface.CallHook("OnPlayerRemovedFromPVPDelay", playerID, leftZone.zoneID);
257 Pool.Free(ref leftZone);
258 CheckHooks(true);
259 }
260 }
261
262 private bool CheckEntityOwner(BaseEntity baseEntity)
263 {
264 if (configData.global.checkEntityOwner && baseEntity.OwnerID.IsSteamId())
265 {
266 PrintDebug($"{baseEntity} is owned by the player({baseEntity.OwnerID}). Skipping event creation.");
267 return false;
268 }
269 return true;
270 }
271
272 private bool CanCreateDynamicPVP(string eventName, BaseEntity entity)
273 {
274 if (Interface.CallHook("OnCreateDynamicPVP", eventName, entity) != null)
275 {
276 PrintDebug($"There are other plugins that prevent {eventName} events from being created.", true);
277 return false;
278 }
279 return true;
280 }
281
282 private void CheckHooks(bool pvpDelay = false)
283 {
284 if (pvpDelay)
285 {
286 if (pvpDelays.Count > 0)
287 {
288 Subscribe(nameof(CanEntityTakeDamage));
289 }
290 else
291 {
292 Unsubscribe(nameof(CanEntityTakeDamage));
293 }
294 }
295 else
296 {
297 if (activeDynamicZones.Count > 0)
298 {
299 Subscribe(nameof(OnEnterZone));
300 Subscribe(nameof(OnExitZone));
301 }
302 else
303 {
304 Unsubscribe(nameof(OnEnterZone));
305 Unsubscribe(nameof(OnExitZone));
306 }
307
308 bool hasCommands = false;
309 foreach (var entry in activeDynamicZones)
310 {
311 var baseEventS = GetBaseEventS(entry.Value);
312 if (baseEventS != null)
313 {
314 if (baseEventS.commandList.Count > 0)
315 {
316 hasCommands = true;
317 break;
318 }
319 }
320 }
321 if (hasCommands)
322 {
323 Subscribe(nameof(OnPlayerCommand));
324 Subscribe(nameof(OnServerCommand));
325 }
326 else
327 {
328 Unsubscribe(nameof(OnPlayerCommand));
329 Unsubscribe(nameof(OnServerCommand));
330 }
331 }
332 }
333
334 private BaseEventS GetBaseEventS(string eventName)
335 {
336 if (Enum.IsDefined(typeof(GeneralEventType), eventName))
337 {
338 GeneralEventType generalEventType;
339 if (Enum.TryParse(eventName, true, out generalEventType))
340 {
341 switch (generalEventType)
342 {
343 case GeneralEventType.Bradley: return configData.generalEvents.bradleyAPC;
344 case GeneralEventType.HackableCrate: return configData.generalEvents.hackableCrate;
345 case GeneralEventType.Helicopter: return configData.generalEvents.patrolHelicopter;
346 case GeneralEventType.SupplyDrop: return configData.generalEvents.timedSupply;
347 case GeneralEventType.SupplySignal: return configData.generalEvents.supplySignal;
348 case GeneralEventType.ExcavatorIgnition: return configData.generalEvents.excavatorIgnition;
349 case GeneralEventType.CargoShip: return configData.generalEvents.cargoShip;
350 default:
351 PrintDebug($"ERROR: Unknown GeneralEventType: {generalEventType} | {eventName}.", error: true);
352 break;
353 }
354 }
355 }
356 AutoEventS autoEventS;
357 if (storedData.autoEvents.TryGetValue(eventName, out autoEventS))
358 return autoEventS;
359 TimedEventS timedEventS;
360 if (storedData.timedEvents.TryGetValue(eventName, out timedEventS))
361 return timedEventS;
362 MonumentEventS monumentEventS;
363 if (configData.monumentEvents.TryGetValue(eventName, out monumentEventS))
364 return monumentEventS;
365 PrintDebug($"ERROR: Failed to get base event settings for {eventName}.", error: true);
366 return null;
367 }
368
369 #endregion Methods
370
371 #region Events
372
373 #region General Event
374
375 #region ExcavatorIgnition Event
376
377 private void OnDieselEngineToggled(DieselEngine dieselEngine)
378 {
379 if (dieselEngine == null || dieselEngine.net == null) return;
380 var zoneID = dieselEngine.net.ID.ToString();
381 if (dieselEngine.IsOn())
382 {
383 DeleteDynamicZone(zoneID);
384 HandleGeneralEvent(GeneralEventType.ExcavatorIgnition, dieselEngine, true);
385 }
386 else
387 {
388 HandleDeleteDynamicZone(zoneID);
389 }
390 }
391
392 #endregion ExcavatorIgnition Event
393
394 #region HackableLockedCrate Event
395
396 private void OnEntitySpawned(HackableLockedCrate hackableLockedCrate)
397 {
398 if (hackableLockedCrate == null || hackableLockedCrate.net == null) return;
399 if (!configData.generalEvents.hackableCrate.enabled || !configData.generalEvents.hackableCrate.startWhenSpawned) return;
400 PrintDebug("Trying to create the event when the hackable locked crate is spawned.");
401 NextTick(() => LockedCrateEvent(hackableLockedCrate));
402 }
403
404 private void OnCrateHack(HackableLockedCrate hackableLockedCrate)
405 {
406 if (hackableLockedCrate == null || hackableLockedCrate.net == null) return;
407 PrintDebug("Trying to create the event when the hackable locked crate is starting hack.");
408 NextTick(() => LockedCrateEvent(hackableLockedCrate));
409 }
410
411 private void OnEntityKill(HackableLockedCrate hackableLockedCrate)
412 {
413 if (hackableLockedCrate == null || hackableLockedCrate.net == null) return;
414 if (!configData.generalEvents.hackableCrate.enabled || !configData.generalEvents.hackableCrate.stopWhenKilled) return;
415 HandleDeleteDynamicZone(hackableLockedCrate.net.ID.ToString());
416 }
417
418 private void LockedCrateEvent(HackableLockedCrate hackableLockedCrate)
419 {
420 if (!CheckEntityOwner(hackableLockedCrate))
421 {
422 return;
423 }
424 if (configData.generalEvents.hackableCrate.excludeOilRig && IsOnTheOilRig(hackableLockedCrate))
425 {
426 PrintDebug("The hackable locked crate is on the oil rig. Skipping event creation.");
427 return;
428 }
429 if (configData.generalEvents.hackableCrate.excludeCargoShip && IsOnTheCargoShip(hackableLockedCrate))
430 {
431 PrintDebug("The hackable locked crate is on the cargo ship. Skipping event creation.");
432 return;
433 }
434 HandleGeneralEvent(GeneralEventType.HackableCrate, hackableLockedCrate, true);
435 }
436
437 private bool IsOnTheCargoShip(HackableLockedCrate hackableLockedCrate) =>
438 hackableLockedCrate.GetComponentInParent<CargoShip>() != null;
439
440 private bool IsOnTheOilRig(HackableLockedCrate hackableLockedCrate)
441 {
442 if (oilRigPosition != Vector3.zero &&
443 Vector3Ex.Distance2D(hackableLockedCrate.transform.position, oilRigPosition) < 50f)
444 {
445 return true;
446 }
447
448 if (largeOilRigPosition != Vector3.zero &&
449 Vector3Ex.Distance2D(hackableLockedCrate.transform.position, largeOilRigPosition) < 50f)
450 {
451 return true;
452 }
453 return false;
454 }
455
456 #endregion HackableLockedCrate Event
457
458 #region BaseHelicopter And BradleyAPC Event
459
460 private void OnEntityDeath(BaseHelicopter baseHelicopter, HitInfo info)
461 {
462 if (baseHelicopter == null || baseHelicopter.net == null) return;
463 PatrolHelicopterEvent(baseHelicopter);
464 }
465
466 private void OnEntityDeath(BradleyAPC bradleyApc, HitInfo info)
467 {
468 if (bradleyApc == null || bradleyApc.net == null) return;
469 BradleyApcEvent(bradleyApc);
470 }
471
472 private void PatrolHelicopterEvent(BaseHelicopter baseHelicopter)
473 {
474 if (!configData.generalEvents.patrolHelicopter.enabled) return;
475 PrintDebug("Trying to create the event when the helicopter is dead.");
476 if (!CheckEntityOwner(baseHelicopter))
477 {
478 return;
479 }
480 HandleGeneralEvent(GeneralEventType.Helicopter, baseHelicopter, false);
481 }
482
483 private void BradleyApcEvent(BradleyAPC bradleyAPC)
484 {
485 if (!configData.generalEvents.bradleyAPC.enabled) return;
486 PrintDebug("Trying to create the event when the bradley apc is dead.");
487 if (!CheckEntityOwner(bradleyAPC))
488 {
489 return;
490 }
491 HandleGeneralEvent(GeneralEventType.Bradley, bradleyAPC, false);
492 }
493
494 #endregion BaseHelicopter And BradleyAPC Event
495
496 #region SupplyDrop And SupplySignal Event
497
498 private readonly Dictionary<Vector3, Timer> activeSupplySignals = new Dictionary<Vector3, Timer>();
499
500 private void OnCargoPlaneSignaled(CargoPlane cargoPlane, SupplySignal supplySignal)
501 {
502 NextTick(() =>
503 {
504 if (supplySignal == null || cargoPlane == null) return;
505 var dropPosition = cargoPlane.dropPosition;
506 if (activeSupplySignals.ContainsKey(dropPosition)) return;
507 var value = timer.Once(900f, () => activeSupplySignals.Remove(dropPosition));
508 activeSupplySignals.Add(dropPosition, value);
509 PrintDebug($"A supply signal is thrown at {dropPosition}");
510 });
511 }
512
513 private void OnEntitySpawned(SupplyDrop supplyDrop)
514 {
515 NextTick(() => SupplyDropEvent(supplyDrop, false));
516 }
517
518 private void OnSupplyDropLanded(SupplyDrop supplyDrop)
519 {
520 if (supplyDrop == null || supplyDrop.net == null) return;
521 if (activeDynamicZones.ContainsKey(supplyDrop.net.ID.ToString()))
522 {
523 return;
524 }
525 NextTick(() => SupplyDropEvent(supplyDrop, true));
526 }
527
528 private void OnEntityKill(SupplyDrop supplyDrop)
529 {
530 if (supplyDrop == null || supplyDrop.net == null) return;
531 var zoneID = supplyDrop.net.ID.ToString();
532 string eventName;
533 if (activeDynamicZones.TryGetValue(zoneID, out eventName))
534 {
535 switch (eventName)
536 {
537 case nameof(GeneralEventType.SupplySignal):
538 if (!configData.generalEvents.supplySignal.enabled || !configData.generalEvents.supplySignal.stopWhenKilled)
539 {
540 return;
541 }
542 break;
543
544 case nameof(GeneralEventType.SupplyDrop):
545 if (!configData.generalEvents.timedSupply.enabled || !configData.generalEvents.timedSupply.stopWhenKilled)
546 {
547 return;
548 }
549 break;
550
551 default: return;
552 }
553 HandleDeleteDynamicZone(zoneID);
554 }
555 }
556
557 private void SupplyDropEvent(SupplyDrop supplyDrop, bool isLanded)
558 {
559 if (supplyDrop == null || supplyDrop.net == null) return;
560 PrintDebug($"Trying to create the event when the supply drop is {(isLanded ? "landed" : "spawned")} at {supplyDrop.transform.position}.");
561 if (!CheckEntityOwner(supplyDrop))
562 {
563 return;
564 }
565
566 Action action = null;
567 var isFromSupplySignal = IsProbablySupplySignal(supplyDrop.transform.position, ref action);
568 PrintDebug($"Supply drop is from supply signal: {isFromSupplySignal}");
569 if (isFromSupplySignal)
570 {
571 if (!configData.generalEvents.supplySignal.enabled)
572 {
573 PrintDebug("Event for supply signals disabled. Skipping event creation.");
574 return;
575 }
576
577 if (isLanded ? configData.generalEvents.supplySignal.startWhenSpawned : !configData.generalEvents.supplySignal.startWhenSpawned)
578 {
579 PrintDebug($"{(isLanded ? "Landed" : "Spawned")} for supply signals disabled.");
580 return;
581 }
582 action?.Invoke();
583 HandleGeneralEvent(GeneralEventType.SupplySignal, supplyDrop, true);
584 }
585 else
586 {
587 if (!configData.generalEvents.timedSupply.enabled)
588 {
589 PrintDebug("Event for timed supply disabled. Skipping event creation.");
590 return;
591 }
592 if (isLanded ? configData.generalEvents.timedSupply.startWhenSpawned : !configData.generalEvents.timedSupply.startWhenSpawned)
593 {
594 PrintDebug($"{(isLanded ? "Landed" : "Spawned")} for timed supply disabled.");
595 return;
596 }
597
598 HandleGeneralEvent(GeneralEventType.SupplyDrop, supplyDrop, true);
599 }
600 }
601
602 private bool IsProbablySupplySignal(Vector3 position, ref Action action)
603 {
604 PrintDebug($"Checking {activeSupplySignals.Count} active supply signals");
605 if (activeSupplySignals.Count > 0)
606 {
607 foreach (var entry in activeSupplySignals)
608 {
609 var distance = Vector3Ex.Distance2D(entry.Key, position);
610 PrintDebug($"Found a supply signal at {entry.Key} located {distance}m away.");
611 if (distance <= configData.global.compareRadius)
612 {
613 action = () =>
614 {
615 entry.Value?.Destroy();
616 activeSupplySignals.Remove(entry.Key);
617 PrintDebug($"Removing Supply signal from active list. Active supply signals remaining: {activeSupplySignals.Count}");
618 };
619 PrintDebug($"Found matching a supply signal.");
620 return true;
621 }
622 }
623 PrintDebug("No matches found, probably from a timed event cargo plane");
624 return false;
625 }
626 PrintDebug("No active signals, must be from a timed event cargo plane");
627 return false;
628 }
629
630 #endregion SupplyDrop And SupplySignal Event
631
632 #region CargoShip Event
633
634 private void OnEntitySpawned(CargoShip cargoShip)
635 {
636 if (cargoShip == null || cargoShip.net == null) return;
637 if (!configData.generalEvents.cargoShip.enabled) return;
638 PrintDebug("Trying to create the event when the cargo ship is spawned.");
639 if (!CheckEntityOwner(cargoShip))
640 {
641 return;
642 }
643 var eventName = GeneralEventType.CargoShip.ToString();
644 if (!CanCreateDynamicPVP(eventName, cargoShip))
645 {
646 return;
647 }
648
649 NextTick(() => HandleParentedEntityEvent(eventName, cargoShip));
650 }
651
652 private void OnEntityKill(CargoShip cargoShip)
653 {
654 if (cargoShip == null || cargoShip.net == null) return;
655 if (!configData.generalEvents.cargoShip.enabled) return;
656 HandleDeleteDynamicZone(cargoShip.net.ID.ToString());
657 }
658
659 #endregion CargoShip Event
660
661 #endregion General Event
662
663 #region Monument Event
664
665 private IEnumerator CreateMonumentEvents()
666 {
667 var changed = false;
668 var createdEvents = new List<string>();
669 var landmarks = typeof(TerrainPath).GetField("Landmarks", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)?.GetValue(TerrainMeta.Path) as List<LandmarkInfo>;
670 foreach (var landmarkInfo in landmarks)
671 {
672 if (!landmarkInfo.shouldDisplayOnMap) continue;
673 var monumentName = landmarkInfo.displayPhrase.english.Replace("\n", "");
674 if (string.IsNullOrEmpty(monumentName)) continue;
675 switch (landmarkInfo.name)
676 {
677 case "OilrigAI": oilRigPosition = landmarkInfo.transform.position; break;
678 case "OilrigAI2": largeOilRigPosition = landmarkInfo.transform.position; break;
679 case "assets/bundled/prefabs/autospawn/monument/harbor/harbor_1.prefab": monumentName += " A"; break;
680 case "assets/bundled/prefabs/autospawn/monument/harbor/harbor_2.prefab": monumentName += " B"; break;
681 //case "assets/bundled/prefabs/autospawn/monument/harbor/fishing_village_b.prefab": monumentName += " A"; break;
682 //case "assets/bundled/prefabs/autospawn/monument/harbor/fishing_village_c.prefab": monumentName += " B"; break;
683 }
684 PrintError($"{monumentName}");
685 MonumentEventS monumentEventS;
686 if (!configData.monumentEvents.TryGetValue(monumentName, out monumentEventS))
687 {
688 changed = true;
689 monumentEventS = new MonumentEventS();
690 configData.monumentEvents.Add(monumentName, monumentEventS);
691 Puts($"A new monument {monumentName} was found and added to the config.");
692 }
693 if (monumentEventS.enabled)
694 {
695 if (HandleMonumentEvent(monumentName, landmarkInfo.transform, monumentEventS))
696 {
697 createdEvents.Add(monumentName);
698 }
699
700 yield return CoroutineEx.waitForSeconds(0.5f);
701 }
702 }
703 if (changed)
704 {
705 SaveConfig();
706 }
707 if (createdEvents.Count > 0)
708 {
709 PrintDebug($"{createdEvents.Count}({string.Join(", ", createdEvents)}) monument events were successfully created.", true);
710 }
711
712 createdEvents.Clear();
713 foreach (var entry in storedData.autoEvents)
714 {
715 if (entry.Value.autoStart)
716 {
717 if (CreateDynamicZone(entry.Key, entry.Value.position, entry.Value.zoneID))
718 {
719 createdEvents.Add(entry.Key);
720 }
721 yield return CoroutineEx.waitForSeconds(0.5f);
722 }
723 }
724 if (createdEvents.Count > 0)
725 {
726 PrintDebug($"{createdEvents.Count}({string.Join(", ", createdEvents)}) auto events were successfully created.", true);
727 }
728 createEventsCoroutine = null;
729 }
730
731 #endregion Monument Event
732
733 #region Chat/Console Command Handler
734
735 private object OnPlayerCommand(BasePlayer player, string command, string[] args) => CheckCommand(player, command, true);
736
737 private object OnServerCommand(ConsoleSystem.Arg arg) => CheckCommand(arg?.Player(), arg?.cmd?.FullName, false);
738
739 private object CheckCommand(BasePlayer player, string command, bool isChat)
740 {
741 if (player == null || string.IsNullOrEmpty(command)) return null;
742 command = command.ToLower().TrimStart('/');
743 if (string.IsNullOrEmpty(command)) return null;
744
745 string[] result = GetPlayerZoneIDs(player);
746 if (result == null || result.Length == 0 || result.Length == 1 && string.IsNullOrEmpty(result[0])) return null;
747
748 foreach (var zoneID in result)
749 {
750 string eventName;
751 if (activeDynamicZones.TryGetValue(zoneID, out eventName))
752 {
753 PrintDebug($"Checking command: {command} , zoneID: {zoneID}");
754 var baseEventS = GetBaseEventS(eventName);
755 if (baseEventS == null || baseEventS.commandList.Count <= 0) continue;
756
757 var commandExist = baseEventS.commandList.Any(entry =>
758 isChat
759 ? entry.StartsWith("/") && entry.Substring(1).Equals(command)
760 : !entry.StartsWith("/") && command.Contains(entry));
761
762 if (baseEventS.useBlacklistCommands)
763 {
764 if (commandExist)
765 {
766 PrintDebug($"Use blacklist, Blocked command: {command}", true);
767 return False;
768 }
769 }
770 else
771 {
772 if (!commandExist)
773 {
774 PrintDebug($"Use whitelist, Blocked command: {command}", true);
775 return False;
776 }
777 }
778 }
779 }
780 return null;
781 }
782
783 #endregion Chat/Console Command Handler
784
785 #endregion Events
786
787 #region DynamicZone Handler
788
789 private void HandleParentedEntityEvent(string eventName, BaseEntity parentEntity, bool delay = true)
790 {
791 if (parentEntity == null || parentEntity.net == null) return;
792 var baseEventS = GetBaseEventS(eventName);
793 if (baseEventS == null) return;
794 if (delay && baseEventS.eventStartDelay > 0f)
795 {
796 timer.Once(baseEventS.eventStartDelay, () => HandleParentedEntityEvent(eventName, parentEntity, false));
797 return;
798 }
799 PrintDebug($"Trying to create parented entity event {eventName} on entity({parentEntity}).");
800 var zoneID = parentEntity.net.ID.ToString();
801 var iParentZone = baseEventS.GetDynamicZoneS() as IParentZone;
802 if (CreateDynamicZone(eventName, parentEntity.transform.position, zoneID, delay: false))
803 {
804 timer.Once(0.25f, () =>
805 {
806 var zone = GetZoneByID(zoneID);
807 PrintDebug($"Gets the zone({zoneID} | {zone}) created by the {eventName} event.", true);
808 if (parentEntity != null && zone != null)
809 {
810 var zoneTransform = zone.transform;
811 zoneTransform.SetParent(parentEntity.transform);
812 zoneTransform.rotation = parentEntity.transform.rotation;
813 zoneTransform.position = iParentZone != null ? parentEntity.transform.TransformPoint(iParentZone.center) : parentEntity.transform.position;
814 PrintDebug($"The zone({zoneID} | {eventName}) was parented to entity({parentEntity}).", true);
815 }
816 else
817 {
818 PrintDebug($"ERROR: The zone({zoneID} | {zone} | {parentEntity}) created by the {eventName} event is empty.", error: true);
819 DeleteDynamicZone(zoneID);
820 }
821 });
822 }
823 }
824
825 private bool HandleMonumentEvent(string eventName, Transform transform, MonumentEventS monumentEventS)
826 {
827 var position = monumentEventS.transformPosition != Vector3.zero ? transform.TransformPoint(monumentEventS.transformPosition) : transform.position;
828 return CreateDynamicZone(eventName, position, monumentEventS.zoneID, monumentEventS.GetDynamicZoneS().ZoneSettings(transform));
829 }
830
831 private bool HandleGeneralEvent(GeneralEventType generalEventType, BaseEntity baseEntity, bool useEntityID)
832 {
833 var eventName = generalEventType.ToString();
834 if (useEntityID && activeDynamicZones.ContainsKey(baseEntity.net.ID.ToString()))
835 {
836 PrintDebug($"The event({eventName} | {baseEntity.net.ID}) created by entity({baseEntity}) already exists");
837 return false;
838 }
839 if (!CanCreateDynamicPVP(eventName, baseEntity))
840 {
841 return false;
842 }
843 var baseEventS = GetBaseEventS(eventName);
844 if (baseEventS == null) return false;
845 var position = baseEntity.transform.position;
846 position.y = TerrainMeta.HeightMap.GetHeight(position);
847 return CreateDynamicZone(eventName, position, useEntityID ? baseEntity.net.ID.ToString() : null, baseEventS.GetDynamicZoneS().ZoneSettings(baseEntity.transform));
848 }
849
850 private bool CreateDynamicZone(string eventName, Vector3 position, string zoneID = "", string[] zoneSettings = null, bool delay = true)
851 {
852 if (position == Vector3.zero)
853 {
854 PrintDebug($"ERROR: Invalid location, zone({eventName}) creation failed. Skipping event creation.", error: true);
855 return false;
856 }
857 var baseEventS = GetBaseEventS(eventName);
858 if (baseEventS == null) return false;
859 if (delay && baseEventS.eventStartDelay > 0f)
860 {
861 timer.Once(baseEventS.eventStartDelay, () => CreateDynamicZone(eventName, position, zoneID, zoneSettings, false));
862 return false;
863 }
864
865 float duration = -1;
866 var iTimedEvent = baseEventS as ITimedEvent;
867 if (iTimedEvent != null)
868 {
869 var iKillEvent = baseEventS as IKillEvent;
870 if (iKillEvent == null || !iKillEvent.stopWhenKilled)
871 {
872 duration = iTimedEvent.duration;
873 }
874 }
875
876 if (string.IsNullOrEmpty(zoneID))
877 {
878 zoneID = DateTime.Now.ToString("HHmmssffff");
879 }
880
881 var dynamicZoneS = baseEventS.GetDynamicZoneS();
882 zoneSettings = zoneSettings ?? dynamicZoneS.ZoneSettings();
883
884 PrintDebug($"Trying create zone: {eventName}({zoneID} | {position}) {(dynamicZoneS is ISphereZone ? $"(Radius: {(dynamicZoneS as ISphereZone).radius}m)" : $"(Size: {(dynamicZoneS as ICubeZone)?.size})")} {(dynamicZoneS is IParentZone ? $"(Center: {(dynamicZoneS as IParentZone).center}) " : null)}(Duration: {duration}s).");
885 var zoneAdded = CreateZone(zoneID, zoneSettings, position);
886 if (zoneAdded)
887 {
888 if (!activeDynamicZones.ContainsKey(zoneID))
889 {
890 activeDynamicZones.Add(zoneID, eventName);
891 CheckHooks();
892 }
893
894 var stringBuilder = Pool.Get<StringBuilder>();
895 var iSphereZone = dynamicZoneS as ISphereZone;
896 var iDomeEvent = baseEventS as IDomeEvent;
897 if (DomeCreateAllowed(iDomeEvent, iSphereZone))
898 {
899 var domeAdded = CreateDome(zoneID, position, iSphereZone.radius, iDomeEvent.domesDarkness);
900 if (!domeAdded) PrintDebug($"ERROR: Dome NOT added for zone({zoneID}).", error: true);
901 else stringBuilder.Append("Dome,");
902 }
903
904 var iBotSpawnEvent = baseEventS as IBotSpawnEvent;
905 if (BotSpawnAllowed(iBotSpawnEvent))
906 {
907 var botsSpawned = SpawnBots(position, iBotSpawnEvent.botProfileName, zoneID);
908 if (!botsSpawned) PrintDebug($"ERROR: Bot NOT spawned for zone({zoneID}).", error: true);
909 else stringBuilder.Append("Bots,");
910 }
911
912 var mappingAdded = CreateMapping(zoneID, baseEventS.mapping);
913 if (!mappingAdded) PrintDebug($"ERROR: Mapping Not added for zone({zoneID}).", error: true);
914 else stringBuilder.Append("Mapping,");
915
916 if (duration > 0f)
917 {
918 TryRemoveEventTimer(zoneID);
919 eventTimers.Add(zoneID, timer.Once(duration, () => HandleDeleteDynamicZone(zoneID)));
920 }
921 PrintDebug($"Created zone({zoneID} | {eventName}) ({stringBuilder.ToString().TrimEnd(',')}).", true);
922 stringBuilder.Clear();
923 Pool.Free(ref stringBuilder);
924 return true;
925 }
926
927 PrintDebug($"ERROR: Create zone({eventName}) failed.", error: true);
928 return false;
929 }
930
931 private void HandleDeleteDynamicZone(string zoneID)
932 {
933 string eventName;
934 if (string.IsNullOrEmpty(zoneID) || !activeDynamicZones.TryGetValue(zoneID, out eventName))
935 {
936 PrintDebug($"ERROR: Invalid zoneID: {zoneID}.", error: true);
937 return;
938 }
939 var baseEventS = GetBaseEventS(eventName);
940 if (baseEventS == null) return;
941 if (baseEventS.eventStopDelay > 0f)
942 {
943 TryRemoveEventTimer(zoneID);
944 if (baseEventS.GetDynamicZoneS() is IParentZone)
945 {
946 var zone = GetZoneByID(zoneID);
947 if (zone != null)
948 {
949 zone.transform.SetParent(null, true);
950 }
951 }
952 eventTimers.Add(zoneID, timer.Once(baseEventS.eventStopDelay, () => DeleteDynamicZone(zoneID)));
953 }
954 else
955 {
956 DeleteDynamicZone(zoneID);
957 }
958 }
959
960 private bool DeleteDynamicZone(string zoneID)
961 {
962 string eventName;
963 if (string.IsNullOrEmpty(zoneID) || !activeDynamicZones.TryGetValue(zoneID, out eventName))
964 {
965 PrintDebug($"ERROR: Invalid zoneID: {zoneID}.", error: true);
966 return false;
967 }
968
969 TryRemoveEventTimer(zoneID);
970 var stringBuilder = Pool.Get<StringBuilder>();
971 var baseEventS = GetBaseEventS(eventName);
972 if (baseEventS == null) return false;
973 var dynamicZoneS = baseEventS.GetDynamicZoneS();
974 if (DomeCreateAllowed(baseEventS as IDomeEvent, dynamicZoneS as ISphereZone))
975 {
976 var domeRemoved = RemoveDome(zoneID);
977 if (!domeRemoved) PrintDebug($"ERROR: Dome NOT removed for zone({zoneID} | {eventName}).", error: true);
978 else stringBuilder.Append("Dome,");
979 }
980
981 if (BotSpawnAllowed(baseEventS as IBotSpawnEvent))
982 {
983 var botsRemoved = KillBots(zoneID);
984 if (!botsRemoved) PrintDebug($"ERROR: Bot NOT killed for zone({zoneID} | {eventName}).", error: true);
985 else stringBuilder.Append("Bots,");
986 }
987
988 var mappingRemoved = RemoveMapping(zoneID);
989 if (!mappingRemoved) PrintDebug($"ERROR: Mapping NOT removed for zone({zoneID} | {eventName}).", error: true);
990 else stringBuilder.Append("Mapping,");
991
992 var zoneRemoved = RemoveZone(zoneID, eventName);
993 if (zoneRemoved)
994 {
995 if (activeDynamicZones.Remove(zoneID))
996 {
997 CheckHooks();
998 }
999 PrintDebug($"Deleted zone({zoneID} | {eventName}) ({stringBuilder.ToString().TrimEnd(',')}).", true);
1000 stringBuilder.Clear();
1001 Pool.Free(ref stringBuilder);
1002 return true;
1003 }
1004 PrintDebug($"ERROR: Delete zone({zoneID} | {eventName} | {stringBuilder.ToString().TrimEnd(',')}) failed.", error: true);
1005 stringBuilder.Clear();
1006 Pool.Free(ref stringBuilder);
1007 return false;
1008 }
1009
1010 private void DeleteOldDynamicZones()
1011 {
1012 var zoneIDs = GetZoneIDs();
1013 if (zoneIDs == null || zoneIDs.Length <= 0) return;
1014 int attempts = 0, successes = 0;
1015 foreach (var zoneID in zoneIDs)
1016 {
1017 string zoneName = GetZoneName(zoneID);
1018 if (zoneName == ZONE_NAME)
1019 {
1020 attempts++;
1021 var zoneRemoved = RemoveZone(zoneID);
1022 if (zoneRemoved) successes++;
1023 RemoveMapping(zoneID);
1024 }
1025 }
1026 PrintDebug($"Deleted {successes} of {attempts} existing DynamicPVP zones", true);
1027 }
1028
1029 #endregion DynamicZone Handler
1030
1031 #region ZoneDome Integration
1032
1033 private readonly Dictionary<string, List<SphereEntity>> zoneSpheres = new Dictionary<string, List<SphereEntity>>();
1034
1035 private static bool DomeCreateAllowed(IDomeEvent iDomeEventS, ISphereZone iSphereZone) => iDomeEventS != null && iDomeEventS.domesEnabled && iSphereZone?.radius > 0f;
1036
1037 private bool CreateDome(string zoneID, Vector3 position, float radius, int darkness)
1038 {
1039 if (radius <= 0) return false;
1040 var sphereEntities = Pool.GetList<SphereEntity>();
1041 for (int i = 0; i < darkness; i++)
1042 {
1043 var sphereEntity = GameManager.server.CreateEntity(PREFAB_SPHERE, position) as SphereEntity;
1044 if (sphereEntity == null) { PrintDebug("ERROR: sphere entity is null", error: true); return false; }
1045 sphereEntity.enableSaving = false;
1046 sphereEntity.Spawn();
1047 sphereEntity.LerpRadiusTo(radius * 2f, radius);
1048 sphereEntities.Add(sphereEntity);
1049 }
1050 zoneSpheres.Add(zoneID, sphereEntities);
1051 return true;
1052 }
1053
1054 private bool RemoveDome(string zoneID)
1055 {
1056 List<SphereEntity> sphereEntities;
1057 if (!zoneSpheres.TryGetValue(zoneID, out sphereEntities)) return false;
1058 foreach (var sphereEntity in sphereEntities)
1059 {
1060 sphereEntity.LerpRadiusTo(0, sphereEntity.currentRadius);
1061 }
1062 timer.Once(5f, () =>
1063 {
1064 foreach (var sphereEntity in sphereEntities)
1065 {
1066 if (sphereEntity != null && !sphereEntity.IsDestroyed)
1067 {
1068 sphereEntity.KillMessage();
1069 }
1070 }
1071 zoneSpheres.Remove(zoneID);
1072 Pool.FreeList(ref sphereEntities);
1073 });
1074 return true;
1075 }
1076
1077 #endregion ZoneDome Integration
1078
1079 #region TruePVE/NextGenPVE Integration
1080
1081 private object CanEntityTakeDamage(BasePlayer victim, HitInfo info)
1082 {
1083 if (info == null || victim == null || !victim.userID.IsSteamId()) return null;
1084 var attacker = info.InitiatorPlayer ?? (info.Initiator != null && info.Initiator.OwnerID.IsSteamId() ? BasePlayer.FindByID(info.Initiator.OwnerID) : null);//The attacker cannot be fully captured
1085 if (attacker == null || !attacker.userID.IsSteamId()) return null;
1086 LeftZone victimLeftZone;
1087 if (pvpDelays.TryGetValue(victim.userID, out victimLeftZone))
1088 {
1089 if (configData.global.pvpDelayFlags.HasFlag(PVPDelayFlags.ZonePlayersCanDamageDelayedPlayers) && !string.IsNullOrEmpty(victimLeftZone.zoneID) && IsPlayerInZone(victimLeftZone, attacker))//ZonePlayer attack DelayedPlayer
1090 {
1091 return True;
1092 }
1093 LeftZone attackerLeftZone;
1094 if (configData.global.pvpDelayFlags.HasFlag(PVPDelayFlags.DelayedPlayersCanDamageDelayedPlayers) && pvpDelays.TryGetValue(attacker.userID, out attackerLeftZone) && victimLeftZone.zoneID == attackerLeftZone.zoneID)//DelayedPlayer attack DelayedPlayer
1095 {
1096 return True;
1097 }
1098
1099 return null;
1100 }
1101 else
1102 {
1103 LeftZone attackerLeftZone;
1104 if (pvpDelays.TryGetValue(attacker.userID, out attackerLeftZone))
1105 {
1106 if (configData.global.pvpDelayFlags.HasFlag(PVPDelayFlags.DelayedPlayersCanDamageZonePlayers) && !string.IsNullOrEmpty(attackerLeftZone.zoneID) && IsPlayerInZone(attackerLeftZone, victim))//DelayedPlayer attack ZonePlayer
1107 {
1108 return True;
1109 }
1110
1111 return null;
1112 }
1113 }
1114 return null;
1115 }
1116
1117 private bool CreateMapping(string zoneID, string mapping)
1118 {
1119 if (TruePVE != null) return (bool)TruePVE.Call("AddOrUpdateMapping", zoneID, mapping);
1120 if (NextGenPVE != null) return (bool)NextGenPVE.Call("AddOrUpdateMapping", zoneID, mapping);
1121 return false;
1122 }
1123
1124 private bool RemoveMapping(string zoneID)
1125 {
1126 if (TruePVE != null) return (bool)TruePVE.Call("RemoveMapping", zoneID);
1127 if (NextGenPVE != null) return (bool)NextGenPVE.Call("RemoveMapping", zoneID);
1128 return false;
1129 }
1130
1131 #endregion TruePVE/NextGenPVE Integration
1132
1133 #region BotSpawn Integration
1134
1135 private bool BotSpawnAllowed(IBotSpawnEvent iBotSpawnEventS)
1136 {
1137 if (BotSpawn == null || iBotSpawnEventS == null || string.IsNullOrEmpty(iBotSpawnEventS.botProfileName)) return false;
1138 return iBotSpawnEventS.botsEnabled;
1139 }
1140
1141 private bool SpawnBots(Vector3 zoneLocation, string zoneProfile, string zoneGroupID)
1142 {
1143 var result = CreateGroupSpawn(zoneLocation, zoneProfile, zoneGroupID);
1144 if (result == null || result.Length < 2)
1145 {
1146 PrintDebug("AddGroupSpawn returned invalid response.");
1147 return false;
1148 }
1149 switch (result[0])
1150 {
1151 case "true": return true;
1152 case "false": return false;
1153 case "error": PrintDebug($"ERROR: AddGroupSpawn failed: {result[1]}", error: true); return false;
1154 }
1155 return false;
1156 }
1157
1158 private bool KillBots(string zoneGroupID)
1159 {
1160 var result = RemoveGroupSpawn(zoneGroupID);
1161 if (result == null || result.Length < 2)
1162 {
1163 PrintDebug("RemoveGroupSpawn returned invalid response.");
1164 return false;
1165 }
1166 if (result[0] == "error")
1167 {
1168 PrintDebug($"ERROR: RemoveGroupSpawn failed: {result[1]}", error: true);
1169 return false;
1170 }
1171 return true;
1172 }
1173
1174 private string[] CreateGroupSpawn(Vector3 location, string profileName, string group) => (string[])BotSpawn?.Call("AddGroupSpawn", location, profileName, group);
1175
1176 private string[] RemoveGroupSpawn(string group) => (string[])BotSpawn?.Call("RemoveGroupSpawn", group);
1177
1178 #endregion BotSpawn Integration
1179
1180 #region ZoneManager Integration
1181
1182 private void OnEnterZone(string zoneID, BasePlayer player)
1183 {
1184 if (player == null || !player.userID.IsSteamId()) return;
1185 string eventName;
1186 if (!activeDynamicZones.TryGetValue(zoneID, out eventName)) return;
1187 PrintDebug($"{player.displayName} has entered a pvp zone({zoneID} | {eventName}).", true);
1188
1189 TryRemovePVPDelay(player.userID, player.displayName);
1190 }
1191
1192 private void OnExitZone(string zoneID, BasePlayer player)
1193 {
1194 if (player == null || !player.userID.IsSteamId()) return;
1195 string eventName;
1196 if (!activeDynamicZones.TryGetValue(zoneID, out eventName)) return;
1197 PrintDebug($"{player.displayName} has left a pvp zone({zoneID} | {eventName}).", true);
1198
1199 var baseEventS = GetBaseEventS(eventName);
1200 if (!baseEventS.pvpDelayEnabled || baseEventS.pvpDelayTime <= 0) return;
1201
1202 var playerID = player.userID;
1203 var playerName = player.displayName;
1204 var leftZone = GetOrAddPVPDelay(player);
1205 leftZone.zoneID = zoneID;
1206 leftZone.zoneTimer = timer.Once(baseEventS.pvpDelayTime, () =>
1207 {
1208 TryRemovePVPDelay(playerID, playerName);
1209 });
1210 Interface.CallHook("OnPlayerAddedToPVPDelay", player.userID, zoneID, baseEventS.pvpDelayTime);
1211 }
1212
1213 private bool CreateZone(string zoneID, string[] zoneArgs, Vector3 zoneLocation) => (bool)ZoneManager.Call("CreateOrUpdateZone", zoneID, zoneArgs, zoneLocation);
1214
1215 private bool RemoveZone(string zoneID, string eventName = "")
1216 {
1217 try
1218 {
1219 return (bool)ZoneManager.Call("EraseZone", zoneID);
1220 }
1221 catch (Exception exception)
1222 {
1223 PrintDebug($"ERROR: EraseZone failed. {exception}");
1224 return true;
1225 }
1226 }
1227
1228 private string[] GetZoneIDs() => (string[])ZoneManager.Call("GetZoneIDs");
1229
1230 private string GetZoneName(string zoneID) => (string)ZoneManager.Call("GetZoneName", zoneID);
1231
1232 private ZoneManager.Zone GetZoneByID(string zoneID) => (ZoneManager.Zone)ZoneManager.Call("GetZoneByID", zoneID);
1233
1234 private string[] GetPlayerZoneIDs(BasePlayer player) => (string[])ZoneManager.Call("GetPlayerZoneIDs", player);
1235
1236 private bool IsPlayerInZone(LeftZone leftZone, BasePlayer player) => (bool)ZoneManager.Call("IsPlayerInZone", leftZone.zoneID, player);
1237
1238 #endregion ZoneManager Integration
1239
1240 #region Debug
1241
1242 private readonly StringBuilder debugStringBuilder = new StringBuilder();
1243
1244 private void PrintDebug(string message, bool warning = false, bool error = false)
1245 {
1246 if (configData.global.debugEnabled)
1247 {
1248 if (error) PrintError(message);
1249 else if (warning) PrintWarning(message);
1250 else Puts(message);
1251 }
1252
1253 if (configData.global.logToFile)
1254 {
1255 debugStringBuilder.AppendLine($"[{DateTime.Now.ToString(CultureInfo.InstalledUICulture)}] | {message}");
1256 }
1257 }
1258
1259 private void SaveDebug()
1260 {
1261 if (!configData.global.logToFile) return;
1262 var debugText = debugStringBuilder.ToString().Trim();
1263 debugStringBuilder.Clear();
1264 if (!string.IsNullOrEmpty(debugText))
1265 {
1266 LogToFile("debug", debugText, this);
1267 }
1268 }
1269
1270 #endregion Debug
1271
1272 #region API
1273
1274 private string[] AllDynamicPVPZones() => activeDynamicZones.Keys.ToArray();
1275
1276 private bool IsDynamicPVPZone(string zoneID) => activeDynamicZones.ContainsKey(zoneID);
1277
1278 private bool EventDataExists(string eventName) => storedData.EventDataExists(eventName);
1279
1280 private bool IsPlayerInPVPDelay(ulong playerID) => pvpDelays.ContainsKey(playerID);
1281
1282 private string GetPlayerPVPDelayedZoneID(ulong playerID)
1283 {
1284 LeftZone leftZone;
1285 if (!pvpDelays.TryGetValue(playerID, out leftZone))
1286 {
1287 return null;
1288 }
1289 return leftZone.zoneID;
1290 }
1291
1292 private string GetEventName(string zoneID)
1293 {
1294 string eventName;
1295 if (activeDynamicZones.TryGetValue(zoneID, out eventName))
1296 {
1297 return eventName;
1298 }
1299 return null;
1300 }
1301
1302 private bool CreateOrUpdateEventData(string eventName, string eventData, bool isTimed = false)
1303 {
1304 if (string.IsNullOrEmpty(eventName) || string.IsNullOrEmpty(eventData)) return false;
1305 if (EventDataExists(eventName)) RemoveEventData(eventName);
1306 if (isTimed)
1307 {
1308 TimedEventS timedEventS;
1309 try { timedEventS = JsonConvert.DeserializeObject<TimedEventS>(eventData); } catch { return false; }
1310 storedData.timedEvents.Add(eventName, timedEventS);
1311 }
1312 else
1313 {
1314 AutoEventS autoEventS;
1315 try { autoEventS = JsonConvert.DeserializeObject<AutoEventS>(eventData); } catch { return false; }
1316 storedData.autoEvents.Add(eventName, autoEventS);
1317 if (autoEventS.autoStart)
1318 {
1319 CreateDynamicZone(eventName, autoEventS.position, autoEventS.zoneID);
1320 }
1321 }
1322 dataChanged = true;
1323 return true;
1324 }
1325
1326 #endregion API
1327
1328 #region Commands
1329
1330 private void CmdDynamicPVP(IPlayer iPlayer, string command, string[] args)
1331 {
1332 if (!iPlayer.IsAdmin && !iPlayer.HasPermission(PERMISSION_ADMIN))
1333 {
1334 Print(iPlayer, Lang("NotAllowed", iPlayer.Id));
1335 return;
1336 }
1337 if (args == null || args.Length < 1)
1338 {
1339 Print(iPlayer, Lang("SyntaxError", iPlayer.Id, configData.chatS.command));
1340 return;
1341 }
1342 if (args[0].ToLower() == "list")
1343 {
1344 var customEventCount = storedData.CustomEventsCount;
1345 if (customEventCount <= 0)
1346 {
1347 Print(iPlayer, Lang("NoCustomEvent", iPlayer.Id));
1348 return;
1349 }
1350 int i = 0;
1351 StringBuilder stringBuilder = new StringBuilder();
1352 stringBuilder.AppendLine(Lang("CustomEvents", iPlayer.Id, customEventCount));
1353 foreach (var entry in storedData.autoEvents)
1354 {
1355 i++;
1356 stringBuilder.AppendLine(Lang("AutoEvent", iPlayer.Id, i, entry.Key, entry.Value.autoStart, entry.Value.position));
1357 }
1358 foreach (var entry in storedData.timedEvents)
1359 {
1360 i++;
1361 stringBuilder.AppendLine(Lang("TimedEvent", iPlayer.Id, i, entry.Key, entry.Value.duration));
1362 }
1363 Print(iPlayer, stringBuilder.ToString());
1364 return;
1365 }
1366 if (args.Length < 2)
1367 {
1368 Print(iPlayer, Lang("NoEventName", iPlayer.Id));
1369 return;
1370 }
1371 string eventName = args[1];
1372 Vector3 position = (iPlayer.Object as BasePlayer)?.transform.position ?? Vector3.zero;
1373 switch (args[0].ToLower())
1374 {
1375 case "add":
1376 bool isTimed = args.Length >= 3;
1377 Print(iPlayer,
1378 !CreateEventData(eventName, position, isTimed)
1379 ? Lang("EventNameExist", iPlayer.Id, eventName)
1380 : Lang("EventDataAdded", iPlayer.Id, eventName));
1381 return;
1382
1383 case "remove":
1384 Print(iPlayer,
1385 !RemoveEventData(eventName)
1386 ? Lang("EventNameNotExist", iPlayer.Id, eventName)
1387 : Lang("EventDataRemoved", iPlayer.Id, eventName));
1388 return;
1389
1390 case "start":
1391 Print(iPlayer,
1392 !StartEvent(eventName, position)
1393 ? Lang("EventNameNotExist", iPlayer.Id, eventName)
1394 : Lang("EventStarted", iPlayer.Id, eventName));
1395 return;
1396
1397 case "stop":
1398 Print(iPlayer,
1399 !StopEvent(eventName)
1400 ? Lang("EventNameExist", iPlayer.Id, eventName)
1401 : Lang("EventStopped", iPlayer.Id, eventName));
1402 return;
1403
1404 case "edit":
1405 if (args.Length >= 3)
1406 {
1407 AutoEventS autoEventS;
1408 if (storedData.autoEvents.TryGetValue(eventName, out autoEventS))
1409 {
1410 switch (args[2].ToLower())
1411 {
1412 case "1":
1413 case "true":
1414 autoEventS.autoStart = true;
1415 Print(iPlayer, Lang("AutoEventAutoStart", iPlayer.Id, eventName, true));
1416 dataChanged = true;
1417 return;
1418
1419 case "0":
1420 case "false":
1421 autoEventS.autoStart = false;
1422 Print(iPlayer, Lang("AutoEventAutoStart", iPlayer.Id, eventName, false));
1423 dataChanged = true;
1424 return;
1425
1426 case "move":
1427 autoEventS.position = position;
1428 Print(iPlayer, Lang("AutoEventMove", iPlayer.Id, eventName));
1429 dataChanged = true;
1430 return;
1431 }
1432 }
1433 else
1434 {
1435 TimedEventS timedEventS;
1436 if (storedData.timedEvents.TryGetValue(eventName, out timedEventS))
1437 {
1438 float duration;
1439 if (float.TryParse(args[2], out duration))
1440 {
1441 timedEventS.duration = duration;
1442 Print(iPlayer, Lang("TimedEventDuration", iPlayer.Id, eventName, duration));
1443 dataChanged = true;
1444 return;
1445 }
1446 }
1447 }
1448 }
1449 Print(iPlayer, Lang("SyntaxError", iPlayer.Id, configData.chatS.command));
1450 return;
1451
1452 case "h":
1453 case "help":
1454 StringBuilder stringBuilder = new StringBuilder();
1455 stringBuilder.AppendLine();
1456 stringBuilder.AppendLine(Lang("Syntax", iPlayer.Id, configData.chatS.command));
1457 stringBuilder.AppendLine(Lang("Syntax1", iPlayer.Id, configData.chatS.command));
1458 stringBuilder.AppendLine(Lang("Syntax2", iPlayer.Id, configData.chatS.command));
1459 stringBuilder.AppendLine(Lang("Syntax3", iPlayer.Id, configData.chatS.command));
1460 stringBuilder.AppendLine(Lang("Syntax4", iPlayer.Id, configData.chatS.command));
1461 stringBuilder.AppendLine(Lang("Syntax5", iPlayer.Id, configData.chatS.command));
1462 stringBuilder.AppendLine(Lang("Syntax6", iPlayer.Id, configData.chatS.command));
1463 Print(iPlayer, stringBuilder.ToString());
1464 return;
1465
1466 default:
1467 Print(iPlayer, Lang("SyntaxError", iPlayer.Id, configData.chatS.command));
1468 return;
1469 }
1470 }
1471
1472 private bool CreateEventData(string eventName, Vector3 position, bool isTimed)
1473 {
1474 if (EventDataExists(eventName)) return false;
1475 if (isTimed)
1476 {
1477 var timedEventS = new TimedEventS();
1478 storedData.timedEvents.Add(eventName, timedEventS);
1479 }
1480 else
1481 {
1482 var autoEventS = new AutoEventS { position = position };
1483 storedData.autoEvents.Add(eventName, autoEventS);
1484 }
1485 dataChanged = true;
1486 return true;
1487 }
1488
1489 private bool RemoveEventData(string eventName)
1490 {
1491 if (!EventDataExists(eventName)) return false;
1492 storedData.RemoveEventData(eventName);
1493 ForceCloseZones(eventName);
1494 dataChanged = true;
1495 return true;
1496 }
1497
1498 private bool StartEvent(string eventName, Vector3 position)
1499 {
1500 if (!EventDataExists(eventName)) return false;
1501 var autoEventS = GetBaseEventS(eventName) as AutoEventS;
1502 if (autoEventS != null)
1503 {
1504 CreateDynamicZone(eventName, autoEventS.position, autoEventS.zoneID);
1505 }
1506 else
1507 {
1508 CreateDynamicZone(eventName, position);
1509 }
1510 return true;
1511 }
1512
1513 private bool StopEvent(string eventName)
1514 {
1515 if (!EventDataExists(eventName)) return false;
1516 ForceCloseZones(eventName);
1517 return true;
1518 }
1519
1520 private bool ForceCloseZones(string eventName)
1521 {
1522 bool closed = false;
1523 foreach (var entry in activeDynamicZones.ToArray())
1524 {
1525 if (entry.Value == eventName)
1526 {
1527 if (DeleteDynamicZone(entry.Key))
1528 {
1529 closed = true;
1530 }
1531 }
1532 }
1533 return closed;
1534 }
1535
1536 #endregion Commands
1537
1538 #region ConfigurationFile
1539
1540 private ConfigData configData;
1541
1542 private class ConfigData
1543 {
1544 [JsonProperty(PropertyName = "Global Settings")]
1545 public GlobalS global = new GlobalS();
1546
1547 [JsonProperty(PropertyName = "Chat Settings")]
1548 public ChatS chatS = new ChatS();
1549
1550 [JsonProperty(PropertyName = "General Event Settings")]
1551 public GeneralEventS generalEvents = new GeneralEventS();
1552
1553 [JsonProperty(PropertyName = "Monument Event Settings")]
1554 public Dictionary<string, MonumentEventS> monumentEvents = new Dictionary<string, MonumentEventS>();
1555
1556 [JsonProperty(PropertyName = "Version")]
1557 public VersionNumber version;
1558 }
1559
1560 private class GlobalS
1561 {
1562 [JsonProperty(PropertyName = "Enable Debug Mode")]
1563 public bool debugEnabled;
1564
1565 [JsonProperty(PropertyName = "Log Debug To File")]
1566 public bool logToFile;
1567
1568 [JsonProperty(PropertyName = "Compare Radius (Used to determine if it is a SupplySignal)")]
1569 public float compareRadius = 2f;
1570
1571 [JsonProperty(PropertyName = "If the entity has an owner, don't create a PVP zone")]
1572 public bool checkEntityOwner = true;
1573
1574 [JsonProperty(PropertyName = "PVP Delay Flags")]
1575 public PVPDelayFlags pvpDelayFlags = PVPDelayFlags.ZonePlayersCanDamageDelayedPlayers | PVPDelayFlags.DelayedPlayersCanDamageDelayedPlayers | PVPDelayFlags.DelayedPlayersCanDamageZonePlayers;
1576 }
1577
1578 public class ChatS
1579 {
1580 [JsonProperty(PropertyName = "Command")]
1581 public string command = "dynpvp";
1582
1583 [JsonProperty(PropertyName = "Chat Prefix")]
1584 public string prefix = "[DynamicPVP]: ";
1585
1586 [JsonProperty(PropertyName = "Chat Prefix Color")]
1587 public string prefixColor = "#00FFFF";
1588
1589 [JsonProperty(PropertyName = "Chat SteamID Icon")]
1590 public ulong steamIDIcon = 0;
1591 }
1592
1593 private class GeneralEventS
1594 {
1595 [JsonProperty(PropertyName = "Bradley Event")]
1596 public TimedEventS bradleyAPC = new TimedEventS();
1597
1598 [JsonProperty(PropertyName = "Patrol Helicopter Event")]
1599 public TimedEventS patrolHelicopter = new TimedEventS();
1600
1601 [JsonProperty(PropertyName = "Supply Signal Event")]
1602 public SupplyDropEventS supplySignal = new SupplyDropEventS();
1603
1604 [JsonProperty(PropertyName = "Timed Supply Event")]
1605 public SupplyDropEventS timedSupply = new SupplyDropEventS();
1606
1607 [JsonProperty(PropertyName = "Hackable Crate Event")]
1608 public HackableCrateEventS hackableCrate = new HackableCrateEventS();
1609
1610 [JsonProperty(PropertyName = "Excavator Ignition Event")]
1611 public MonumentEventS excavatorIgnition = new MonumentEventS();
1612
1613 [JsonProperty(PropertyName = "Cargo Ship Event")]
1614 public CargoShipEventS cargoShip = new CargoShipEventS();
1615 }
1616
1617 #region Event
1618
1619 private abstract class BaseEventS
1620 {
1621 [JsonProperty(PropertyName = "Enable Event", Order = 1)]
1622 public bool enabled;
1623
1624 [JsonProperty(PropertyName = "Enable PVP Delay", Order = 2)]
1625 public bool pvpDelayEnabled;
1626
1627 [JsonProperty(PropertyName = "PVP Delay Time", Order = 3)]
1628 public float pvpDelayTime = 10f;
1629
1630 [JsonProperty(PropertyName = "Delay In Starting Event", Order = 6)]
1631 public float eventStartDelay;
1632
1633 [JsonProperty(PropertyName = "Delay In Stopping Event", Order = 7)]
1634 public float eventStopDelay;
1635
1636 [JsonProperty(PropertyName = "TruePVE Mapping", Order = 8)]
1637 public string mapping = "exclude";
1638
1639 [JsonProperty(PropertyName = "Use Blacklist Commands (If false, a whitelist is used)", Order = 9)]
1640 public bool useBlacklistCommands = true;
1641
1642 [JsonProperty(PropertyName = "Command List (If there is a '/' at the front, it is a chat command)", Order = 10)]
1643 public List<string> commandList = new List<string>();
1644
1645 public abstract BaseDynamicZoneS GetDynamicZoneS();
1646 }
1647
1648 private class DomeMixedEventS : BaseEventS, IDomeEvent
1649 {
1650 public bool domesEnabled { get; set; } = true;
1651 public int domesDarkness { get; set; } = 8;
1652
1653 [JsonProperty(PropertyName = "Dynamic PVP Zone Settings", Order = 20)]
1654 public SphereCubeDynamicZoneS dynamicZoneS { get; set; } = new SphereCubeDynamicZoneS();
1655
1656 public override BaseDynamicZoneS GetDynamicZoneS()
1657 {
1658 return dynamicZoneS;
1659 }
1660 }
1661
1662 private class BotDomeMixedEventS : DomeMixedEventS, IBotSpawnEvent
1663 {
1664 public bool botsEnabled { get; set; }
1665 public string botProfileName { get; set; } = string.Empty;
1666 }
1667
1668 private class MonumentEventS : DomeMixedEventS
1669 {
1670 [JsonProperty(PropertyName = "Zone ID", Order = 21)]
1671 public string zoneID = string.Empty;
1672
1673 [JsonProperty(PropertyName = "Transform Position", Order = 22)]
1674 public Vector3 transformPosition;
1675 }
1676
1677 private class AutoEventS : BotDomeMixedEventS
1678 {
1679 [JsonProperty(PropertyName = "Auto Start", Order = 23)]
1680 public bool autoStart;
1681
1682 [JsonProperty(PropertyName = "Zone ID", Order = 24)]
1683 public string zoneID = string.Empty;
1684
1685 [JsonProperty(PropertyName = "Position", Order = 25)]
1686 public Vector3 position;
1687 }
1688
1689 private class TimedEventS : BotDomeMixedEventS, ITimedEvent
1690 {
1691 public float duration { get; set; } = 600f;
1692 }
1693
1694 private class HackableCrateEventS : TimedEventS, IKillEvent
1695 {
1696 [JsonProperty(PropertyName = "Start Event When Spawned (If false, the event starts when unlocking)", Order = 24)]
1697 public bool startWhenSpawned;
1698
1699 public bool stopWhenKilled { get; set; }
1700
1701 [JsonProperty(PropertyName = "Excluding Hackable Crate On OilRig", Order = 26)]
1702 public bool excludeOilRig = true;
1703
1704 [JsonProperty(PropertyName = "Excluding Hackable Crate on Cargo Ship", Order = 27)]
1705 public bool excludeCargoShip = true;
1706 }
1707
1708 private class SupplyDropEventS : TimedEventS, IKillEvent
1709 {
1710 [JsonProperty(PropertyName = "Start Event When Spawned (If false, the event starts when landed)", Order = 24)]
1711 public bool startWhenSpawned = true;
1712
1713 public bool stopWhenKilled { get; set; }
1714 }
1715
1716 private class CargoShipEventS : BaseEventS
1717 {
1718 [JsonProperty(PropertyName = "Dynamic PVP Zone Settings", Order = 20)]
1719 public CubeParentDynamicZoneS dynamicZoneS { get; set; } = new CubeParentDynamicZoneS
1720 {
1721 size = new Vector3(25.9f, 43.3f, 152.8f),
1722 center = new Vector3(0f, 21.6f, 6.6f),
1723 };
1724
1725 public override BaseDynamicZoneS GetDynamicZoneS()
1726 {
1727 return dynamicZoneS;
1728 }
1729 }
1730
1731 #region Interface
1732
1733 private interface IKillEvent
1734 {
1735 [JsonProperty(PropertyName = "Stop Event When Killed", Order = 25)]
1736 bool stopWhenKilled { get; set; }
1737 }
1738
1739 private interface ITimedEvent
1740 {
1741 [JsonProperty(PropertyName = "Event Duration", Order = 23)]
1742 float duration { get; set; }
1743 }
1744
1745 private interface IBotSpawnEvent
1746 {
1747 [JsonProperty(PropertyName = "Enable Bots (Need BotSpawn Plugin)", Order = 21)]
1748 bool botsEnabled { get; set; }
1749
1750 [JsonProperty(PropertyName = "BotSpawn Profile Name", Order = 22)]
1751 string botProfileName { get; set; }
1752 }
1753
1754 private interface IDomeEvent
1755 {
1756 [JsonProperty(PropertyName = "Enable Domes", Order = 4)]
1757 bool domesEnabled { get; set; }
1758
1759 [JsonProperty(PropertyName = "Domes Darkness", Order = 5)]
1760 int domesDarkness { get; set; }
1761 }
1762
1763 #endregion Interface
1764
1765 #endregion Event
1766
1767 #region Zone
1768
1769 private abstract class BaseDynamicZoneS
1770 {
1771 [JsonProperty(PropertyName = "Zone Comfort", Order = 10)]
1772 public float comfort;
1773
1774 [JsonProperty(PropertyName = "Zone Radiation", Order = 11)]
1775 public float radiation;
1776
1777 [JsonProperty(PropertyName = "Zone Temperature", Order = 12)]
1778 public float temperature;
1779
1780 [JsonProperty(PropertyName = "Enable Safe Zone", Order = 13)]
1781 public bool safeZone;
1782
1783 [JsonProperty(PropertyName = "Eject Spawns", Order = 14)]
1784 public string ejectSpawns = string.Empty;
1785
1786 [JsonProperty(PropertyName = "Zone Parent ID", Order = 15)]
1787 public string parentid = string.Empty;
1788
1789 [JsonProperty(PropertyName = "Enter Message", Order = 16)]
1790 public string enterMessage = "Entering a PVP area!";
1791
1792 [JsonProperty(PropertyName = "Leave Message", Order = 17)]
1793 public string leaveMessage = "Leaving a PVP area.";
1794
1795 [JsonProperty(PropertyName = "Permission Required To Enter Zone", Order = 18)]
1796 public string permission = string.Empty;
1797
1798 [JsonProperty(PropertyName = "Extra Zone Flags", Order = 20)]
1799 public List<string> extraZoneFlags = new List<string>();
1800
1801 private string[] _zoneSettings;
1802
1803 public virtual string[] ZoneSettings(Transform transform = null) => _zoneSettings ?? (_zoneSettings = GetZoneSettings());
1804
1805 protected void GetBaseZoneSettings(List<string> zoneSettings)
1806 {
1807 zoneSettings.Add("name");
1808 zoneSettings.Add(ZONE_NAME);
1809 if (comfort > 0f)
1810 {
1811 zoneSettings.Add("comfort");
1812 zoneSettings.Add(comfort.ToString(CultureInfo.InvariantCulture));
1813 }
1814 if (radiation > 0f)
1815 {
1816 zoneSettings.Add("radiation");
1817 zoneSettings.Add(radiation.ToString(CultureInfo.InvariantCulture));
1818 }
1819 if (temperature != 0f)
1820 {
1821 zoneSettings.Add("temperature");
1822 zoneSettings.Add(temperature.ToString(CultureInfo.InvariantCulture));
1823 }
1824 if (safeZone)
1825 {
1826 zoneSettings.Add("safezone");
1827 zoneSettings.Add(safeZone.ToString());
1828 }
1829 if (!string.IsNullOrEmpty(enterMessage))
1830 {
1831 zoneSettings.Add("enter_message");
1832 zoneSettings.Add(enterMessage);
1833 }
1834 if (!string.IsNullOrEmpty(leaveMessage))
1835 {
1836 zoneSettings.Add("leave_message");
1837 zoneSettings.Add(leaveMessage);
1838 }
1839 if (!string.IsNullOrEmpty(ejectSpawns))
1840 {
1841 zoneSettings.Add("ejectspawns");
1842 zoneSettings.Add(ejectSpawns);
1843 }
1844 if (!string.IsNullOrEmpty(permission))
1845 {
1846 zoneSettings.Add("permission");
1847 zoneSettings.Add(permission);
1848 }
1849 if (!string.IsNullOrEmpty(parentid))
1850 {
1851 zoneSettings.Add("parentid");
1852 zoneSettings.Add(parentid);
1853 }
1854 foreach (var flag in extraZoneFlags)
1855 {
1856 if (string.IsNullOrEmpty(flag)) continue;
1857 zoneSettings.Add(flag);
1858 zoneSettings.Add("true");
1859 }
1860 }
1861
1862 protected abstract string[] GetZoneSettings(Transform transform = null);
1863 }
1864
1865 private class SphereDynamicZoneS : BaseDynamicZoneS, ISphereZone
1866 {
1867 public float radius { get; set; } = 100;
1868
1869 protected override string[] GetZoneSettings(Transform transform = null)
1870 {
1871 var zoneSettings = new List<string> {
1872 "radius", radius.ToString(CultureInfo.InvariantCulture)
1873 };
1874 GetBaseZoneSettings(zoneSettings);
1875 var array = zoneSettings.ToArray();
1876 return array;
1877 }
1878 }
1879
1880 private class CubeDynamicZoneS : BaseDynamicZoneS, ICubeZone
1881 {
1882 public Vector3 size { get; set; }
1883 public float rotation { get; set; }
1884 public bool fixedRotation { get; set; }
1885
1886 public override string[] ZoneSettings(Transform transform = null)
1887 {
1888 return transform == null || fixedRotation ? base.ZoneSettings(transform) : GetZoneSettings(transform);
1889 }
1890
1891 protected override string[] GetZoneSettings(Transform transform = null)
1892 {
1893 var zoneSettings = new List<string> {
1894 "size", $"{size.x} {size.y} {size.z}",
1895 };
1896 if (transform == null || fixedRotation)
1897 {
1898 zoneSettings.Add(rotation.ToString(CultureInfo.InvariantCulture));
1899 }
1900 else
1901 {
1902 zoneSettings.Add((transform.rotation.eulerAngles.y + rotation).ToString(CultureInfo.InvariantCulture));
1903 }
1904 GetBaseZoneSettings(zoneSettings);
1905 var array = zoneSettings.ToArray();
1906 return array;
1907 }
1908 }
1909
1910 private class SphereCubeDynamicZoneS : BaseDynamicZoneS, ICubeZone, ISphereZone
1911 {
1912 public float radius { get; set; }
1913 public Vector3 size { get; set; }
1914 public float rotation { get; set; }
1915 public bool fixedRotation { get; set; }
1916
1917 public override string[] ZoneSettings(Transform transform = null)
1918 {
1919 return transform == null || fixedRotation || radius > 0f ? base.ZoneSettings(transform) : GetZoneSettings(transform);
1920 }
1921
1922 protected override string[] GetZoneSettings(Transform transform = null)
1923 {
1924 var zoneSettings = new List<string>();
1925 if (radius > 0f)
1926 {
1927 zoneSettings.Add("radius");
1928 zoneSettings.Add(radius.ToString(CultureInfo.InvariantCulture));
1929 }
1930 else
1931 {
1932 zoneSettings.Add("size");
1933 zoneSettings.Add($"{size.x} {size.y} {size.z}");
1934 zoneSettings.Add("rotation");
1935 if (transform == null || fixedRotation)
1936 {
1937 zoneSettings.Add(rotation.ToString(CultureInfo.InvariantCulture));
1938 }
1939 else
1940 {
1941 zoneSettings.Add((transform.rotation.eulerAngles.y + rotation).ToString(CultureInfo.InvariantCulture));
1942 }
1943 }
1944 GetBaseZoneSettings(zoneSettings);
1945 var array = zoneSettings.ToArray();
1946 return array;
1947 }
1948 }
1949
1950 private class SphereParentDynamicZoneS : SphereDynamicZoneS, IParentZone
1951 {
1952 public Vector3 center { get; set; }
1953 }
1954
1955 private class CubeParentDynamicZoneS : CubeDynamicZoneS, IParentZone
1956 {
1957 public Vector3 center { get; set; }
1958
1959 protected override string[] GetZoneSettings(Transform transform = null)
1960 {
1961 var zoneSettings = new List<string> {
1962 "size", $"{size.x} {size.y} {size.z}",
1963 };
1964 //if (transform == null || fixedRotation)
1965 //{
1966 // zoneSettings.Add(rotation.ToString(CultureInfo.InvariantCulture));
1967 //}
1968 //else
1969 //{
1970 // zoneSettings.Add((transform.rotation.eulerAngles.y + rotation).ToString(CultureInfo.InvariantCulture));
1971 //}
1972 GetBaseZoneSettings(zoneSettings);
1973 var array = zoneSettings.ToArray();
1974 return array;
1975 }
1976 }
1977
1978 #region Interface
1979
1980 public interface ISphereZone
1981 {
1982 [JsonProperty(PropertyName = "Zone Radius", Order = 0)]
1983 float radius { get; set; }
1984 }
1985
1986 public interface ICubeZone
1987 {
1988 [JsonProperty(PropertyName = "Zone Size", Order = 1)]
1989 Vector3 size { get; set; }
1990
1991 [JsonProperty(PropertyName = "Zone Rotation", Order = 2)]
1992 float rotation { get; set; }
1993
1994 [JsonProperty(PropertyName = "Fixed Rotation", Order = 3)]
1995 bool fixedRotation { get; set; }
1996 }
1997
1998 public interface IParentZone
1999 {
2000 [JsonProperty(PropertyName = "Transform Position", Order = 5)]
2001 Vector3 center { get; set; }
2002 }
2003
2004 #endregion Interface
2005
2006 #endregion Zone
2007
2008 protected override void LoadConfig()
2009 {
2010 base.LoadConfig();
2011 try
2012 {
2013 configData = Config.ReadObject<ConfigData>();
2014 if (configData == null)
2015 {
2016 LoadDefaultConfig();
2017 }
2018 else
2019 {
2020 UpdateConfigValues();
2021 }
2022 }
2023 catch (Exception ex)
2024 {
2025 PrintError($"The configuration file is corrupted. \n{ex}");
2026 LoadDefaultConfig();
2027 }
2028 SaveConfig();
2029 }
2030
2031 protected override void LoadDefaultConfig()
2032 {
2033 PrintWarning("Creating a new configuration file");
2034 configData = new ConfigData();
2035 configData.version = Version;
2036 }
2037
2038 protected override void SaveConfig() => Config.WriteObject(configData);
2039
2040 private void UpdateConfigValues()
2041 {
2042 if (configData.version < Version)
2043 {
2044 if (configData.version <= default(VersionNumber))
2045 {
2046 //string prefix, prefixColor;
2047 //if (GetConfigValue(out prefix, "Chat Settings", "Chat Prefix") && GetConfigValue(out prefixColor, "Chat Settings", "Chat Prefix Color"))
2048 //{
2049 // configData.chatS.prefix = $"<color={prefixColor}>{prefix}</color>: ";
2050 //}
2051 }
2052
2053 if (configData.version <= new VersionNumber(4, 2, 0))
2054 {
2055 configData.global.compareRadius = 2f;
2056 }
2057
2058 if (configData.version <= new VersionNumber(4, 2, 4))
2059 {
2060 LoadData();
2061 SaveData();
2062 }
2063
2064 if (configData.version <= new VersionNumber(4, 2, 6))
2065 {
2066 bool value;
2067 if (GetConfigValue(out value, "General Event Settings", "Supply Signal Event", "Supply Drop Event Start When Spawned (If false, the event starts when landed)"))
2068 {
2069 configData.generalEvents.supplySignal.startWhenSpawned = value;
2070 }
2071 if (GetConfigValue(out value, "General Event Settings", "Timed Supply Event", "Supply Drop Event Start When Spawned (If false, the event starts when landed)"))
2072 {
2073 configData.generalEvents.timedSupply.startWhenSpawned = value;
2074 }
2075 if (GetConfigValue(out value, "General Event Settings", "Hackable Crate Event", "Hackable Crate Event Start When Spawned (If false, the event starts when unlocking)"))
2076 {
2077 configData.generalEvents.hackableCrate.startWhenSpawned = value;
2078 }
2079 }
2080 configData.version = Version;
2081 }
2082 }
2083
2084 private bool GetConfigValue<T>(out T value, params string[] path)
2085 {
2086 var configValue = Config.Get(path);
2087 if (configValue != null)
2088 {
2089 if (configValue is T)
2090 {
2091 value = (T)configValue;
2092 return true;
2093 }
2094 try
2095 {
2096 value = Config.ConvertValue<T>(configValue);
2097 return true;
2098 }
2099 catch (Exception ex)
2100 {
2101 PrintError($"GetConfigValue ERROR: path: {string.Join("\\", path)}\n{ex}");
2102 }
2103 }
2104
2105 value = default(T);
2106 return false;
2107 }
2108
2109 #endregion ConfigurationFile
2110
2111 #region DataFile
2112
2113 private StoredData storedData;
2114
2115 private class StoredData
2116 {
2117 public readonly Dictionary<string, TimedEventS> timedEvents = new Dictionary<string, TimedEventS>();
2118 public readonly Dictionary<string, AutoEventS> autoEvents = new Dictionary<string, AutoEventS>();
2119
2120 public bool EventDataExists(string eventName) => timedEvents.ContainsKey(eventName) || autoEvents.ContainsKey(eventName);
2121
2122 public bool RemoveEventData(string eventName) => timedEvents.Remove(eventName) || autoEvents.Remove(eventName);
2123
2124 [JsonIgnore] public int CustomEventsCount => timedEvents.Count + autoEvents.Count;
2125 }
2126
2127 private void LoadData()
2128 {
2129 try
2130 {
2131 storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
2132 }
2133 catch
2134 {
2135 storedData = null;
2136 }
2137 finally
2138 {
2139 if (storedData == null)
2140 {
2141 ClearData();
2142 }
2143 }
2144 }
2145
2146 private void ClearData()
2147 {
2148 storedData = new StoredData();
2149 SaveData();
2150 }
2151
2152 private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
2153
2154 #endregion DataFile
2155
2156 #region LanguageFile
2157
2158 private void Print(IPlayer iPlayer, string message)
2159 {
2160 if (iPlayer == null) return;
2161 if (iPlayer.Id == "server_console") iPlayer.Reply(message, configData.chatS.prefix);
2162 else
2163 {
2164 var player = iPlayer.Object as BasePlayer;
2165 if (player != null) Player.Message(player, message, $"<color={configData.chatS.prefixColor}>{configData.chatS.prefix}</color>", configData.chatS.steamIDIcon);
2166 else iPlayer.Reply(message, $"<color={configData.chatS.prefixColor}>{configData.chatS.prefix}</color>");
2167 }
2168 }
2169
2170 private void Print(BasePlayer player, string message)
2171 {
2172 if (string.IsNullOrEmpty(message)) return;
2173 Player.Message(player, message, string.IsNullOrEmpty(configData.chatS.prefix) ? null : $"<color={configData.chatS.prefixColor}>{configData.chatS.prefix}</color>", configData.chatS.steamIDIcon);
2174 }
2175
2176 private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);
2177
2178 protected override void LoadDefaultMessages()
2179 {
2180 lang.RegisterMessages(new Dictionary<string, string>
2181 {
2182 ["NotAllowed"] = "You do not have permission to use this command",
2183 ["NoCustomEvent"] = "There is no custom event data",
2184 ["CustomEvents"] = "There are {0} custom event data",
2185 ["AutoEvent"] = "{0}.[AutoEvent]: '{1}'. AutoStart: {2}. Position: {3}",
2186 ["TimedEvent"] = "{0}.[TimedEvent]: '{1}'. Duration: {2}",
2187 ["NoEventName"] = "Please type event name",
2188 ["EventNameExist"] = "The event name {0} already exists",
2189 ["EventNameNotExist"] = "The event name {0} does not exist",
2190 ["EventDataAdded"] = "'{0}' event data was added successfully",
2191 ["EventDataRemoved"] = "'{0}' event data was removed successfully",
2192 ["EventStarted"] = "'{0}' event started successfully",
2193 ["EventStopped"] = "'{0}' event stopped successfully",
2194
2195 ["AutoEventAutoStart"] = "'{0}' event auto start is {1}",
2196 ["AutoEventMove"] = "'{0}' event moves to your current location",
2197 ["TimedEventDuration"] = "'{0}' event duration is changed to {1} seconds",
2198
2199 ["SyntaxError"] = "Syntax error, please type '<color=#ce422b>/{0} <help | h></color>' to view help",
2200 ["Syntax"] = "<color=#ce422b>/{0} add <eventName> [timed]</color> - Add event data. If added 'timed', it will be a timed event",
2201 ["Syntax1"] = "<color=#ce422b>/{0} remove <eventName></color> - Remove event data",
2202 ["Syntax2"] = "<color=#ce422b>/{0} start <eventName></color> - Start event",
2203 ["Syntax3"] = "<color=#ce422b>/{0} stop <eventName></color> - Stop event",
2204 ["Syntax4"] = "<color=#ce422b>/{0} edit <eventName> <true/false></color> - Changes auto start state of auto event",
2205 ["Syntax5"] = "<color=#ce422b>/{0} edit <eventName> <move></color> - Move auto event to your current location",
2206 ["Syntax6"] = "<color=#ce422b>/{0} edit <eventName> <time(seconds)></color> - Changes the duration of a timed event",
2207 }, this);
2208
2209 lang.RegisterMessages(new Dictionary<string, string>
2210 {
2211 ["NotAllowed"] = "您没有权限使用该命令",
2212 ["NoCustomEvent"] = "您没有创建任何自定义事件数据",
2213 ["CustomEvents"] = "当前自定义事件数有 {0}个",
2214 ["AutoEvent"] = "{0}.[自动事件]: '{1}'. 自动启用: {2}. 位置: {3}",
2215 ["TimedEvent"] = "{0}.[定时事件]: '{1}'. 持续时间: {2}",
2216 ["NoEventName"] = "请输入事件名字",
2217 ["EventNameExist"] = "'{0}' 事件名字已存在",
2218 ["EventNameNotExist"] = "'{0}' 事件名字不存在",
2219 ["EventDataAdded"] = "'{0}' 事件数据添加成功",
2220 ["EventDataRemoved"] = "'{0}' 事件数据删除成功",
2221 ["EventStarted"] = "'{0}' 事件成功开启",
2222 ["EventStopped"] = "'{0}' 事件成功停止",
2223
2224 ["AutoEventAutoStart"] = "'{0}' 事件自动开启状态为 {1}",
2225 ["AutoEventMove"] = "'{0}' 事件移到了您的当前位置",
2226 ["TimedEventDuration"] = "'{0}' 事件的持续时间改为了 {1}秒",
2227
2228 ["SyntaxError"] = "语法错误, 输入 '<color=#ce422b>/{0} <help | h></color>' 查看帮助",
2229 ["Syntax"] = "<color=#ce422b>/{0} add <eventName> [timed]</color> - 添加事件数据。如果后面加上'timed',将添加定时事件数据",
2230 ["Syntax1"] = "<color=#ce422b>/{0} remove <eventName></color> - 删除事件数据",
2231 ["Syntax2"] = "<color=#ce422b>/{0} start <eventName></color> - 开启事件",
2232 ["Syntax3"] = "<color=#ce422b>/{0} stop <eventName></color> - 停止事件",
2233 ["Syntax4"] = "<color=#ce422b>/{0} edit <eventName> <true/false></color> - 改变自动事件的自动启动状态",
2234 ["Syntax5"] = "<color=#ce422b>/{0} edit <eventName> <move></color> - 移动自动事件的位置到您的当前位置",
2235 ["Syntax6"] = "<color=#ce422b>/{0} edit <eventName> <time(seconds)></color> - 修改定时事件的持续时间",
2236 }, this, "zh-CN");
2237 }
2238
2239 #endregion LanguageFile
2240 }
2241}