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

FREE AutomatedWorkcarts : 0.36.1 0.36.1

Нет прав для скачивания
C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using static BaseEntity;
using static TrainCar;
using static TrainEngine;
using static TrainTrackSpline;

namespace Oxide.Plugins
{
    [Info("Automated Workcarts", "WhiteThunder", "0.36.1")]
    [Description("Automates workcarts with NPC conductors.")]
    internal class AutomatedWorkcarts : CovalencePlugin
    {
        #region Fields

        [PluginReference]
        private Plugin CargoTrainEvent;

        private const string PermissionToggle = "automatedworkcarts.toggle";
        private const string PermissionManageTriggers = "automatedworkcarts.managetriggers";

        private const string ShopkeeperPrefab = "assets/prefabs/npc/bandit/shopkeepers/bandit_shopkeeper.prefab";
        private const string GenericMapMarkerPrefab = "assets/prefabs/tools/map/genericradiusmarker.prefab";
        private const string VendingMapMarkerPrefab = "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab";
        private const string ExplosionMapMakerPrefab = "assets/prefabs/tools/map/explosionmarker.prefab";
        private const string CrateMarkerPrefab = "assets/prefabs/tools/map/cratemarker.prefab";
        private const string BradleyExplosionEffectPrefab = "assets/prefabs/npc/m2bradley/effects/bradley_explosion.prefab";

        private static readonly FieldInfo TrainCouplingIsValidField = typeof(TrainCoupling).GetField("isValid", BindingFlags.NonPublic | BindingFlags.Instance)
            ?? typeof(TrainCoupling).GetField("isValid", BindingFlags.Public | BindingFlags.Instance);

        private readonly object False = false;
        private static readonly Regex IdRegex = new("\\$id", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        private readonly BasePlayer[] _playerQueryResults = new BasePlayer[64];

        private Configuration _config;
        private StoredPluginData _data;
        private StoredTunnelData _tunnelData;
        private StoredMapData _mapData;

        private readonly SpawnedTrainCarTracker _spawnedTrainCarTracker = new();
        private readonly DisableSpawnPointManager _disableSpawnPointManager = new();
        private readonly TriggerManager _triggerManager;
        private readonly TrainManager _trainManager;
        private readonly RouteManager _routeManager;
        private readonly ColorMarkerUpdateManager _colorMarkerUpdateManager;

        private Coroutine _startupCoroutine;
        private Timer _showStatesTimer;

        public AutomatedWorkcarts()
        {
            _trainManager = new TrainManager(this, _spawnedTrainCarTracker);
            _triggerManager = new TriggerManager(this, _trainManager);
            _routeManager = new RouteManager(this);
            _colorMarkerUpdateManager = new ColorMarkerUpdateManager(this);
        }

        #endregion

        #region Hooks

        private void Init()
        {
            _config.Init();
            _data = StoredPluginData.Load();
            _tunnelData = StoredTunnelData.Load();

            permission.RegisterPermission(PermissionToggle, this);
            permission.RegisterPermission(PermissionManageTriggers, this);

            if (!_config.MapMarkers.AnyColorsEnabled)
            {
                Unsubscribe(nameof(OnPlayerConnected));
            }
        }

        private void OnServerInitialized()
        {
            if (!_config.UsingDefaults)
            {
                var addedPrefabs = _config.PopulateConfig();
                if (addedPrefabs.Count > 0)
                {
                    LogWarning($"Discovered and added {addedPrefabs.Count} train engine prefabs to Configuration.\n - {string.Join("\n - ", addedPrefabs)}");
                    SaveConfig();
                }
            }

            _config.OnServerInitialized();
            _mapData = StoredMapData.Load();
            _startupCoroutine = ServerMgr.Instance.StartCoroutine(new TrackedCoroutine(this).WithEnumerator(DoStartupRoutine()));
        }

        private void Unload()
        {
            if (_startupCoroutine != null)
            {
                ServerMgr.Instance.StopCoroutine(_startupCoroutine);
            }

            OnServerSave();
            _triggerManager.DestroyAll();
            _trainManager.Unload();
            _disableSpawnPointManager.Unload();
            _routeManager.Unload();
            _colorMarkerUpdateManager.Unload();
        }

        private void OnServerSave()
        {
            if (_trainManager.UpdateTrainEngineData())
            {
                _data.Save();
            }
            else
            {
                _data.SaveIfDirty();
            }
        }

        private void OnNewSave()
        {
            _data = StoredPluginData.Clear();
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player.IsReceivingSnapshot)
            {
                timer.Once(1f, () => OnPlayerConnected(player));
                return;
            }

            _colorMarkerUpdateManager.Restart();
        }

        private object OnTrainCarUncouple(TrainCar trainCar, BasePlayer player)
        {
            // Disallow uncoupling train cars from automated trains.
            return _trainManager.HasTrainController(trainCar)
                ? False
                : null;
        }

        #endregion

        #region Commands

