C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
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.RegularExpressions;
using UnityEngine;
using static BaseEntity;
using static TrainCar;
using static TrainEngine;
using static TrainTrackSpline;
namespace Oxide.Plugins {
[Info("Automated Workcarts", "WhiteThunder", "0.32.0")]
[Description("Automates workcarts with NPC conductors.")]
internal class AutomatedWorkcarts : CovalencePlugin {
#region Fields
[PluginReference]
private Plugin CargoTrainEvent;
private static AutomatedWorkcarts _pluginInstance;
private const string PermissionToggle = "automatedworkcarts.toggle";
private const string PermissionManageTriggers = "automatedworkcarts.managetriggers";
private const string ClassicWorkcartPrefab = "assets/content/vehicles/workcart/workcart.entity.prefab";
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 BradleyExplosionEffectPrefab = "assets/prefabs/npc/m2bradley/effects/bradley_explosion.prefab";
private const string IdPlaceholder = "$id";
private static readonly FieldInfo TrainCouplingIsValidField = typeof(TrainCoupling).GetField("isValid", BindingFlags.NonPublic | BindingFlags.Instance) ?? typeof(TrainCoupling).GetField("isValid", BindingFlags.Public | BindingFlags.Instance);
private static readonly object False = false;
private static readonly Regex IdRegex = new Regex("\\$id", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private readonly BasePlayer[] _playerQueryResults = new BasePlayer[64];
private Configuration _pluginConfig;
private StoredPluginData _pluginData;
private StoredTunnelData _tunnelData;
private StoredMapData _mapData;
private readonly SpawnedTrainCarTracker _spawnedTrainCarTracker = new SpawnedTrainCarTracker();
private TriggerManager _triggerManager;
private TrainManager _trainManager;
private Coroutine _startupCoroutine;
#endregion
#region Hooks
private void Init() {
_pluginInstance = this;
_pluginData = StoredPluginData.Load();
_tunnelData = StoredTunnelData.Load();
_trainManager = new TrainManager(_pluginConfig, _pluginData, _spawnedTrainCarTracker);
_triggerManager = new TriggerManager(_pluginConfig, _trainManager, _tunnelData);
permission.RegisterPermission(PermissionToggle, this);
permission.RegisterPermission(PermissionManageTriggers, this);
Unsubscribe(nameof(OnEntitySpawned));
if (!_pluginConfig.GenericMapMarker.Enabled) {
Unsubscribe(nameof(OnPlayerConnected));
}
}
private void OnServerInitialized() {
if (!_pluginConfig.EnableTerrainCollision) {
foreach (var entity in BaseNetworkable.serverEntities) {
var trainCar = entity as TrainCar;
if (trainCar != null) {
EnableTerrainCollision(trainCar, false);
}
}
Subscribe(nameof(OnEntitySpawned));
}
_tunnelData.MigrateTriggers();
_mapData = StoredMapData.Load();
_triggerManager.SetMapData(_mapData);
_startupCoroutine = ServerMgr.Instance.StartCoroutine(DoStartupRoutine());
}
private void Unload() {
if (!_pluginConfig.EnableTerrainCollision) {
foreach (var entity in BaseNetworkable.serverEntities) {
var trainCar = entity as TrainCar;
if (trainCar != null) {
EnableTerrainCollision(trainCar, true);
}
}
}
if (_startupCoroutine != null) ServerMgr.Instance.StopCoroutine(_startupCoroutine);
OnServerSave();
_triggerManager.DestroyAll();
_trainManager.Unload();
_pluginInstance = null;
}
private void OnServerSave() {
if (_trainManager.UpdateTrainEngineData()) {
_pluginData.Save();
}
else {
_pluginData.SaveIfDirty();
}
}
private void OnNewSave() {
_pluginData = StoredPluginData.Clear();
}
private void OnPlayerConnected(BasePlayer player) {
if (player.IsReceivingSnapshot) {
timer.Once(1f, () => OnPlayerConnected(player));
return;
}
_trainManager.ResendAllGenericMarkers();
}
private void OnEntitySpawned(TrainCar trainCar) {
EnableTerrainCollision(trainCar, false);
}
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(trainCar)) {
ReplyToPlayer(player, Lang.ErrorMaxConductors, _trainManager.CountedConductors, _pluginConfig.MaxConductors);
return;
}
TrainEngineData trainEngineData = null;
if (args.Length > 0) {
var routeName = GetRouteNameFromArg(player, 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 (!_pluginConfig.EnableMapTriggers) {
ReplyToPlayer(player, Lang.ErrorMapTriggersDisabled);
return;
}
Vector3 trackPosition;
if (!TryGetTrackPosition(player.Object as BasePlayer, out trackPosition)) {
ReplyToPlayer(player, Lang.ErrorNoTrackFound);
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;
Vector3 trackPosition;
if (!TryGetTrackPosition(player.Object as BasePlayer, out trackPosition)) {
ReplyToPlayer(player, Lang.ErrorNoTrackFound);
return;
}
DungeonCellWrapper dungeonCellWrapper;
if (!VerifySupportedNearbyTrainTunnel(player, trackPosition, out dungeonCellWrapper)) return;
if (!_pluginConfig.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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.UpdateTriggerSyntax, out triggerData, out 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.UpdateTriggerSyntax, out triggerData, out 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out triggerData, out optionArgs)) 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out triggerData, out optionArgs)) 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out triggerData, out optionArgs)) return;
Vector3 trackPosition;
if (!VerifyTrackPosition(player, out trackPosition)) return;
if (triggerData.TriggerType == TrainTriggerType.Tunnel) {
DungeonCellWrapper dungeonCellWrapper;
if (!VerifySupportedNearbyTrainTunnel(player, trackPosition, out 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out triggerData, out optionArgs)) 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out triggerData, out optionArgs)) 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 tunnelTriggerInstance = triggerInstance as TunnelTriggerInstance;
if (tunnelTriggerInstance != null) {
rotation *= Quaternion.Inverse(tunnelTriggerInstance.DungeonCellWrapper.Rotation);
}
_triggerManager.RotateTrigger(triggerData, rotation.eulerAngles.y % 360);
_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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.SimpleTriggerSyntax, out triggerData, out optionArgs)) 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.AddCommandSyntax, out triggerData, out 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.RemoveCommandSyntax, out triggerData, out optionArgs)) return;
int commandIndex;
if (optionArgs.Length < 1 || !int.TryParse(optionArgs[0], out 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) {
TriggerData triggerData;
string[] optionArgs;
if (!VerifyCanModifyTrigger(player, cmd, args, Lang.RemoveCommandSyntax, out triggerData, out 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 CommandShowStops(IPlayer player, string cmd, string[] args) {
if (player.IsServer || !VerifyPermission(player, PermissionManageTriggers) || !VerifyAnyTriggers(player)) return;
int duration = 60;
string routeName = null;
foreach (var arg in args) {
if (duration == 60) {
int argIntValue;
if (int.TryParse(arg, out argIntValue)) {
duration = argIntValue;
continue;
}
}
if (routeName == null) {
var routeNameArg = GetRouteNameFromArg(player, 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));
}
#endregion
#region API
[HookMethod("API_AutomateWorkcart")]
private bool API_AutomateWorkcart(TrainEngine trainEngine) {
return _trainManager.HasTrainController(trainEngine) ? true : _trainManager.TryCreateTrainController(trainEngine);
}
[HookMethod("API_StopAutomatingWorkcart")]
private void API_StopAutomatingWorkcart(TrainEngine trainEngine, bool immediate = false) {
_trainManager.KillTrainController(trainEngine);
}
[HookMethod("API_IsWorkcartAutomated")]
private bool API_IsWorkcartAutomated(TrainEngine trainEngine) {
return _trainManager.HasTrainController(trainEngine);
}
[HookMethod("API_GetAutomatedWorkcarts")]
private 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) {
var result = CargoTrainEvent?.Call("IsTrainSpecial", trainEngine.net.ID);
return result is bool && (bool)result;
}
#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 VerifyTrackPosition(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;
}
else 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);
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;
int triggerId;
TrainTriggerType triggerType;
if (args.Length > 0 && IsTriggerArg(player, args[0], out triggerId, out 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 string GetRouteNameFromArg(IPlayer player, string routeName, bool requirePrefix = true) {
if (routeName.StartsWith("@")) return routeName.Substring(1);
return requirePrefix ? null : routeName;
}
private bool VerifyValidArgAndModifyTrigger(IPlayer player, string cmd, string arg, TriggerData triggerData, string errorMessageName) {
var argLower = arg.ToLower();
if (argLower == "start" || argLower == "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 string[] {
prefab.TrainCarAlias
}
;
}
else {
var length = triggerData.TrainCars.Length;
Array.Resize(ref triggerData.TrainCars, length + 1);
triggerData.TrainCars[length] = prefab.TrainCarAlias;
}
return true;
}
float stopDuration;
if (float.TryParse(arg, out stopDuration)) {
triggerData.StopDuration = stopDuration;
return true;
}
var routeName = GetRouteNameFromArg(player, arg, requirePrefix : true);
if (!string.IsNullOrWhiteSpace(routeName)) {
triggerData.Route = routeName;
return true;
}
SpeedInstruction speedInstruction;
if (Enum.TryParse<SpeedInstruction>(arg, true, out 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;
}
DirectionInstruction directionInstruction;
if (Enum.TryParse<DirectionInstruction>(arg, true, out directionInstruction)) {
triggerData.Direction = directionInstruction.ToString();
return true;
}
TrackSelectionInstruction trackSelectionInstruction;
if (Enum.TryParse<TrackSelectionInstruction>(arg, true, out 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 IEnumerator DoStartupRoutine() {
yield return _triggerManager.CreateAll();
TrackStart();
var foundTrainEngineIds = new HashSet<uint>();
foreach (var entity in BaseNetworkable.serverEntities) {
var trainEngine = entity as TrainEngine;
if (trainEngine == null) continue;
var trainEngineData = _pluginData.GetTrainEngineData(trainEngine.net.ID);
if (trainEngineData != null) {
foundTrainEngineIds.Add(trainEngine.net.ID);
timer.Once(UnityEngine.Random.Range(0, 1f), () => {
if (trainEngine != null && !IsTrainOwned(trainEngine) && _trainManager.CanHaveMoreConductors(trainEngine) && !_trainManager.HasTrainController(trainEngine)) {
_trainManager.TryCreateTrainController(trainEngine, trainEngineData : trainEngineData);
}
}
);
}
}
_pluginData.TrimToTrainEngineIds(foundTrainEngineIds);
TrackEnd();
}
private void EnableTerrainCollision(TrainCar trainCar, bool enabled) {
Physics.IgnoreCollision(trainCar.frontCollisionTrigger.triggerCollider, TerrainMeta.Collider, !enabled);
Physics.IgnoreCollision(trainCar.rearCollisionTrigger.triggerCollider, TerrainMeta.Collider, !enabled);
}
private bool AutomationWasBlocked(TrainEngine trainEngine) {
object hookResult = ExposedHooks.OnWorkcartAutomationStart(trainEngine);
if (hookResult is bool && (bool)hookResult == false) return true;
if (IsCargoTrain(trainEngine)) return true;
return false;
}
public static void LogInfo(string message) => Interface.Oxide.LogInfo($"[Automated Workcarts] {message}");
public static void LogError(string message) => Interface.Oxide.LogError($"[Automated Workcarts] {message}");
public static void LogWarning(string message) => Interface.Oxide.LogWarning($"[Automated Workcarts] {message}");
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, Vector3 otherForward, out TrainCar forwardTrainCar, out TrainCar backwardTrainCar) {
var position = trainCar.transform.position;
var otherPosition = otherTrainCar.transform.position;
var forwardPosition = position + forward * 100f;
forwardTrainCar = trainCar;
backwardTrainCar = otherTrainCar;
if ((forwardPosition - position).sqrMagnitude > (forwardPosition - otherPosition).sqrMagnitude) {
forwardTrainCar = otherTrainCar;
backwardTrainCar = trainCar;
}
}
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;
trainCar.Invoke(() => EnableSavingRecursive(trainCar, false), 0);
return trainCar;
}
private static float GetSplineDistance(TrainTrackSpline spline, Vector3 position) {
float distanceOnSpline;
spline.GetDistance(position, 1, out distanceOnSpline);
return distanceOnSpline;
}
private static TrainCar AddTrainCar(TrainCar trainCar, string prefabName, TrackSelection trackSelection, bool allowRearCoupling = true) {
var rearSpline = trainCar.FrontTrackSection;
var position = trainCar.transform.position;
var distanceOnSpline = GetSplineDistance(rearSpline, position);
var askerIsForward = rearSpline.IsForward(trainCar.transform.forward, distanceOnSpline);
var splineInfo = new SplineInfo {
Spline = rearSpline, Distance = distanceOnSpline, Ascending = !askerIsForward, IsForward = askerIsForward,
}
;
var finalDistance = Math.Abs(trainCar.rearCoupling.localPosition.z) + GetTrainCarFrontCouplingOffsetZ(prefabName);
var spawnDistance = Mathf.Max(finalDistance, 22);
SplineInfo finalSplineInfo;
var finalPosition = GetPositionAlongTrack(splineInfo, finalDistance, trackSelection, out finalSplineInfo);
SplineInfo spawnSplineInfo;
var resultPosition = GetPositionAlongTrack(finalSplineInfo, spawnDistance - finalDistance, trackSelection, out spawnSplineInfo);
var resultRotation = GetSplineTangentRotation(spawnSplineInfo.Spline, spawnSplineInfo.Distance, trainCar.transform.rotation);
var rearTrainCar = SpawnTrainCar(prefabName, resultPosition, resultRotation);
if (rearTrainCar != null) {
if (rearTrainCar.FrontTrackSection == null) {
rearTrainCar.Kill();
return null;
}
rearTrainCar.MoveFrontWheelsAlongTrackSpline(rearTrainCar.FrontTrackSection, rearTrainCar.FrontWheelSplineDist, spawnDistance - finalDistance, rearTrainCar.RearTrackSection != rearTrainCar.FrontTrackSection ? rearTrainCar.RearTrackSection : null, trackSelection );
rearTrainCar.transform.position = finalPosition;
TryCoupleTrainCars(trainCar, rearTrainCar);
}
return rearTrainCar;
}
private static void TryCoupleTrainCars(TrainCar front, TrainCar rear) {
front.coupling.rearCoupling.TryCouple(rear.coupling.frontCoupling, reflect : true);
}
private static float GetTrainCarFrontCouplingOffsetZ(string prefabName) {
var prefab = GameManager.server.FindPrefab(prefabName)?.GetComponent<TrainCar>();
if (prefab == null) return 0;
return prefab.frontCoupling.localPosition.z;
}
private static ConnectedTrackInfo GetAdjacentTrackInfo(TrainTrackSpline spline, TrainTrackSpline.TrackSelection selection, bool isAscending = true, bool askerIsForward = true) {
var trackOptions = isAscending ? spline.nextTracks : spline.prevTracks;
if (trackOptions.Count == 0) return null;
if (trackOptions.Count == 1) return trackOptions[0];
switch (selection) {
case TrainTrackSpline.TrackSelection.Left : return isAscending == askerIsForward ? trackOptions.FirstOrDefault() : trackOptions.LastOrDefault();
case TrainTrackSpline.TrackSelection.Right : return isAscending == askerIsForward ? trackOptions.LastOrDefault() : trackOptions.FirstOrDefault();
default : return trackOptions[isAscending ? spline.straightestNextIndex : spline.straightestPrevIndex];
}
}
private static Quaternion GetSplineTangentRotation(TrainTrackSpline spline, float distanceOnSpline, Quaternion approximateRotation) {
Vector3 tangentDirection;
spline.GetPositionAndTangent(distanceOnSpline, approximateRotation * Vector3.forward, out tangentDirection);
return Quaternion.LookRotation(tangentDirection);
}
private struct SplineInfo {
public TrainTrackSpline Spline;
public float Distance;
public bool Ascending;
public bool IsForward;
}
private static Vector3 GetPositionAlongTrack(SplineInfo splineInfo, float desiredDistance, TrackSelection trackSelection, out SplineInfo resultSplineInfo, out float remainingDistance) {
resultSplineInfo = splineInfo;
remainingDistance = desiredDistance;
var i = 0;
while (remainingDistance > 0) {
if (i++ > 1000) {
LogError("Something is wrong.Please contact the plugin developer.");
return Vector3.zero;
}
var splineLength = resultSplineInfo.Spline.GetLength();
var newDistanceOnSpline = resultSplineInfo.Ascending ? resultSplineInfo.Distance + remainingDistance : resultSplineInfo.Distance - remainingDistance;
remainingDistance -= resultSplineInfo.Ascending ? splineLength - resultSplineInfo.Distance : resultSplineInfo.Distance;
if (newDistanceO
// ... [Code preview truncated due to length. Full source in attached file] ...