// #define DEBUG using System; using System.Collections.Generic; using System.Linq; using System.Text; using Facepunch; using Network; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Oxide.Core; using Oxide.Core.Plugins; using Oxide.Game.Rust; using Rust; using Rust.Modular; using UnityEngine; using Random = UnityEngine.Random; namespace Oxide.Plugins { [Info("Vehicle Licence", "Sorrow/TheDoc/Arainrr", "1.7.43")] [Description("Allows players to buy vehicles and then spawn or store it")] public class VehicleLicence: RustPlugin { #region Fields [PluginReference] private readonly Plugin Economics, ServerRewards, Friends, Clans, NoEscape, LandOnCargoShip, RustTranslationAPI; private const string PERMISSION_USE = "vehiclelicence.use"; private const string PERMISSION_ALL = "vehiclelicence.all"; private const string PERMISSION_ADMIN = "vehiclelicence.admin"; private const string PERMISSION_BYPASS_COST = "vehiclelicence.bypasscost"; private const int ITEMID_FUEL = -946369541; private const string PREFAB_ITEM_DROP = "assets/prefabs/misc/item drop/item_drop.prefab"; private const string PREFAB_TUGBOAT = "assets/content/vehicles/boats/tugboat/tugboat.prefab"; private const string PREFAB_ROWBOAT = "assets/content/vehicles/boats/rowboat/rowboat.prefab"; private const string PREFAB_RHIB = "assets/content/vehicles/boats/rhib/rhib.prefab"; private const string PREFAB_SEDAN = "assets/content/vehicles/sedan_a/sedantest.entity.prefab"; private const string PREFAB_HOTAIRBALLOON = "assets/prefabs/deployable/hot air balloon/hotairballoon.prefab"; private const string PREFAB_MINICOPTER = "assets/content/vehicles/minicopter/minicopter.entity.prefab"; private const string PREFAB_TRANSPORTCOPTER = "assets/content/vehicles/scrap heli carrier/scraptransporthelicopter.prefab"; private const string PREFAB_CHINOOK = "assets/prefabs/npc/ch47/ch47.entity.prefab"; private const string PREFAB_RIDABLEHORSE = "assets/rust.ai/nextai/testridablehorse.prefab"; private const string PREFAB_WORKCART = "assets/content/vehicles/trains/workcart/workcart.entity.prefab"; private const string PREFAB_SEDANRAIL = "assets/content/vehicles/sedan_a/sedanrail.entity.prefab"; private const string PREFAB_MAGNET_CRANE = "assets/content/vehicles/crane_magnet/magnetcrane.entity.prefab"; private const string PREFAB_SUBMARINE_DUO = "assets/content/vehicles/submarine/submarineduo.entity.prefab"; private const string PREFAB_SUBMARINE_SOLO = "assets/content/vehicles/submarine/submarinesolo.entity.prefab"; private const string PREFAB_CHASSIS_SMALL = "assets/content/vehicles/modularcar/car_chassis_2module.entity.prefab"; private const string PREFAB_CHASSIS_MEDIUM = "assets/content/vehicles/modularcar/car_chassis_3module.entity.prefab"; private const string PREFAB_CHASSIS_LARGE = "assets/content/vehicles/modularcar/car_chassis_4module.entity.prefab"; private const string PREFAB_SNOWMOBILE = "assets/content/vehicles/snowmobiles/snowmobile.prefab"; private const string PREFAB_SNOWMOBILE_TOMAHA = "assets/content/vehicles/snowmobiles/tomahasnowmobile.prefab"; // Train Engine private const string PREFAB_TRAINENGINE = "assets/content/vehicles/trains/workcart/workcart_aboveground.entity.prefab"; private const string PREFAB_TRAINENGINE_COVERED = "assets/content/vehicles/trains/workcart/workcart_aboveground2.entity.prefab"; private const string PREFAB_TRAINENGINE_LOCOMOTIVE = "assets/content/vehicles/trains/locomotive/locomotive.entity.prefab"; // Train Car private const string PREFAB_TRAINWAGON_A = "assets/content/vehicles/trains/wagons/trainwagona.entity.prefab"; private const string PREFAB_TRAINWAGON_B = "assets/content/vehicles/trains/wagons/trainwagonb.entity.prefab"; private const string PREFAB_TRAINWAGON_C = "assets/content/vehicles/trains/wagons/trainwagonc.entity.prefab"; private const string PREFAB_TRAINWAGON_UNLOADABLE = "assets/content/vehicles/trains/wagons/trainwagonunloadable.entity.prefab"; private const string PREFAB_TRAINWAGON_UNLOADABLE_FUEL = "assets/content/vehicles/trains/wagons/trainwagonunloadablefuel.entity.prefab"; private const string PREFAB_TRAINWAGON_UNLOADABLE_LOOT = "assets/content/vehicles/trains/wagons/trainwagonunloadableloot.entity.prefab"; private const string PREFAB_CABOOSE = "assets/content/vehicles/trains/caboose/traincaboose.entity.prefab"; private const int LAYER_GROUND = Layers.Solid | Layers.Mask.Water; private readonly object _false = false; public static VehicleLicence Instance { get; private set; } public readonly Dictionary < BaseEntity, Vehicle > vehiclesCache = new Dictionary < BaseEntity, Vehicle > (); public readonly Dictionary < string, BaseVehicleSettings > allVehicleSettings = new Dictionary < string, BaseVehicleSettings > (); public readonly Dictionary < string, string > commandToVehicleType = new Dictionary < string, string > (StringComparer.OrdinalIgnoreCase); public enum NormalVehicleType { Rowboat, Tugboat, RHIB, Sedan, HotAirBalloon, MiniCopter, TransportHelicopter, Chinook, RidableHorse, WorkCart, SedanRail, MagnetCrane, SubmarineSolo, SubmarineDuo, Snowmobile, TomahaSnowmobile } [JsonConverter(typeof (StringEnumConverter))] public enum ChassisType { Small, Medium, Large } [JsonConverter(typeof (StringEnumConverter))] public enum TrainComponentType { Engine, CoveredEngine, Locomotive, WagonA, WagonB, WagonC, Unloadable, UnloadableLoot, UnloadableFuel, Caboose, } #endregion Fields #region Oxide Hooks private void Init() { LoadData(); Instance = this; permission.RegisterPermission(PERMISSION_USE, this); permission.RegisterPermission(PERMISSION_ALL, this); permission.RegisterPermission(PERMISSION_ADMIN, this); permission.RegisterPermission(PERMISSION_BYPASS_COST, this); foreach(NormalVehicleType value in Enum.GetValues(typeof (NormalVehicleType))) { allVehicleSettings.Add(value.ToString(), GetBaseVehicleSettings(value)); } foreach(var entry in configData.modularVehicles) { allVehicleSettings.Add(entry.Key, entry.Value); } foreach(var entry in configData.trainVehicles) { allVehicleSettings.Add(entry.Key, entry.Value); } foreach(var entry in allVehicleSettings) { var settings = entry.Value; if (settings.UsePermission && !string.IsNullOrEmpty(settings.Permission)) { if (!permission.PermissionExists(settings.Permission, this)) { permission.RegisterPermission(settings.Permission, this); } } foreach(var perm in settings.CooldownPermissions.Keys) { if (!permission.PermissionExists(perm, this)) { permission.RegisterPermission(perm, this); } } foreach(var command in settings.Commands) { if (string.IsNullOrEmpty(command)) { continue; } if (!commandToVehicleType.ContainsKey(command)) { commandToVehicleType.Add(command, entry.Key); } else { PrintError($"You have the same two commands({command})."); } if (configData.chat.useUniversalCommand) { cmd.AddChatCommand(command, this, nameof(CmdUniversal)); } if (!string.IsNullOrEmpty(configData.chat.customKillCommandPrefix)) { cmd.AddChatCommand(configData.chat.customKillCommandPrefix + command, this, nameof(CmdCustomKill)); } } } cmd.AddChatCommand(configData.chat.helpCommand, this, nameof(CmdLicenseHelp)); cmd.AddChatCommand(configData.chat.buyCommand, this, nameof(CmdBuyVehicle)); cmd.AddChatCommand(configData.chat.spawnCommand, this, nameof(CmdSpawnVehicle)); cmd.AddChatCommand(configData.chat.recallCommand, this, nameof(CmdRecallVehicle)); cmd.AddChatCommand(configData.chat.killCommand, this, nameof(CmdKillVehicle)); Unsubscribe(nameof(CanMountEntity)); Unsubscribe(nameof(OnEntityTakeDamage)); Unsubscribe(nameof(OnEntityDismounted)); Unsubscribe(nameof(OnEntityEnter)); Unsubscribe(nameof(CanLootEntity)); Unsubscribe(nameof(OnEntitySpawned)); Unsubscribe(nameof(OnRidableAnimalClaimed)); } private void OnServerInitialized() { var currentTimestamp = TimeEx.currentTimestamp; foreach(var playerData in storedData.playerData) { foreach(var entry in playerData.Value) { entry.Value.PlayerId = playerData.Key; entry.Value.VehicleType = entry.Key; if (configData.global.storeVehicle) { entry.Value.LastRecall = entry.Value.LastDismount = currentTimestamp; if (entry.Value.EntityId == 0) { continue; } NetworkableId id = new NetworkableId(entry.Value.EntityId); entry.Value.Entity = BaseNetworkable.serverEntities.Find(id) as BaseEntity; if (entry.Value.Entity == null || entry.Value.Entity.IsDestroyed) { entry.Value.EntityId = 0; } else { vehiclesCache.Add(entry.Value.Entity, entry.Value); } } } } if (configData.global.preventMounting) { Subscribe(nameof(CanMountEntity)); } if (configData.global.noDecay) { Subscribe(nameof(OnEntityTakeDamage)); } if (configData.global.preventDamagePlayer) { Subscribe(nameof(OnEntityEnter)); } if (configData.global.preventLooting) { Subscribe(nameof(CanLootEntity)); } if (configData.global.autoClaimFromVendor) { Subscribe(nameof(OnEntitySpawned)); Subscribe(nameof(OnRidableAnimalClaimed)); } if (configData.global.checkVehiclesInterval > 0 && allVehicleSettings.Any(x => x.Value.WipeTime > 0)) { Subscribe(nameof(OnEntityDismounted)); timer.Every(configData.global.checkVehiclesInterval, CheckVehicles); } } private void Unload() { if (!configData.global.storeVehicle) { foreach(var entry in vehiclesCache.ToArray()) { if (entry.Key != null && !entry.Key.IsDestroyed) { RefundVehicleItems(entry.Value, isUnload: true); entry.Key.Kill(BaseNetworkable.DestroyMode.Gib); } entry.Value.EntityId = 0; } } SaveData(); Instance = null; } private void OnServerSave() { timer.Once(Random.Range(0 f, 60 f), SaveData); } private void OnPlayerConnected(BasePlayer player) { if (player == null || !player.userID.IsSteamId()) { return; } if (permission.UserHasPermission(player.UserIDString, PERMISSION_BYPASS_COST)) { PurchaseAllVehicles(player.userID); } } private void OnEntityDismounted(BaseMountable entity, BasePlayer player) { if (entity == null) { return; } var vehicleParent = entity.VehicleParent(); if (vehicleParent == null || vehicleParent.IsDestroyed) { return; } Vehicle vehicle; if (!vehiclesCache.TryGetValue(vehicleParent, out vehicle)) { return; } vehicle.OnDismount(); } #region Mount private object CanMountEntity(BasePlayer friend, BaseMountable entity) { if (friend == null || entity == null) { return null; } var vehicleParent = entity.VehicleParent(); if (vehicleParent == null || vehicleParent.IsDestroyed) { return null; } Vehicle vehicle; if (!vehiclesCache.TryGetValue(vehicleParent, out vehicle)) { return null; } if (AreFriends(vehicle.PlayerId, friend.userID)) { return null; } if (configData.global.preventDriverSeat && vehicleParent.HasMountPoints()) { foreach(var mountPointInfo in vehicleParent.allMountPoints) { if (mountPointInfo != null && mountPointInfo.mountable == entity) { if (!mountPointInfo.isDriver) { return null; } break; } } } if (HasAdminPermission(friend)) { return null; } SendCantUseMessage(friend, vehicle); return _false; } #endregion Mount #region Loot private object CanLootEntity(BasePlayer friend, RidableHorse horse) { if (friend == null || horse == null) { return null; } return CanLootEntityInternal(friend, horse); } private object CanLootEntity(BasePlayer friend, StorageContainer container) { if (friend == null || container == null) { return null; } var parentEntity = container.GetParentEntity(); if (parentEntity == null) { return null; } return CanLootEntityInternal(friend, parentEntity); } private object CanLootEntityInternal(BasePlayer friend, BaseEntity parentEntity) { Vehicle vehicle; if (!TryGetVehicle(parentEntity, out vehicle)) { return null; } if (AreFriends(vehicle.PlayerId, friend.userID)) { return null; } if (HasAdminPermission(friend)) { return null; } SendCantUseMessage(friend, vehicle); return _false; } #endregion Loot #region Decay private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo) { if (entity == null || hitInfo?.damageTypes == null) { return; } if (hitInfo.damageTypes.Has(DamageType.Decay)) { Vehicle vehicle; if (!TryGetVehicle(entity, out vehicle)) { return; } hitInfo.damageTypes.Scale(DamageType.Decay, 0); } } #endregion Decay #region Claim private void OnEntitySpawned(BaseSubmarine baseSubmarine) { TryClaimVehicle(baseSubmarine); } private void OnEntitySpawned(MotorRowboat motorRowboat) { TryClaimVehicle(motorRowboat); } private void OnEntitySpawned(MiniCopter miniCopter) { TryClaimVehicle(miniCopter); } private void OnEntitySpawned(Tugboat tugboat) { TryClaimVehicle(tugboat); } private void OnRidableAnimalClaimed(BaseRidableAnimal ridableAnimal, BasePlayer player) { TryClaimVehicle(ridableAnimal, player); } #endregion Claim #region Damage // ScrapTransportHelicopter / ModularCar / TrainEngine / MagnetCrane private object OnEntityEnter(TriggerHurtNotChild triggerHurtNotChild, BasePlayer player) { if (triggerHurtNotChild == null || player == null || triggerHurtNotChild.SourceEntity == null) { return null; } var sourceEntity = triggerHurtNotChild.SourceEntity; if (vehiclesCache.ContainsKey(sourceEntity)) { var baseVehicle = sourceEntity as BaseVehicle; if (baseVehicle != null && player.userID.IsSteamId()) { if (baseVehicle is TrainEngine) { var transform = triggerHurtNotChild.transform; MoveToPosition(player, transform.position + (Random.value >= 0.5 f ? -transform.right : transform.right) * 2.5 f); return _false; } Vector3 pos; if (GetDismountPosition(baseVehicle, player, out pos)) { MoveToPosition(player, pos); } } //triggerHurtNotChild.enabled = false; return _false; } return null; } // HotAirBalloon private object OnEntityEnter(TriggerHurt triggerHurt, BasePlayer player) { if (triggerHurt == null || player == null) { return null; } var sourceEntity = triggerHurt.gameObject.ToBaseEntity(); if (sourceEntity == null) { return null; } if (vehiclesCache.ContainsKey(sourceEntity)) { if (player.userID.IsSteamId()) { MoveToPosition(player, sourceEntity.CenterPoint() + Vector3.down); } //triggerHurt.enabled = false; return _false; } return null; } #endregion Damage #region Destroy private void OnEntityDeath(BaseCombatEntity entity, HitInfo info) { OnEntityDeathOrKill(entity, true); } private void OnEntityKill(BaseCombatEntity entity) { OnEntityDeathOrKill(entity); } #endregion Destroy #region Reskin private object OnEntityReskin(BaseEntity entity, ItemSkinDirectory.Skin skin, BasePlayer player) { if (entity == null || player == null) { return null; } Vehicle vehicle; if (TryGetVehicle(entity, out vehicle)) { return _false; } return null; } #endregion Reskin #endregion Oxide Hooks #region Methods #region Message private void SendCantUseMessage(BasePlayer friend, Vehicle vehicle) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); if (settings != null) { var player = RustCore.FindPlayerById(vehicle.PlayerId); var playerName = player?.displayName ?? ServerMgr.Instance.persistance.GetPlayerName(vehicle.PlayerId) ?? "Unknown"; Print(friend, Lang("CantUse", friend.UserIDString, settings.DisplayName, $ "{playerName}")); } } #endregion Message #region CheckEntity private void OnEntityDeathOrKill(BaseCombatEntity entity, bool isCrash = false) { if (entity == null) { return; } Vehicle vehicle; if (!vehiclesCache.TryGetValue(entity, out vehicle)) { return; } RefundVehicleItems(vehicle, isCrash); var settings = GetBaseVehicleSettings(vehicle.VehicleType); if (isCrash && settings.RemoveLicenseOnceCrash) { RemoveVehicleLicense(vehicle.PlayerId, vehicle.VehicleType); } vehicle.OnDeath(); vehiclesCache.Remove(entity); } #endregion CheckEntity #region CheckVehicles private void CheckVehicles() { var currentTimestamp = TimeEx.currentTimestamp; foreach(var entry in vehiclesCache.ToArray()) { if (entry.Key == null || entry.Key.IsDestroyed) { continue; } if (VehicleIsActive(entry.Key, entry.Value, currentTimestamp)) { continue; } if (VehicleAnyMounted(entry.Key)) { continue; } entry.Key.Kill(BaseNetworkable.DestroyMode.Gib); } } private bool VehicleIsActive(BaseEntity entity, Vehicle vehicle, double currentTimestamp) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); if (settings.WipeTime <= 0) { return true; } if (settings.ExcludeCupboard && entity.GetBuildingPrivilege() != null) { return true; } return currentTimestamp - vehicle.LastDismount < settings.WipeTime; } #endregion CheckVehicles #region Refund private void RefundVehicleItems(Vehicle vehicle, bool isCrash = false, bool isUnload = false) { var entity = vehicle.Entity; if (entity == null || entity.IsDestroyed) { return; } var settings = GetBaseVehicleSettings(vehicle.VehicleType); settings.RefundVehicleItems(vehicle, isCrash, isUnload); } private static void DropItemContainer(BaseEntity entity, ulong playerId, List < Item > collect) { var droppedItemContainer = GameManager.server.CreateEntity(PREFAB_ITEM_DROP, entity.GetDropPosition(), entity.transform.rotation) as DroppedItemContainer; if (droppedItemContainer != null) { droppedItemContainer.inventory = new ItemContainer(); droppedItemContainer.inventory.ServerInitialize(null, Mathf.Min(collect.Count, droppedItemContainer.maxItemCount)); droppedItemContainer.inventory.GiveUID(); droppedItemContainer.inventory.entityOwner = droppedItemContainer; droppedItemContainer.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true); for (var i = collect.Count - 1; i >= 0; i--) { var item = collect[i]; if (!item.MoveToContainer(droppedItemContainer.inventory)) { item.DropAndTossUpwards(droppedItemContainer.transform.position); } } droppedItemContainer.OwnerID = playerId; droppedItemContainer.Spawn(); } } #endregion Refund #region TryPay private bool TryPay(BasePlayer player, Dictionary < string, PriceInfo > prices, out string resources) { if (permission.UserHasPermission(player.UserIDString, PERMISSION_BYPASS_COST)) { resources = null; return true; } if (!CanPay(player, prices, out resources)) { return false; } var collect = Pool.GetList < Item > (); foreach(var entry in prices) { if (entry.Value.amount <= 0) { continue; } var itemDefinition = ItemManager.FindItemDefinition(entry.Key); if (itemDefinition != null) { player.inventory.Take(collect, itemDefinition.itemid, entry.Value.amount); player.Command("note.inv", itemDefinition.itemid, -entry.Value.amount); continue; } switch (entry.Key.ToLower()) { case "economics": Economics?.Call("Withdraw", player.userID, (double) entry.Value.amount); continue; case "serverrewards": ServerRewards?.Call("TakePoints", player.userID, entry.Value.amount); continue; } } foreach(var item in collect) { item.Remove(); } Pool.FreeList(ref collect); resources = null; return true; } private bool CanPay(BasePlayer player, Dictionary < string, PriceInfo > prices, out string resources) { var entries = new Hash < string, int > (); var language = RustTranslationAPI != null ? lang.GetLanguage(player.UserIDString) : null; foreach(var entry in prices) { if (entry.Value.amount <= 0) { continue; } int missingAmount; var itemDefinition = ItemManager.FindItemDefinition(entry.Key); if (itemDefinition != null) { missingAmount = entry.Value.amount - player.inventory.GetAmount(itemDefinition.itemid); } else { missingAmount = CheckBalance(entry.Key, entry.Value.amount, player.userID); } if (missingAmount <= 0) { continue; } var displayName = GetItemDisplayName(language, entry.Key, entry.Value.displayName); entries[displayName] += missingAmount; } if (entries.Count > 0) { var stringBuilder = new StringBuilder(); foreach(var entry in entries) { stringBuilder.AppendLine($"* {Lang(" PriceFormat ", player.UserIDString, entry.Key, entry.Value)}"); } resources = stringBuilder.ToString(); return false; } resources = null; return true; } private int CheckBalance(string key, int price, ulong playerId) { switch (key.ToLower()) { case "economics": var balance = Economics?.Call("Balance", playerId); if (balance is double) { var n = price - (double) balance; return n <= 0 ? 0 : (int) Math.Ceiling(n); } return price; case "serverrewards": var points = ServerRewards?.Call("CheckPoints", playerId); if (points is int) { var n = price - (int) points; return n <= 0 ? 0 : n; } return price; default: PrintError($"Unknown Currency Type '{key}'"); return price; } } #endregion TryPay #region AreFriends private bool AreFriends(ulong playerId, ulong friendId) { if (playerId == friendId) { return true; } if (configData.global.useTeams && SameTeam(playerId, friendId)) { return true; } if (configData.global.useFriends && HasFriend(playerId, friendId)) { return true; } if (configData.global.useClans && SameClan(playerId, friendId)) { return true; } return false; } private static bool SameTeam(ulong playerId, ulong friendId) { if (!RelationshipManager.TeamsEnabled()) { return false; } var playerTeam = RelationshipManager.ServerInstance.FindPlayersTeam(playerId); if (playerTeam == null) { return false; } var friendTeam = RelationshipManager.ServerInstance.FindPlayersTeam(friendId); if (friendTeam == null) { return false; } return playerTeam == friendTeam; } private bool HasFriend(ulong playerId, ulong friendId) { if (Friends == null) { return false; } return (bool) Friends.Call("HasFriend", playerId, friendId); } private bool SameClan(ulong playerId, ulong friendId) { if (Clans == null) { return false; } //Clans var isMember = Clans.Call("IsClanMember", playerId.ToString(), friendId.ToString()); if (isMember != null) { return (bool) isMember; } //Rust:IO Clans var playerClan = Clans.Call("GetClanOf", playerId); if (playerClan == null) { return false; } var friendClan = Clans.Call("GetClanOf", friendId); if (friendClan == null) { return false; } return playerClan == friendClan; } #endregion AreFriends #region IsPlayerBlocked private bool IsPlayerBlocked(BasePlayer player) { if (NoEscape == null) { return false; } if (configData.global.useRaidBlocker && IsRaidBlocked(player.UserIDString)) { Print(player, Lang("RaidBlocked", player.UserIDString)); return true; } if (configData.global.useCombatBlocker && IsCombatBlocked(player.UserIDString)) { Print(player, Lang("CombatBlocked", player.UserIDString)); return true; } return false; } private bool IsRaidBlocked(string playerId) { return (bool) NoEscape.Call("IsRaidBlocked", playerId); } private bool IsCombatBlocked(string playerId) { return (bool) NoEscape.Call("IsCombatBlocked", playerId); } #endregion IsPlayerBlocked #region GetSettings private BaseVehicleSettings GetBaseVehicleSettings(string vehicleType) { BaseVehicleSettings settings; return allVehicleSettings.TryGetValue(vehicleType, out settings) ? settings : null; } private BaseVehicleSettings GetBaseVehicleSettings(NormalVehicleType normalVehicleType) { switch (normalVehicleType) { case NormalVehicleType.Tugboat: return configData.normalVehicles.tugboat; case NormalVehicleType.Rowboat: return configData.normalVehicles.rowboat; case NormalVehicleType.RHIB: return configData.normalVehicles.rhib; case NormalVehicleType.Sedan: return configData.normalVehicles.sedan; case NormalVehicleType.HotAirBalloon: return configData.normalVehicles.hotAirBalloon; case NormalVehicleType.MiniCopter: return configData.normalVehicles.miniCopter; case NormalVehicleType.TransportHelicopter: return configData.normalVehicles.transportHelicopter; case NormalVehicleType.Chinook: return configData.normalVehicles.chinook; case NormalVehicleType.RidableHorse: return configData.normalVehicles.ridableHorse; case NormalVehicleType.WorkCart: return configData.normalVehicles.workCart; case NormalVehicleType.SedanRail: return configData.normalVehicles.sedanRail; case NormalVehicleType.MagnetCrane: return configData.normalVehicles.magnetCrane; case NormalVehicleType.SubmarineSolo: return configData.normalVehicles.submarineSolo; case NormalVehicleType.SubmarineDuo: return configData.normalVehicles.submarineDuo; case NormalVehicleType.Snowmobile: return configData.normalVehicles.snowmobile; case NormalVehicleType.TomahaSnowmobile: return configData.normalVehicles.tomahaSnowmobile; default: return null; } } #endregion GetSettings #region Permission private bool HasAdminPermission(BasePlayer player) { return permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN); } private bool CanViewVehicleInfo(BasePlayer player, string vehicleType, BaseVehicleSettings settings) { if (settings.Purchasable && settings.Commands.Count > 0) { return HasVehiclePermission(player, vehicleType); } return false; } private bool HasVehiclePermission(BasePlayer player, string vehicleType) { var settings = GetBaseVehicleSettings(vehicleType); if (!settings.UsePermission || string.IsNullOrEmpty(settings.Permission)) { return true; } return permission.UserHasPermission(player.UserIDString, PERMISSION_ALL) || permission.UserHasPermission(player.UserIDString, settings.Permission); } #endregion Permission #region Claim private void TryClaimVehicle(BaseVehicle baseVehicle) { NextTick(() => { if (baseVehicle == null) { return; } var player = baseVehicle.creatorEntity as BasePlayer; if (player == null || !player.userID.IsSteamId() || !baseVehicle.OnlyOwnerAccessible()) { return; } var vehicleType = GetClaimableVehicleType(baseVehicle); if (vehicleType.HasValue) { TryClaimVehicle(player, baseVehicle, vehicleType.Value.ToString()); } }); } private void TryClaimVehicle(BaseVehicle baseVehicle, BasePlayer player) { if (player == null || !player.userID.IsSteamId()) { return; } var vehicleType = GetClaimableVehicleType(baseVehicle); if (vehicleType.HasValue) { TryClaimVehicle(player, baseVehicle, vehicleType.Value.ToString()); } } private bool TryClaimVehicle(BasePlayer player, BaseEntity entity, string vehicleType) { Vehicle vehicle; if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle)) { if (!configData.global.autoUnlockFromVendor) { return false; } storedData.AddVehicleLicense(player.userID, vehicleType); vehicle = storedData.GetVehicleLicense(player.userID, vehicleType); } if (vehicle.Entity == null || vehicle.Entity.IsDestroyed) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); if (settings != null) { settings.PreSetupVehicle(entity, vehicle, player); settings.SetupVehicle(entity, vehicle, player, false); } CacheVehicleEntity(entity, vehicle, player); return true; } return false; } #endregion Claim private bool TryGetVehicle(BaseEntity entity, out Vehicle vehicle) { if (!vehiclesCache.TryGetValue(entity, out vehicle)) { var vehicleModule = entity as BaseVehicleModule; if (vehicleModule == null) { return false; } var parent = vehicleModule.Vehicle; if (parent == null || !vehiclesCache.TryGetValue(parent, out vehicle)) { return false; } } return true; } #region Helpers private static NormalVehicleType ? GetClaimableVehicleType(BaseVehicle baseVehicle) { if (baseVehicle is BaseRidableAnimal) { return NormalVehicleType.RidableHorse; } if (baseVehicle is ScrapTransportHelicopter) { return NormalVehicleType.TransportHelicopter; } if (baseVehicle is MiniCopter) { return NormalVehicleType.MiniCopter; } if (baseVehicle is RHIB) { return NormalVehicleType.RHIB; } if (baseVehicle is Tugboat) { return NormalVehicleType.Tugboat; } if (baseVehicle is MotorRowboat) { return NormalVehicleType.Rowboat; } if (baseVehicle is SubmarineDuo) { return NormalVehicleType.SubmarineDuo; } if (baseVehicle is BaseSubmarine) { return NormalVehicleType.SubmarineSolo; } return null; } private static bool GetDismountPosition(BaseVehicle baseVehicle, BasePlayer player, out Vector3 result) { var parentVehicle = baseVehicle.VehicleParent(); if (parentVehicle != null) { return GetDismountPosition(parentVehicle, player, out result); } var list = Pool.GetList < Vector3 > (); foreach(var transform in baseVehicle.dismountPositions) { if (baseVehicle.ValidDismountPosition(player, transform.position)) { list.Add(transform.position); if (baseVehicle.dismountStyle == BaseVehicle.DismountStyle.Ordered) { break; } } } if (list.Count == 0) { result = Vector3.zero; Pool.FreeList(ref list); return false; } var pos = player.transform.position; list.Sort((a, b) => Vector3.Distance(a, pos).CompareTo(Vector3.Distance(b, pos))); result = list[0]; Pool.FreeList(ref list); return true; } private static bool VehicleAnyMounted(BaseEntity entity) { var baseVehicle = entity as BaseVehicle; if (baseVehicle != null && baseVehicle.AnyMounted()) { return true; } return entity.GetComponentsInChildren < BasePlayer > ()?.Length > 0; } private static void DismountAllPlayers(BaseEntity entity) { var baseVehicle = entity as BaseVehicle; if (baseVehicle != null) { //(vehicle as BaseVehicle).DismountAllPlayers(); foreach(var mountPointInfo in baseVehicle.allMountPoints) { if (mountPointInfo != null && mountPointInfo.mountable != null) { var mounted = mountPointInfo.mountable.GetMounted(); if (mounted != null) { mountPointInfo.mountable.DismountPlayer(mounted); } } } } var players = entity.GetComponentsInChildren < BasePlayer > (); foreach(var player in players) { player.SetParent(null, true, true); } } private static Vector3 GetGroundPositionLookingAt(BasePlayer player, float distance, bool needUp = true) { RaycastHit hitInfo; var headRay = player.eyes.HeadRay(); if (Physics.Raycast(headRay, out hitInfo, distance, LAYER_GROUND)) { return hitInfo.point; } return GetGroundPosition(headRay.origin + headRay.direction * distance, needUp); } private static Vector3 GetGroundPosition(Vector3 position, bool needUp = true) { RaycastHit hitInfo; position.y = Physics.Raycast(needUp ? position + Vector3.up * 250 : position, Vector3.down, out hitInfo, needUp ? 400 f : 50 f, LAYER_GROUND) ? hitInfo.point.y : TerrainMeta.HeightMap.GetHeight(position); return position; } private static bool IsInWater(Vector3 position) { var colliders = Pool.GetList < Collider > (); Vis.Colliders(position, 0.5 f, colliders); var flag = colliders.Any(x => x.gameObject.layer == (int) Layer.Water); Pool.FreeList(ref colliders); return flag || WaterLevel.Test(position, false, false); } private static void MoveToPosition(BasePlayer player, Vector3 position) { player.Teleport(position); player.ForceUpdateTriggers(); //if (player.HasParent()) player.SetParent(null, true, true); player.SendNetworkUpdateImmediate(); } #region Train Car #endregion #endregion Helpers #endregion Methods #region API [HookMethod(nameof(IsLicensedVehicle))] public bool IsLicensedVehicle(BaseEntity entity) { return vehiclesCache.ContainsKey(entity); } [HookMethod(nameof(GetLicensedVehicle))] public BaseEntity GetLicensedVehicle(ulong playerId, string license) { return storedData.GetVehicleLicense(playerId, license)?.Entity; } [HookMethod(nameof(HasVehicleLicense))] public bool HasVehicleLicense(ulong playerId, string license) { return storedData.HasVehicleLicense(playerId, license); } [HookMethod(nameof(RemoveVehicleLicense))] public bool RemoveVehicleLicense(ulong playerId, string license) { return storedData.RemoveVehicleLicense(playerId, license); } [HookMethod(nameof(AddVehicleLicense))] public bool AddVehicleLicense(ulong playerId, string license) { return storedData.AddVehicleLicense(playerId, license); } [HookMethod(nameof(GetVehicleLicenses))] public List < string > GetVehicleLicenses(ulong playerId) { return storedData.GetVehicleLicenseNames(playerId); } [HookMethod(nameof(PurchaseAllVehicles))] public void PurchaseAllVehicles(ulong playerId) { storedData.PurchaseAllVehicles(playerId); } #endregion API #region Commands #region Universal Command private void CmdUniversal(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) { Print(player, Lang("NotAllowed", player.UserIDString)); return; } string vehicleType; if (IsValidOption(player, command, out vehicleType)) { var bypassCooldown = args.Length > 0 && IsValidBypassCooldownOption(args[0]); HandleUniversalCmd(player, vehicleType, bypassCooldown, command); } } private void HandleUniversalCmd(BasePlayer player, string vehicleType, bool bypassCooldown, string command) { Vehicle vehicle; if (storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle)) { string reason; var position = Vector3.zero; var rotation = Quaternion.identity; if (vehicle.Entity != null && !vehicle.Entity.IsDestroyed) { //recall if (CanRecall(player, vehicle, bypassCooldown, command, out reason, ref position, ref rotation)) { RecallVehicle(player, vehicle, position, rotation); return; } } else { //spawn if (CanSpawn(player, vehicle, bypassCooldown, command, out reason, ref position, ref rotation)) { SpawnVehicle(player, vehicle, position, rotation); return; } } Print(player, reason); return; } //buy BuyVehicle(player, vehicleType); } #endregion Universal Command #region Custom Kill Command private void CmdCustomKill(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) { Print(player, Lang("NotAllowed", player.UserIDString)); return; } command = command.Remove(0, configData.chat.customKillCommandPrefix.Length); HandleKillCmd(player, command); } #endregion Custom Kill Command #region Help Command private void CmdLicenseHelp(BasePlayer player, string command, string[] args) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Lang("Help", player.UserIDString)); stringBuilder.AppendLine(Lang("HelpLicence1", player.UserIDString, configData.chat.buyCommand)); stringBuilder.AppendLine(Lang("HelpLicence2", player.UserIDString, configData.chat.spawnCommand)); stringBuilder.AppendLine(Lang("HelpLicence3", player.UserIDString, configData.chat.recallCommand)); stringBuilder.AppendLine(Lang("HelpLicence4", player.UserIDString, configData.chat.killCommand)); foreach(var entry in allVehicleSettings) { if (CanViewVehicleInfo(player, entry.Key, entry.Value)) { if (configData.chat.useUniversalCommand) { var firstCmd = entry.Value.Commands[0]; stringBuilder.AppendLine(Lang("HelpLicence5", player.UserIDString, firstCmd, entry.Value.DisplayName)); } } } Print(player, stringBuilder.ToString()); } #endregion Help Command #region Remove Command [ConsoleCommand("vl.remove")] private void CCmdRemoveVehicle(ConsoleSystem.Arg arg) { if (arg.IsAdmin && arg.Args != null && arg.Args.Length == 2) { var option = arg.Args[0]; string vehicleType; if (!IsValidVehicleType(option, out vehicleType)) { Print(arg, $ "{option} is not a valid vehicle type"); return; } switch (arg.Args[1].ToLower()) { case "*": case "all": { storedData.RemoveLicenseForAllPlayers(vehicleType); var vehicleName = GetBaseVehicleSettings(vehicleType).DisplayName; Print(arg, $ "You successfully removed the vehicle({vehicleName}) of all players"); } return; default: { var target = RustCore.FindPlayer(arg.Args[1]); if (target == null) { Print(arg, $ "Player '{arg.Args[1]}' not found"); return; } var vehicleName = GetBaseVehicleSettings(vehicleType).DisplayName; if (RemoveVehicleLicense(target.userID, vehicleType)) { Print(arg, $ "You successfully removed the vehicle({vehicleName}) of {target.displayName}"); return; } Print(arg, $ "{target.displayName} has not purchased vehicle({vehicleName}) and cannot be removed"); } return; } } } [ConsoleCommand("vl.cleardata")] private void CCmdClearVehicle(ConsoleSystem.Arg arg) { if (arg.IsAdmin) { foreach(var vehicle in vehiclesCache.Keys.ToArray()) { vehicle.Kill(BaseNetworkable.DestroyMode.Gib); } vehiclesCache.Clear(); ClearData(); Print(arg, "You successfully cleaned up all vehicle data"); } } #endregion Remove Command #region Buy Command [ConsoleCommand("vl.buy")] private void CCmdBuyVehicle(ConsoleSystem.Arg arg) { if (arg.IsAdmin && arg.Args != null && arg.Args.Length == 2) { var option = arg.Args[0]; string vehicleType; if (!IsValidVehicleType(option, out vehicleType)) { Print(arg, $ "{option} is not a valid vehicle type"); return; } switch (arg.Args[1].ToLower()) { case "*": case "all": { storedData.AddLicenseForAllPlayers(vehicleType); var vehicleName = GetBaseVehicleSettings(vehicleType).DisplayName; Print(arg, $ "You successfully purchased the vehicle({vehicleName}) for all players"); } return; default: { var target = RustCore.FindPlayer(arg.Args[1]); if (target == null) { Print(arg, $ "Player '{arg.Args[1]}' not found"); return; } var vehicleName = GetBaseVehicleSettings(vehicleType).DisplayName; if (AddVehicleLicense(target.userID, vehicleType)) { Print(arg, $ "You successfully purchased the vehicle({vehicleName}) for {target.displayName}"); return; } Print(arg, $ "{target.displayName} has purchased vehicle({vehicleName})"); } return; } } var player = arg.Player(); if (player == null) { Print(arg, $ "The server console cannot use the '{arg.cmd.FullName}' command"); } else { CmdBuyVehicle(player, arg.cmd.FullName, arg.Args); } } private void CmdBuyVehicle(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) { Print(player, Lang("NotAllowed", player.UserIDString)); return; } if (args == null || args.Length < 1) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Lang("Help", player.UserIDString)); foreach(var entry in allVehicleSettings) { if (CanViewVehicleInfo(player, entry.Key, entry.Value)) { var firstCmd = entry.Value.Commands[0]; if (entry.Value.PurchasePrices.Count > 0) { var prices = FormatPriceInfo(player, entry.Value.PurchasePrices); stringBuilder.AppendLine(Lang("HelpBuyPrice", player.UserIDString, configData.chat.buyCommand, firstCmd, entry.Value.DisplayName, prices)); } else { stringBuilder.AppendLine(Lang("HelpBuy", player.UserIDString, configData.chat.buyCommand, firstCmd, entry.Value.DisplayName)); } } } Print(player, stringBuilder.ToString()); return; } string vehicleType; if (IsValidOption(player, args[0], out vehicleType)) { BuyVehicle(player, vehicleType); } } private bool BuyVehicle(BasePlayer player, string vehicleType) { var settings = GetBaseVehicleSettings(vehicleType); if (!settings.Purchasable) { Print(player, Lang("VehicleCannotBeBought", player.UserIDString, settings.DisplayName)); return false; } var vehicles = storedData.GetPlayerVehicles(player.userID, false); if (vehicles.ContainsKey(vehicleType)) { Print(player, Lang("VehicleAlreadyPurchased", player.UserIDString, settings.DisplayName)); return false; } string resources; if (settings.PurchasePrices.Count > 0 && !TryPay(player, settings.PurchasePrices, out resources)) { Print(player, Lang("NoResourcesToPurchaseVehicle", player.UserIDString, settings.DisplayName, resources)); return false; } vehicles.Add(vehicleType, Vehicle.Create(player.userID, vehicleType)); SaveData(); Print(player, Lang("VehiclePurchased", player.UserIDString, settings.DisplayName, configData.chat.spawnCommand)); return true; } #endregion Buy Command #region Spawn Command [ConsoleCommand("vl.spawn")] private void CCmdSpawnVehicle(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) { Print(arg, $ "The server console cannot use the '{arg.cmd.FullName}' command"); } else { CmdSpawnVehicle(player, arg.cmd.FullName, arg.Args); } } private void CmdSpawnVehicle(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) { Print(player, Lang("NotAllowed", player.UserIDString)); return; } if (args == null || args.Length < 1) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Lang("Help", player.UserIDString)); foreach(var entry in allVehicleSettings) { if (CanViewVehicleInfo(player, entry.Key, entry.Value)) { var firstCmd = entry.Value.Commands[0]; if (entry.Value.SpawnPrices.Count > 0) { var prices = FormatPriceInfo(player, entry.Value.SpawnPrices); stringBuilder.AppendLine(Lang("HelpSpawnPrice", player.UserIDString, configData.chat.spawnCommand, firstCmd, entry.Value.DisplayName, prices)); } else { stringBuilder.AppendLine(Lang("HelpSpawn", player.UserIDString, configData.chat.spawnCommand, firstCmd, entry.Value.DisplayName)); } } } Print(player, stringBuilder.ToString()); return; } string vehicleType; if (IsValidOption(player, args[0], out vehicleType)) { var bypassCooldown = args.Length > 1 && IsValidBypassCooldownOption(args[1]); SpawnVehicle(player, vehicleType, bypassCooldown, command + " " + args[0]); } } private bool SpawnVehicle(BasePlayer player, string vehicleType, bool bypassCooldown, string command) { var settings = GetBaseVehicleSettings(vehicleType); Vehicle vehicle; if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle)) { Print(player, Lang("VehicleNotYetPurchased", player.UserIDString, settings.DisplayName, configData.chat.buyCommand)); return false; } if (vehicle.Entity != null && !vehicle.Entity.IsDestroyed) { Print(player, Lang("AlreadyVehicleOut", player.UserIDString, settings.DisplayName, configData.chat.recallCommand)); return false; } string reason; var position = Vector3.zero; var rotation = Quaternion.identity; if (CanSpawn(player, vehicle, bypassCooldown, command, out reason, ref position, ref rotation)) { SpawnVehicle(player, vehicle, position, rotation); return false; } Print(player, reason); return true; } private bool CanSpawn(BasePlayer player, Vehicle vehicle, bool bypassCooldown, string command, out string reason, ref Vector3 position, ref Quaternion rotation) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); BaseEntity randomVehicle = null; if (configData.global.limitVehicles > 0) { var activeVehicles = storedData.ActiveVehicles(player.userID); var count = activeVehicles.Count(); if (count >= configData.global.limitVehicles) { if (configData.global.killVehicleLimited) { randomVehicle = activeVehicles.ElementAt(Random.Range(0, count)); } else { reason = Lang("VehiclesLimit", player.UserIDString, configData.global.limitVehicles); return false; } } } if (!CanPlayerAction(player, vehicle, settings, out reason, ref position, ref rotation)) { return false; } var obj = Interface.CallHook("CanLicensedVehicleSpawn", player, vehicle.VehicleType, position, rotation); if (obj != null) { var s = obj as string; reason = s ?? Lang("SpawnWasBlocked", player.UserIDString, settings.DisplayName); return false; } #if DEBUG if (player.IsAdmin) { reason = null; return true; } #endif if (!CheckCooldown(player, vehicle, settings, bypassCooldown, true, command, out reason)) { return false; } string resources; if (settings.SpawnPrices.Count > 0 && !TryPay(player, settings.SpawnPrices, out resources)) { reason = Lang("NoResourcesToSpawnVehicle", player.UserIDString, settings.DisplayName, resources); return false; } if (randomVehicle != null) { randomVehicle.Kill(BaseNetworkable.DestroyMode.Gib); } reason = null; return true; } private void SpawnVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); var entity = settings.SpawnVehicle(player, vehicle, position, rotation); if (entity == null) { return; } Interface.CallHook("OnLicensedVehicleSpawned", entity, player, vehicle.VehicleType); Print(player, Lang("VehicleSpawned", player.UserIDString, settings.DisplayName)); } private void CacheVehicleEntity(BaseEntity entity, Vehicle vehicle, BasePlayer player) { vehicle.PlayerId = player.userID; vehicle.VehicleType = vehicle.VehicleType; vehicle.Entity = entity; vehicle.EntityId = entity.net.ID.Value; vehicle.LastDismount = vehicle.LastRecall = TimeEx.currentTimestamp; vehiclesCache.Add(entity, vehicle); } #endregion Spawn Command #region Recall Command [ConsoleCommand("vl.recall")] private void CCmdRecallVehicle(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) { Print(arg, $ "The server console cannot use the '{arg.cmd.FullName}' command"); } else { CmdRecallVehicle(player, arg.cmd.FullName, arg.Args); } } private void CmdRecallVehicle(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) { Print(player, Lang("NotAllowed", player.UserIDString)); return; } if (args == null || args.Length < 1) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Lang("Help", player.UserIDString)); foreach(var entry in allVehicleSettings) { if (CanViewVehicleInfo(player, entry.Key, entry.Value)) { var firstCmd = entry.Value.Commands[0]; if (entry.Value.RecallPrices.Count > 0) { var prices = FormatPriceInfo(player, entry.Value.RecallPrices); stringBuilder.AppendLine(Lang("HelpRecallPrice", player.UserIDString, configData.chat.recallCommand, firstCmd, entry.Value.DisplayName, prices)); } else { stringBuilder.AppendLine(Lang("HelpRecall", player.UserIDString, configData.chat.recallCommand, firstCmd, entry.Value.DisplayName)); } } } Print(player, stringBuilder.ToString()); return; } string vehicleType; if (IsValidOption(player, args[0], out vehicleType)) { var bypassCooldown = args.Length > 1 && IsValidBypassCooldownOption(args[1]); RecallVehicle(player, vehicleType, bypassCooldown, command + " " + args[0]); } } private bool RecallVehicle(BasePlayer player, string vehicleType, bool bypassCooldown, string command) { var settings = GetBaseVehicleSettings(vehicleType); Vehicle vehicle; if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle)) { Print(player, Lang("VehicleNotYetPurchased", player.UserIDString, settings.DisplayName, configData.chat.buyCommand)); return false; } if (vehicle.Entity != null && !vehicle.Entity.IsDestroyed) { string reason; var position = Vector3.zero; var rotation = Quaternion.identity; if (CanRecall(player, vehicle, bypassCooldown, command, out reason, ref position, ref rotation)) { RecallVehicle(player, vehicle, position, rotation); return true; } Print(player, reason); return false; } Print(player, Lang("VehicleNotOut", player.UserIDString, settings.DisplayName, configData.chat.spawnCommand)); return false; } private bool CanRecall(BasePlayer player, Vehicle vehicle, bool bypassCooldown, string command, out string reason, ref Vector3 position, ref Quaternion rotation) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); if (settings.RecallMaxDistance > 0 && Vector3.Distance(player.transform.position, vehicle.Entity.transform.position) > settings.RecallMaxDistance) { reason = Lang("RecallTooFar", player.UserIDString, settings.RecallMaxDistance, settings.DisplayName); return false; } if (configData.global.anyMountedRecall && VehicleAnyMounted(vehicle.Entity)) { reason = Lang("PlayerMountedOnVehicle", player.UserIDString, settings.DisplayName); return false; } if (!CanPlayerAction(player, vehicle, settings, out reason, ref position, ref rotation)) { return false; } var obj = Interface.CallHook("CanLicensedVehicleRecall", vehicle.Entity, player, vehicle.VehicleType, position, rotation); if (obj != null) { var s = obj as string; reason = s ?? Lang("RecallWasBlocked", player.UserIDString, settings.DisplayName); return false; } #if DEBUG if (player.IsAdmin) { reason = null; return true; } #endif if (!CheckCooldown(player, vehicle, settings, bypassCooldown, false, command, out reason)) { return false; } string resources; if (settings.RecallPrices.Count > 0 && !TryPay(player, settings.RecallPrices, out resources)) { reason = Lang("NoResourcesToRecallVehicle", player.UserIDString, settings.DisplayName, resources); return false; } reason = null; return true; } private void RecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { var settings = GetBaseVehicleSettings(vehicle.VehicleType); settings.PreRecallVehicle(player, vehicle, position, rotation); vehicle.Entity.transform.SetPositionAndRotation(position, rotation); vehicle.Entity.transform.hasChanged = true; settings.PostRecallVehicle(player, vehicle, position, rotation); vehicle.OnRecall(); if (vehicle.Entity == null || vehicle.Entity.IsDestroyed) { Print(player, Lang("NotSpawnedOrRecalled", player.UserIDString, settings.DisplayName)); return; } Interface.CallHook("OnLicensedVehicleRecalled", vehicle.Entity, player, vehicle.VehicleType); Print(player, Lang("VehicleRecalled", player.UserIDString, settings.DisplayName)); } #endregion Recall Command #region Kill Command [ConsoleCommand("vl.kill")] private void CCmdKillVehicle(ConsoleSystem.Arg arg) { var player = arg.Player(); if (player == null) { Print(arg, $ "The server console cannot use the '{arg.cmd.FullName}' command"); } else { CmdKillVehicle(player, arg.cmd.FullName, arg.Args); } } private void CmdKillVehicle(BasePlayer player, string command, string[] args) { if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) { Print(player, Lang("NotAllowed", player.UserIDString)); return; } if (args == null || args.Length < 1) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(Lang("Help", player.UserIDString)); foreach(var entry in allVehicleSettings) { if (CanViewVehicleInfo(player, entry.Key, entry.Value)) { var firstCmd = entry.Value.Commands[0]; if (!string.IsNullOrEmpty(configData.chat.customKillCommandPrefix)) { stringBuilder.AppendLine(Lang("HelpKillCustom", player.UserIDString, configData.chat.killCommand, firstCmd, configData.chat.customKillCommandPrefix + firstCmd, entry.Value.DisplayName)); } else { stringBuilder.AppendLine(Lang("HelpKill", player.UserIDString, configData.chat.killCommand, firstCmd, entry.Value.DisplayName)); } } } Print(player, stringBuilder.ToString()); return; } HandleKillCmd(player, args[0]); } private void HandleKillCmd(BasePlayer player, string option) { string vehicleType; if (IsValidOption(player, option, out vehicleType)) { KillVehicle(player, vehicleType); } } private bool KillVehicle(BasePlayer player, string vehicleType) { var settings = GetBaseVehicleSettings(vehicleType); Vehicle vehicle; if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle)) { Print(player, Lang("VehicleNotYetPurchased", player.UserIDString, settings.DisplayName, configData.chat.buyCommand)); return false; } if (vehicle.Entity != null && !vehicle.Entity.IsDestroyed) { if (!CanKill(player, vehicle, settings)) { return false; } vehicle.Entity.Kill(BaseNetworkable.DestroyMode.Gib); Print(player, Lang("VehicleKilled", player.UserIDString, settings.DisplayName)); return true; } Print(player, Lang("VehicleNotOut", player.UserIDString, settings.DisplayName, configData.chat.spawnCommand)); return false; } private bool CanKill(BasePlayer player, Vehicle vehicle, BaseVehicleSettings settings) { if (configData.global.anyMountedKill && VehicleAnyMounted(vehicle.Entity)) { Print(player, Lang("PlayerMountedOnVehicle", player.UserIDString, settings.DisplayName)); return false; } if (settings.KillMaxDistance > 0 && Vector3.Distance(player.transform.position, vehicle.Entity.transform.position) > settings.KillMaxDistance) { Print(player, Lang("KillTooFar", player.UserIDString, settings.KillMaxDistance, settings.DisplayName)); return false; } return true; } #endregion Kill Command #region Command Helpers private bool IsValidBypassCooldownOption(string option) { return !string.IsNullOrEmpty(configData.chat.bypassCooldownCommand) && string.Equals(option, configData.chat.bypassCooldownCommand, StringComparison.OrdinalIgnoreCase); } private bool IsValidOption(BasePlayer player, string option, out string vehicleType) { if (!commandToVehicleType.TryGetValue(option, out vehicleType)) { Print(player, Lang("OptionNotFound", player.UserIDString, option)); return false; } if (!HasVehiclePermission(player, vehicleType)) { Print(player, Lang("NotAllowed", player.UserIDString)); vehicleType = null; return false; } if (IsPlayerBlocked(player)) { vehicleType = null; return false; } return true; } private bool IsValidVehicleType(string option, out string vehicleType) { foreach(var entry in allVehicleSettings) { if (string.Equals(entry.Key, option, StringComparison.OrdinalIgnoreCase)) { vehicleType = entry.Key; return true; } } vehicleType = null; return false; } private string FormatPriceInfo(BasePlayer player, Dictionary < string, PriceInfo > prices) { var language = RustTranslationAPI != null ? lang.GetLanguage(player.UserIDString) : null; return string.Join(", ", from p in prices select Lang("PriceFormat", player.UserIDString, GetItemDisplayName(language, p.Key, p.Value.displayName), p.Value.amount)); } private bool CanPlayerAction(BasePlayer player, Vehicle vehicle, BaseVehicleSettings settings, out string reason, ref Vector3 position, ref Quaternion rotation) { if (configData.global.preventBuildingBlocked && player.IsBuildingBlocked()) { reason = Lang("BuildingBlocked", player.UserIDString, settings.DisplayName); return false; } if (configData.global.preventSafeZone && player.InSafeZone()) { reason = Lang("PlayerInSafeZone", player.UserIDString, settings.DisplayName); return false; } if (configData.global.preventMountedOrParented && HasMountedOrParented(player, settings)) { reason = Lang("MountedOrParented", player.UserIDString, settings.DisplayName); return false; } if (!settings.TryGetVehicleParams(player, vehicle, out reason, ref position, ref rotation)) { return false; } reason = null; return true; } private bool HasMountedOrParented(BasePlayer player, BaseVehicleSettings settings) { if (player.GetMountedVehicle() != null) { return true; } var parentEntity = player.GetParentEntity(); if (parentEntity != null) { if (configData.global.spawnLookingAt) { if (LandOnCargoShip != null && parentEntity is CargoShip && settings.IsFightVehicle) { return false; } } return true; } return false; } private bool CheckCooldown(BasePlayer player, Vehicle vehicle, BaseVehicleSettings settings, bool bypassCooldown, bool isSpawnCooldown, string command, out string reason) { var cooldown = settings.GetCooldown(player, isSpawnCooldown); if (cooldown > 0) { var timeLeft = Math.Ceiling(cooldown - (TimeEx.currentTimestamp - (isSpawnCooldown ? vehicle.LastDeath : vehicle.LastRecall))); if (timeLeft > 0) { var bypassPrices = isSpawnCooldown ? settings.BypassSpawnCooldownPrices : settings.BypassRecallCooldownPrices; if (bypassCooldown && bypassPrices.Count > 0) { string resources; if (!TryPay(player, bypassPrices, out resources)) { reason = Lang(isSpawnCooldown ? "NoResourcesToSpawnVehicleBypass" : "NoResourcesToRecallVehicleBypass", player.UserIDString, settings.DisplayName, resources); return false; } if (isSpawnCooldown) { vehicle.LastDeath = 0; } else { vehicle.LastRecall = 0; } } else { if (string.IsNullOrEmpty(configData.chat.bypassCooldownCommand) || bypassPrices.Count <= 0) { reason = Lang(isSpawnCooldown ? "VehicleOnSpawnCooldown" : "VehicleOnRecallCooldown", player.UserIDString, timeLeft, settings.DisplayName); } else { reason = Lang(isSpawnCooldown ? "VehicleOnSpawnCooldownPay" : "VehicleOnRecallCooldownPay", player.UserIDString, timeLeft, settings.DisplayName, command + " " + configData.chat.bypassCooldownCommand, FormatPriceInfo(player, isSpawnCooldown ? settings.BypassSpawnCooldownPrices : settings.BypassRecallCooldownPrices)); } return false; } } } reason = null; return true; } #endregion Command Helpers #endregion Commands #region RustTranslationAPI private string GetItemTranslationByShortName(string language, string itemShortName) { return (string) RustTranslationAPI.Call("GetItemTranslationByShortName", language, itemShortName); } private string GetItemDisplayName(string language, string itemShortName, string displayName) { if (RustTranslationAPI != null) { var displayName1 = GetItemTranslationByShortName(language, itemShortName); if (!string.IsNullOrEmpty(displayName1)) { return displayName1; } } return displayName; } #endregion RustTranslationAPI #region ConfigurationFile public ConfigData configData { get; private set; } public class ConfigData { [JsonProperty(PropertyName = "Settings")] public GlobalSettings global = new GlobalSettings(); [JsonProperty(PropertyName = "Chat Settings")] public ChatSettings chat = new ChatSettings(); [JsonProperty(PropertyName = "Normal Vehicle Settings")] public NormalVehicleSettings normalVehicles = new NormalVehicleSettings(); [JsonProperty(PropertyName = "Modular Vehicle Settings", ObjectCreationHandling = ObjectCreationHandling.Replace)] public Dictionary < string, ModularVehicleSettings > modularVehicles = new Dictionary < string, ModularVehicleSettings > { ["SmallCar"] = new ModularVehicleSettings { Purchasable = true, DisplayName = "Small Modular Car", Distance = 5, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.smallmodularcar", Commands = new List < string > { "small", "smallcar" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 1600, displayName = "Scrap" } }, SpawnPrices = new Dictionary < string, PriceInfo > { ["metal.refined"] = new PriceInfo { amount = 10, displayName = "High Quality Metal" } }, RecallPrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 5, displayName = "Scrap" } }, SpawnCooldown = 7200, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 3600, recallCooldown = 10 } }, ChassisType = ChassisType.Small, ModuleItems = new List < ModuleItem > { new ModuleItem { shortName = "vehicle.1mod.cockpit.with.engine", healthPercentage = 50 f }, new ModuleItem { shortName = "vehicle.1mod.storage", healthPercentage = 50 f } }, EngineItems = new List < EngineItem > { new EngineItem { shortName = "carburetor1", conditionPercentage = 20 f }, new EngineItem { shortName = "crankshaft1", conditionPercentage = 20 f }, new EngineItem { shortName = "piston1", conditionPercentage = 20 f }, new EngineItem { shortName = "sparkplug1", conditionPercentage = 20 f }, new EngineItem { shortName = "valve1", conditionPercentage = 20 f } } }, ["MediumCar"] = new ModularVehicleSettings { Purchasable = true, DisplayName = "Medium Modular Car", Distance = 5, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.mediumodularcar", Commands = new List < string > { "medium", "mediumcar" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 2400, displayName = "Scrap" } }, SpawnPrices = new Dictionary < string, PriceInfo > { ["metal.refined"] = new PriceInfo { amount = 50, displayName = "High Quality Metal" } }, RecallPrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 8, displayName = "Scrap" } }, SpawnCooldown = 9000, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 4500, recallCooldown = 10 } }, ChassisType = ChassisType.Medium, ModuleItems = new List < ModuleItem > { new ModuleItem { shortName = "vehicle.1mod.cockpit.with.engine", healthPercentage = 50 f }, new ModuleItem { shortName = "vehicle.1mod.rear.seats", healthPercentage = 50 f }, new ModuleItem { shortName = "vehicle.1mod.flatbed", healthPercentage = 50 f } }, EngineItems = new List < EngineItem > { new EngineItem { shortName = "carburetor2", conditionPercentage = 20 f }, new EngineItem { shortName = "crankshaft2", conditionPercentage = 20 f }, new EngineItem { shortName = "piston2", conditionPercentage = 20 f }, new EngineItem { shortName = "sparkplug2", conditionPercentage = 20 f }, new EngineItem { shortName = "valve2", conditionPercentage = 20 f } } }, ["LargeCar"] = new ModularVehicleSettings { Purchasable = true, DisplayName = "Large Modular Car", Distance = 6, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.largemodularcar", Commands = new List < string > { "large", "largecar" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 3000, displayName = "Scrap" } }, SpawnPrices = new Dictionary < string, PriceInfo > { ["metal.refined"] = new PriceInfo { amount = 100, displayName = "High Quality Metal" } }, RecallPrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 10, displayName = "Scrap" } }, SpawnCooldown = 10800, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 5400, recallCooldown = 10 } }, ChassisType = ChassisType.Large, ModuleItems = new List < ModuleItem > { new ModuleItem { shortName = "vehicle.1mod.engine", healthPercentage = 50 f }, new ModuleItem { shortName = "vehicle.1mod.cockpit.armored", healthPercentage = 50 f }, new ModuleItem { shortName = "vehicle.1mod.passengers.armored", healthPercentage = 50 f }, new ModuleItem { shortName = "vehicle.1mod.storage", healthPercentage = 50 f } }, EngineItems = new List < EngineItem > { new EngineItem { shortName = "carburetor3", conditionPercentage = 10 f }, new EngineItem { shortName = "crankshaft3", conditionPercentage = 10 f }, new EngineItem { shortName = "piston3", conditionPercentage = 10 f }, new EngineItem { shortName = "piston3", conditionPercentage = 10 f }, new EngineItem { shortName = "sparkplug3", conditionPercentage = 10 f }, new EngineItem { shortName = "sparkplug3", conditionPercentage = 10 f }, new EngineItem { shortName = "valve3", conditionPercentage = 10 f }, new EngineItem { shortName = "valve3", conditionPercentage = 10 f } } } }; [JsonProperty(PropertyName = "Train Vehicle Settings", ObjectCreationHandling = ObjectCreationHandling.Replace)] public Dictionary < string, TrainVehicleSettings > trainVehicles = new Dictionary < string, TrainVehicleSettings > { ["WorkCartAboveGround"] = new TrainVehicleSettings { Purchasable = true, DisplayName = "Work Cart Above Ground", Distance = 12, MinDistanceForPlayers = 6, UsePermission = true, Permission = "vehiclelicence.workcartaboveground", Commands = new List < string > { "cartground", "workcartground" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 2000, displayName = "Scrap" } }, SpawnCooldown = 1800, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 900, recallCooldown = 10 } }, TrainComponents = new List < TrainComponent > { new TrainComponent { type = TrainComponentType.Engine } } }, ["WorkCartCovered"] = new TrainVehicleSettings { Purchasable = true, DisplayName = "Covered Work Cart", Distance = 12, MinDistanceForPlayers = 6, UsePermission = true, Permission = "vehiclelicence.coveredworkcart", Commands = new List < string > { "cartcovered", "coveredworkcart" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 2000, displayName = "Scrap" } }, SpawnCooldown = 1800, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 900, recallCooldown = 10 } }, TrainComponents = new List < TrainComponent > { new TrainComponent { type = TrainComponentType.CoveredEngine } } }, ["CompleteTrain"] = new TrainVehicleSettings { Purchasable = true, DisplayName = "Complete Train", Distance = 12, MinDistanceForPlayers = 6, UsePermission = true, Permission = "vehiclelicence.completetrain", Commands = new List < string > { "ctrain", "completetrain" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 6000, displayName = "Scrap" } }, SpawnCooldown = 3600, RecallCooldown = 60, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 900, recallCooldown = 10 } }, TrainComponents = new List < TrainComponent > { new TrainComponent { type = TrainComponentType.Engine }, new TrainComponent { type = TrainComponentType.WagonA }, new TrainComponent { type = TrainComponentType.WagonB }, new TrainComponent { type = TrainComponentType.WagonC }, new TrainComponent { type = TrainComponentType.Unloadable }, new TrainComponent { type = TrainComponentType.UnloadableLoot } } } }; [JsonProperty(PropertyName = "Version")] public VersionNumber version; } public class ChatSettings { [JsonProperty(PropertyName = "Use Universal Chat Command")] public bool useUniversalCommand = true; [JsonProperty(PropertyName = "Help Chat Command")] public string helpCommand = "license"; [JsonProperty(PropertyName = "Buy Chat Command")] public string buyCommand = "buy"; [JsonProperty(PropertyName = "Spawn Chat Command")] public string spawnCommand = "spawn"; [JsonProperty(PropertyName = "Recall Chat Command")] public string recallCommand = "recall"; [JsonProperty(PropertyName = "Kill Chat Command")] public string killCommand = "kill"; [JsonProperty(PropertyName = "Custom Kill Chat Command Prefix")] public string customKillCommandPrefix = "no"; [JsonProperty(PropertyName = "Bypass Cooldown Command")] public string bypassCooldownCommand = "pay"; [JsonProperty(PropertyName = "Chat Prefix")] public string prefix = "[VehicleLicense]: "; [JsonProperty(PropertyName = "Chat SteamID Icon")] public ulong steamIDIcon = 76561198924840872; } public class GlobalSettings { [JsonProperty(PropertyName = "Store Vehicle On Plugin Unloaded / Server Restart")] public bool storeVehicle = true; [JsonProperty(PropertyName = "Clear Vehicle Data On Map Wipe")] public bool clearVehicleOnWipe; [JsonProperty(PropertyName = "Interval to check vehicle for wipe (Seconds)")] public float checkVehiclesInterval = 300; [JsonProperty(PropertyName = "Spawn vehicle in the direction you are looking at")] public bool spawnLookingAt = true; [JsonProperty(PropertyName = "Automatically claim vehicles purchased from vehicle vendors")] public bool autoClaimFromVendor; [JsonProperty(PropertyName = "Vehicle vendor purchases will unlock the license for the player")] public bool autoUnlockFromVendor; [JsonProperty(PropertyName = "Limit the number of vehicles at a time")] public int limitVehicles; [JsonProperty(PropertyName = "Kill a random vehicle when the number of vehicles is limited")] public bool killVehicleLimited; [JsonProperty(PropertyName = "Prevent vehicles from damaging players")] public bool preventDamagePlayer = true; [JsonProperty(PropertyName = "Prevent vehicles from shattering")] public bool preventShattering = true; [JsonProperty(PropertyName = "Prevent vehicles from spawning or recalling in safe zone")] public bool preventSafeZone = true; [JsonProperty(PropertyName = "Prevent vehicles from spawning or recalling when the player are building blocked")] public bool preventBuildingBlocked = true; [JsonProperty(PropertyName = "Prevent vehicles from spawning or recalling when the player is mounted or parented")] public bool preventMountedOrParented = true; [JsonProperty(PropertyName = "Check if any player mounted when recalling a vehicle")] public bool anyMountedRecall = true; [JsonProperty(PropertyName = "Check if any player mounted when killing a vehicle")] public bool anyMountedKill; [JsonProperty(PropertyName = "Dismount all players when a vehicle is recalled")] public bool dismountAllPlayersRecall = true; [JsonProperty(PropertyName = "Prevent other players from mounting vehicle")] public bool preventMounting = true; [JsonProperty(PropertyName = "Prevent mounting on driver's seat only")] public bool preventDriverSeat = true; [JsonProperty(PropertyName = "Prevent other players from looting fuel container and inventory")] public bool preventLooting = true; [JsonProperty(PropertyName = "Use Teams")] public bool useTeams; [JsonProperty(PropertyName = "Use Clans")] public bool useClans = true; [JsonProperty(PropertyName = "Use Friends")] public bool useFriends = true; [JsonProperty(PropertyName = "Vehicle No Decay")] public bool noDecay; [JsonProperty(PropertyName = "Vehicle No Fire Ball")] public bool noFireBall = true; [JsonProperty(PropertyName = "Vehicle No Server Gibs")] public bool noServerGibs = true; [JsonProperty(PropertyName = "Chinook No Map Marker")] public bool noMapMarker = true; [JsonProperty(PropertyName = "Use Raid Blocker (Need NoEscape Plugin)")] public bool useRaidBlocker; [JsonProperty(PropertyName = "Use Combat Blocker (Need NoEscape Plugin)")] public bool useCombatBlocker; } public class NormalVehicleSettings { [JsonProperty(PropertyName = "Sedan Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public SedanSettings sedan = new SedanSettings { Purchasable = true, DisplayName = "Sedan", Distance = 5, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.sedan", Commands = new List < string > { "car", "sedan" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 300, displayName = "Scrap" } }, SpawnCooldown = 300, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 150, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Chinook Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public ChinookSettings chinook = new ChinookSettings { Purchasable = true, DisplayName = "Chinook", Distance = 15, MinDistanceForPlayers = 6, UsePermission = true, Permission = "vehiclelicence.chinook", Commands = new List < string > { "ch47", "chinook" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 3000, displayName = "Scrap" } }, SpawnCooldown = 3000, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 1500, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Rowboat Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public RowboatSettings rowboat = new RowboatSettings { Purchasable = true, DisplayName = "Row Boat", Distance = 5, MinDistanceForPlayers = 2, UsePermission = true, Permission = "vehiclelicence.rowboat", Commands = new List < string > { "row", "rowboat" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 500, displayName = "Scrap" } }, SpawnCooldown = 300, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 150, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Tugboat Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public TugboatSettings tugboat = new TugboatSettings { Purchasable = true, DisplayName = "Tugboat...Boat", Distance = 10, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.tug", Commands = new List < string > { "tugboat" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 10000, displayName = "Scrap" } }, SpawnCooldown = 450, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 225, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "RHIB Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public RhibSettings rhib = new RhibSettings { Purchasable = true, DisplayName = "Rigid Hulled Inflatable Boat", Distance = 10, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.rhib", Commands = new List < string > { "rhib" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 1000, displayName = "Scrap" } }, SpawnCooldown = 450, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 225, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Hot Air Balloon Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public HotAirBalloonSettings hotAirBalloon = new HotAirBalloonSettings { Purchasable = true, DisplayName = "Hot Air Balloon", Distance = 20, MinDistanceForPlayers = 5, UsePermission = true, Permission = "vehiclelicence.hotairballoon", Commands = new List < string > { "hab", "hotairballoon" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 500, displayName = "Scrap" } }, SpawnCooldown = 900, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 450, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Ridable Horse Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public RidableHorseSettings ridableHorse = new RidableHorseSettings { Purchasable = true, DisplayName = "Ridable Horse", Distance = 6, MinDistanceForPlayers = 1, UsePermission = true, Permission = "vehiclelicence.ridablehorse", Commands = new List < string > { "horse", "ridablehorse" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 700, displayName = "Scrap" } }, SpawnCooldown = 3000, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 1500, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Mini Copter Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public MiniCopterSettings miniCopter = new MiniCopterSettings { Purchasable = true, DisplayName = "Mini Copter", Distance = 8, MinDistanceForPlayers = 2, UsePermission = true, Permission = "vehiclelicence.minicopter", Commands = new List < string > { "mini", "minicopter" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 4000, displayName = "Scrap" } }, SpawnCooldown = 1800, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 900, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Transport Helicopter Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public TransportHelicopterSettings transportHelicopter = new TransportHelicopterSettings { Purchasable = true, DisplayName = "Transport Copter", Distance = 10, MinDistanceForPlayers = 4, UsePermission = true, Permission = "vehiclelicence.transportcopter", Commands = new List < string > { "tcop", "transportcopter" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 5000, displayName = "Scrap" } }, SpawnCooldown = 2400, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 1200, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Work Cart Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public WorkCartSettings workCart = new WorkCartSettings { Purchasable = true, DisplayName = "Work Cart", Distance = 12, MinDistanceForPlayers = 6, UsePermission = true, Permission = "vehiclelicence.workcart", Commands = new List < string > { "cart", "workcart" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 2000, displayName = "Scrap" } }, SpawnCooldown = 1800, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 900, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Sedan Rail Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public WorkCartSettings sedanRail = new WorkCartSettings { Purchasable = true, DisplayName = "Sedan Rail", Distance = 6, MinDistanceForPlayers = 3, UsePermission = true, Permission = "vehiclelicence.sedanrail", Commands = new List < string > { "carrail", "sedanrail" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 500, displayName = "Scrap" } }, SpawnCooldown = 600, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 300, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Magnet Crane Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public MagnetCraneSettings magnetCrane = new MagnetCraneSettings { Purchasable = true, DisplayName = "Magnet Crane", Distance = 16, MinDistanceForPlayers = 8, UsePermission = true, Permission = "vehiclelicence.magnetcrane", Commands = new List < string > { "crane", "magnetcrane" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 2000, displayName = "Scrap" } }, SpawnCooldown = 600, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 300, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Submarine Solo Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public SubmarineSoloSettings submarineSolo = new SubmarineSoloSettings { Purchasable = true, DisplayName = "Submarine Solo", Distance = 5, MinDistanceForPlayers = 2, UsePermission = true, Permission = "vehiclelicence.submarinesolo", Commands = new List < string > { "subsolo", "solo" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 600, displayName = "Scrap" } }, SpawnCooldown = 300, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 150, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Submarine Duo Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public SubmarineDuoSettings submarineDuo = new SubmarineDuoSettings { Purchasable = true, DisplayName = "Submarine Duo", Distance = 5, MinDistanceForPlayers = 2, UsePermission = true, Permission = "vehiclelicence.submarineduo", Commands = new List < string > { "subduo", "duo" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 1000, displayName = "Scrap" } }, SpawnCooldown = 300, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 150, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Snowmobile Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public SnowmobileSettings snowmobile = new SnowmobileSettings { Purchasable = true, DisplayName = "Snowmobile", Distance = 5, MinDistanceForPlayers = 2, UsePermission = true, Permission = "vehiclelicence.snowmobile", Commands = new List < string > { "snow", "snowmobile" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 1000, displayName = "Scrap" } }, SpawnCooldown = 300, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 150, recallCooldown = 10 } } }; [JsonProperty(PropertyName = "Tomaha Snowmobile Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)] public SnowmobileSettings tomahaSnowmobile = new SnowmobileSettings { Purchasable = true, DisplayName = "Tomaha Snowmobile", Distance = 5, MinDistanceForPlayers = 2, UsePermission = true, Permission = "vehiclelicence.tomahasnowmobile", Commands = new List < string > { "tsnow", "tsnowmobile" }, PurchasePrices = new Dictionary < string, PriceInfo > { ["scrap"] = new PriceInfo { amount = 1000, displayName = "Scrap" } }, SpawnCooldown = 300, RecallCooldown = 30, CooldownPermissions = new Dictionary < string, CooldownPermission > { ["vehiclelicence.vip"] = new CooldownPermission { spawnCooldown = 150, recallCooldown = 10 } } }; } #region BaseSettings [JsonObject(MemberSerialization.OptIn)] public abstract class BaseVehicleSettings { #region Properties [JsonProperty(PropertyName = "Purchasable")] public bool Purchasable { get; set; } [JsonProperty(PropertyName = "Display Name")] public string DisplayName { get; set; } [JsonProperty(PropertyName = "Use Permission")] public bool UsePermission { get; set; } [JsonProperty(PropertyName = "Permission")] public string Permission { get; set; } [JsonProperty(PropertyName = "Distance To Spawn")] public float Distance { get; set; } [JsonProperty(PropertyName = "Time Before Vehicle Wipe (Seconds)")] public double WipeTime { get; set; } [JsonProperty(PropertyName = "Exclude cupboard zones when wiping")] public bool ExcludeCupboard { get; set; } [JsonProperty(PropertyName = "Maximum Health")] public float MaxHealth { get; set; } [JsonProperty(PropertyName = "Can Recall Maximum Distance")] public float RecallMaxDistance { get; set; } [JsonProperty(PropertyName = "Can Kill Maximum Distance")] public float KillMaxDistance { get; set; } [JsonProperty(PropertyName = "Minimum distance from player to recall or spawn")] public float MinDistanceForPlayers { get; set; } = 3 f; [JsonProperty(PropertyName = "Remove License Once Crashed")] public bool RemoveLicenseOnceCrash { get; set; } [JsonProperty(PropertyName = "Commands")] public List < string > Commands { get; set; } = new List < string > (); [JsonProperty(PropertyName = "Purchase Prices")] public Dictionary < string, PriceInfo > PurchasePrices { get; set; } = new Dictionary < string, PriceInfo > (); [JsonProperty(PropertyName = "Spawn Prices")] public Dictionary < string, PriceInfo > SpawnPrices { get; set; } = new Dictionary < string, PriceInfo > (); [JsonProperty(PropertyName = "Recall Prices")] public Dictionary < string, PriceInfo > RecallPrices { get; set; } = new Dictionary < string, PriceInfo > (); [JsonProperty(PropertyName = "Recall Cooldown Bypass Prices")] public Dictionary < string, PriceInfo > BypassRecallCooldownPrices { get; set; } = new Dictionary < string, PriceInfo > (); [JsonProperty(PropertyName = "Spawn Cooldown Bypass Prices")] public Dictionary < string, PriceInfo > BypassSpawnCooldownPrices { get; set; } = new Dictionary < string, PriceInfo > (); [JsonProperty(PropertyName = "Spawn Cooldown (Seconds)")] public double SpawnCooldown { get; set; } [JsonProperty(PropertyName = "Recall Cooldown (Seconds)")] public double RecallCooldown { get; set; } [JsonProperty(PropertyName = "Cooldown Permissions")] public Dictionary < string, CooldownPermission > CooldownPermissions { get; set; } = new Dictionary < string, CooldownPermission > (); #endregion Properties protected ConfigData configData => Instance.configData; public virtual bool IsWaterVehicle => false; public virtual bool IsTrainVehicle => false; public virtual bool IsNormalVehicle => true; public virtual bool IsFightVehicle => false; public virtual bool IsModularVehicle => false; public virtual bool IsConnectableVehicle => false; protected virtual EntityFuelSystem GetFuelSystem(BaseEntity entity) { return null; } protected virtual IEnumerable < ItemContainer > GetInventories(BaseEntity entity) { yield break; } #region Spawn protected virtual string GetVehiclePrefab(string vehicleType) { NormalVehicleType normalVehicleType; if (Enum.TryParse(vehicleType, out normalVehicleType) && Enum.IsDefined(typeof (NormalVehicleType), normalVehicleType)) { switch (normalVehicleType) { case NormalVehicleType.Rowboat: return PREFAB_ROWBOAT; case NormalVehicleType.RHIB: return PREFAB_RHIB; case NormalVehicleType.Tugboat: return PREFAB_TUGBOAT; case NormalVehicleType.Sedan: return PREFAB_SEDAN; case NormalVehicleType.HotAirBalloon: return PREFAB_HOTAIRBALLOON; case NormalVehicleType.MiniCopter: return PREFAB_MINICOPTER; case NormalVehicleType.TransportHelicopter: return PREFAB_TRANSPORTCOPTER; case NormalVehicleType.Chinook: return PREFAB_CHINOOK; case NormalVehicleType.RidableHorse: return PREFAB_RIDABLEHORSE; case NormalVehicleType.WorkCart: return PREFAB_WORKCART; case NormalVehicleType.SedanRail: return PREFAB_SEDANRAIL; case NormalVehicleType.MagnetCrane: return PREFAB_MAGNET_CRANE; case NormalVehicleType.SubmarineSolo: return PREFAB_SUBMARINE_SOLO; case NormalVehicleType.SubmarineDuo: return PREFAB_SUBMARINE_DUO; case NormalVehicleType.Snowmobile: return PREFAB_SNOWMOBILE; case NormalVehicleType.TomahaSnowmobile: return PREFAB_SNOWMOBILE_TOMAHA; default: return null; } } return null; } public virtual BaseEntity SpawnVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { var prefab = GetVehiclePrefab(vehicle.VehicleType); if (string.IsNullOrEmpty(prefab)) { throw new ArgumentException($"Prefab not found for {vehicle.VehicleType}"); } var entity = GameManager.server.CreateEntity(prefab, position, rotation); if (entity == null) { return null; } PreSetupVehicle(entity, vehicle, player); entity.Spawn(); SetupVehicle(entity, vehicle, player); if (!entity.IsDestroyed) { Instance.CacheVehicleEntity(entity, vehicle, player); } else { Instance.Print(player, Instance.Lang("NotSpawnedOrRecalled", player.UserIDString, DisplayName)); return null; } return entity; } #region Setup public virtual void PreSetupVehicle(BaseEntity entity, Vehicle vehicle, BasePlayer player) { entity.enableSaving = configData.global.storeVehicle; entity.OwnerID = player.userID; } public virtual void SetupVehicle(BaseEntity entity, Vehicle vehicle, BasePlayer player, bool justCreated = true) { if (MaxHealth > 0 && Math.Abs(MaxHealth - entity.MaxHealth()) > 0 f) { (entity as BaseCombatEntity)?.InitializeHealth(MaxHealth, MaxHealth); } var helicopterVehicle = entity as BaseHelicopterVehicle; if (helicopterVehicle != null) { if (configData.global.noServerGibs) { helicopterVehicle.serverGibs.guid = string.Empty; } if (configData.global.noFireBall) { helicopterVehicle.fireBall.guid = string.Empty; } if (configData.global.noMapMarker) { var ch47Helicopter = entity as CH47Helicopter; if (ch47Helicopter != null) { if (ch47Helicopter.mapMarkerInstance) { ch47Helicopter.mapMarkerInstance.Kill(); } ch47Helicopter.mapMarkerEntityPrefab.guid = string.Empty; } } } if (configData.global.preventShattering) { var magnetLiftable = entity.GetComponent < MagnetLiftable > (); if (magnetLiftable != null) { UnityEngine.Object.Destroy(magnetLiftable); } } } #endregion Setup #endregion Spawn #region Recall public virtual void PreRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { if (configData.global.dismountAllPlayersRecall) { DismountAllPlayers(vehicle.Entity); } if (CanDropInventory()) { TryDropVehicleInventory(player, vehicle); } if (vehicle.Entity.HasParent()) { vehicle.Entity.SetParent(null, true, true); } } public virtual void PostRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) {} #region DropInventory protected virtual bool CanDropInventory() { return false; } private void TryDropVehicleInventory(BasePlayer player, Vehicle vehicle) { var droppedItemContainer = DropVehicleInventory(player, vehicle); if (droppedItemContainer != null) { Instance.Print(player, Instance.Lang("VehicleInventoryDropped", player.UserIDString, DisplayName)); } } protected virtual DroppedItemContainer DropVehicleInventory(BasePlayer player, Vehicle vehicle) { var inventories = GetInventories(vehicle.Entity); foreach(var inventory in inventories) { if (inventory != null) { return inventory.Drop(PREFAB_ITEM_DROP, vehicle.Entity.GetDropPosition(), vehicle.Entity.transform.rotation, 0); } } return null; } #endregion DropInventory #region Train Car protected bool TryGetTrainCarPositionAndRotation(BasePlayer player, Vehicle vehicle, ref string reason, ref Vector3 original, ref Quaternion rotation) { float distResult; TrainTrackSpline splineResult; if (!TrainTrackSpline.TryFindTrackNear(original, Distance, out splineResult, out distResult)) { reason = Instance.Lang("TooFarTrainTrack", player.UserIDString); return false; } var position = splineResult.GetPosition(distResult); if (!SpaceIsClearForTrainTrack(vehicle, position, rotation)) { reason = Instance.Lang("TooCloseTrainBarricadeOrWorkCart", player.UserIDString); return false; } original = position; reason = null; return true; } protected bool TryMoveToTrainTrackNear(TrainCar trainCar) { float distResult; TrainTrackSpline splineResult; if (TrainTrackSpline.TryFindTrackNear(trainCar.GetFrontWheelPos(), 2 f, out splineResult, out distResult)) { trainCar.FrontWheelSplineDist = distResult; Vector3 tangent; var positionAndTangent = splineResult.GetPositionAndTangent(trainCar.FrontWheelSplineDist, trainCar.transform.forward, out tangent); trainCar.SetTheRestFromFrontWheelData(ref splineResult, positionAndTangent, tangent, trainCar.localTrackSelection, null, true); trainCar.FrontTrackSection = splineResult; if (trainCar.SpaceIsClear()) { return true; } } return false; } protected bool SpaceIsClearForTrainTrack(Vehicle vehicle, Vector3 position, Quaternion rotation) { var colliders = Pool.GetList < Collider > (); if (vehicle.Entity == null) { var prefab = GetVehiclePrefab(vehicle.VehicleType); if (!string.IsNullOrEmpty(prefab)) { var trainEngine = GameManager.server.FindPrefab(prefab)?.GetComponent < TrainEngine > (); if (trainEngine != null) { GamePhysics.OverlapOBB(new OBB(position, trainEngine.transform.lossyScale, rotation, trainEngine.bounds), colliders, Layers.Mask.Vehicle_World); } } } else { GamePhysics.OverlapOBB(new OBB(position, vehicle.Entity.transform.lossyScale, rotation, vehicle.Entity.bounds), colliders, Layers.Mask.Vehicle_World); } var free = true; foreach(var item in colliders) { var baseEntity = item.ToBaseEntity(); if (baseEntity == vehicle.Entity) { continue; } free = false; break; } Pool.FreeList(ref colliders); return free; } #endregion #endregion Recall #region Refund protected virtual bool CanRefundFuel(bool isCrash, bool isUnload) { return false; } protected virtual bool CanRefundInventory(bool isCrash, bool isUnload) { return false; } protected virtual void CollectVehicleItems(List < Item > items, Vehicle vehicle, bool isCrash, bool isUnload) { if (CanRefundFuel(isCrash, isUnload)) { var fuelSystem = GetFuelSystem(vehicle.Entity); if (fuelSystem != null) { var fuelContainer = fuelSystem.GetFuelContainer(); if (fuelContainer != null && fuelContainer.inventory != null) { items.AddRange(fuelContainer.inventory.itemList); } } } if (CanRefundInventory(isCrash, isUnload)) { var inventories = GetInventories(vehicle.Entity); foreach(var inventory in inventories) { items.AddRange(inventory.itemList); } } } public void RefundVehicleItems(Vehicle vehicle, bool isCrash, bool isUnload) { var collect = Pool.GetList < Item > (); CollectVehicleItems(collect, vehicle, isCrash, isUnload); if (collect.Count > 0) { var player = RustCore.FindPlayerById(vehicle.PlayerId); if (player == null) { DropItemContainer(vehicle.Entity, vehicle.PlayerId, collect); } else { for (var i = collect.Count - 1; i >= 0; i--) { var item = collect[i]; player.GiveItem(item); } if (player.IsConnected) { Instance.Print(player, Instance.Lang("RefundedVehicleItems", player.UserIDString, DisplayName)); } } } Pool.FreeList(ref collect); } #endregion Refund #region GiveFuel protected void TryGiveFuel(BaseEntity entity, IFuelVehicle iFuelVehicle) { if (iFuelVehicle == null || iFuelVehicle.SpawnFuelAmount <= 0) { return; } var fuelSystem = GetFuelSystem(entity); if (fuelSystem != null) { var fuelContainer = fuelSystem.GetFuelContainer(); if (fuelContainer != null && fuelContainer.inventory != null) { var fuelItem = ItemManager.CreateByItemID(ITEMID_FUEL, iFuelVehicle.SpawnFuelAmount); if (!fuelItem.MoveToContainer(fuelContainer.inventory)) { fuelItem.Remove(); } } } } #endregion GiveFuel #region Permission public double GetCooldown(BasePlayer player, bool isSpawn) { var cooldown = isSpawn ? SpawnCooldown : RecallCooldown; foreach(var entry in CooldownPermissions) { var currentCooldown = isSpawn ? entry.Value.spawnCooldown : entry.Value.recallCooldown; if (cooldown > currentCooldown && Instance.permission.UserHasPermission(player.UserIDString, entry.Key)) { cooldown = currentCooldown; } } return cooldown; } #endregion Permission #region TryGetVehicleParams public virtual bool TryGetVehicleParams(BasePlayer player, Vehicle vehicle, out string reason, ref Vector3 spawnPos, ref Quaternion spawnRot) { Vector3 original; Quaternion rotation; if (!TryGetPositionAndRotation(player, vehicle, out reason, out original, out rotation)) { return false; } CorrectPositionAndRotation(player, vehicle, original, rotation, out spawnPos, out spawnRot); return true; } protected virtual float GetSpawnRotationAngle() { return 90 f; } protected virtual Vector3 GetOriginalPosition(BasePlayer player) { if (configData.global.spawnLookingAt || IsWaterVehicle || IsTrainVehicle) { return GetGroundPositionLookingAt(player, Distance, !IsTrainVehicle); } return player.transform.position; } protected virtual bool TryGetPositionAndRotation(BasePlayer player, Vehicle vehicle, out string reason, out Vector3 original, out Quaternion rotation) { original = GetOriginalPosition(player); rotation = Quaternion.identity; if (MinDistanceForPlayers > 0) { var nearbyPlayers = Pool.GetList < BasePlayer > (); Vis.Entities(original, MinDistanceForPlayers, nearbyPlayers, Layers.Mask.Player_Server); var flag = nearbyPlayers.Any(x => x.userID.IsSteamId() && x != player); Pool.FreeList(ref nearbyPlayers); if (flag) { reason = Instance.Lang("PlayersOnNearby", player.UserIDString, DisplayName); return false; } } if (IsWaterVehicle && !IsInWater(original)) { reason = Instance.Lang("NotLookingAtWater", player.UserIDString, DisplayName); return false; } reason = null; return true; } protected virtual void CorrectPositionAndRotation(BasePlayer player, Vehicle vehicle, Vector3 original, Quaternion rotation, out Vector3 spawnPos, out Quaternion spawnRot) { spawnPos = original; if (IsTrainVehicle) { var forward = player.eyes.HeadForward().WithY(0); spawnRot = forward != Vector3.zero ? Quaternion.LookRotation(forward) : Quaternion.identity; return; } if (configData.global.spawnLookingAt) { var needGetGround = true; if (IsWaterVehicle) { RaycastHit hit; if (Physics.Raycast(spawnPos, Vector3.up, out hit, 100, LAYER_GROUND) && hit.GetEntity() is StabilityEntity) { needGetGround = false; //At the dock } } else { if (TryGetCenterOfFloorNearby(ref spawnPos)) { needGetGround = false; //At the floor } } if (needGetGround) { spawnPos = GetGroundPosition(spawnPos); } } else { GetPositionWithNoPlayersNearby(player, ref spawnPos); } var normalized = (spawnPos - player.transform.position).normalized; var angle = normalized != Vector3.zero ? Quaternion.LookRotation(normalized).eulerAngles.y : Random.Range(0 f, 360 f); var rotationAngle = GetSpawnRotationAngle(); spawnRot = Quaternion.Euler(Vector3.up * (angle + rotationAngle)); } private void GetPositionWithNoPlayersNearby(BasePlayer player, ref Vector3 spawnPos) { var minDistance = Mathf.Min(MinDistanceForPlayers, 2.5 f); var maxDistance = Mathf.Max(Distance, minDistance); var players = new BasePlayer[1]; var sourcePos = spawnPos; for (var i = 0; i < 10; i++) { spawnPos.x = sourcePos.x + Random.Range(minDistance, maxDistance) * (Random.value >= 0.5 f ? 1 : -1); spawnPos.z = sourcePos.z + Random.Range(minDistance, maxDistance) * (Random.value >= 0.5 f ? 1 : -1); if (BaseEntity.Query.Server.GetPlayersInSphere(spawnPos, minDistance, players, p => p.userID.IsSteamId() && p != player) == 0) { break; } } spawnPos = GetGroundPosition(spawnPos); } private bool TryGetCenterOfFloorNearby(ref Vector3 spawnPos) { var buildingBlocks = Pool.GetList < BuildingBlock > (); Vis.Entities(spawnPos, 2 f, buildingBlocks, Layers.Mask.Construction); if (buildingBlocks.Count > 0) { var position = spawnPos; var closestBuildingBlock = buildingBlocks .Where(x => !x.ShortPrefabName.Contains("wall")) .OrderBy(x => (x.transform.position - position).magnitude).FirstOrDefault(); if (closestBuildingBlock != null) { var worldSpaceBounds = closestBuildingBlock.WorldSpaceBounds(); spawnPos = worldSpaceBounds.position; spawnPos.y += worldSpaceBounds.extents.y; Pool.FreeList(ref buildingBlocks); return true; } } Pool.FreeList(ref buildingBlocks); return false; } #endregion TryGetVehicleParams } public abstract class FuelVehicleSettings: BaseVehicleSettings, IFuelVehicle { public int SpawnFuelAmount { get; set; } public bool RefundFuelOnKill { get; set; } = true; public bool RefundFuelOnCrash { get; set; } = true; public override void SetupVehicle(BaseEntity entity, Vehicle vehicle, BasePlayer player, bool justCreated = true) { if (justCreated) { TryGiveFuel(entity, this); } base.SetupVehicle(entity, vehicle, player, justCreated); } protected override bool CanRefundFuel(bool isCrash, bool isUnload) { return isUnload || (isCrash ? RefundFuelOnCrash : RefundFuelOnKill); } } public abstract class InventoryVehicleSettings: BaseVehicleSettings, IInventoryVehicle { public bool RefundInventoryOnKill { get; set; } = true; public bool RefundInventoryOnCrash { get; set; } = true; public bool DropInventoryOnRecall { get; set; } protected override bool CanDropInventory() { return DropInventoryOnRecall; } protected override bool CanRefundInventory(bool isCrash, bool isUnload) { return isUnload || (isCrash ? RefundInventoryOnCrash : RefundInventoryOnKill); } } public abstract class InvFuelVehicleSettings: BaseVehicleSettings, IFuelVehicle, IInventoryVehicle { public int SpawnFuelAmount { get; set; } public bool RefundFuelOnKill { get; set; } = true; public bool RefundFuelOnCrash { get; set; } = true; public bool RefundInventoryOnKill { get; set; } = true; public bool RefundInventoryOnCrash { get; set; } = true; public bool DropInventoryOnRecall { get; set; } public override void SetupVehicle(BaseEntity entity, Vehicle vehicle, BasePlayer player, bool justCreated = true) { if (justCreated) { TryGiveFuel(entity, this); } base.SetupVehicle(entity, vehicle, player, justCreated); } protected override bool CanDropInventory() { return DropInventoryOnRecall; } protected override bool CanRefundInventory(bool isCrash, bool isUnload) { return isUnload || (isCrash ? RefundInventoryOnCrash : RefundInventoryOnKill); } protected override bool CanRefundFuel(bool isCrash, bool isUnload) { return isUnload || (isCrash ? RefundFuelOnCrash : RefundFuelOnKill); } } #endregion BaseSettings #region Interface public interface IFuelVehicle { [JsonProperty(PropertyName = "Amount Of Fuel To Spawn", Order = 20)] int SpawnFuelAmount { get; set; } [JsonProperty(PropertyName = "Refund Fuel On Kill", Order = 21)] bool RefundFuelOnKill { get; set; } [JsonProperty(PropertyName = "Refund Fuel On Crash", Order = 22)] bool RefundFuelOnCrash { get; set; } } public interface IInventoryVehicle { [JsonProperty(PropertyName = "Refund Inventory On Kill", Order = 30)] bool RefundInventoryOnKill { get; set; } [JsonProperty(PropertyName = "Refund Inventory On Crash", Order = 31)] bool RefundInventoryOnCrash { get; set; } [JsonProperty(PropertyName = "Drop Inventory Items When Vehicle Recall", Order = 49)] bool DropInventoryOnRecall { get; set; } } public interface IModularVehicle { [JsonProperty(PropertyName = "Refund Engine Items On Kill", Order = 40)] bool RefundEngineOnKill { get; set; } [JsonProperty(PropertyName = "Refund Engine Items On Crash", Order = 41)] bool RefundEngineOnCrash { get; set; } [JsonProperty(PropertyName = "Refund Module Items On Kill", Order = 42)] bool RefundModuleOnKill { get; set; } [JsonProperty(PropertyName = "Refund Module Items On Crash", Order = 43)] bool RefundModuleOnCrash { get; set; } } public interface IAmmoVehicle { [JsonProperty(PropertyName = "Amount Of Ammo To Spawn", Order = 20)] int SpawnAmmoAmount { get; set; } } public interface ITrainVehicle {} #endregion Interface #region Struct public struct CooldownPermission { public double spawnCooldown; public double recallCooldown; } public struct ModuleItem { public string shortName; public float healthPercentage; } public struct EngineItem { public string shortName; public float conditionPercentage; } public struct PriceInfo { public int amount; public string displayName; } public struct TrainComponent { public TrainComponentType type; } #endregion Struct #region VehicleSettings public class SedanSettings: BaseVehicleSettings {} public class ChinookSettings: BaseVehicleSettings {} public class RowboatSettings: InvFuelVehicleSettings { public override bool IsWaterVehicle => true; protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as MotorRowboat)?.GetFuelSystem(); } protected override IEnumerable < ItemContainer > GetInventories(BaseEntity entity) { yield return (entity as MotorRowboat)?.storageUnitInstance.Get(true)?.inventory; } } public class RhibSettings: RowboatSettings {} public class TugboatSettings: InvFuelVehicleSettings { public override bool IsWaterVehicle => true; protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as MotorRowboat)?.GetFuelSystem(); } } public class HotAirBalloonSettings: InvFuelVehicleSettings { protected override float GetSpawnRotationAngle() { return 180 f; } protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as HotAirBalloon)?.fuelSystem; } protected override IEnumerable < ItemContainer > GetInventories(BaseEntity entity) { yield return (entity as HotAirBalloon)?.storageUnitInstance.Get(true)?.inventory; } } public class MiniCopterSettings: FuelVehicleSettings { public override bool IsFightVehicle => true; protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as MiniCopter)?.GetFuelSystem(); } } public class TransportHelicopterSettings: MiniCopterSettings {} public class RidableHorseSettings: InventoryVehicleSettings { protected override IEnumerable < ItemContainer > GetInventories(BaseEntity entity) { yield return (entity as RidableHorse)?.inventory; } public override void PostRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { base.PostRecallVehicle(player, vehicle, position, rotation); var ridableHorse = vehicle.Entity as RidableHorse; if (ridableHorse != null) { ridableHorse.TryLeaveHitch(); ridableHorse.DropToGround(ridableHorse.transform.position, true); //ridableHorse.UpdateDropToGroundForDuration(2f); } } protected override void CorrectPositionAndRotation(BasePlayer player, Vehicle vehicle, Vector3 original, Quaternion rotation, out Vector3 spawnPos, out Quaternion spawnRot) { base.CorrectPositionAndRotation(player, vehicle, original, rotation, out spawnPos, out spawnRot); spawnPos += Vector3.up * 0.3 f; } } // Only work cart (TrainEngine) public class WorkCartSettings: FuelVehicleSettings { public override bool IsTrainVehicle => true; public bool IsConnectableEngine(TrainEngine trainEngine) { return trainEngine.frontCoupling != null && trainEngine.rearCoupling != null; } protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as TrainEngine)?.GetFuelSystem(); } public override void PostRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { base.PostRecallVehicle(player, vehicle, position, rotation); var trainEngine = vehicle.Entity as TrainEngine; if (trainEngine != null) { TryMoveToTrainTrackNear(trainEngine); } } protected override bool TryGetPositionAndRotation(BasePlayer player, Vehicle vehicle, out string reason, out Vector3 original, out Quaternion rotation) { if (base.TryGetPositionAndRotation(player, vehicle, out reason, out original, out rotation)) { if (!TryGetTrainCarPositionAndRotation(player, vehicle, ref reason, ref original, ref rotation)) { return false; } } return true; } } public class MagnetCraneSettings: FuelVehicleSettings { protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as MagnetCrane)?.GetFuelSystem(); } } public class SubmarineSoloSettings: InvFuelVehicleSettings, IAmmoVehicle { private const int AMMO_ITEM_ID = -1671551935; public int SpawnAmmoAmount { get; set; } public override bool IsWaterVehicle => true; protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as BaseSubmarine)?.GetFuelSystem(); } protected override IEnumerable < ItemContainer > GetInventories(BaseEntity entity) { yield return (entity as BaseSubmarine)?.GetItemContainer()?.inventory; yield return (entity as BaseSubmarine)?.GetTorpedoContainer()?.inventory; } public override void SetupVehicle(BaseEntity entity, Vehicle vehicle, BasePlayer player, bool justCreated = true) { if (justCreated) { TryGiveAmmo(entity); } base.SetupVehicle(entity, vehicle, player, justCreated); } private void TryGiveAmmo(BaseEntity entity) { if (entity == null || SpawnAmmoAmount <= 0) { return; } var ammoContainer = (entity as BaseSubmarine)?.GetTorpedoContainer(); if (ammoContainer != null && ammoContainer.inventory != null) { var ammoItem = ItemManager.CreateByItemID(AMMO_ITEM_ID, SpawnAmmoAmount); if (!ammoItem.MoveToContainer(ammoContainer.inventory)) { ammoItem.Remove(); } } } } public class SubmarineDuoSettings: SubmarineSoloSettings {} public class SnowmobileSettings: InvFuelVehicleSettings { protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as Snowmobile)?.GetFuelSystem(); } protected override IEnumerable < ItemContainer > GetInventories(BaseEntity entity) { yield return (entity as Snowmobile)?.GetItemContainer()?.inventory; } } public class ModularVehicleSettings: InvFuelVehicleSettings, IModularVehicle { #region Properties public bool RefundEngineOnKill { get; set; } = true; public bool RefundEngineOnCrash { get; set; } = true; public bool RefundModuleOnKill { get; set; } = true; public bool RefundModuleOnCrash { get; set; } = true; [JsonProperty(PropertyName = "Chassis Type (Small, Medium, Large)", Order = 50)] public ChassisType ChassisType { get; set; } = ChassisType.Small; [JsonProperty(PropertyName = "Vehicle Module Items", Order = 51)] public List < ModuleItem > ModuleItems { get; set; } = new List < ModuleItem > (); [JsonProperty(PropertyName = "Vehicle Engine Items", Order = 52)] public List < EngineItem > EngineItems { get; set; } = new List < EngineItem > (); #endregion Properties #region ModuleItems private List < ModuleItem > _validModuleItems; public IEnumerable < ModuleItem > ValidModuleItems { get { if (_validModuleItems == null) { _validModuleItems = new List < ModuleItem > (); foreach(var modularItem in ModuleItems) { var itemDefinition = ItemManager.FindItemDefinition(modularItem.shortName); if (itemDefinition != null) { var itemModVehicleModule = itemDefinition.GetComponent < ItemModVehicleModule > (); if (itemModVehicleModule == null || !itemModVehicleModule.entityPrefab.isValid) { Instance.PrintError($"'{modularItem}' is not a valid vehicle module"); continue; } _validModuleItems.Add(modularItem); } } } return _validModuleItems; } } public IEnumerable < Item > CreateModuleItems() { foreach(var moduleItem in ValidModuleItems) { var item = ItemManager.CreateByName(moduleItem.shortName); if (item != null) { item.condition = item.maxCondition * (moduleItem.healthPercentage / 100 f); item.MarkDirty(); yield return item; } } } #endregion ModuleItems #region EngineItems private List < EngineItem > _validEngineItems; public IEnumerable < EngineItem > ValidEngineItems { get { if (_validEngineItems == null) { _validEngineItems = new List < EngineItem > (); foreach(var modularItem in EngineItems) { var itemDefinition = ItemManager.FindItemDefinition(modularItem.shortName); if (itemDefinition != null) { var itemModEngineItem = itemDefinition.GetComponent < ItemModEngineItem > (); if (itemModEngineItem == null) { Instance.PrintError($"'{modularItem}' is not a valid engine item"); continue; } _validEngineItems.Add(modularItem); } } } return _validEngineItems; } } public IEnumerable < Item > CreateEngineItems() { foreach(var engineItem in ValidEngineItems) { var item = ItemManager.CreateByName(engineItem.shortName); if (item != null) { item.condition = item.maxCondition * (engineItem.conditionPercentage / 100 f); item.MarkDirty(); yield return item; } } } #endregion EngineItems public override bool IsNormalVehicle => false; public override bool IsModularVehicle => true; protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as ModularCar)?.GetFuelSystem(); } #region Spawn protected override string GetVehiclePrefab(string vehicleType) { switch (ChassisType) { case ChassisType.Small: return PREFAB_CHASSIS_SMALL; case ChassisType.Medium: return PREFAB_CHASSIS_MEDIUM; case ChassisType.Large: return PREFAB_CHASSIS_LARGE; default: return null; } } #region Setup public override void SetupVehicle(BaseEntity entity, Vehicle vehicle, BasePlayer player, bool justCreated = true) { var modularCar = entity as ModularCar; if (modularCar != null) { if (ValidModuleItems.Any()) { AttacheVehicleModules(modularCar, vehicle); } if (ValidEngineItems.Any()) { Instance.NextTick(() => { AddItemsToVehicleEngine(modularCar, vehicle); }); } } base.SetupVehicle(entity, vehicle, player, justCreated); } #endregion Setup #endregion Spawn #region Recall public override void PreRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { base.PreRecallVehicle(player, vehicle, position, rotation); if (vehicle.Entity is ModularCar) { var modularCarGarages = Pool.GetList < ModularCarGarage > (); Vis.Entities(vehicle.Entity.transform.position, 3 f, modularCarGarages, Layers.Mask.Deployed | Layers.Mask.Default); var modularCarGarage = modularCarGarages.FirstOrDefault(x => x.carOccupant == vehicle.Entity); Pool.FreeList(ref modularCarGarages); if (modularCarGarage != null) { modularCarGarage.enabled = false; modularCarGarage.ReleaseOccupant(); modularCarGarage.Invoke(() => modularCarGarage.enabled = true, 0.25 f); } } } #region DropInventory protected override DroppedItemContainer DropVehicleInventory(BasePlayer player, Vehicle vehicle) { var modularCar = vehicle.Entity as ModularCar; if (modularCar != null) { foreach(var moduleEntity in modularCar.AttachedModuleEntities) { if (moduleEntity is VehicleModuleEngine) { continue; } var moduleStorage = moduleEntity as VehicleModuleStorage; if (moduleStorage != null) { return moduleStorage.GetContainer()?.inventory?.Drop(PREFAB_ITEM_DROP, vehicle.Entity.GetDropPosition(), vehicle.Entity.transform.rotation, 0); } } } return null; } #endregion DropInventory #endregion Recall #region Refund private void GetRefundStatus(bool isCrash, bool isUnload, out bool refundFuel, out bool refundInventory, out bool refundEngine, out bool refundModule) { if (isUnload) { refundFuel = refundInventory = refundEngine = refundModule = true; return; } refundFuel = isCrash ? RefundFuelOnCrash : RefundFuelOnKill; refundInventory = isCrash ? RefundInventoryOnCrash : RefundInventoryOnKill; refundEngine = isCrash ? RefundEngineOnCrash : RefundEngineOnKill; refundModule = isCrash ? RefundModuleOnCrash : RefundModuleOnKill; } protected override void CollectVehicleItems(List < Item > items, Vehicle vehicle, bool isCrash, bool isUnload) { var modularCar = vehicle.Entity as ModularCar; if (modularCar != null) { bool refundFuel, refundInventory, refundEngine, refundModule; GetRefundStatus(isCrash, isUnload, out refundFuel, out refundInventory, out refundEngine, out refundModule); foreach(var moduleEntity in modularCar.AttachedModuleEntities) { if (refundEngine) { var moduleEngine = moduleEntity as VehicleModuleEngine; if (moduleEngine != null) { var engineContainer = moduleEngine.GetContainer()?.inventory; if (engineContainer != null) { items.AddRange(engineContainer.itemList); } continue; } } if (refundInventory) { var moduleStorage = moduleEntity as VehicleModuleStorage; if (moduleStorage != null && !(moduleEntity is VehicleModuleEngine)) { var storageContainer = moduleStorage.GetContainer()?.inventory; if (storageContainer != null) { items.AddRange(storageContainer.itemList); } } } } if (refundFuel) { var fuelSystem = GetFuelSystem(modularCar); if (fuelSystem != null) { var fuelContainer = fuelSystem.GetFuelContainer()?.inventory; if (fuelContainer != null) { items.AddRange(fuelContainer.itemList); } } } if (refundModule) { var moduleContainer = modularCar.Inventory?.ModuleContainer; if (moduleContainer != null) { items.AddRange(moduleContainer.itemList); } } //var chassisContainer = modularCar.Inventory?.ChassisContainer; //if (chassisContainer != null) //{ // collect.AddRange(chassisContainer.itemList); //} } } #endregion Refund #region VehicleModules private void AttacheVehicleModules(ModularCar modularCar, Vehicle vehicle) { foreach(var moduleItem in CreateModuleItems()) { if (!modularCar.TryAddModule(moduleItem)) { Instance?.PrintError($"Module item '{moduleItem.info.shortname}' in '{vehicle.VehicleType}' cannot be attached to the vehicle"); moduleItem.Remove(); } } } private void AddItemsToVehicleEngine(ModularCar modularCar, Vehicle vehicle) { if (modularCar == null || modularCar.IsDestroyed) { return; } foreach(var moduleEntity in modularCar.AttachedModuleEntities) { var vehicleModuleEngine = moduleEntity as VehicleModuleEngine; if (vehicleModuleEngine != null) { var engineInventory = vehicleModuleEngine.GetContainer()?.inventory; if (engineInventory != null) { foreach(var engineItem in CreateEngineItems()) { var moved = false; for (var i = 0; i < engineInventory.capacity; i++) { if (engineItem.MoveToContainer(engineInventory, i, false)) { moved = true; break; } } if (!moved) { Instance?.PrintError($"Engine item '{engineItem.info.shortname}' in '{vehicle.VehicleType}' cannot be move to the vehicle engine inventory"); engineItem.Remove(); engineItem.DoRemove(); } } } } } } #endregion VehicleModules } public class TrainVehicleSettings: FuelVehicleSettings, ITrainVehicle { #region Properties [JsonProperty(PropertyName = "Train Components", Order = 50)] public List < TrainComponent > TrainComponents { get; set; } = new List < TrainComponent > (); #endregion Properties public override bool IsNormalVehicle => false; public override bool IsTrainVehicle => true; public override bool IsConnectableVehicle => true; protected override EntityFuelSystem GetFuelSystem(BaseEntity entity) { return (entity as TrainCar)?.GetFuelSystem(); } protected override string GetVehiclePrefab(string vehicleType) { return TrainComponents.Count > 0 ? GetTrainVehiclePrefab(TrainComponents[0].type) : base.GetVehiclePrefab(vehicleType); } #region Spawn private static string GetTrainVehiclePrefab(TrainComponentType componentType) { switch (componentType) { case TrainComponentType.Engine: return PREFAB_TRAINENGINE; case TrainComponentType.CoveredEngine: return PREFAB_TRAINENGINE_COVERED; case TrainComponentType.Locomotive: return PREFAB_TRAINENGINE_LOCOMOTIVE; case TrainComponentType.WagonA: return PREFAB_TRAINWAGON_A; case TrainComponentType.WagonB: return PREFAB_TRAINWAGON_B; case TrainComponentType.WagonC: return PREFAB_TRAINWAGON_C; case TrainComponentType.Unloadable: return PREFAB_TRAINWAGON_UNLOADABLE; case TrainComponentType.UnloadableLoot: return PREFAB_TRAINWAGON_UNLOADABLE_LOOT; case TrainComponentType.UnloadableFuel: return PREFAB_TRAINWAGON_UNLOADABLE_FUEL; case TrainComponentType.Caboose: return PREFAB_CABOOSE; default: return null; } } public override BaseEntity SpawnVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { TrainCar prevTrainCar = null, primaryTrainCar = null; foreach(var component in TrainComponents) { var prefab = GetTrainVehiclePrefab(component.type); if (string.IsNullOrEmpty(prefab)) { throw new ArgumentException($"Prefab not found for {vehicle.VehicleType}({component.type})"); } float distResult; TrainTrackSpline splineResult; if (prevTrainCar == null) { if (TrainTrackSpline.TryFindTrackNear(position, 20 f, out splineResult, out distResult)) { position = splineResult.GetPosition(distResult); prevTrainCar = GameManager.server.CreateEntity(prefab, position, rotation) as TrainCar; if (prevTrainCar == null) { continue; } PreSetupVehicle(prevTrainCar, vehicle, player); prevTrainCar.Spawn(); prevTrainCar.CancelInvoke(prevTrainCar.KillMessage); SetupVehicle(prevTrainCar, vehicle, player); } } else { var newTrainCar = GameManager.server.CreateEntity(prefab, prevTrainCar.transform.position, prevTrainCar.transform.rotation) as TrainCar; if (newTrainCar == null) { continue; } position += prevTrainCar.transform.rotation * (newTrainCar.bounds.center - Vector3.forward * (newTrainCar.bounds.extents.z + prevTrainCar.bounds.extents.z)); if (TrainTrackSpline.TryFindTrackNear(position, 20 f, out splineResult, out distResult)) { position = splineResult.GetPosition(distResult); newTrainCar.transform.position = position; PreSetupVehicle(newTrainCar, vehicle, player); newTrainCar.Spawn(); newTrainCar.CancelInvoke(newTrainCar.KillMessage); SetupVehicle(newTrainCar, vehicle, player); float minSplineDist; var distance = prevTrainCar.RearTrackSection.GetDistance(position, 1 f, out minSplineDist); var preferredAltTrack = prevTrainCar.RearTrackSection != prevTrainCar.FrontTrackSection ? prevTrainCar.RearTrackSection : null; newTrainCar.MoveFrontWheelsAlongTrackSpline(prevTrainCar.RearTrackSection, minSplineDist, distance, preferredAltTrack, TrainTrackSpline.TrackSelection.Default); newTrainCar.coupling.frontCoupling.TryCouple(prevTrainCar.coupling.rearCoupling, true); prevTrainCar = newTrainCar; } } if (primaryTrainCar == null) { primaryTrainCar = prevTrainCar; } } if (primaryTrainCar == null || primaryTrainCar.IsDestroyed) { Instance.Print(player, Instance.Lang("NotSpawnedOrRecalled", player.UserIDString, DisplayName)); return null; } Instance.CacheVehicleEntity(primaryTrainCar, vehicle, player); return primaryTrainCar; } #endregion Spawn #region Recall public override void PreRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { base.PreRecallVehicle(player, vehicle, position, rotation); var trainCar = vehicle.Entity as TrainCar; if (trainCar != null) { trainCar.coupling.Uncouple(true); trainCar.coupling.Uncouple(false); } } public override void PostRecallVehicle(BasePlayer player, Vehicle vehicle, Vector3 position, Quaternion rotation) { base.PostRecallVehicle(player, vehicle, position, rotation); var trainCar = vehicle.Entity as TrainCar; if (trainCar != null) { TryMoveToTrainTrackNear(trainCar); } } #endregion Recall #region Refund protected override void CollectVehicleItems(List < Item > items, Vehicle vehicle, bool isCrash, bool isUnload) { // Refund primary engine fuel only if (CanRefundFuel(isCrash, isUnload)) { var trainCar = vehicle.Entity as TrainCar; if (trainCar != null) { var fuelSystem = GetFuelSystem(trainCar); if (fuelSystem != null) { var fuelContainer = fuelSystem.GetFuelContainer()?.inventory; if (fuelContainer != null) { items.AddRange(fuelContainer.itemList); } } } } } #endregion Refund #region TryGetVehicleParams protected override bool TryGetPositionAndRotation(BasePlayer player, Vehicle vehicle, out string reason, out Vector3 original, out Quaternion rotation) { if (base.TryGetPositionAndRotation(player, vehicle, out reason, out original, out rotation)) { if (!TryGetTrainCarPositionAndRotation(player, vehicle, ref reason, ref original, ref rotation)) { return false; } } return true; } // protected override void CorrectPositionAndRotation(BasePlayer player, Vehicle vehicle, Vector3 original, Quaternion rotation, out Vector3 spawnPos, out Quaternion spawnRot) // { // base.CorrectPositionAndRotation(player, vehicle, original, rotation, out spawnPos, out spawnRot); // // No rotation on recall // if (vehicle.Entity != null) // { // spawnRot = vehicle.Entity.transform.rotation; // } // } #endregion TryGetVehicleParams } #endregion VehicleSettings protected override void LoadConfig() { base.LoadConfig(); try { PreprocessOldConfig(); configData = Config.ReadObject < ConfigData > (); if (configData == null) { LoadDefaultConfig(); } else { UpdateConfigValues(); } } catch (Exception ex) { PrintError($"The configuration file is corrupted. \n{ex}"); LoadDefaultConfig(); } SaveConfig(); } protected override void LoadDefaultConfig() { PrintWarning("Creating a new configuration file"); configData = new ConfigData(); configData.version = Version; } protected override void SaveConfig() { Config.WriteObject(configData); } private void UpdateConfigValues() { if (configData.version < Version) { if (configData.version <= default (VersionNumber)) { string prefix, prefixColor; if (GetConfigValue(out prefix, "Chat Settings", "Chat Prefix") && GetConfigValue(out prefixColor, "Chat Settings", "Chat Prefix Color")) { configData.chat.prefix = $ "{prefix}: "; } } if (configData.version <= new VersionNumber(1, 7, 3)) { configData.normalVehicles.sedan.MinDistanceForPlayers = 3 f; configData.normalVehicles.chinook.MinDistanceForPlayers = 5 f; configData.normalVehicles.rowboat.MinDistanceForPlayers = 2 f; configData.normalVehicles.rhib.MinDistanceForPlayers = 3 f; configData.normalVehicles.hotAirBalloon.MinDistanceForPlayers = 4 f; configData.normalVehicles.ridableHorse.MinDistanceForPlayers = 1 f; configData.normalVehicles.miniCopter.MinDistanceForPlayers = 2 f; configData.normalVehicles.transportHelicopter.MinDistanceForPlayers = 4 f; foreach(var entry in configData.modularVehicles) { switch (entry.Value.ChassisType) { case ChassisType.Small: entry.Value.MinDistanceForPlayers = 2 f; break; case ChassisType.Medium: entry.Value.MinDistanceForPlayers = 2.5 f; break; case ChassisType.Large: entry.Value.MinDistanceForPlayers = 3 f; break; default: continue; } } } if (configData.version >= new VersionNumber(1, 7, 17) && configData.version <= new VersionNumber(1, 7, 18)) { LoadData(); foreach(var data in storedData.playerData) { Vehicle vehicle; if (data.Value.TryGetValue("SubmarineDouble", out vehicle)) { data.Value.Remove("SubmarineDouble"); data.Value.Add(nameof(NormalVehicleType.SubmarineDuo), vehicle); } } SaveData(); } configData.version = Version; } } private bool GetConfigValue < T > (out T value, params string[] path) { var configValue = Config.Get(path); if (configValue != null) { if (configValue is T) { value = (T) configValue; return true; } try { value = Config.ConvertValue < T > (configValue); return true; } catch (Exception ex) { PrintError($"GetConfigValue ERROR: path: {string.Join("\\ ", path)}\n{ex}"); } } value = default (T); return false; } private void SetConfigValue(params object[] pathAndTrailingValue) { Config.Set(pathAndTrailingValue); } #region Preprocess Old Config private void PreprocessOldConfig() { var config = Config.ReadObject < JObject > (); if (config == null) { return; } //Interface.Oxide.DataFileSystem.WriteObject(Name + "_old", jObject); VersionNumber oldVersion; if (GetConfigVersionPre(config, out oldVersion)) { if (oldVersion < Version) { if (oldVersion < new VersionNumber(1, 7, 35)) { try { if (config["Train Vehicle Settings"] == null) { config["Train Vehicle Settings"] = JObject.FromObject(new ConfigData().trainVehicles); } var workCartAboveGround = GetConfigValue(config, "Normal Vehicle Settings", "Work Cart Above Ground Vehicle"); if (workCartAboveGround != null) { var settings = workCartAboveGround.ToObject < TrainVehicleSettings > (); settings.TrainComponents = new List < TrainComponent > { new TrainComponent { type = TrainComponentType.Engine } }; config["Train Vehicle Settings"]["WorkCartAboveGround"] = JObject.FromObject(settings);; } var coveredWorkCart = GetConfigValue(config, "Normal Vehicle Settings", "Covered Work Cart Vehicle"); if (coveredWorkCart != null) { var settings = coveredWorkCart.ToObject < TrainVehicleSettings > (); settings.TrainComponents = new List < TrainComponent > { new TrainComponent { type = TrainComponentType.CoveredEngine } }; config["Train Vehicle Settings"]["WorkCartCovered"] = JObject.FromObject(settings); } } catch { // ignored } } Config.WriteObject(config); // Interface.Oxide.DataFileSystem.WriteObject(Name + "_new", jObject); } } } private JObject GetConfigValue(JObject config, params string[] path) { if (path.Length < 1) { throw new ArgumentException("path is empty"); } try { JToken jToken; if (!config.TryGetValue(path[0], out jToken)) { return null; } for (var i = 1; i < path.Length; i++) { var jObject = jToken as JObject; if (jObject == null || !jObject.TryGetValue(path[i], out jToken)) { return null; } } return jToken as JObject; } catch (Exception ex) { PrintError($"GetConfigValue ERROR: path: {string.Join("\\ ", path)}\n{ex}"); } return null; } private bool GetConfigValuePre < T > (JObject config, out T value, params string[] path) { if (path.Length < 1) { throw new ArgumentException("path is empty"); } try { JToken jToken; if (!config.TryGetValue(path[0], out jToken)) { value = default (T); return false; } for (var i = 1; i < path.Length; i++) { var jObject = jToken.ToObject < JObject > (); if (jObject == null || !jObject.TryGetValue(path[i], out jToken)) { value = default (T); return false; } } value = jToken.ToObject < T > (); return true; } catch (Exception ex) { PrintError($"GetConfigValuePre ERROR: path: {string.Join("\\ ", path)}\n{ex}"); } value = default (T); return false; } private void SetConfigValuePre(JObject config, object value, params string[] path) { if (path.Length < 1) { throw new ArgumentException("path is empty"); } try { JToken jToken; if (!config.TryGetValue(path[0], out jToken)) { if (path.Length == 1) { jToken = JToken.FromObject(value); config.Add(path[0], jToken); return; } jToken = new JObject(); config.Add(path[0], jToken); } for (var i = 1; i < path.Length - 1; i++) { var jObject = jToken as JObject; if (jObject == null || !jObject.TryGetValue(path[i], out jToken)) { jToken = new JObject(); jObject?.Add(path[i], jToken); } } var targetToken = jToken as JObject; if (targetToken != null) { targetToken[path[path.Length - 1]] = JToken.FromObject(value); } // (jToken as JObject)?.TryAdd(path[path.Length - 1], JToken.FromObject(value)); } catch (Exception ex) { PrintError($"SetConfigValuePre ERROR: value: {value} path: {string.Join("\\ ", path)}\n{ex}"); } } private bool GetConfigVersionPre(JObject config, out VersionNumber version) { try { JToken jToken; if (config.TryGetValue("Version", out jToken)) { version = jToken.ToObject < VersionNumber > (); return true; } } catch { // ignored } version = default (VersionNumber); return false; } #endregion Preprocess Old Config #endregion ConfigurationFile #region DataFile public StoredData storedData { get; private set; } public class StoredData { public readonly Dictionary < ulong, Dictionary < string, Vehicle >> playerData = new Dictionary < ulong, Dictionary < string, Vehicle >> (); public IEnumerable < BaseEntity > ActiveVehicles(ulong playerId) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { yield break; } foreach(var vehicle in vehicles.Values) { if (vehicle.Entity != null && !vehicle.Entity.IsDestroyed) { yield return vehicle.Entity; } } } public Dictionary < string, Vehicle > GetPlayerVehicles(ulong playerId, bool readOnly = true) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { if (!readOnly) { vehicles = new Dictionary < string, Vehicle > (); playerData.Add(playerId, vehicles); return vehicles; } return null; } return vehicles; } public bool IsVehiclePurchased(ulong playerId, string vehicleType, out Vehicle vehicle) { vehicle = GetVehicleLicense(playerId, vehicleType); if (vehicle == null) { return false; } return true; } public Vehicle GetVehicleLicense(ulong playerId, string vehicleType) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { return null; } Vehicle vehicle; if (!vehicles.TryGetValue(vehicleType, out vehicle)) { return null; } return vehicle; } public bool HasVehicleLicense(ulong playerId, string vehicleType) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { return false; } return vehicles.ContainsKey(vehicleType); } public bool AddVehicleLicense(ulong playerId, string vehicleType) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { vehicles = new Dictionary < string, Vehicle > (); playerData.Add(playerId, vehicles); } if (vehicles.ContainsKey(vehicleType)) { return false; } vehicles.Add(vehicleType, Vehicle.Create(playerId, vehicleType)); Instance.SaveData(); return true; } public bool RemoveVehicleLicense(ulong playerId, string vehicleType) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { return false; } if (!vehicles.Remove(vehicleType)) { return false; } Instance.SaveData(); return true; } public List < string > GetVehicleLicenseNames(ulong playerId) { Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { return new List < string > (); } return vehicles.Keys.ToList(); } public void PurchaseAllVehicles(ulong playerId) { var changed = false; Dictionary < string, Vehicle > vehicles; if (!playerData.TryGetValue(playerId, out vehicles)) { vehicles = new Dictionary < string, Vehicle > (); playerData.Add(playerId, vehicles); } foreach(var vehicleType in Instance.allVehicleSettings.Keys) { if (!vehicles.ContainsKey(vehicleType)) { vehicles.Add(vehicleType, Vehicle.Create(playerId, vehicleType)); changed = true; } } if (changed) { Instance.SaveData(); } } public void AddLicenseForAllPlayers(string vehicleType) { foreach(var entry in playerData) { if (!entry.Value.ContainsKey(vehicleType)) { entry.Value.Add(vehicleType, Vehicle.Create(entry.Key, vehicleType)); } } } public void RemoveLicenseForAllPlayers(string vehicleType) { foreach(var entry in playerData) { entry.Value.Remove(vehicleType); } } public void ResetPlayerData() { foreach(var vehicleEntries in playerData) { foreach(var vehicleEntry in vehicleEntries.Value) { vehicleEntry.Value.Reset(); } } } } [JsonObject(MemberSerialization.OptIn)] public class Vehicle { [JsonProperty("entityID")] public ulong EntityId { get; set; } [JsonProperty("lastDeath")] public double LastDeath { get; set; } public ulong PlayerId { get; set; } public BaseEntity Entity { get; set; } public string VehicleType { get; set; } public double LastRecall { get; set; } public double LastDismount { get; set; } public void OnDismount() { LastDismount = TimeEx.currentTimestamp; } public void OnRecall() { LastRecall = TimeEx.currentTimestamp; } public void OnDeath() { Entity = null; EntityId = 0; LastDeath = TimeEx.currentTimestamp; } public void Reset() { EntityId = 0; LastDeath = 0; } public static Vehicle Create(ulong playerId, string vehicleType) { var vehicle = new Vehicle(); vehicle.VehicleType = vehicleType; vehicle.PlayerId = playerId; return vehicle; } } private void LoadData() { try { storedData = Interface.Oxide.DataFileSystem.ReadObject < StoredData > (Name); } catch { storedData = null; } if (storedData == null) { ClearData(); } } private void ClearData() { storedData = new StoredData(); SaveData(); } private void SaveData() { Interface.Oxide.DataFileSystem.WriteObject(Name, storedData); } private void OnNewSave() { if (configData.global.clearVehicleOnWipe) { ClearData(); } else { storedData.ResetPlayerData(); SaveData(); } } #endregion DataFile #region LanguageFile private void Print(BasePlayer player, string message) { Player.Message(player, message, configData.chat.prefix, configData.chat.steamIDIcon); } private void Print(ConsoleSystem.Arg arg, string message) { var player = arg.Player(); if (player == null) { Puts(message); } else { PrintToConsole(player, message); } } private string Lang(string key, string id = null, params object[] args) { try { return string.Format(lang.GetMessage(key, this, id), args); } catch (Exception) { PrintError($"Error in the language formatting of '{key}'. (userid: {id}. lang: {lang.GetLanguage(id)}. args: {string.Join(", ", args)})"); throw; } } protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary < string, string > { ["Help"] = "These are the available commands:", ["HelpLicence1"] = "/{0} -- To buy a vehicle", ["HelpLicence2"] = "/{0} -- To spawn a vehicle", ["HelpLicence3"] = "/{0} -- To recall a vehicle", ["HelpLicence4"] = "/{0} -- To kill a vehicle", ["HelpLicence5"] = "/{0} -- To buy, spawn or recall a {1}", ["PriceFormat"] = "{0} x{1}", ["HelpBuy"] = "/{0} {1} -- To buy a {2}", ["HelpBuyPrice"] = "/{0} {1} -- To buy a {2}. Price: {3}", ["HelpSpawn"] = "/{0} {1} -- To spawn a {2}", ["HelpSpawnPrice"] = "/{0} {1} -- To spawn a {2}. Price: {3}", ["HelpRecall"] = "/{0} {1} -- To recall a {2}", ["HelpRecallPrice"] = "/{0} {1} -- To recall a {2}. Price: {3}", ["HelpKill"] = "/{0} {1} -- To kill a {2}", ["HelpKillCustom"] = "/{0} {1} or /{2} -- To kill a {3}", ["NotAllowed"] = "You do not have permission to use this command.", ["RaidBlocked"] = "You may not do that while raid blocked.", ["CombatBlocked"] = "You may not do that while combat blocked.", ["OptionNotFound"] = "This {0} option doesn't exist.", ["VehiclePurchased"] = "You have purchased a {0}, type /{1} for more information.", ["VehicleAlreadyPurchased"] = "You have already purchased {0}.", ["VehicleCannotBeBought"] = "{0} is unpurchasable", ["VehicleNotOut"] = "{0} is not out, type /{1} for more information.", ["AlreadyVehicleOut"] = "You already have a {0} outside, type /{1} for more information.", ["VehicleNotYetPurchased"] = "You have not yet purchased a {0}, type /{1} for more information.", ["VehicleSpawned"] = "You spawned your {0}.", ["VehicleRecalled"] = "You recalled your {0}.", ["VehicleKilled"] = "You killed your {0}.", ["VehicleOnSpawnCooldown"] = "You must wait {0} seconds before you can spawn your {1}.", ["VehicleOnRecallCooldown"] = "You must wait {0} seconds before you can recall your {1}.", ["VehicleOnSpawnCooldownPay"] = "You must wait {0} seconds before you can spawn your {1}. You can bypass this cooldown by using the /{2} command to pay {3}", ["VehicleOnRecallCooldownPay"] = "You must wait {0} seconds before you can recall your {1}. You can bypass this cooldown by using the /{2} command to pay {3}", ["NotLookingAtWater"] = "You must be looking at water to spawn or recall a {0}.", ["BuildingBlocked"] = "You can't spawn a {0} if you don't have the building privileges.", ["RefundedVehicleItems"] = "Your {0} vehicle items was refunded to your inventory.", ["PlayerMountedOnVehicle"] = "It cannot be recalled or killed when players mounted on your {0}.", ["PlayerInSafeZone"] = "You cannot spawn or recall your {0} in the safe zone.", ["VehicleInventoryDropped"] = "Your {0} vehicle inventory cannot be recalled, it have dropped to the ground.", ["NoResourcesToPurchaseVehicle"] = "You don't have enough resources to buy a {0}. You are missing: \n{1}", ["NoResourcesToSpawnVehicle"] = "You don't have enough resources to spawn a {0}. You are missing: \n{1}", ["NoResourcesToSpawnVehicleBypass"] = "You don't have enough resources to bypass the cooldown to spawn a {0}. You are missing: \n{1}", ["NoResourcesToRecallVehicle"] = "You don't have enough resources to recall a {0}. You are missing: \n{1}", ["NoResourcesToRecallVehicleBypass"] = "You don't have enough resources to bypass the cooldown to recall a {0}. You are missing: \n{1}", ["MountedOrParented"] = "You cannot spawn or recall a {0} when mounted or parented.", ["RecallTooFar"] = "You must be within {0} meters of {1} to recall.", ["KillTooFar"] = "You must be within {0} meters of {1} to kill.", ["PlayersOnNearby"] = "You cannot spawn or recall a {0} when there are players near the position you are looking at.", ["RecallWasBlocked"] = "An external plugin blocked you from recalling a {0}.", ["SpawnWasBlocked"] = "An external plugin blocked you from spawning a {0}.", ["VehiclesLimit"] = "You can have up to {0} vehicles at a time.", ["TooFarTrainTrack"] = "You are too far from the train track.", ["TooCloseTrainBarricadeOrWorkCart"] = "You are too close to the train barricade or work cart.", ["NotSpawnedOrRecalled"] = "For some reason, your {0} vehicle was not spawned/recalled", ["CantUse"] = "Sorry! This {0} belongs to {1}.You cannot use it." }, this); lang.RegisterMessages(new Dictionary < string, string > { ["Help"] = "可用命令列表:", ["HelpLicence1"] = "/{0} -- 购买一辆载具", ["HelpLicence2"] = "/{0} -- 生成一辆载具", ["HelpLicence3"] = "/{0} -- 召回一辆载具", ["HelpLicence4"] = "/{0} -- 摧毁一辆载具", ["HelpLicence5"] = "/{0} -- 购买,生成,召回一辆 {1}", ["PriceFormat"] = "{0} x{1}", ["HelpBuy"] = "/{0} {1} -- 购买一辆 {2}", ["HelpBuyPrice"] = "/{0} {1} -- 购买一辆 {2},价格: {3}", ["HelpSpawn"] = "/{0} {1} -- 生成一辆 {2}", ["HelpSpawnPrice"] = "/{0} {1} -- 生成一辆 {2},价格: {3}", ["HelpRecall"] = "/{0} {1} -- 召回一辆 {2}", ["HelpRecallPrice"] = "/{0} {1} -- 召回一辆 {2},价格: {3}", ["HelpKill"] = "/{0} {1} -- 摧毁一辆 {2}", ["HelpKillCustom"] = "/{0} {1} 或者 /{2} -- 摧毁一辆 {3}", ["NotAllowed"] = "您没有权限使用该命令", ["RaidBlocked"] = "您被突袭阻止了,不能使用该命令", ["CombatBlocked"] = "您被战斗阻止了,不能使用该命令", ["OptionNotFound"] = "选项 {0} 不存在", ["VehiclePurchased"] = "您购买了 {0}, 输入 /{1} 了解更多信息", ["VehicleAlreadyPurchased"] = "您已经购买了 {0}", ["VehicleCannotBeBought"] = "{0} 是不可购买的", ["VehicleNotOut"] = "您还没有生成您的 {0}, 输入 /{1} 了解更多信息", ["AlreadyVehicleOut"] = "您已经生成了您的 {0}, 输入 /{1} 了解更多信息", ["VehicleNotYetPurchased"] = "您还没有购买 {0}, 输入 /{1} 了解更多信息", ["VehicleSpawned"] = "您生成了您的 {0}", ["VehicleRecalled"] = "您召回了您的 {0}", ["VehicleKilled"] = "您摧毁了您的 {0}", ["VehicleOnSpawnCooldown"] = "您必须等待 {0} 秒,才能生成您的 {1}", ["VehicleOnRecallCooldown"] = "您必须等待 {0} 秒,才能召回您的 {1}", ["VehicleOnSpawnCooldownPay"] = "您必须等待 {0} 秒,才能生成您的 {1}。你可以使用 /{2} 命令支付 {3} 来绕过这个冷却时间", ["VehicleOnRecallCooldownPay"] = "您必须等待 {0} 秒,才能召回您的 {1}。你可以使用 /{2} 命令支付 {3} 来绕过这个冷却时间", ["NotLookingAtWater"] = "您必须看着水面才能生成您的 {0}", ["BuildingBlocked"] = "您没有领地柜权限,无法生成您的 {0}", ["RefundedVehicleItems"] = "您的 {0} 载具物品已经归还回您的库存", ["PlayerMountedOnVehicle"] = "您的 {0} 上坐着玩家,无法被召回或摧毁", ["PlayerInSafeZone"] = "您不能在安全区域内生成或召回您的 {0}", ["VehicleInventoryDropped"] = "您的 {0} 载具物品不能召回,它已经掉落在地上了", ["NoResourcesToPurchaseVehicle"] = "您没有足够的资源购买 {0},还需要: \n{1}", ["NoResourcesToSpawnVehicle"] = "您没有足够的资源生成 {0},还需要: \n{1}", ["NoResourcesToSpawnVehicleBypass"] = "您没有足够的资源绕过冷却时间来生成 {0},还需要: \n{1}", ["NoResourcesToRecallVehicle"] = "您没有足够的资源召回 {0},还需要: \n{1}", ["NoResourcesToRecallVehicleBypass"] = "您没有足够的资源绕过冷却时间来召回 {0},还需要: \n{1}", ["MountedOrParented"] = "当您坐着或者在附着在实体上时无法生成或召回 {0}", ["RecallTooFar"] = "您必须在 {0} 米内才能召回您的 {1}", ["KillTooFar"] = "您必须在 {0} 米内才能摧毁您的 {1}", ["PlayersOnNearby"] = "您正在看着的位置附近有玩家时无法生成或召回 {0}", ["RecallWasBlocked"] = "有其他插件阻止您召回 {0}.", ["SpawnWasBlocked"] = "有其他插件阻止您生成 {0}.", ["VehiclesLimit"] = "您在同一时间内最多可以拥有 {0} 辆载具", ["TooFarTrainTrack"] = "您距离铁路轨道太远了", ["TooCloseTrainBarricadeOrWorkCart"] = "您距离铁轨障碍物或其它火车太近了", ["NotSpawnedOrRecalled"] = "由于某些原因,您的 {0} 载具无法生成或召回", ["CantUse"] = "您不能使用它,这个 {0} 属于 {1}" }, this, "zh-CN"); lang.RegisterMessages(new Dictionary < string, string > { ["Help"] = "Список доступных команд:", ["HelpLicence1"] = "/{0} -- Купить транспорт", ["HelpLicence2"] = "/{0} -- Создать транспорт", ["HelpLicence3"] = "/{0} -- Вызвать транспорт", ["HelpLicence4"] = "/{0} -- Уничтожить транспорт", ["HelpLicence5"] = "/{0} -- Купить, создать, или вызвать {1}", ["PriceFormat"] = "{0} x{1}", ["HelpBuy"] = "/{0} {1} -- Купить {2}.", ["HelpBuyPrice"] = "/{0} {1} -- Купить {2}. Цена: {3}", ["HelpSpawn"] = "/{0} {1} -- Создать {2}", ["HelpSpawnPrice"] = "/{0} {1} -- Вызывать {2}. Цена: {3}", ["HelpRecall"] = "/{0} {1} -- Вызвать {2}", ["HelpRecallPrice"] = "/{0} {1} -- Вызвать {2}. Цена: {3}", ["HelpKill"] = "/{0} {1} -- Уничтожить {2}", ["HelpKillCustom"] = "/{0} {1} или же /{2} -- Уничтожить {3}", ["NotAllowed"] = "У вас нет разрешения для использования данной команды.", ["RaidBlocked"] = "Вы не можете это сделать из-за блокировки (рейд).", ["CombatBlocked"] = "Вы не можете это сделать из-за блокировки (бой).", ["OptionNotFound"] = "Опция {0} не существует.", ["VehiclePurchased"] = "Вы приобрели {0}, напишите /{1} для получения дополнительной информации.", ["VehicleAlreadyPurchased"] = "Вы уже приобрели {0}.", ["VehicleCannotBeBought"] = "{0} приобрести невозможно", ["VehicleNotOut"] = "{0} отсутствует. Напишите /{1} для получения дополнительной информации.", ["AlreadyVehicleOut"] = "У вас уже есть {0}, напишите /{1} для получения дополнительной информации.", ["VehicleNotYetPurchased"] = "Вы ещё не приобрели {0}. Напишите /{1} для получения дополнительной информации.", ["VehicleSpawned"] = "Вы создали ваш {0}.", ["VehicleRecalled"] = "Вы вызвали ваш {0}.", ["VehicleKilled"] = "Вы уничтожили ваш {0}.", ["VehicleOnSpawnCooldown"] = "Вам необходимо подождать {0} секунд прежде, чем создать свой {1}.", ["VehicleOnRecallCooldown"] = "Вам необходимо подождать {0} секунд прежде, чем вызвать свой {1}.", ["VehicleOnSpawnCooldownPay"] = "Вам необходимо подождать {0} секунд прежде, чем создать свой {1}. Вы можете обойти это время восстановления, используя команду /{2}, чтобы заплатить {3}", ["VehicleOnRecallCooldownPay"] = "Вам необходимо подождать {0} секунд прежде, чем вызвать свой {1}. Вы можете обойти это время восстановления, используя команду /{2}, чтобы заплатить {3}", ["NotLookingAtWater"] = "Вы должны смотреть на воду, чтобы создать или вызвать {0}.", ["BuildingBlocked"] = "Вы не можете создать {0} если отсутствует право строительства.", ["RefundedVehicleItems"] = "Запчасти от вашего {0} были возвращены в ваш инвентарь.", ["PlayerMountedOnVehicle"] = "Нельзя вызвать, когда игрок находится в вашем {0}.", ["PlayerInSafeZone"] = "Вы не можете создать, или вызвать ваш {0} в безопасной зоне.", ["VehicleInventoryDropped"] = "Инвентарь из вашего {0} не может быть вызван, он выброшен на землю.", ["NoResourcesToPurchaseVehicle"] = "У вас недостаточно ресурсов для покупки {0}. Вам не хватает: \n{1}", ["NoResourcesToSpawnVehicle"] = "У вас недостаточно ресурсов для покупки {0}. Вам не хватает: \n{1}", ["NoResourcesToSpawnVehicleBypass"] = "У вас недостаточно ресурсов для покупки {0}. Вам не хватает: \n{1}", ["NoResourcesToRecallVehicle"] = "У вас недостаточно ресурсов для покупки {0}. Вам не хватает: \n{1}", ["NoResourcesToRecallVehicleBypass"] = "У вас недостаточно ресурсов для покупки {0}. Вам не хватает: \n{1}", ["MountedOrParented"] = "Вы не можете создать {0} когда сидите или привязаны к объекту.", ["RecallTooFar"] = "Вы должны быть в пределах {0} метров от {1}, чтобы вызывать.", ["KillTooFar"] = "Вы должны быть в пределах {0} метров от {1}, уничтожить.", ["PlayersOnNearby"] = "Вы не можете создать {0} когда рядом с той позицией, на которую вы смотрите, есть игроки.", ["RecallWasBlocked"] = "Внешний плагин заблокировал вам вызвать {0}.", ["SpawnWasBlocked"] = "Внешний плагин заблокировал вам создать {0}.", ["VehiclesLimit"] = "У вас может быть до {0} автомобилей одновременно", ["TooFarTrainTrack"] = "Вы слишком далеко от железнодорожных путей", ["TooCloseTrainBarricadeOrWorkCart"] = "Вы слишком близко к железнодорожной баррикаде или рабочей тележке", ["NotSpawnedOrRecalled"] = "По какой-то причине ваш {0} автомобилей не был вызван / отозван", ["CantUse"] = "Простите! Этот {0} принадлежит {1}. Вы не можете его использовать." }, this, "ru"); } #endregion LanguageFile } }