        [Command("aw.toggle")]
        private void CommandAutomateTrain(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer
                || !VerifyPermission(player, PermissionToggle))
                return;

            var basePlayer = player.Object as BasePlayer;

            var trainCar = GetTrainCarWhereAiming(basePlayer);
            if (trainCar == null)
            {
                ReplyToPlayer(player, Lang.ErrorNoWorkcartFound);
                return;
            }

            var trainController = _trainManager.GetTrainController(trainCar);
            if (trainController == null)
            {
                var leadTrainEngine = GetLeadTrainEngine(trainCar);
                if (leadTrainEngine == null)
                {
                    ReplyToPlayer(player, Lang.ErrorNoWorkcart);
                    return;
                }

                if (IsTrainOwned(trainCar))
                {
                    ReplyToPlayer(player, Lang.ErrorWorkcartOwned);
                    return;
                }

                if (!_trainManager.CanHaveMoreConductors())
                {
                    ReplyToPlayer(player, Lang.ErrorMaxConductors, _trainManager.CountedConductors, _config.MaxConductors);
                    return;
                }

                TrainEngineData trainEngineData = null;

                if (args.Length > 0)
                {
                    var routeName = GetRouteNameFromArg(args[0], requirePrefix: false);
                    if (!string.IsNullOrWhiteSpace(routeName))
                    {
                        trainEngineData = new TrainEngineData { Route = routeName };
                    }
                }

                if (_trainManager.TryCreateTrainController(leadTrainEngine, trainEngineData: trainEngineData))
                {
                    var baseMessage = trainEngineData != null
                        ? GetMessage(player, Lang.ToggleOnWithRouteSuccess, trainEngineData.Route)
                        : GetMessage(player, Lang.ToggleOnSuccess);

                    player.Reply(baseMessage + " " + GetConductorCountMessage(player));

                    if (player.HasPermission(PermissionManageTriggers))
                    {
                        if (trainEngineData?.Route != null)
                        {
                            _triggerManager.SetPlayerDisplayedRoute(basePlayer, trainEngineData.Route);
                        }

                        _triggerManager.ShowAllRepeatedly(basePlayer);
                    }
                }
                else
                {
                    ReplyToPlayer(player, Lang.ErrorAutomateBlocked);
                }
            }
            else
            {
                _trainManager.KillTrainController(trainCar);
                player.Reply(GetMessage(player, Lang.ToggleOffSuccess) + " " + GetConductorCountMessage(player));
            }
        }

        [Command("aw.resetall")]
        private void CommandResetTrains(IPlayer player, string cmd, string[] args)
        {
            if (!player.IsServer
                && !VerifyPermission(player, PermissionToggle))
                return;

            var trainCount = _trainManager.ResetAll();
            ReplyToPlayer(player, Lang.ResetAllSuccess, trainCount);
        }

        [Command("aw.addtrigger", "awt.add")]
        private void CommandAddTrigger(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer
                || !VerifyPermission(player, PermissionManageTriggers))
                return;

            if (!_config.EnableMapTriggers)
            {
                ReplyToPlayer(player, Lang.ErrorMapTriggersDisabled);
                return;
            }

            if (!VerifyAimingAtTrackPosition(player, out var trackPosition))
                return;

            var triggerData = new TriggerData { Position = trackPosition };
            AddTriggerShared(player, cmd, args, triggerData);
        }

        [Command("aw.addtunneltrigger", "awt.addt")]
        private void CommandAddTunnelTrigger(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer
                || !VerifyPermission(player, PermissionManageTriggers))
                return;

            if (!VerifyAimingAtTrackPosition(player, out var trackPosition)
                || !VerifySupportedNearbyTrainTunnel(player, trackPosition, out var dungeonCellWrapper))
                return;

            if (!_config.IsTunnelTypeEnabled(dungeonCellWrapper.TunnelType))
            {
                ReplyToPlayer(player, Lang.ErrorTunnelTypeDisabled, dungeonCellWrapper.TunnelType);
                return;
            }

            var triggerData = new TriggerData
            {
                TunnelType = dungeonCellWrapper.TunnelType.ToString(),
                Position = dungeonCellWrapper.InverseTransformPoint(trackPosition),
            };

            AddTriggerShared(player, cmd, args, triggerData, dungeonCellWrapper);
        }

        private void AddTriggerShared(IPlayer player, string cmd, string[] args, TriggerData triggerData, DungeonCellWrapper dungeonCellWrapper = null)
        {
            foreach (var arg in args)
            {
                if (!VerifyValidArgAndModifyTrigger(player, cmd, arg, triggerData, Lang.AddTriggerSyntax))
                    return;
            }

            if (!triggerData.IsSpawner
                && !triggerData.AddConductor
                && !triggerData.Destroy
                && triggerData.GetTrackSelectionInstruction() == null
                && triggerData.GetSpeedInstruction() == null
                && triggerData.GetDirectionInstruction() == null)
            {
                triggerData.Speed = EngineSpeeds.Zero.ToString();
            }

            var basePlayer = player.Object as BasePlayer;

            if (triggerData.IsSpawner)
            {
                var rotation = Quaternion.Euler(basePlayer.viewAngles);
                if (dungeonCellWrapper != null)
                {
                    rotation *= Quaternion.Inverse(dungeonCellWrapper.Rotation);
                }

                triggerData.RotationAngle = rotation.eulerAngles.y % 360;
            }

            _triggerManager.AddTrigger(triggerData);

            if (triggerData.Route != null)
            {
                _triggerManager.SetPlayerDisplayedRoute(basePlayer, triggerData.Route);
            }

            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.AddTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.updatetrigger", "awt.update")]
        private void CommandUpdateTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.UpdateTriggerSyntax, out var triggerData, out var optionArgs))
                return;

            if (optionArgs.Length == 0)
            {
                ReplyToPlayer(player, Lang.UpdateTriggerSyntax, cmd, GetTriggerOptions(player));
                return;
            }

            var newTriggerData = triggerData.Clone();
            foreach (var arg in optionArgs)
            {
                if (!VerifyValidArgAndModifyTrigger(player, cmd, arg, newTriggerData, Lang.UpdateTriggerSyntax))
                    return;
            }

            _triggerManager.UpdateTrigger(triggerData, newTriggerData);

            var basePlayer = player.Object as BasePlayer;
            if (triggerData.Route != null)
            {
                _triggerManager.SetPlayerDisplayedRoute(basePlayer, triggerData.Route);
            }

            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.replacetrigger", "awt.replace")]
        private void CommandReplaceTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.UpdateTriggerSyntax, out var triggerData, out var optionArgs))
                return;

            if (optionArgs.Length == 0)
            {
                ReplyToPlayer(player, Lang.UpdateTriggerSyntax, cmd, GetTriggerOptions(player));
                return;
            }

            var newTriggerData = new TriggerData();
            foreach (var arg in optionArgs)
            {
                if (!VerifyValidArgAndModifyTrigger(player, cmd, arg, newTriggerData, Lang.UpdateTriggerSyntax))
                    return;
            }

            _triggerManager.UpdateTrigger(triggerData, newTriggerData);

            var basePlayer = player.Object as BasePlayer;
            if (triggerData.Route != null)
            {
                _triggerManager.SetPlayerDisplayedRoute(basePlayer, triggerData.Route);
            }

            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.enabletrigger", "awt.enable")]
        private void CommandEnableTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out var triggerData, out _))
                return;

            var newTriggerData = triggerData.Clone();
            newTriggerData.Enabled = true;
            _triggerManager.UpdateTrigger(triggerData, newTriggerData);

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.disabletrigger", "awt.disable")]
        private void CommandDisableTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out var triggerData, out _))
                return;

            var newTriggerData = triggerData.Clone();
            newTriggerData.Enabled = false;
            _triggerManager.UpdateTrigger(triggerData, newTriggerData);

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.movetrigger", "awt.move")]
        private void CommandMoveTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out var triggerData, out _))
                return;

            if (!VerifyAimingAtTrackPosition(player, out var trackPosition))
                return;

            if (triggerData.TriggerType == TrainTriggerType.Tunnel)
            {
                if (!VerifySupportedNearbyTrainTunnel(player, trackPosition, out var dungeonCellWrapper))
                    return;

                if (dungeonCellWrapper.TunnelType != triggerData.GetTunnelType())
                {
                    ReplyToPlayer(player, Lang.ErrorUnsupportedTunnel);
                    return;
                }

                trackPosition = dungeonCellWrapper.InverseTransformPoint(trackPosition);
            }

            _triggerManager.MoveTrigger(triggerData, trackPosition);

            var basePlayer = player.Object as BasePlayer;
            if (triggerData.Route != null)
            {
                _triggerManager.SetPlayerDisplayedRoute(basePlayer, triggerData.Route);
            }

            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.MoveTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.removetrigger", "awt.remove")]
        private void CommandRemoveTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out var triggerData, out _))
                return;

            _triggerManager.RemoveTrigger(triggerData);

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.RemoveTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.rotatetrigger", "awt.rotate")]
        private void CommandSetTriggerRotation(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out var triggerData, out _))
                return;

            var basePlayer = player.Object as BasePlayer;
            var playerPosition = basePlayer.transform.position;

            var triggerInstance = _triggerManager.FindNearestTrigger(playerPosition, triggerData);
            var rotation = Quaternion.Euler(basePlayer.viewAngles);
            var needsRespawn = false;

            if (triggerInstance.Spline != null)
            {
                rotation = GetSplineTangentRotation(triggerInstance.Spline, triggerInstance.DistanceOnSpline, rotation);

                if (Vector3.Dot(triggerInstance.SpawnRotation * Vector3.forward, rotation * Vector3.forward) < 0)
                {
                    needsRespawn = true;
                }
            }

            if (triggerInstance is TunnelTriggerInstance tunnelTriggerInstance)
            {
                rotation *= Quaternion.Inverse(tunnelTriggerInstance.DungeonCellWrapper.Rotation);
            }

            _triggerManager.RotateTrigger(triggerData, rotation.eulerAngles.y % 360);

            if (needsRespawn)
            {
                _triggerManager.RespawnTrigger(triggerData);
            }

            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.RotateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.respawntrigger", "awt.respawn")]
        private void CommandRespawnTrigger(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out var triggerData, out _))
                return;

            if (!triggerData.IsSpawner)
            {
                ReplyToPlayer(player, Lang.ErrorRequiresSpawnTrigger);
                return;
            }

            if (!triggerData.Enabled)
            {
                ReplyToPlayer(player, Lang.ErrorTriggerDisabled);
                return;
            }

            _triggerManager.RespawnTrigger(triggerData);

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
        }

        [Command("aw.addtriggercommand", "awt.addcommand", "awt.addcmd")]
        private void CommandAddCommand(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.AddCommandSyntax, out var triggerData, out var optionArgs))
                return;

            if (optionArgs.Length < 1)
            {
                ReplyToPlayer(player, Lang.AddCommandSyntax, cmd);
                return;
            }

            var quotedCommands = optionArgs.Select(command => command.Contains(" ") ? $"\"{command}\"" : command).ToArray();
            _triggerManager.AddTriggerCommand(triggerData, string.Join(" ", quotedCommands));

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.removetriggercommand", "awt.removecommand", "awt.removecmd")]
        private void CommandRemoveCommand(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.RemoveCommandSyntax, out var triggerData, out var optionArgs))
                return;

            if (optionArgs.Length < 1 || !int.TryParse(optionArgs[0], out var commandIndex))
            {
                ReplyToPlayer(player, Lang.RemoveCommandSyntax, cmd);
                return;
            }

            if (commandIndex < 1 || commandIndex > triggerData.Commands.Count)
            {
                ReplyToPlayer(player, Lang.RemoveCommandErrorIndex, commandIndex);
                return;
            }

            _triggerManager.RemoveTriggerCommand(triggerData, commandIndex - 1);

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.settriggertrain", "awt.train")]
        private void CommandTriggerTrain(IPlayer player, string cmd, string[] args)
        {
            if (!VerifyCanModifyTrigger(player, cmd, args, Lang.RemoveCommandSyntax, out var triggerData, out var optionArgs))
                return;

            var trainCarAliases = new List<string>();
            foreach (var arg in optionArgs)
            {
                var trainCarPrefab = TrainCarPrefab.FindPrefab(arg);
                if (trainCarPrefab == null)
                {
                    ReplyToPlayer(player, Lang.ErrorUnrecognizedTrainCar, arg);
                    return;
                }

                trainCarAliases.Add(trainCarPrefab.TrainCarAlias);
            }

            var newTriggerData =  triggerData.Clone();
            newTriggerData.TrainCars = trainCarAliases.ToArray();
            _triggerManager.UpdateTrigger(triggerData, newTriggerData);

            var basePlayer = player.Object as BasePlayer;
            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, Lang.UpdateTriggerSuccess, GetTriggerPrefix(player, triggerData), triggerData.Id);
        }

        [Command("aw.showtriggers", "awt.show")]
        private void CommandShowTriggers(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer
                || !VerifyPermission(player, PermissionManageTriggers)
                || !VerifyAnyTriggers(player))
                return;

            var duration = 60;
            string routeName = null;

            foreach (var arg in args)
            {
                if (duration == 60)
                {
                    if (int.TryParse(arg, out var argIntValue))
                    {
                        duration = argIntValue;
                        continue;
                    }
                }

                if (routeName == null)
                {
                    var routeNameArg = GetRouteNameFromArg(arg, requirePrefix: false);
                    if (!string.IsNullOrWhiteSpace(routeNameArg))
                        routeName = routeNameArg;
                }
            }

            var basePlayer = player.Object as BasePlayer;

            _triggerManager.SetPlayerDisplayedRoute(basePlayer, routeName);
            _triggerManager.ShowAllRepeatedly(basePlayer, duration);

            if (routeName != null)
            {
                ReplyToPlayer(player, Lang.ShowTriggersWithRouteSuccess, routeName, FormatTime(duration));
            }
            else
            {
                ReplyToPlayer(player, Lang.ShowTriggersSuccess, FormatTime(duration));
            }
        }

        // Debugging command, usable by one player at a time.
        [Command("aw.showstates")]
        private void CommandShowStates(IPlayer player, string cmd, string[] args)
        {
            if (player.IsServer
                || !VerifyPermission(player, PermissionManageTriggers)
                || !VerifyAnyTriggers(player))
                return;

            if (args.Length < 1 || !int.TryParse(args[0], out var duration))
            {
                duration = 60;
            }

            var drawDuration = 0.5f;
            var maxDistanceSquared = Mathf.Pow(_config.TriggerDisplayDistance, 2);
            var basePlayer = player.Object as BasePlayer;

            _showStatesTimer?.Destroy();
            _showStatesTimer = timer.Repeat(drawDuration, Mathf.RoundToInt(duration / drawDuration), () =>
            {
                if (basePlayer == null || basePlayer.IsDestroyed || !basePlayer.IsConnected)
                {
                    _showStatesTimer.Destroy();
                    _showStatesTimer = null;
                    return;
                }

                _trainManager.ShowNearbyTrainStates(basePlayer, maxDistanceSquared, drawDuration);
            });
        }

        #endregion

        #region API

        [HookMethod(nameof(API_AutomateWorkcart))]
        public bool API_AutomateWorkcart(TrainEngine trainEngine)
        {
            return _trainManager.HasTrainController(trainEngine)
                || _trainManager.TryCreateTrainController(trainEngine);
        }

        [HookMethod(nameof(API_StopAutomatingWorkcart))]
        public void API_StopAutomatingWorkcart(TrainEngine trainEngine)
        {
            _trainManager.KillTrainController(trainEngine);
        }

        [HookMethod(nameof(API_IsWorkcartAutomated))]
        public bool API_IsWorkcartAutomated(TrainEngine trainEngine)
        {
            return _trainManager.HasTrainController(trainEngine);
        }

        [HookMethod(nameof(API_GetAutomatedWorkcarts))]
        public TrainEngine[] API_GetAutomatedWorkcarts()
        {
            return _trainManager.GetAutomatedTrainEngines();
        }

        #endregion

        #region Exposed Hooks

        private static class ExposedHooks
        {
            public static object OnWorkcartAutomationStart(TrainEngine trainEngine)
            {
                return Interface.CallHook("OnWorkcartAutomationStart", trainEngine);
            }

            public static void OnWorkcartAutomationStarted(TrainEngine trainEngine)
            {
                Interface.CallHook("OnWorkcartAutomationStarted", trainEngine);
            }

            public static void OnWorkcartAutomationStopped(TrainEngine trainEngine)
            {
                Interface.CallHook("OnWorkcartAutomationStopped", trainEngine);
            }
        }

        #endregion

        #region Dependencies

        private bool IsCargoTrain(TrainEngine trainEngine)
        {
            return CargoTrainEvent?.Call("IsTrainSpecial", trainEngine.net.ID) is true;
        }

        #endregion

        #region Helper Methods - Static

        private static List<T> FindPrefabsOfType<T>() where T : BaseEntity
        {
            var prefabList = new List<T>();

            foreach (var assetPath in GameManifest.Current.entities)
            {
                var entity = GameManager.server.FindPrefab(assetPath)?.GetComponent<T>();
                if (entity == null)
                    continue;

                prefabList.Add(entity);
            }

            return prefabList;
        }

        private static BaseMountable GetDriverSeat(TrainEngine trainEngine)
        {
            foreach (var mountPoint in trainEngine.mountPoints)
            {
                if (mountPoint.isDriver)
                    return mountPoint.mountable;
            }

            return null;
        }

        private struct TrainPassengerInfo
        {
            public bool HasPlayerDriver;
            public bool HasNpcDriver;
            public bool HasPlayerPassenger;
            public bool HasNpcPassenger;

            public bool HasDriver => HasPlayerDriver || HasNpcDriver;
            public bool HasPassenger => HasPlayerPassenger || HasNpcPassenger;
            public bool HasAnyOccupant => HasDriver || HasPassenger;
        }

        private static TrainPassengerInfo GetPassengerInfo(CompleteTrain completeTrain)
        {
            var passengerInfo = new TrainPassengerInfo();

            foreach (var trainCar in completeTrain.trainCars)
            {
                foreach (var child in trainCar.children)
                {
                    if (child is not BasePlayer basePlayer)
                        continue;

                    if (basePlayer.userID.IsSteamId())
                    {
                        passengerInfo.HasPlayerPassenger = true;
                    }
                    else
                    {
                        passengerInfo.HasNpcPassenger = true;
                    }
                }

                if (trainCar is TrainEngine trainEngine)
                {
                    var driverSeat = GetDriverSeat(trainEngine);
                    if (driverSeat.GetMounted() is not { } basePlayer)
                        continue;

                    if (basePlayer.userID.IsSteamId())
                    {
                        passengerInfo.HasPlayerDriver = true;
                    }
                    else
                    {
                        passengerInfo.HasNpcDriver = true;
                    }
                }
            }

            return passengerInfo;
        }

        #endregion

        #region Helper Methods - Command Checks

        private bool VerifyPermission(IPlayer player, string permissionName)
        {
            if (player.HasPermission(permissionName))
                return true;

            ReplyToPlayer(player, Lang.ErrorNoPermission);
            return false;
        }

        private bool VerifyAnyTriggers(IPlayer player)
        {
            if (_mapData.MapTriggers.Count > 0
                || _tunnelData.TunnelTriggers.Count > 0)
                return true;

            ReplyToPlayer(player, Lang.ErrorNoTriggers);
            return false;
        }

        private bool VerifyTriggerExists(IPlayer player, int triggerId, TrainTriggerType triggerType, out TriggerData triggerData)
        {
            triggerData = _triggerManager.FindTrigger(triggerId, triggerType);
            if (triggerData != null)
                return true;

            _triggerManager.ShowAllRepeatedly(player.Object as BasePlayer);
            ReplyToPlayer(player, Lang.ErrorTriggerNotFound, GetTriggerPrefix(player, triggerType), triggerId);
            return false;
        }

        private bool VerifyAimingAtTrackPosition(IPlayer player, out Vector3 trackPosition)
        {
            if (TryGetTrackPosition(player.Object as BasePlayer, out trackPosition))
                return true;

            ReplyToPlayer(player, Lang.ErrorNoTrackFound);
            return false;
        }

        private bool IsTriggerArg(IPlayer player, string arg, out int triggerId, out TrainTriggerType triggerType)
        {
            triggerType = TrainTriggerType.Map;
            triggerId = 0;

            if (arg.StartsWith("#"))
            {
                arg = arg.Substring(1);
            }

            if (arg.Length <= 1)
                return false;

            var triggerPrefix = arg.Substring(0, 1).ToLower();
            var triggerIdString = arg.Substring(1).ToLower();

            if (!int.TryParse(triggerIdString, out triggerId))
                return false;

            if (triggerPrefix == GetTriggerPrefix(player, TrainTriggerType.Tunnel).ToLower())
            {
                triggerType = TrainTriggerType.Tunnel;
                return true;
            }

            if (triggerPrefix == GetTriggerPrefix(player, TrainTriggerType.Map).ToLower())
            {
                triggerType = TrainTriggerType.Map;
                return true;
            }

            return false;
        }

        private bool VerifyTriggerWhereAiming(IPlayer player, string cmd, string[] args, string errorMessageName, out TriggerData triggerData, out string[] optionArgs)
        {
            var basePlayer = player.Object as BasePlayer;
            optionArgs = args;
            triggerData = null;

            triggerData = _triggerManager.FindNearestTriggerWhereAiming(basePlayer)?.TriggerData;
            if (triggerData != null)
                return true;

            _triggerManager.ShowAllRepeatedly(basePlayer);
            ReplyToPlayer(player, errorMessageName, cmd, GetTriggerOptions(player));
            return false;
        }

        private bool VerifyCanModifyTrigger(IPlayer player, string cmd, string[] args, string errorMessageName, out TriggerData triggerData, out string[] optionArgs)
        {
            triggerData = null;
            optionArgs = null;

            if (!VerifyPermission(player, PermissionManageTriggers)
                || !VerifyAnyTriggers(player))
                return false;

            if (args.Length > 0 && IsTriggerArg(player, args[0], out var triggerId, out var triggerType))
            {
                optionArgs = args.Skip(1).ToArray();
                return VerifyTriggerExists(player, triggerId, triggerType, out triggerData);
            }

            if (player.IsServer)
            {
                // Server commands must specify a trigger id.
                ReplyToPlayer(player, errorMessageName, cmd, GetTriggerOptions(player));
                return false;
            }

            return VerifyTriggerWhereAiming(player, cmd, args, errorMessageName, out triggerData, out optionArgs);
        }

        private bool VerifySupportedNearbyTrainTunnel(IPlayer player, Vector3 trackPosition, out DungeonCellWrapper dungeonCellWrapper)
        {
            dungeonCellWrapper = FindNearestDungeonCell(trackPosition);
            if (dungeonCellWrapper == null || dungeonCellWrapper.TunnelType == TunnelType.Unsupported)
            {
                ReplyToPlayer(player, Lang.ErrorUnsupportedTunnel);
                return false;
            }

            return true;
        }

        private bool VerifyValidArgAndModifyTrigger(IPlayer player, string cmd, string arg, TriggerData triggerData, string errorMessageName)
        {
            var argLower = arg.ToLower();
            if (argLower is "start" or "conductor")
            {
                triggerData.AddConductor = true;
                return true;
            }

            if (argLower.StartsWith("brake"))
            {
                triggerData.Brake = true;
                return true;
            }

            if (argLower.StartsWith("destroy"))
            {
                triggerData.Destroy = true;
                return true;
            }

            if (argLower.StartsWith("enable"))
            {
                triggerData.Enabled = true;
                return true;
            }

            if (argLower.StartsWith("disable"))
            {
                triggerData.Enabled = false;
                return true;
            }

            var prefab = TrainCarPrefab.FindPrefab(argLower);
            if (prefab != null)
            {
                if (triggerData.TrainCars == null)
                {
                    triggerData.TrainCars = new[] { prefab.TrainCarAlias };
                }
                else
                {
                    var length = triggerData.TrainCars.Length;
                    Array.Resize(ref triggerData.TrainCars, length + 1);
                    triggerData.TrainCars[length] = prefab.TrainCarAlias;
                }

                return true;
            }

            if (arg[^1] == '%' && float.TryParse(arg[..^1], out var percentage))
            {
                triggerData.Chance = Mathf.Clamp(percentage / 100f, 0, 1);
                return true;
            }

            if (float.TryParse(arg, out var stopDuration))
            {
                triggerData.StopDuration = stopDuration;
                return true;
            }

            var routeName = GetRouteNameFromArg(arg, requirePrefix: true);
            if (!string.IsNullOrWhiteSpace(routeName))
            {
                triggerData.Route = routeName;
                return true;
            }

            if (Enum.TryParse(arg, true, out SpeedInstruction speedInstruction))
            {
                var speedString = speedInstruction.ToString();

                // If zero speed is already set, assume this is the departure speed.
                if (triggerData.Speed == SpeedInstruction.Zero.ToString())
                {
                    triggerData.DepartureSpeed = speedString;
                }
                else
                {
                    triggerData.Speed = speedString;
                }

                return true;
            }

            if (Enum.TryParse(arg, true, out DirectionInstruction directionInstruction))
            {
                triggerData.Direction = directionInstruction.ToString();
                return true;
            }

            if (Enum.TryParse(arg, true, out TrackSelectionInstruction trackSelectionInstruction))
            {
                triggerData.TrackSelection = trackSelectionInstruction.ToString();
                return true;
            }

            ReplyToPlayer(player, errorMessageName, cmd, GetTriggerOptions(player));
            return false;
        }

        #endregion

        #region Helper Methods - Coupling

        private static void UpdateAllowedCouplings(TrainCar trainCar, bool allowFront, bool allowRear)
        {
            var coupling = trainCar.coupling;
            var frontCoupling = coupling.frontCoupling;
            var rearCoupling = coupling.rearCoupling;

            if (trainCar.frontCoupling == null || trainCar.rearCoupling == null)
            {
                // Some train cars do not allow coupling, such as the classic workcart.
                return;
            }

            if (!allowFront && frontCoupling.IsCoupled)
            {
                frontCoupling.Uncouple(reflect: true);
            }

            if (!allowRear && rearCoupling.IsCoupled)
            {
                rearCoupling.Uncouple(reflect: true);
            }

            if (frontCoupling.isValid != allowFront)
            {
                TrainCouplingIsValidField.SetValue(frontCoupling, allowFront);
            }

            if (rearCoupling.isValid != allowRear)
            {
                TrainCouplingIsValidField.SetValue(rearCoupling, allowRear);
            }
        }

        private static void DisableTrainCoupling(CompleteTrain completeTrain)
        {
            var firstTrainCar = completeTrain.trainCars.FirstOrDefault();
            var lastTrainCar = completeTrain.trainCars.LastOrDefault();
            if (firstTrainCar == null || lastTrainCar == null)
                return;

            UpdateAllowedCouplings(firstTrainCar, firstTrainCar.coupling.IsFrontCoupled, firstTrainCar.coupling.IsRearCoupled);

            if (lastTrainCar != firstTrainCar)
            {
                UpdateAllowedCouplings(lastTrainCar, lastTrainCar.coupling.IsFrontCoupled, lastTrainCar.coupling.IsRearCoupled);
            }
        }

        private static void EnableTrainCoupling(CompleteTrain completeTrain)
        {
            var firstTrainCar = completeTrain.trainCars.FirstOrDefault();
            var lastTrainCar = completeTrain.trainCars.LastOrDefault();
            if (firstTrainCar == null || lastTrainCar == null)
                return;

            UpdateAllowedCouplings(firstTrainCar, allowFront: true, allowRear: true);

            if (lastTrainCar != firstTrainCar)
            {
                UpdateAllowedCouplings(lastTrainCar, allowFront: true, allowRear: true);
            }
        }

        #endregion

        #region Helper Methods

        private static void LogError(string message) => Interface.Oxide.LogError($"[Automated Workcarts] {message}");
        private static void LogWarning(string message) => Interface.Oxide.LogWarning($"[Automated Workcarts] {message}");

        private static int GetNextTriggerId(List<TriggerData> triggerList)
        {
            var highestTriggerId = 0;

            foreach (var triggerData in triggerList)
            {
                highestTriggerId = Math.Max(highestTriggerId, triggerData.Id);
            }

            return highestTriggerId + 1;
        }

        private static string GetRouteNameFromArg(string routeName, bool requirePrefix = true)
        {
            if (routeName.StartsWith("@"))
                return routeName[1..];

            return requirePrefix ? null : routeName;
        }

        private static float GetThrottleFraction(EngineSpeeds throttle)
        {
            switch (throttle)
            {
                case EngineSpeeds.Rev_Hi: return -1;
                case EngineSpeeds.Rev_Med: return -0.5f;
                case EngineSpeeds.Rev_Lo: return -0.2f;
                case EngineSpeeds.Fwd_Lo: return 0.2f;
                case EngineSpeeds.Fwd_Med: return 0.5f;
                case EngineSpeeds.Fwd_Hi: return 1;
                default: return 0;
            }
        }

        private static TrainEngine GetLeadTrainEngine(CompleteTrain completeTrain)
        {
            if (completeTrain.PrimaryTrainCar == completeTrain.trainCars[0])
            {
                for (var i = 0; i < completeTrain.trainCars.Count; i++)
                {
                    var trainEngine = completeTrain.trainCars[i] as TrainEngine;
                    if ((object)trainEngine != null)
                        return trainEngine;
                }
            }
            else
            {
                for (var i = completeTrain.trainCars.Count - 1; i >= 0; i--)
                {
                    var trainEngine = completeTrain.trainCars[i] as TrainEngine;
                    if ((object)trainEngine != null)
                        return trainEngine;
                }
            }

            return null;
        }

        private static TrainEngine GetLeadTrainEngine(TrainCar trainCar)
        {
            var trainEngine = trainCar as TrainEngine;
            if (trainEngine != null)
                return trainEngine;

            return GetLeadTrainEngine(trainCar.completeTrain);
        }

        private static void DetermineTrainCarOrientations(TrainCar trainCar, Vector3 forward, TrainCar otherTrainCar, out TrainCar forwardTrainCar)
        {
            var position = trainCar.transform.position;
            var otherPosition = otherTrainCar.transform.position;
            var forwardPosition = position + forward * 100f;

            forwardTrainCar = trainCar;

            if ((forwardPosition - position).sqrMagnitude > (forwardPosition - otherPosition).sqrMagnitude)
            {
                forwardTrainCar = otherTrainCar;
            }
        }

        private static Vector3 GetTrainCarForward(TrainCar trainCar)
        {
            return trainCar.GetTrackSpeed() >= 0
                ? trainCar.transform.forward
                : -trainCar.transform.forward;
        }

        private static void EnableInvincibility(TrainCar trainCar)
        {
            trainCar.initialSpawnTime = float.MaxValue;
        }

        private static void DisableInvincibility(TrainCar trainCar)
        {
            trainCar.initialSpawnTime = Time.time;
        }

        private static void EnableSavingRecursive(BaseEntity entity, bool enableSaving)
        {
            entity.EnableSaving(enableSaving);

            foreach (var child in entity.children)
            {
                if (child is BasePlayer)
                    continue;

                EnableSavingRecursive(child, enableSaving);
            }
        }

        private static TrainCar SpawnTrainCar(string prefabName, Vector3 position, Quaternion rotation)
        {
            var trainCar = GameManager.server.CreateEntity(prefabName, position, rotation) as TrainCar;
            if (trainCar == null)
                return null;

            // Ensure the train car does not decay for some time.
            trainCar.lastDecayTick = Time.realtimeSinceStartup;

            trainCar.limitNetworking = true;
            trainCar.EnableSaving(false);
            trainCar.Spawn();

            if (trainCar.IsDestroyed)
                return null;

            // Don't let the train kill itself if there isn't quite enough space.
            trainCar.CancelInvoke(trainCar.KillMessage);

            trainCar.Invoke(() => EnableSavingRecursive(trainCar, false), 0);

            return trainCar;
        }

        private static float GetSplineDistance(TrainTrackSpline spline, Vector3 position)
        {
            spline.GetDistance(position, 1, out var distanceOnSpline);
            return distanceOnSpline;
        }

        private static TrainCar AddTrainCar(TrainCar frontTrainCar, TrainCarPrefab frontTrainCarPrefab, TrainCarPrefab trainCarPrefab, TrackSelection trackSelection)
        {
            var rearCouplingTransform = frontTrainCarPrefab.Reverse
                ? frontTrainCar.frontCoupling
                : frontTrainCar.rearCoupling;

            if (rearCouplingTransform == null)
                return null;

            var rearWheelPos = frontTrainCarPrefab.Reverse
                ? frontTrainCar.GetFrontWheelPos()
                : frontTrainCar.GetRearWheelPos();

            var wheelToRearCouplingDistance = Math.Abs(rearWheelPos.z - rearCouplingTransform.position.z);

            var rearSpline = frontTrainCarPrefab.Reverse
                ? frontTrainCar.FrontTrackSection
                : frontTrainCar.RearTrackSection;

            var rearWheelDistanceOnSpline = GetSplineDistance(rearSpline, rearWheelPos);

            var frontTrainCarForward = frontTrainCarPrefab.Reverse
                ? -frontTrainCar.transform.forward
                : frontTrainCar.transform.forward;

            var askerIsForward = rearSpline.IsForward(frontTrainCarForward, rearWheelDistanceOnSpline);
            var splineInfo = new SplineInfo
            {
                Spline = rearSpline,
                Distance = rearWheelDistanceOnSpline,
                Ascending = !askerIsForward,
                IsForward = askerIsForward,
// ... [Code preview truncated due to length. Full source in attached file] ...

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