Иконка ресурса

FREE DynamicPVP : 5.0.2 5.0.2

Нет прав для скачивания
C#:
// PATCHED by penguin-rust-base: fixed removed Oxide APIs (FindByID→FindAwakeOrSleeping, net.connection→Connection) + ulong→string conversion (line 3459) + removed .Get() on userID (lines 535, 3620)
 //Requires: ZoneManager
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Prefabs.Misc;
using UnityEngine;
namespace Oxide.Plugins;
[Info("Dynamic PVP", "HunterZ/CatMeat/Arainrr", "5.0.2", ResourceId = 2728)]
[Description("Creates temporary PvP zones on certain actions/events")]
public class DynamicPVP : RustPlugin {
#region Fields
    [PluginReference]
    Plugin Backpacks, BotReSpawn, TruePVE, ZoneManager;
    private const string PermissionAdmin = "dynamicpvp.admin";
    private const string PrefabLargeOilRig = "assets/bundled/prefabs/autospawn/monument/offshore/oilrig_1.prefab";
    private const string PrefabOilRig = "assets/bundled/prefabs/autospawn/monument/offshore/oilrig_2.prefab";
    private const string PrefabSphereDome = "assets/prefabs/visualization/sphere.prefab";
    private const string PrefabSphereRedRing = "assets/bundled/prefabs/modding/events/twitch/br_sphere_red.prefab";
    private const string PrefabSphereGreenRing = "assets/bundled/prefabs/modding/events/twitch/br_sphere_green.prefab";
    private const string PrefabSphereBlueRing = "assets/bundled/prefabs/modding/events/twitch/br_sphere.prefab";
    private const string PrefabSpherePurpleRing = "assets/bundled/prefabs/modding/events/twitch/br_sphere_purple.prefab";
    private const string ZoneName = "DynamicPVP";
    private readonly Dictionary<string, Timer> _eventDeleteTimers = new();
    private readonly Dictionary<ulong, LeftZone> _pvpDelays = new();
    // Map of event names + base events by ZoneID
    private readonly Dictionary<string, BaseEvent> _activeDynamicZones = new();
    // ZoneID/NetID of Deep Sea zones that we may have tried to create
    private readonly HashSet<string> _potentialDeepSeaZones = new();
    // plugin integration zone tracking - used for managing hook subscriptions
    //  and for faster lookups
    private enum PluginZoneCategory {
        BackpacksForce, BackpacksPrevent, LootDefender, RestoreUponDeath
    }
    private readonly Dictionary<PluginZoneCategory, HashSet<string>> _activePluginZones = new();
    private Vector3 _oilRigPosition = Vector3.zero;
    private Vector3 _largeOilRigPosition = Vector3.zero;
    private bool _useExcludePlayer;
    private bool _brokenTunnels;
    private bool _dataChanged;
    private Coroutine _coroutine;
    private readonly YieldInstruction _fastYield = null;
    private readonly YieldInstruction _throttleYield = CoroutineEx.waitForSeconds(0.1f);
    private readonly YieldInstruction _pauseYield = CoroutineEx.waitForSeconds(0.5f);
    private float _targetFps = -1.0f;
    private sealed class LeftZone : Pool.IPooled {
        public string zoneId;
        public BaseEvent baseEvent;
        public Timer zoneTimer;
        private void Reset() {
            zoneId = null;
            baseEvent = null;
            zoneTimer?.Destroy();
            zoneTimer = null;
        }
        public void EnterPool() => Reset();
        public void LeavePool() => Reset();
    }
    private enum CoroutineTypes {
        CreateStartupEvents, CreateDeepSeaEvents, DeleteDeepSeaEvents
    }
    [Flags]
    [JsonConverter(typeof(StringEnumConverter))]
    private enum PvpDelayTypes {
        None = 0, ZonePlayersCanDamageDelayedPlayers = 1, DelayedPlayersCanDamageZonePlayers = 1 << 1, DelayedPlayersCanDamageDelayedPlayers = 1 << 2
    }
    // general and deep sea event types
    // these are managed as a single enum to prevent name collisions
    public enum GeneralEventType {
        Bradley, Helicopter, TimedSupply, SupplySignal, CargoShip, HackableCrate, ExcavatorIgnition, GhostShip, DeepSeaIsland, IslandCannon
    }
    [Flags]
    private enum HookCheckReasons {
        None = 0, DelayAdded = 1 << 0, DelayRemoved = 1 << 1, ZoneAdded = 1 << 2, ZoneRemoved = 1 << 3
    }
    private enum HookCategory {
        Command, PluginBackpacksForce, PluginBackpacksPrevent, PluginLootDefender, PluginRestoreUponDeath, PvpDelay, Zone
    }
    // hook names by hook category
    private readonly Dictionary<HookCategory, List<string>> _hooksByCategory = new() {
        {
            HookCategory.Command, new List<string> {
                nameof(OnPlayerCommand), nameof(OnServerCommand)
            }
        }
        , {
            HookCategory.PluginBackpacksForce, new List<string> {
                nameof(OnPlayerDeath)
            }
        }
        , {
            HookCategory.PluginBackpacksPrevent, new List<string> {
                nameof(CanDropBackpack)
            }
        }
        , {
            HookCategory.PluginLootDefender, new List<string> {
                nameof(OnLootLockedEntity)
            }
        }
        , {
            HookCategory.PluginRestoreUponDeath, new List<string> {
                nameof(OnRestoreUponDeath)
            }
        }
        , {
            HookCategory.PvpDelay, new List<string> {
                nameof(CanEntityTakeDamage)
            }
        }
        , {
            HookCategory.Zone, new List<string> {
                nameof(OnEnterZone), nameof(OnExitZone)
            }
        }
    }
    ;
    // current hook subscription state by hook category
    private readonly Dictionary<HookCategory, bool> _subscriptionsByCategory = new();
    private readonly Collider[] _colliderBuffer = new Collider[8];
    private enum MonumentEventType {
        Default, Custom, TunnelEntrance, TunnelLink, TunnelSection, UnderwaterLabs
    }
    private readonly Dictionary<string, OriginalMonumentGeometry> _originalMonumentGeometries = new();
#endregion Fields
#region Oxide Hooks
    private void Init() {
        foreach (PluginZoneCategory pzCat in Enum.GetValues(typeof(PluginZoneCategory))) {
            _activePluginZones[pzCat] = Pool.Get<HashSet<string>>();
        }
        _brokenTunnels = false;
        LoadData();
        permission.RegisterPermission(PermissionAdmin, this);
        AddCovalenceCommand(_configData.Chat.Command, nameof(CmdDynamicPVP));
        Unsubscribe(nameof(CanDropBackpack));
        Unsubscribe(nameof(CanEntityTakeDamage));
        Unsubscribe(nameof(OnCargoPlaneSignaled));
        Unsubscribe(nameof(OnCargoShipEgress));
        Unsubscribe(nameof(OnCargoShipHarborApproach));
        Unsubscribe(nameof(OnCargoShipHarborArrived));
        Unsubscribe(nameof(OnCargoShipHarborLeave));
        Unsubscribe(nameof(OnCrateHack));
        Unsubscribe(nameof(OnCrateHackEnd));
        Unsubscribe(nameof(OnDeepSeaClosed));
        Unsubscribe(nameof(OnDeepSeaOpened));
        Unsubscribe(nameof(OnDieselEngineToggled));
        Unsubscribe(nameof(OnEnterZone));
        Unsubscribe(nameof(OnEntityDeath));
        Unsubscribe(nameof(OnEntityKill));
        Unsubscribe(nameof(OnEntitySpawned));
        Unsubscribe(nameof(OnExitZone));
        Unsubscribe(nameof(OnLootEntity));
        Unsubscribe(nameof(OnLootLockedEntity));
        Unsubscribe(nameof(OnPlayerCommand));
        Unsubscribe(nameof(OnPlayerDeath));
        Unsubscribe(nameof(OnRestoreUponDeath));
        Unsubscribe(nameof(OnServerCommand));
        Unsubscribe(nameof(OnSupplyDropLanded));
        foreach (var category in _hooksByCategory.Keys) {
            _subscriptionsByCategory[category] = false;
        }
        if (_configData.Global.LogToFile) {
            _debugStringBuilder = Pool.Get<StringBuilder>();
        }
        // setup new TruePVE "ExcludePlayer" support
        _useExcludePlayer = _configData.Global.UseExcludePlayer;
        // if ExcludePlayer is disabled in config but is supported...
        if (!_useExcludePlayer && null != TruePVE && TruePVE.Version >= new VersionNumber(2, 2, 3)) {
            // ...and all PVP delays are enabled, auto-enable internally and warn
            if ((PvpDelayTypes.ZonePlayersCanDamageDelayedPlayers | PvpDelayTypes.DelayedPlayersCanDamageZonePlayers | PvpDelayTypes.DelayedPlayersCanDamageDelayedPlayers) == _configData.Global.PvpDelayFlags) {
                _useExcludePlayer = true;
                Puts("All PVP delay flags active and TruePVE 2.2.3+ detected, so TruePVE PVP delays will be used for performance and cross-plugin support; please consider enabling TruePVE PVP Delay API in the config file to skip this check");
            }
            // else just nag, since settings are not compatible
            else {
                Puts("Some/all PVP delay flags NOT active, but TruePVE 2.2.3+ detected; please consider switching to TruePVE PVP Delay API in the config file for performance and cross-plugin support");
            }
        }
        // else ExcludePlayer is already enabled, or TruePVE 2.2.3+ not running
    }
    private void OnServerInitialized() {
        if (null == ZoneManager || ZoneManager.Version < new VersionNumber(3, 1, 10)) {
            PrintError("Zone Manager missing or outdated; please update for proper function of this plugin!");
        }
        // resubscribe to any hooks that are conditional due to config options
        // code is grouped alphabetically by first hook name, to make it easier t
        //  see what gets subscribed and why
        if (_configData.GeneralEvents.SupplySignal.Enabled || _configData.GeneralEvents.TimedSupply.Enabled) {
            // this is subscribed if either drop event is enabled, because we need to
            //  differentiate either way
            Subscribe(nameof(OnCargoPlaneSignaled));
            Subscribe(nameof(OnSupplyDropDropped));
            // this is now subscribed regardless of start on spawn-vs-landing, as we
            //  need to tether the zone to the drop on landing in both cases
            Subscribe(nameof(OnSupplyDropLanded));
        }
        if (_configData.GeneralEvents.CargoShip.Enabled) {
            Subscribe(nameof(OnCargoShipEgress));
            Subscribe(nameof(OnCargoShipHarborApproach));
            Subscribe(nameof(OnCargoShipHarborArrived));
            Subscribe(nameof(OnCargoShipHarborLeave));
        }
        if (_configData.GeneralEvents.HackableCrate.Enabled && !_configData.GeneralEvents.HackableCrate.StartWhenSpawned) {
            Subscribe(nameof(OnCrateHack));
        }
        if (_configData.GeneralEvents.HackableCrate.Enabled && _configData.GeneralEvents.HackableCrate.TimerStartWhenUnlocked) {
            Subscribe(nameof(OnCrateHackEnd));
        }
        if (AnySeepSeaEventEnabled()) {
            Subscribe(nameof(OnDeepSeaClosed));
            Subscribe(nameof(OnDeepSeaOpened));
        }
        if (_configData.GeneralEvents.ExcavatorIgnition.Enabled) {
            Subscribe(nameof(OnDieselEngineToggled));
        }
        if (_configData.GeneralEvents.PatrolHelicopter.Enabled || _configData.GeneralEvents.BradleyApc.Enabled) {
            Subscribe(nameof(OnEntityDeath));
        }
        if (_configData.GeneralEvents.TimedSupply.Enabled || _configData.GeneralEvents.SupplySignal.Enabled || _configData.GeneralEvents.HackableCrate.Enabled || _configData.GeneralEvents.CargoShip.Enabled) {
            Subscribe(nameof(OnEntityKill));
        }
        if ((_configData.GeneralEvents.HackableCrate.Enabled && _configData.GeneralEvents.HackableCrate.StartWhenSpawned) || _configData.GeneralEvents.CargoShip.Enabled) {
            Subscribe(nameof(OnEntitySpawned));
        }
        if ((_configData.GeneralEvents.TimedSupply.Enabled && _configData.GeneralEvents.TimedSupply.TimerStartWhenLooted) || (_configData.GeneralEvents.SupplySignal.Enabled && _configData.GeneralEvents.SupplySignal.TimerStartWhenLooted) || (_configData.GeneralEvents.HackableCrate.Enabled && _configData.GeneralEvents.HackableCrate.TimerStartWhenLooted)) {
            Subscribe(nameof(OnLootEntity));
        }
        NextTick(() => TryStartCoroutine(CoroutineTypes.CreateStartupEvents));
    }
    private void Unload() {
        if (_coroutine != null) {
            ServerMgr.Instance.StopCoroutine(_coroutine);
        }
        if (_activeDynamicZones.Count > 0) {
            PrintDebug($"Unload(): Deleting {_activeDynamicZones.Count} active zone(s)");
            // copy zone keys to a temporary list, because each deletion will modify
            //  _activeDynamicZones
            var zoneKeys = Pool.Get<List<string>>();
            zoneKeys.AddRange(_activeDynamicZones.Keys);
            foreach (var key in zoneKeys) {
                DeleteDynamicZone(key);
            }
            Pool.FreeUnmanaged(ref zoneKeys);
            _activeDynamicZones.Clear();
            _potentialDeepSeaZones.Clear();
        }
        // copy LeftZone records to a temporary list to that we can reverse iterate
        var leftZones = Pool.Get<List<LeftZone>>();
        leftZones.AddRange(_pvpDelays.Values);
        for (var i = leftZones.Count - 1; i >= 0; --i) {
            // this is cheating because it leaves leftZones[i] in a dangling state,
            //  but it's okay because we're going to free/clear everything anyway
            var value = leftZones[i];
            Pool.Free(ref value);
        }
        Pool.FreeUnmanaged(ref leftZones);
        _pvpDelays.Clear();
        // also remove LeftZone class from pool framework, in case it changes on
        //  plugin reload
        Pool.Directory.TryRemove(typeof(LeftZone), out _);
        // copy sphere lists to a temporary list so that we can reverse iterate
        var spheres = Pool.Get<List<List<SphereEntity>>>();
        spheres.AddRange(_zoneSpheres.Values);
        for (var i = _zoneSpheres.Count - 1; i >= 0; --i) {
            // this is cheating because it leaves spheres[i] in a dangling state, but
            //  it's okay because we're going to free/clear everything anyway
            var sphereEntities = spheres[i];
            foreach (var sphereEntity in sphereEntities) {
                if (!sphereEntity || sphereEntity.IsDestroyed) continue;
                sphereEntity.KillMessage();
            }
            Pool.FreeUnmanaged(ref sphereEntities);
        }
        Pool.FreeUnmanaged(ref spheres);
        _zoneSpheres.Clear();
        SaveData();
        SaveDebug();
        if (null == _debugStringBuilder) return;
        Pool.FreeUnmanaged(ref _debugStringBuilder);
        _debugStringBuilder = null;
        _originalMonumentGeometries.Clear();
        foreach (var apZone in _activePluginZones.Values) {
            // this is cheating because it leaves apZone in a dangling state, but it's
            //  okay because we're going to clear the list just after this
            var apZoneI = apZone;
            Pool.FreeUnmanaged(ref apZoneI);
        }
        _activePluginZones.Clear();
        DomeEvent._domeEventsToCheck = null;
        _activeSignaledPlanesAndDrops.Clear();
    }
    private void OnServerSave() => timer.Once(UnityEngine.Random.Range(0f, 60f), () => {
        SaveDebug();
        if (!_dataChanged) return;
        SaveData();
        _dataChanged = false;
    }
    );
    private void OnPlayerRespawned(BasePlayer player) {
        if (!player || !player.userID.IsSteamId()) {
            PrintDebug("OnPlayerRespawned(): Ignoring respawn of null/NPC player");
            return;
        }
        TryRemovePVPDelay(player);
    }
#endregion Oxide Hooks
#region Methods
    // try to start event creation coroutine
    // startup: true => CreateStartupEvents, false => CreateDeepSeaEvents
    // if coroutine already running, waits for it to stop and then starts it
    private void TryStartCoroutine(CoroutineTypes type, DeepSeaManager deepSeaManager = null) {
        if (null != _coroutine) {
            Puts("Waiting for current event creation coroutine to finish...");
            timer.Once(1.0f, () => TryStartCoroutine(type, deepSeaManager));
            return;
        }
        if (!deepSeaManager) {
            deepSeaManager = DeepSeaManager.ServerInstance;
        }
        switch (type) {
            case CoroutineTypes.CreateStartupEvents : _coroutine = ServerMgr.Instance.StartCoroutine(CreateStartupEvents());
            return;
            case CoroutineTypes.CreateDeepSeaEvents : if (deepSeaManager) {
                _coroutine = ServerMgr.Instance.StartCoroutine(CreateDeepSeaEvents(deepSeaManager, true, false));
            }
            else {
                Puts("ERROR: Can't start Deep Sea Events creation coroutine because DeepSeaManager is null");
            }
            return;
            case CoroutineTypes.DeleteDeepSeaEvents : _coroutine = ServerMgr.Instance.StartCoroutine(DeleteDeepSeaEvents());
            return;
            default : throw new ArgumentOutOfRangeException(nameof(type), type, null);
        }
    }
    private void TryRemoveEventTimer(string zoneId) {
        if (_eventDeleteTimers.Remove(zoneId, out var value)) {
            value?.Destroy();
        }
    }
    private LeftZone GetOrAddPVPDelay(BasePlayer player, string zoneId, BaseEvent baseEvent) {
        PrintDebug($"GetOrAddPVPDelay(): Adding {player.displayName} to PVP delay");
        var added = false;
        if (_pvpDelays.TryGetValue(player.userID, out var leftZone)) {
            leftZone.zoneTimer?.Destroy();
        }
        else {
            added = true;
            leftZone = Pool.Get<LeftZone>();
            _pvpDelays.Add(player.userID, leftZone);
        }
        leftZone.zoneId = zoneId;
        leftZone.baseEvent = baseEvent;
        if (added) {
            CheckHooks(HookCheckReasons.DelayAdded, baseEvent);
        }
        return leftZone;
    }
    private bool TryRemovePVPDelay(BasePlayer player) {
        PrintDebug($"TryRemovePVPDelay(): Removing {player.displayName} from PVP delay");
        var playerId = player.userID;
        if (!_pvpDelays.Remove(playerId, out var leftZone)) return false;
        Interface.CallHook("OnPlayerRemovedFromPVPDelay", playerId, leftZone.zoneId, player);
        CheckHooks(HookCheckReasons.DelayRemoved, null);
        // baseEvent not needed
        Pool.Free(ref leftZone);
        return true;
    }
    private bool CheckEntityOwner(BaseEntity baseEntity) {
        if (!_configData.Global.CheckEntityOwner || !baseEntity.OwnerID.IsSteamId() ||  // HeliSignals and BradleyDrops exception
        baseEntity.skinID != 0) {
            return true;
        }
        PrintDebug($"CheckEntityOwner(): Skipping event creation because baseEntity={baseEntity} is owned by player={baseEntity.OwnerID}");
        return false;
    }
    private bool CanCreateDynamicPVP(string eventName, BaseEntity entity) {
        if (Interface.CallHook("OnCreateDynamicPVP", eventName, entity) == null) {
            return true;
        }
        PrintDebug($"CanCreateDynamicPVP(): Skipping event creation for eventName={eventName} due to OnCreateDynamicPVP hook result");
        return false;
    }
    private bool HasCommands() {
        // track which events we've checked, to avoid redundant calls to
        //  GetBaseEvent(); note that use of pool API means we need to free this
        //  on every return
        var checkedEvents = Pool.Get<HashSet<BaseEvent>>();
        // check for command-containing zones referenced by PVP delays, which
        //  either work when PVP delayed, or are an active zone
        // HZ: I guess this is really trying to catch the corner case of players
        //  in PVP delay because a zone expired?
        foreach (var leftZone in _pvpDelays.Values) {
            if (leftZone.baseEvent == null || leftZone.baseEvent.CommandList.Count <= 0) {
                continue;
            }
            if (leftZone.baseEvent.CommandWorksForPVPDelay || _activeDynamicZones.ContainsValue(leftZone.baseEvent)) {
                Pool.FreeUnmanaged(ref checkedEvents);
                return true;
            }
            checkedEvents.Add(leftZone.baseEvent);
        }
        foreach (var baseEvent in _activeDynamicZones.Values) {
            // optimization: skip if we've already checked this in the other loop
            if (checkedEvents.Contains(baseEvent)) {
                continue;
            }
            if (null == baseEvent || baseEvent.CommandList.Count <= 0) continue;
            Pool.FreeUnmanaged(ref checkedEvents);
            return true;
        }
        Pool.FreeUnmanaged(ref checkedEvents);
        return false;
    }
    /// toggle dynamic hook subscription(s) based on need
    private void UpdateDynamicHook(bool needSubscription, HookCategory hookCategory) {
        // abort if subscription tracking undefined, or subscription need already
        //  met, or hooks not defined
        if (!_subscriptionsByCategory.TryGetValue(hookCategory, out var haveSubscription) || needSubscription == haveSubscription || !_hooksByCategory.TryGetValue(hookCategory, out var hooks)) {
            return;
        }
        // (un)subscribe per subscription need
        foreach (var hook in hooks) {
            if (needSubscription) {
                Subscribe(hook);
            }
            else {
                Unsubscribe(hook);
            }
        }
        // record that we've achieved desired subscription state
        _subscriptionsByCategory[hookCategory] = needSubscription;
    }
    private void CheckCommandHooks(bool added) {
        // optimization: avoid calling HasCommands() if added + already subscribed
        if (added && _subscriptionsByCategory.TryGetValue(HookCategory.Command, out var subscribed) && subscribed) {
            return;
        }
        UpdateDynamicHook(HasCommands(), HookCategory.Command);
    }
    /// update plugin integration tracking/subscriptions as appropriate
    private void CheckPluginHooks(BaseEvent baseEvent) {
        // this currently only supports checks when baseEvent is provided
        if (null == baseEvent) return;
        foreach (PluginZoneCategory pzCat in Enum.GetValues(typeof(PluginZoneCategory))) {
            if (HasPluginZoneCategory(baseEvent, pzCat)) {
                UpdateDynamicHook(_activePluginZones[pzCat].Count > 0, ToHookCategory(pzCat));
            }
        }
    }
    private void CheckPvpDelayHooks() => UpdateDynamicHook(!_useExcludePlayer && _pvpDelays.Count > 0, HookCategory.PvpDelay);
    private void CheckZoneHooks() => UpdateDynamicHook(_activeDynamicZones.Count > 0, HookCategory.Zone);
    /// check whether hook subscription changes are warranted
    //
    // baseEvent is used as an optimization to only check plugin integration
    //  hook subscriptions when relevant zones are added/removed
    private void CheckHooks(HookCheckReasons reasons, BaseEvent baseEvent) {
        // update command hooks based on PVP delay or zone changes
        if (reasons.HasFlag(HookCheckReasons.DelayAdded) || reasons.HasFlag(HookCheckReasons.ZoneAdded)) {
            CheckCommandHooks(true);
        }
        else if (reasons.HasFlag(HookCheckReasons.DelayRemoved) || reasons.HasFlag(HookCheckReasons.ZoneRemoved)) {
            CheckCommandHooks(false);
        }
        // update PVP delay hooks based on PVP delay changes
        if (reasons.HasFlag(HookCheckReasons.DelayAdded) || reasons.HasFlag(HookCheckReasons.DelayRemoved)) {
            CheckPvpDelayHooks();
        }
        // update plugin and zone hooks based on zone changes
        if (reasons.HasFlag(HookCheckReasons.ZoneAdded) || reasons.HasFlag(HookCheckReasons.ZoneRemoved)) {
            CheckPluginHooks(baseEvent);
            CheckZoneHooks();
        }
    }
#endregion Methods
#region Events
#region Startup
    // utility method to return an appropriate yield instruction based on
    //  whether this is a long pause for debug logging to catch up, whether
    //  current server framerate is too low, etc.
    private YieldInstruction DynamicYield(bool pause = false) {
        // perform one-time caching of target FPS
        if (_targetFps <= 0) _targetFps = Mathf.Min(ConVar.FPS.limit, 30);
        return pause && _configData.Global.DebugEnabled ? _pauseYield : Performance.report.frameRate >= _targetFps ? _fastYield : _throttleYield;
    }
    // coroutine to orchestrate creation of all relevant events on startup
    private IEnumerator CreateStartupEvents() {
        var startTime = DateTime.UtcNow;
        Puts("Creating General Events");
        yield return CreateGeneralEvents();
        var deepSeaManager = DeepSeaManager.ServerInstance;
        switch (AnySeepSeaEventEnabled()) {
            case true when IsDeepSeaOpen(deepSeaManager) :  // this will get logged at a lower level
            yield return CreateDeepSeaEvents(deepSeaManager, delay : false, init : true);
            break;
            case true when deepSeaManager : Puts("Skipping Deep Sea Events (Deep Sea closed)");
            break;
            case true : Puts("Skipping Deep Sea Events (Deep Sea disabled)");
            break;
            default : Puts("Skipping Deep Sea Events (no events enabled)");
            break;
        }
        // this will get logged at a lower level
        yield return CreateMonumentEvents();
        Puts("Creating Auto Events");
        yield return CreateAutoEvents();
        Puts($"Startup event creation completed in {(DateTime.UtcNow - startTime).TotalSeconds} seconds");
        _coroutine = null;
    }
#endregion Startup
#region General Events
    // coroutine to determine whether any General Events should be created based
    //  on currently existing entities of interest
    // this is expected to only be called on startup
    private IEnumerator CreateGeneralEvents() {
        // determine up-front whether there are any general events to create,
        //  because iterating over all net entities is not cheap
        var checkGeneralEvents = false;
        // TODO: Bradley, Patrol Helicopter, Supply Drop, Timed Supply
        checkGeneralEvents |= _configData.GeneralEvents.CargoShip.Enabled;
        // NOTE: StopWhenKilled is checked because we don't want to start events
        //  whose end is determined by a timer, as we don't know elapsed times
        checkGeneralEvents |= _configData.GeneralEvents.HackableCrate.Enabled && _configData.GeneralEvents.HackableCrate.StopWhenKilled;
        checkGeneralEvents |= _configData.GeneralEvents.ExcavatorIgnition.Enabled;
        if (checkGeneralEvents) {
            foreach (var serverEntity in BaseNetworkable.serverEntities) {
                switch (serverEntity) {
                    // Cargo Ship Event
                    case CargoShip cargoShip : StartupCargoShip(cargoShip);
                    yield return DynamicYield();
                    break;
                    // Excavator Ignition Event
                    case DieselEngine dieselEngine : StartupDieselEngine(dieselEngine);
                    yield return DynamicYield();
                    break;
                    // Hackable Crate Event
                    case HackableLockedCrate hackableLockedCrate : StartupHackableLockedCrate(hackableLockedCrate);
                    yield return DynamicYield();
                    break;
                }
            }
        }
        yield return DynamicYield(true);
    }
#region ExcavatorIgnition Event
    // invoke appropriate hook handler for current DieselEngine state
    // this is only used on startup, to (re)create events for already-existing
    //  DieselEngine entities
    private void StartupDieselEngine(DieselEngine dieselEngine) {
        if (!dieselEngine) {
            PrintDebug("StartupDieselEngine(): DieselEngine is null");
            return;
        }
        if (!_configData.GeneralEvents.ExcavatorIgnition.Enabled) {
            PrintDebug("StartupDieselEngine(): Excavator Ignition Event is disabled");
            return;
        }
        if (!dieselEngine.IsOn()) {
            PrintDebug("StartupDieselEngine(): DieselEngine is off");
            return;
        }
        PrintDebug("StartupDieselEngine(): Found activated Giant Excavator");
        OnDieselEngineToggled(dieselEngine);
    }
    private void OnDieselEngineToggled(DieselEngine dieselEngine) {
        if (!dieselEngine || null == dieselEngine.net) {
            PrintDebug("OnDieselEngineToggled(): ERROR: Engine or Net is null", DebugLevel.Error);
            return;
        }
        var zoneId = dieselEngine.net.ID.ToString();
        if (dieselEngine.IsOn()) {
            PrintDebug($"OnDieselEngineToggled(): Requesting 'just-in-case' delete of zoneId={zoneId} due to excavator enable");
            DeleteDynamicZone(zoneId);
            HandleGeneralEvent(_configData.GeneralEvents.ExcavatorIgnition, dieselEngine, true);
        }
        else {
            PrintDebug($"OnDieselEngineToggled(): Scheduling delete of zoneId={zoneId} due to excavator disable");
            HandleDeleteDynamicZone(zoneId);
        }
    }
#endregion ExcavatorIgnition Event
#region HackableLockedCrate Event
    // invoke appropriate hook handler for current HackableLockedCrate state
    // this is only used on startup, to (re)create events for already-existing
    //  HackableLockedCrate entities
    private void StartupHackableLockedCrate(HackableLockedCrate hackableLockedCrate) {
        if (!hackableLockedCrate) {
            PrintDebug("StartupHackableLockedCrate(): HackableLockedCrate is null");
            return;
        }
        var baseEvent = _configData.GeneralEvents.HackableCrate;
        if (!baseEvent.Enabled) {
            PrintDebug("StartupHackableLockedCrate(): Hackable Crate Event is disabled");
            return;
        }
        if (!baseEvent.StopWhenKilled) {
            PrintDebug("StartupHackableLockedCrate(): Hackable Crate Event doesn't stop when killed");
            return;
        }
        if (0 != hackableLockedCrate.FirstLooterId && baseEvent.TimerStartWhenLooted) {
            // looted and stop after time since loot enabled
            // we don't know elapsed time, so err on the side of assuming the event
            //  has already ended
            PrintDebug("StartupHackableLockedCrate(): Found looted hackable locked crate, and TimerStartWhenLooted set; ignoring because elapsed time unknown");
        }
        else if (hackableLockedCrate.HasFlag(HackableLockedCrate.Flag_FullyHacked) && baseEvent.TimerStartWhenUnlocked) {
            // unlocked and stop after time since unlock enabled
            // we don't know elapsed time, so err on the side of assuming the event
            //  has already ended
            PrintDebug("StartupHackableLockedCrate(): Found unlocked hackable locked crate and TimerStartWhenUnlocked set; ignoring because elapsed time unknown");
        }
        else if (hackableLockedCrate.HasFlag(HackableLockedCrate.Flag_Hacking) && !baseEvent.StartWhenSpawned) {
            // hacking and start on hacking enabled
            PrintDebug("StartupHackableLockedCrate(): Found hacking hackable locked crate and StartWhenSpawned NOT set; triggering OnCrateHack()");
            OnCrateHack(hackableLockedCrate);
        }
        else if (baseEvent.StartWhenSpawned) {
            // any other state and start on spawn + stop when killed enabled
            PrintDebug("StartupHackableLockedCrate(): Found hackable locked crate, and StartWhenSpawned set; triggering OnEntitySpawned()");
            OnEntitySpawned(hackableLockedCrate);
        }
        else {
            PrintDebug("StartupHackableLockedCrate(): Found hackable locked crate, but ignoring because of either start on hack, or stop on timer with elapsed time unknown");
        }
    }
    private void OnEntitySpawned(HackableLockedCrate hackableLockedCrate) {
        var baseEvent = _configData.GeneralEvents.HackableCrate;
        if (!baseEvent.Enabled) {
            return;
        }
        if (!hackableLockedCrate || null == hackableLockedCrate.net) {
            PrintDebug("OnEntitySpawned(): ERROR: HackableLockedCrate or Net is null", DebugLevel.Error);
            return;
        }
        if (!baseEvent.StartWhenSpawned) {
            PrintDebug("OnEntitySpawned(HackableLockedCrate): Ignoring due to start-when-spawned false");
            return;
        }
        PrintDebug("OnEntitySpawned(): Trying to create HackableCrate spawn event");
        NextTick(() => LockedCrateEvent(hackableLockedCrate));
    }
    private void OnCrateHack(HackableLockedCrate hackableLockedCrate) {
        if (!hackableLockedCrate || null == hackableLockedCrate.net) {
            PrintDebug("OnCrateHack(): ERROR: Crate or Net is null", DebugLevel.Error);
            return;
        }
        PrintDebug("OnCrateHack(): Trying to create hackable crate hack event");
        NextTick(() => LockedCrateEvent(hackableLockedCrate));
    }
    private void OnCrateHackEnd(HackableLockedCrate hackableLockedCrate) {
        if (!hackableLockedCrate || null == hackableLockedCrate.net) {
            PrintDebug("OnCrateHackEnd(): ERROR: Crate or Net is null", DebugLevel.Error);
            return;
        }
        var zoneId = hackableLockedCrate.net.ID.ToString();
        var duration = _configData.GeneralEvents.HackableCrate.Duration;
        PrintDebug($"OnCrateHackEnd(): Scheduling delete of zoneId={zoneId} in {duration} seconds");
        HandleDeleteDynamicZone(zoneId, duration);
    }
    private void OnLootEntity(BasePlayer player, HackableLockedCrate hackableLockedCrate) {
        if (!hackableLockedCrate || null == hackableLockedCrate.net) {
            PrintDebug("OnLootEntity(HackableLockedCrate): ERROR: Crate or Net is null", DebugLevel.Error);
            return;
        }
        var baseEvent = _configData.GeneralEvents.HackableCrate;
        if (!baseEvent.Enabled || !baseEvent.TimerStartWhenLooted) {
            PrintDebug("OnLootEntity(HackableLockedCrate): Ignoring due to event or loot delay disabled");
            return;
        }
        var zoneId = hackableLockedCrate.net.ID.ToString();
        var duration = baseEvent.Duration;
        PrintDebug($"OnLootEntity(HackableLockedCrate): Scheduling delete of zoneId={zoneId} in {duration} seconds");
        HandleDeleteDynamicZone(zoneId, duration);
    }
    private void OnEntityKill(HackableLockedCrate hackableLockedCrate) {
        // all overloads of this hook are called if any of them are needed, so abort
        //  quickly for performance
        if (!_configData.GeneralEvents.HackableCrate.Enabled) {
            PrintDebug("OnEntityKill(HackableLockedCrate): Ignoring due to event disabled");
            return;
        }
        if (!hackableLockedCrate || null == hackableLockedCrate.net) {
            PrintDebug("OnEntityKill(HackableLockedCrate): ERROR: Crate or Net is null", DebugLevel.Error);
            return;
        }
        var zoneId = hackableLockedCrate.net.ID.ToString();
        if (!_activeDynamicZones.TryGetValue(zoneId, out var baseEvent)) {
            PrintDebug($"OnEntityKill(HackableLockedCrate): Ignoring due to no event for ID={zoneId}");
            return;
        }
        if (baseEvent is not HackableCrateEvent hackableCrateEvent) {
            // pathological
            PrintDebug($"OnEntityKill(HackableLockedCrate): Unknown HackableLockedCrate eventName={baseEvent.GetName()} for zoneId={zoneId}", DebugLevel.Warning);
            return;
        }
        // untether everything parented to the crate so that it doesn't get killed
        //  along with it
        ZM_GetZoneByID(zoneId)?.transform.SetParent(null, true);
        ParentDome(zoneId, Vector3.zero);
        if (!hackableCrateEvent.StopWhenKilled) {
            PrintDebug($"OnEntityKill(HackableLockedCrate): Not requesting delete of zoneId={zoneId} because stop-when-killed is false");
            return;
        }
        if (_eventDeleteTimers.ContainsKey(zoneId)) {
            PrintDebug($"OnEntityKill(HackableLockedCrate): Not requesting delete of zoneId={zoneId} because a delete timer is already active");
            return;
        }
        HandleDeleteDynamicZone(zoneId);
    }
    private void LockedCrateEvent(HackableLockedCrate hackableLockedCrate) {
        if (!CheckEntityOwner(hackableLockedCrate)) {
            return;
        }
        var baseEvent = _configData.GeneralEvents.HackableCrate;
        if (baseEvent.ExcludeOilRig && IsOnTheOilRig(hackableLockedCrate)) {
            PrintDebug("LockedCrateEvent(): The hackable locked crate is on an oil rig. Skipping event creation.");
            return;
        }
        if (baseEvent.ExcludeCargoShip && IsOnTheCargoShip(hackableLockedCrate)) {
            PrintDebug("LockedCrateEvent(): The hackable locked crate is on a cargo ship. Skipping event creation.");
            return;
        }
        if (baseEvent.ExcludeGhostShip && IsOnGhostShip(hackableLockedCrate)) {
            PrintDebug("LockedCrateEvent(): The hackable locked crate is on a ghost ship. Skipping event creation.");
            return;
        }
        // call this here, because otherwise it's difficult to ensure that we call
        //  it exactly once
        if (!CanCreateDynamicPVP(baseEvent.GetName(), hackableLockedCrate)) {
            return;
        }
        // NOTE: we are already NextTick() protected here
        HandleParentedEntityEvent(baseEvent, hackableLockedCrate, parentOnCreate : true);
    }
    private static bool IsOnTheCargoShip(HackableLockedCrate hackableLockedCrate) => true == hackableLockedCrate?.transform.parent?.HasComponent<CargoShip>();
    private static bool IsOnGhostShip(HackableLockedCrate hackableLockedCrate) => true == hackableLockedCrate?.transform.parent?.HasComponent<GhostShip>();
    private bool IsOnTheOilRig(HackableLockedCrate hackableLockedCrate) {
        // this may now get called before monument event creation if hackable
        //  crates exist on startup, so populate oilrig positions here if needed
        if (Vector3.zero == _oilRigPosition || Vector3.zero == _largeOilRigPosition) {
            foreach (LandmarkInfo landmarkInfo in TerrainMeta.Path.Landmarks) {
                switch (landmarkInfo.name) {
                    case PrefabLargeOilRig : _largeOilRigPosition = landmarkInfo.transform.position;
                    break;
                    case PrefabOilRig : _oilRigPosition = landmarkInfo.transform.position;
                    break;
                }
                if (Vector3.zero != _oilRigPosition && Vector3.zero != _largeOilRigPosition) {
                    break;
                }
            }
        }
        if (_oilRigPosition != Vector3.zero && Vector3Ex.Distance2D(hackableLockedCrate.transform.position, _oilRigPosition) < 50f) {
            return true;
        }
        if (_largeOilRigPosition != Vector3.zero && Vector3Ex.Distance2D(hackableLockedCrate.transform.position, _largeOilRigPosition) < 50f) {
            return true;
        }
        return false;
    }
#endregion HackableLockedCrate Event
#region PatrolHelicopter And BradleyAPC Event
    private void OnEntityDeath(PatrolHelicopter patrolHelicopter, HitInfo info) {
        if (!_configData.GeneralEvents.PatrolHelicopter.Enabled) {
            return;
        }
        if (!patrolHelicopter || null == patrolHelicopter.net) {
            return;
        }
        PatrolHelicopterEvent(patrolHelicopter);
    }
    private void OnEntityDeath(BradleyAPC bradleyApc, HitInfo info) {
        if (!_configData.GeneralEvents.BradleyApc.Enabled) {
            return;
        }
        if (!bradleyApc || null == bradleyApc.net) {
            return;
        }
        BradleyApcEvent(bradleyApc);
    }
    private void PatrolHelicopterEvent(PatrolHelicopter patrolHelicopter) {
        var baseEvent = _configData.GeneralEvents.PatrolHelicopter;
        if (!baseEvent.Enabled) {
            return;
        }
        PrintDebug("PatrolHelicopterEvent(): Trying to create Patrol Helicopter killed event.");
        if (!CheckEntityOwner(patrolHelicopter)) {
            return;
        }
        HandleGeneralEvent(baseEvent, patrolHelicopter, useEntityId : false);
    }
    private void BradleyApcEvent(BradleyAPC bradleyApc) {
        var baseEvent = _configData.GeneralEvents.BradleyApc;
        if (!baseEvent.Enabled) {
            return;
        }
        PrintDebug("BradleyApcEvent(): Trying to create Bradley APC killed event.");
        if (!CheckEntityOwner(bradleyApc)) {
            return;
        }
        HandleGeneralEvent(baseEvent, bradleyApc, useEntityId : false);
    }
#endregion PatrolHelicopter And BradleyAPC Event
#region SupplyDrop And SupplySignal Event
    // stores NetIDs of active CargoPlane and SupplyDrop instances spawned by a
    //  SupplySignal
    private readonly HashSet<NetworkableId> _activeSignaledPlanesAndDrops = new();
    private void OnCargoPlaneSignaled(CargoPlane plane, SupplySignal signal) => NextTick(() => {
        if (!plane || !signal) {
            return;
        }
        // record that plane was signaled
        var planeID = plane.net.ID;
        if (!_activeSignaledPlanesAndDrops.Add(planeID)) {
            return;
        }
        PrintDebug($"OnCargoPlaneSignaled(): Supply Signal {signal.net.ID} spawned Cargo Plane {planeID} with drop position {plane.dropPosition}");
    }
    );
    private void OnSupplyDropDropped(BaseEntity entity, CargoPlane plane) => NextTick(() => {
        var supplyDrop = entity as SupplyDrop;
        if (!supplyDrop || null == supplyDrop.net || !plane || null == plane.net) {
            return;
        }
        // if plane was signaled, record drop as signaled too
        var planeID = plane.net.ID;
        var supplyDropID = supplyDrop.net.ID;
        if (_activeSignaledPlanesAndDrops.Contains(planeID)) {
            _activeSignaledPlanesAndDrops.Add(supplyDropID);
            PrintDebug($"OnSupplyDropDropped(): Signaled Cargo Plane {planeID} spawned Supply Drop {supplyDropID} at position {supplyDrop.transform.position}");
        }
        else {
            // must be a timed drop rather than a signaled one
            PrintDebug($"OnSupplyDropDropped(): Timed Cargo Plane {planeID} spawned Supply Drop {supplyDropID} at position {supplyDrop.transform.position}");
        }
        HandleSupplyDropEvent(supplyDrop, false);
    }
    );
    private void OnSupplyDropLanded(SupplyDrop supplyDrop) => NextTick(() => {
        if (!supplyDrop || null == supplyDrop.net) {
            return;
        }
        var zoneId = supplyDrop.net.ID.ToString();
        if (_activeDynamicZones.TryGetValue(zoneId, out var baseEvent)) {
            // event was already created on spawn; parent the event to the entity, so
            //  that they move together
            // NOTES:
            // - don't delete on failure, because leaving the existing zone on the
            //    ground is better than deleting it
            // - no need to delay parenting, as the zone presumably has already
            //    existed for a bit
            ParentEventToEntity(zoneId, baseEvent, supplyDrop, deleteOnFailure : false, delay : false);
            return;
        }
        HandleSupplyDropEvent(supplyDrop, true);
    }
    );
    private void OnLootEntity(BasePlayer _, SupplyDrop supplyDrop) {
        if (!supplyDrop || null == supplyDrop.net) {
            return;
        }
        var zoneId = supplyDrop.net.ID.ToString();
        if (!_activeDynamicZones.TryGetValue(zoneId, out var baseEvent)) {
            // no active zone for this supply drop
            return;
        }
        if (baseEvent is not SupplyDropEvent supplyDropEvent) {
            // pathological
            PrintDebug($"OnLootEntity(): Unknown SupplyDrop eventName={baseEvent.GetName()} for zoneId={zoneId}", DebugLevel.Warning);
            return;
        }
        if (!supplyDropEvent.Enabled || !supplyDropEvent.TimerStartWhenLooted) {
            return;
        }
        HandleDeleteDynamicZone(zoneId, supplyDropEvent.Duration);
    }
    private void OnEntityKill(SupplyDrop supplyDrop) {
        // all overloads of this hook are called if any of them are needed, so abort
        //  quickly for performance
        if (!_configData.GeneralEvents.SupplySignal.Enabled && !_configData.GeneralEvents.TimedSupply.Enabled) {
            PrintDebug("OnEntityKill(SupplyDrop): Ignoring due to both SupplyDrop events disabled");
            return;
        }
        if (!supplyDrop || null == supplyDrop.net) {
            PrintDebug("OnEntityKill(SupplyDrop): ERROR: Drop or Net is null", DebugLevel.Error);
            return;
        }
        var supplyDropID = supplyDrop.net.ID;
        _activeSignaledPlanesAndDrops.Remove(supplyDropID);
        var zoneId = supplyDropID.ToString();
        if (!_activeDynamicZones.TryGetValue(zoneId, out var baseEvent)) {
            PrintDebug($"OnEntityKill(SupplyDrop): Ignoring due to no event for ID={zoneId}");
            return;
        }
        if (baseEvent is not SupplyDropEvent supplyDropEvent) {
            // pathological
            PrintDebug($"OnEntityKill(SupplyDrop): Unknown SupplyDrop eventName={baseEvent.GetName()} for zoneId={zoneId}", DebugLevel.Warning);
            return;
        }
        // untether everything parented to the drop so that it doesn't get killed
        //  along with it
        ZM_GetZoneByID(zoneId)?.transform.SetParent(null, true);
        ParentDome(zoneId, Vector3.zero);
        // abort if specific drop event flavor is disabled, or not configured to
        //  stop on kill
        if (supplyDropEvent is not {
            Enabled : true
        }
        || !supplyDropEvent.StopWhenKilled) {
            PrintDebug($"OnEntityKill(SupplyDrop): Not requesting delete of zoneId={zoneId} because event {supplyDropEvent.GetName()} is disabled, or stop-when-killed is false");
            return;
        }
        if (_eventDeleteTimers.ContainsKey(zoneId)) {
            PrintDebug($"OnEntityKill(SupplyDrop): Not requesting delete of zoneId={zoneId} because a delete timer is already active");
            return;
        }
        HandleDeleteDynamicZone(zoneId);
    }
    private void OnEntityKill(CargoPlane plane) {
        // all overloads of this hook are called if any of them are needed, so abort
        //  quickly for performance
        if (!_configData.GeneralEvents.SupplySignal.Enabled && !_configData.GeneralEvents.TimedSupply.Enabled) {
            return;
        }
        if (!plane || null == plane.net) {
            return;
        }
        _activeSignaledPlanesAndDrops.Remove(plane.net.ID);
    }
    private static string GetSupplyDropStateName(bool isLanded) => isLanded ? "Landed" : "Spawned";
    private void HandleSupplyDropEvent(SupplyDrop supplyDrop, bool isLanded) {
        if (!supplyDrop || null == supplyDrop.net) {
            return;
        }
        PrintDebug($"HandleSupplyDropEvent(): Trying to create supply drop {GetSupplyDropStateName(isLanded)} event at {supplyDrop.transform.position}.");
        if (!CheckEntityOwner(supplyDrop
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху