· 4 years ago · Jun 08, 2021, 01:32 PM
1//#define DEBUG
2
3using System;
4using System.Collections.Generic;
5using System.Linq;
6using System.Reflection;
7using System.Text;
8using Facepunch;
9using Network;
10using Newtonsoft.Json;
11using Newtonsoft.Json.Converters;
12using Oxide.Core;
13using Oxide.Core.Plugins;
14using Oxide.Game.Rust;
15using Rust.Modular;
16using UnityEngine;
17
18namespace Oxide.Plugins
19{
20 [Info("Vehicle Licence", "Sorrow/TheDoc/Arainrr", "1.7.16")]
21 [Description("Allows players to buy vehicles and then spawn or store it")]
22 public class VehicleLicence : RustPlugin
23 {
24 #region Fields
25
26 [PluginReference] private readonly Plugin Economics, ServerRewards, Friends, Clans, NoEscape, LandOnCargoShip, RustTranslationAPI;
27
28 private const string PERMISSION_USE = "vehiclelicence.use";
29 private const string PERMISSION_ALL = "vehiclelicence.all";
30 private const string PERMISSION_BYPASS_COST = "vehiclelicence.bypasscost";
31
32 private const int ITEMID_FUEL = -946369541;
33 private const string PREFAB_ITEM_DROP = "assets/prefabs/misc/item drop/item_drop.prefab";
34
35 private const string PREFAB_ROWBOAT = "assets/content/vehicles/boats/rowboat/rowboat.prefab";
36 private const string PREFAB_RHIB = "assets/content/vehicles/boats/rhib/rhib.prefab";
37 private const string PREFAB_SEDAN = "assets/content/vehicles/sedan_a/sedantest.entity.prefab";
38 private const string PREFAB_HOTAIRBALLOON = "assets/prefabs/deployable/hot air balloon/hotairballoon.prefab";
39 private const string PREFAB_MINICOPTER = "assets/content/vehicles/minicopter/minicopter.entity.prefab";
40 private const string PREFAB_TRANSPORTCOPTER = "assets/content/vehicles/scrap heli carrier/scraptransporthelicopter.prefab";
41 private const string PREFAB_CHINOOK = "assets/prefabs/npc/ch47/ch47.entity.prefab";
42 private const string PREFAB_RIDABLEHORSE = "assets/rust.ai/nextai/testridablehorse.prefab";
43 private const string PREFAB_WORKCART = "assets/content/vehicles/workcart/workcart.entity.prefab";
44 private const string PREFAB_MAGNET_CRANE = "assets/content/vehicles/crane_magnet/magnetcrane.entity.prefab";
45
46 private const string PREFAB_CHASSIS_SMALL = "assets/content/vehicles/modularcar/car_chassis_2module.entity.prefab";
47 private const string PREFAB_CHASSIS_MEDIUM = "assets/content/vehicles/modularcar/car_chassis_3module.entity.prefab";
48 private const string PREFAB_CHASSIS_LARGE = "assets/content/vehicles/modularcar/car_chassis_4module.entity.prefab";
49
50 private const int LAYER_GROUND = Rust.Layers.Solid | Rust.Layers.Mask.Water;
51
52 private Timer checkVehiclesTimer;
53 private static object False = false;
54 public static VehicleLicence Instance { get; private set; }
55 public readonly Dictionary<BaseEntity, Vehicle> vehiclesCache = new Dictionary<BaseEntity, Vehicle>();
56 public readonly Dictionary<string, BaseVehicleS> allBaseVehicleSettings = new Dictionary<string, BaseVehicleS>();
57 public readonly Dictionary<string, string> commandToVehicleType = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
58
59 public enum NormalVehicleType
60 {
61 Rowboat,
62 RHIB,
63 Sedan,
64 HotAirBalloon,
65 MiniCopter,
66 TransportHelicopter,
67 Chinook,
68 RidableHorse,
69 WorkCart,
70 MagnetCrane,
71 }
72
73 public enum ChassisType
74 {
75 Small,
76 Medium,
77 Large
78 }
79
80 #endregion Fields
81
82 #region Oxide Hooks
83
84 private void Init()
85 {
86 LoadData();
87 Instance = this;
88 permission.RegisterPermission(PERMISSION_USE, this);
89 permission.RegisterPermission(PERMISSION_ALL, this);
90 permission.RegisterPermission(PERMISSION_BYPASS_COST, this);
91
92 foreach (NormalVehicleType value in Enum.GetValues(typeof(NormalVehicleType)))
93 {
94 allBaseVehicleSettings.Add(value.ToString(), GetBaseVehicleS(value));
95 }
96 foreach (var entry in configData.modularCarS)
97 {
98 allBaseVehicleSettings.Add(entry.Key, entry.Value);
99 }
100
101 foreach (var entry in allBaseVehicleSettings)
102 {
103 var baseVehicleS = entry.Value;
104 if (baseVehicleS.usePermission && !string.IsNullOrEmpty(baseVehicleS.permission) &&
105 !permission.PermissionExists(baseVehicleS.permission, this))
106 {
107 permission.RegisterPermission(baseVehicleS.permission, this);
108 }
109
110 foreach (var perm in baseVehicleS.cooldownPermissions.Keys)
111 {
112 if (!permission.PermissionExists(perm, this))
113 {
114 permission.RegisterPermission(perm, this);
115 }
116 }
117 foreach (var command in baseVehicleS.commands)
118 {
119 if (string.IsNullOrEmpty(command)) continue;
120 if (!commandToVehicleType.ContainsKey(command))
121 {
122 commandToVehicleType.Add(command, entry.Key);
123 }
124 else
125 {
126 PrintError($"You have the same two commands({command}).");
127 }
128 if (configData.chatS.useUniversalCommand)
129 {
130 cmd.AddChatCommand(command, this, nameof(CmdUniversal));
131 }
132
133 if (!string.IsNullOrEmpty(configData.chatS.customKillCommandPrefix))
134 {
135 cmd.AddChatCommand(configData.chatS.customKillCommandPrefix + command, this, nameof(CmdCustomKill));
136 }
137 }
138 }
139
140 cmd.AddChatCommand(configData.chatS.helpCommand, this, nameof(CmdLicenseHelp));
141 cmd.AddChatCommand(configData.chatS.buyCommand, this, nameof(CmdBuyVehicle));
142 cmd.AddChatCommand(configData.chatS.spawnCommand, this, nameof(CmdSpawnVehicle));
143 cmd.AddChatCommand(configData.chatS.recallCommand, this, nameof(CmdRecallVehicle));
144 cmd.AddChatCommand(configData.chatS.killCommand, this, nameof(CmdKillVehicle));
145 Unsubscribe(nameof(CanMountEntity));
146 Unsubscribe(nameof(OnEntityTakeDamage));
147 Unsubscribe(nameof(OnEntityDismounted));
148 Unsubscribe(nameof(OnEntityEnter));
149 Unsubscribe(nameof(CanLootEntity));
150 Unsubscribe(nameof(OnEntitySpawned));
151 }
152
153 private void OnServerInitialized()
154 {
155 if (configData.globalS.storeVehicle)
156 {
157 var currentTimestamp = TimeEx.currentTimestamp;
158 foreach (var vehicleEntries in storedData.playerData)
159 {
160 foreach (var vehicleEntry in vehicleEntries.Value)
161 {
162 vehicleEntry.Value.lastRecall = vehicleEntry.Value.lastDismount = currentTimestamp;
163 vehicleEntry.Value.playerID = vehicleEntries.Key;
164 vehicleEntry.Value.vehicleType = vehicleEntry.Key;
165 if (vehicleEntry.Value.entityID == 0)
166 {
167 continue;
168 }
169 vehicleEntry.Value.entity = BaseNetworkable.serverEntities.Find(vehicleEntry.Value.entityID) as BaseEntity;
170 if (vehicleEntry.Value.entity == null || vehicleEntry.Value.entity.IsDestroyed)
171 {
172 vehicleEntry.Value.entityID = 0;
173 }
174 else
175 {
176 vehiclesCache.Add(vehicleEntry.Value.entity, vehicleEntry.Value);
177 }
178 }
179 }
180 }
181 if (configData.globalS.preventMounting)
182 {
183 Subscribe(nameof(CanMountEntity));
184 }
185 if (configData.globalS.noDecay)
186 {
187 Subscribe(nameof(OnEntityTakeDamage));
188 }
189 if (configData.globalS.preventDamagePlayer)
190 {
191 Subscribe(nameof(OnEntityEnter));
192 }
193 if (configData.globalS.preventLooting)
194 {
195 Subscribe(nameof(CanLootEntity));
196 }
197 if (configData.globalS.autoClaimFromVendor)
198 {
199 Subscribe(nameof(OnEntitySpawned));
200 Subscribe(nameof(OnRidableAnimalClaimed));
201 }
202 if (configData.globalS.checkVehiclesInterval > 0 && allBaseVehicleSettings.Any(x => x.Value.wipeTime > 0))
203 {
204 Subscribe(nameof(OnEntityDismounted));
205 checkVehiclesTimer = timer.Every(configData.globalS.checkVehiclesInterval, CheckVehicles);
206 }
207 }
208
209 private void Unload()
210 {
211 checkVehiclesTimer?.Destroy();
212 if (!configData.globalS.storeVehicle)
213 {
214 foreach (var entry in vehiclesCache.ToArray())
215 {
216 if (entry.Key != null && !entry.Key.IsDestroyed)
217 {
218 RefundVehicleItems(entry.Value, entry.Key, isUnload: true);
219 entry.Key.Kill(BaseNetworkable.DestroyMode.Gib);
220 }
221 entry.Value.entityID = 0;
222 }
223 }
224 SaveData();
225 False = Instance = null;
226 }
227
228 private void OnServerSave() => timer.Once(UnityEngine.Random.Range(0f, 60f), SaveData);
229
230 private void OnPlayerConnected(BasePlayer player)
231 {
232 if (player == null || !player.userID.IsSteamId()) return;
233 if (permission.UserHasPermission(player.UserIDString, PERMISSION_BYPASS_COST))
234 {
235 PurchaseAllVehicles(player.userID);
236 }
237 }
238
239 private void OnEntityDismounted(BaseMountable entity, BasePlayer player)
240 {
241 var vehicleParent = entity?.VehicleParent();
242 if (vehicleParent == null || vehicleParent.IsDestroyed) return;
243 Vehicle vehicle;
244 if (!vehiclesCache.TryGetValue(vehicleParent, out vehicle)) return;
245 vehicle.OnDismount();
246 }
247
248 #region Mount
249
250 private object CanMountEntity(BasePlayer friend, BaseMountable entity)
251 {
252 var vehicleParent = entity?.VehicleParent();
253 if (vehicleParent == null || vehicleParent.IsDestroyed) return null;
254 Vehicle vehicle;
255 if (!vehiclesCache.TryGetValue(vehicleParent, out vehicle)) return null;
256 if (AreFriends(vehicle.playerID, friend.userID)) return null;
257 if (configData.globalS.preventDriverSeat && vehicleParent.HasMountPoints() &&
258 entity != vehicleParent.mountPoints[0].mountable) return null;
259
260 SendCantUseMessage(friend, vehicle);
261 return False;
262 }
263
264 #endregion Mount
265
266 #region Loot
267
268 private object CanLootEntity(BasePlayer friend, StorageContainer container)
269 {
270 if (friend == null || container == null) return null;
271 var parentEntity = container.GetParentEntity();
272 if (parentEntity == null) return null;
273 Vehicle vehicle;
274 if (!vehiclesCache.TryGetValue(parentEntity, out vehicle))
275 {
276 var vehicleParent = (parentEntity as BaseVehicleModule)?.Vehicle;
277 if (vehicleParent == null || !vehiclesCache.TryGetValue(vehicleParent, out vehicle))
278 {
279 return null;
280 }
281 }
282
283 if (AreFriends(vehicle.playerID, friend.userID))
284 {
285 return null;
286 }
287
288 SendCantUseMessage(friend, vehicle);
289 return False;
290 }
291 #endregion Loot
292
293 #region Claime
294
295 private void OnEntitySpawned(MotorRowboat motorRowboat)
296 {
297 NextTick(() =>
298 {
299 var player = motorRowboat?.creatorEntity as BasePlayer;
300 if (player == null || !player.userID.IsSteamId() || !motorRowboat.OnlyOwnerAccessible()) return;
301 TryClaimVehicle(player, motorRowboat, motorRowboat is RHIB ? NormalVehicleType.RHIB : NormalVehicleType.Rowboat);
302 });
303 }
304
305 private void OnEntitySpawned(MiniCopter miniCopter)
306 {
307 NextTick(() =>
308 {
309 var player = miniCopter?.creatorEntity as BasePlayer;
310 if (player == null || !player.userID.IsSteamId() || !miniCopter.OnlyOwnerAccessible()) return;
311 TryClaimVehicle(player, miniCopter, miniCopter is ScrapTransportHelicopter ? NormalVehicleType.TransportHelicopter : NormalVehicleType.MiniCopter);
312 });
313 }
314
315 private void OnRidableAnimalClaimed(BaseRidableAnimal baseRidableAnimal, BasePlayer player)
316 {
317 if (player == null || !player.userID.IsSteamId()) return;
318 TryClaimVehicle(player, baseRidableAnimal, NormalVehicleType.RidableHorse);
319 }
320
321 #endregion Claime
322
323 #region Decay
324
325 private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
326 {
327 if (entity == null | hitInfo?.damageTypes == null) return;
328 if (hitInfo.damageTypes.Has(Rust.DamageType.Decay))
329 {
330 if (!vehiclesCache.ContainsKey(entity))
331 {
332 var vehicle = (entity as BaseVehicleModule)?.Vehicle;
333 if (vehicle == null || !vehiclesCache.ContainsKey(vehicle))
334 {
335 return;
336 }
337 }
338 hitInfo.damageTypes.Scale(Rust.DamageType.Decay, 0);
339 }
340 }
341
342 #endregion Decay
343
344 #region Damage
345
346 // ScrapTransportHelicopter / ModularCar / TrainEngine / MagnetCrane
347 private object OnEntityEnter(TriggerHurtNotChild triggerHurtNotChild, BasePlayer player)
348 {
349 if (triggerHurtNotChild == null || triggerHurtNotChild.SourceEntity == null || player == null) return null;
350 var sourceEntity = triggerHurtNotChild.SourceEntity;
351 if (vehiclesCache.ContainsKey(sourceEntity))
352 {
353 var baseVehicle = sourceEntity as BaseVehicle;
354 if (baseVehicle != null && player.userID.IsSteamId())
355 {
356 if (baseVehicle is TrainEngine)
357 {
358 var transform = triggerHurtNotChild.transform;
359 MoveToPosition(player, transform.position + (UnityEngine.Random.value >= 0.5f ? -transform.right : transform.right) * 2.5f);
360 return False;
361 }
362 Vector3 pos;
363 if (GetDismountPosition(baseVehicle, player, out pos))
364 {
365 MoveToPosition(player, pos);
366 }
367 }
368 //triggerHurtNotChild.enabled = false;
369 return False;
370 }
371 return null;
372 }
373
374 // HotAirBalloon
375 private object OnEntityEnter(TriggerHurt triggerHurt, BasePlayer player)
376 {
377 if (triggerHurt == null || player == null) return null;
378 var sourceEntity = triggerHurt.gameObject.ToBaseEntity();
379 if (sourceEntity == null) return null;
380 if (vehiclesCache.ContainsKey(sourceEntity))
381 {
382 if (player.userID.IsSteamId())
383 {
384 MoveToPosition(player, sourceEntity.CenterPoint() + Vector3.down);
385 }
386 //triggerHurt.enabled = false;
387 return False;
388 }
389 return null;
390 }
391
392 #endregion Damage
393
394 private void OnEntityDeath(BaseCombatEntity entity, HitInfo info) => CheckEntity(entity, true);
395
396 private void OnEntityKill(BaseCombatEntity entity) => CheckEntity(entity);
397
398 #endregion Oxide Hooks
399
400 #region Methods
401 private void SendCantUseMessage(BasePlayer friend, Vehicle vehicle)
402 {
403 var baseVehicleS = GetBaseVehicleS(vehicle.vehicleType);
404 if (baseVehicleS != null)
405 {
406 var player = RustCore.FindPlayerById(vehicle.playerID);
407 var playerName = player?.displayName ?? ServerMgr.Instance.persistance.GetPlayerName(vehicle.playerID) ?? "Unknown";
408 Print(friend, Lang("CantUse", friend.UserIDString, baseVehicleS.displayName, $"<color=#{(player != null && player.IsConnected ? "69D214" : "FF6347")}>{playerName}</color>"));
409 }
410 }
411
412 #region RustTranslationAPI
413
414 private string GetItemTranslationByShortName(string language, string itemShortName) => (string)RustTranslationAPI.Call("GetItemTranslationByShortName", language, itemShortName);
415
416 private string GetItemDisplayName(string language, string itemShortName, string displayName)
417 {
418 if (RustTranslationAPI != null)
419 {
420 var displayName1 = GetItemTranslationByShortName(language, itemShortName);
421 if (!string.IsNullOrEmpty(displayName1))
422 {
423 return displayName1;
424 }
425 }
426 return displayName;
427 }
428
429 #endregion RustTranslationAPI
430
431
432 #region CheckEntity
433
434 private void CheckEntity(BaseCombatEntity entity, bool isCrash = false)
435 {
436 if (entity == null) return;
437 Vehicle vehicle;
438 if (!vehiclesCache.TryGetValue(entity, out vehicle)) return;
439 vehiclesCache.Remove(entity);
440 vehicle.OnDeath();
441 var baseVehicleS = GetBaseVehicleS(vehicle.vehicleType);
442 RefundVehicleItems(vehicle, entity, isCrash: isCrash, baseVehicleS: baseVehicleS);
443 if (isCrash && baseVehicleS.removeLicenseOnceCrash)
444 {
445 RemoveVehicleLicense(vehicle.playerID, vehicle.vehicleType);
446 }
447 }
448
449 #endregion CheckEntity
450
451 #region CheckVehicles
452
453 private void CheckVehicles()
454 {
455 var currentTimestamp = TimeEx.currentTimestamp;
456 foreach (var entry in vehiclesCache.ToArray())
457 {
458 if (entry.Key == null || entry.Key.IsDestroyed) continue;
459 if (VehicleIsActive(entry.Value, currentTimestamp)) continue;
460 if (VehicleAnyMounted(entry.Key)) continue;
461 entry.Key.Kill(BaseNetworkable.DestroyMode.Gib);
462 }
463 }
464
465 private bool VehicleIsActive(Vehicle vehicle, double currentTimestamp)
466 {
467 var baseVehicleS = GetBaseVehicleS(vehicle.vehicleType);
468 if (baseVehicleS.wipeTime <= 0) return true;
469 return currentTimestamp - vehicle.lastDismount < baseVehicleS.wipeTime;
470 }
471
472 #endregion CheckVehicles
473
474 #region Refund
475
476 private static bool CanRefundFuel(BaseVehicleS baseVehicleS, bool isCrash, bool isUnload)
477 {
478 if (isUnload) return true;
479 var fuelVehicleS = baseVehicleS as IFuelVehicle;
480 return fuelVehicleS != null && (isCrash ? fuelVehicleS.refundFuelOnCrash : fuelVehicleS.refundFuelOnKill);
481 }
482
483 private static bool CanRefundInventory(BaseVehicleS baseVehicleS, bool isCrash, bool isUnload)
484 {
485 if (isUnload) return true;
486 var inventoryVehicleS = baseVehicleS as IInventoryVehicle;
487 return inventoryVehicleS != null && (isCrash ? inventoryVehicleS.refundInventoryOnCrash : inventoryVehicleS.refundInventoryOnKill);
488 }
489
490 private static void ModularCarRefundState(BaseVehicleS baseVehicleS, bool isCrash, bool isUnload, out bool refundFuel, out bool refundInventory, out bool refundEngine, out bool refundModule)
491 {
492 if (isUnload)
493 {
494 refundFuel = refundInventory = refundEngine = refundModule = true;
495 return;
496 }
497 var modularVehicleS = baseVehicleS as ModularVehicleS;
498 if (modularVehicleS == null)
499 {
500 refundFuel = refundInventory = refundEngine = refundModule = false;
501 return;
502 }
503 refundFuel = isCrash ? modularVehicleS.refundFuelOnCrash : modularVehicleS.refundFuelOnKill;
504 refundInventory = isCrash ? modularVehicleS.refundInventoryOnCrash : modularVehicleS.refundInventoryOnKill;
505 refundEngine = isCrash ? modularVehicleS.refundEngineOnCrash : modularVehicleS.refundEngineOnKill;
506 refundModule = isCrash ? modularVehicleS.refundModuleOnCrash : modularVehicleS.refundModuleOnKill;
507 }
508
509 private void RefundVehicleItems(Vehicle vehicle, BaseEntity entity, BaseVehicleS baseVehicleS = null, bool isCrash = false, bool isUnload = false)
510 {
511 if (entity == null) entity = vehicle.entity;
512 if (entity == null) return;
513 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicle.vehicleType);
514 if (baseVehicleS == null) return;
515
516 NormalVehicleType normalVehicleType;
517 var collect = Pool.GetList<Item>();
518 if (Enum.TryParse(vehicle.vehicleType, out normalVehicleType))
519 {
520 switch (normalVehicleType)
521 {
522 case NormalVehicleType.Sedan:
523 case NormalVehicleType.Chinook:
524 return;
525
526 case NormalVehicleType.MiniCopter:
527 case NormalVehicleType.TransportHelicopter:
528 {
529 if (CanRefundFuel(baseVehicleS, isCrash, isUnload))
530 {
531 var fuelContainer = (entity as MiniCopter)?.GetFuelSystem()?.GetFuelContainer()?.inventory;
532 if (fuelContainer != null) collect.AddRange(fuelContainer.itemList);
533 }
534 }
535 break;
536
537 case NormalVehicleType.HotAirBalloon:
538 {
539 if (CanRefundFuel(baseVehicleS, isCrash, isUnload))
540 {
541 var fuelContainer = (entity as HotAirBalloon)?.fuelSystem?.GetFuelContainer()?.inventory;
542 if (fuelContainer != null) collect.AddRange(fuelContainer.itemList);
543 }
544 if (CanRefundInventory(baseVehicleS, isCrash, isUnload))
545 {
546 var itemContainer = ((entity as HotAirBalloon)?.storageUnitInstance.Get(true) as StorageContainer)?.inventory;
547 if (itemContainer != null) collect.AddRange(itemContainer.itemList);
548 }
549 }
550 break;
551
552 case NormalVehicleType.RHIB:
553 case NormalVehicleType.Rowboat:
554 {
555 if (CanRefundFuel(baseVehicleS, isCrash, isUnload))
556 {
557 var fuelContainer = (entity as MotorRowboat)?.GetFuelSystem()?.GetFuelContainer()?.inventory;
558 if (fuelContainer != null) collect.AddRange(fuelContainer.itemList);
559 }
560
561 if (CanRefundInventory(baseVehicleS, isCrash, isUnload))
562 {
563 var itemContainer = ((entity as MotorRowboat)?.storageUnitInstance.Get(true) as StorageContainer)?.inventory;
564 if (itemContainer != null) collect.AddRange(itemContainer.itemList);
565 }
566 }
567 break;
568
569 case NormalVehicleType.RidableHorse:
570 {
571 if (CanRefundInventory(baseVehicleS, isCrash, isUnload))
572 {
573 var itemContainer = (entity as RidableHorse)?.inventory;
574 if (itemContainer != null) collect.AddRange(itemContainer.itemList);
575 }
576 }
577 break;
578
579 case NormalVehicleType.WorkCart:
580 {
581 if (CanRefundFuel(baseVehicleS, isCrash, isUnload))
582 {
583 var fuelContainer = (entity as TrainEngine)?.fuelSystem?.GetFuelContainer()?.inventory;
584 if (fuelContainer != null) collect.AddRange(fuelContainer.itemList);
585 }
586 }
587 break;
588
589 case NormalVehicleType.MagnetCrane:
590 {
591 if (CanRefundFuel(baseVehicleS, isCrash, isUnload))
592 {
593 var fuelContainer = (entity as BaseCrane)?.fuelSystem?.GetFuelContainer()?.inventory;
594 if (fuelContainer != null) collect.AddRange(fuelContainer.itemList);
595 }
596 }
597 break;
598
599 default: return;
600 }
601 }
602 else
603 {
604 var modularCar = entity as ModularCar;
605 if (modularCar == null) return;
606
607 bool refundFuel, refundInventory, refundEngine, refundModule;
608 ModularCarRefundState(baseVehicleS, isCrash, isUnload, out refundFuel, out refundInventory, out refundEngine, out refundModule);
609
610 foreach (var moduleEntity in modularCar.AttachedModuleEntities)
611 {
612 if (refundEngine)
613 {
614 var moduleEngine = moduleEntity as VehicleModuleEngine;
615 if (moduleEngine != null)
616 {
617 var engineContainer = moduleEngine.GetContainer()?.inventory;
618 if (engineContainer != null) collect.AddRange(engineContainer.itemList);
619 continue;
620 }
621 }
622 if (refundInventory)
623 {
624 var moduleStorage = moduleEntity as VehicleModuleStorage;
625 if (moduleStorage != null)
626 {
627 var storageContainer = moduleStorage.GetContainer()?.inventory;
628 if (storageContainer != null) collect.AddRange(storageContainer.itemList);
629 }
630 }
631 }
632 if (refundFuel)
633 {
634 var fuelContainer = modularCar.fuelSystem?.GetFuelContainer()?.inventory;
635 if (fuelContainer != null) collect.AddRange(fuelContainer.itemList);
636 }
637 if (refundModule)
638 {
639 var moduleContainer = modularCar.Inventory?.ModuleContainer;
640 if (moduleContainer != null) collect.AddRange(moduleContainer.itemList);
641 }
642 //var chassisContainer = modularCar.Inventory?.ChassisContainer;
643 //if (chassisContainer != null)
644 //{
645 // collect.AddRange(chassisContainer.itemList);
646 //}
647 }
648
649 if (collect.Count > 0)
650 {
651 var player = RustCore.FindPlayerById(vehicle.playerID);
652 if (player == null)
653 {
654 DropItemContainer(entity, vehicle.playerID, collect);
655 }
656 else
657 {
658 for (var i = collect.Count - 1; i >= 0; i--)
659 {
660 var item = collect[i];
661 player.GiveItem(item);
662 }
663
664 if (player.IsConnected)
665 {
666 Print(player, Lang("RefundedVehicleItems", player.UserIDString, baseVehicleS.displayName));
667 }
668 }
669 }
670 Pool.FreeList(ref collect);
671 }
672
673 private static void DropItemContainer(BaseEntity entity, ulong playerID, List<Item> collect)
674 {
675 var droppedItemContainer = GameManager.server.CreateEntity(PREFAB_ITEM_DROP, entity.GetDropPosition(), entity.transform.rotation) as DroppedItemContainer;
676 droppedItemContainer.inventory = new ItemContainer();
677 droppedItemContainer.inventory.ServerInitialize(null, Mathf.Min(collect.Count, droppedItemContainer.maxItemCount));
678 droppedItemContainer.inventory.GiveUID();
679 droppedItemContainer.inventory.entityOwner = droppedItemContainer;
680 droppedItemContainer.inventory.SetFlag(ItemContainer.Flag.NoItemInput, true);
681 for (var i = collect.Count - 1; i >= 0; i--)
682 {
683 var item = collect[i];
684 if (!item.MoveToContainer(droppedItemContainer.inventory))
685 {
686 item.DropAndTossUpwards(droppedItemContainer.transform.position);
687 }
688 }
689
690 droppedItemContainer.OwnerID = playerID;
691 droppedItemContainer.Spawn();
692 }
693
694 #endregion Refund
695
696 #region GiveFuel
697
698 private static void TryGiveFuel(BaseEntity entity, string vehicleType, IFuelVehicle iFuelVehicle)
699 {
700 if (iFuelVehicle == null || iFuelVehicle.spawnFuelAmount <= 0) return;
701 ItemContainer fuelContainer;
702 NormalVehicleType normalVehicleType;
703 if (Enum.TryParse(vehicleType, out normalVehicleType))
704 {
705 switch (normalVehicleType)
706 {
707 case NormalVehicleType.Sedan:
708 case NormalVehicleType.Chinook:
709 case NormalVehicleType.RidableHorse:
710 return;
711
712 case NormalVehicleType.MiniCopter:
713 case NormalVehicleType.TransportHelicopter:
714 fuelContainer = (entity as MiniCopter)?.GetFuelSystem()?.GetFuelContainer()?.inventory;
715 break;
716
717 case NormalVehicleType.HotAirBalloon:
718 fuelContainer = (entity as HotAirBalloon)?.fuelSystem?.GetFuelContainer()?.inventory;
719 break;
720
721 case NormalVehicleType.RHIB:
722 case NormalVehicleType.Rowboat:
723 fuelContainer = (entity as MotorRowboat)?.GetFuelSystem()?.GetFuelContainer()?.inventory;
724 break;
725
726 case NormalVehicleType.WorkCart:
727 fuelContainer = (entity as TrainEngine)?.fuelSystem?.GetFuelContainer()?.inventory;
728 break;
729
730 case NormalVehicleType.MagnetCrane:
731 fuelContainer = (entity as BaseCrane)?.fuelSystem?.GetFuelContainer()?.inventory;
732 break;
733
734 default: return;
735 }
736 }
737 else
738 {
739 fuelContainer = (entity as ModularCar)?.fuelSystem?.GetFuelContainer()?.inventory;
740 }
741
742 if (fuelContainer == null /*|| fuelContainer.FindItemByItemID(ITEMID_FUEL) != null*/) return;
743 var fuel = ItemManager.CreateByItemID(ITEMID_FUEL, iFuelVehicle.spawnFuelAmount);
744 if (!fuel.MoveToContainer(fuelContainer))
745 {
746 fuel.Remove();
747 }
748 }
749
750 #endregion GiveFuel
751
752 #region VehicleModules
753
754 private void AttacheVehicleModules(ModularCar modularCar, ModularVehicleS modularVehicleS, string vehicleType)
755 {
756 foreach (var moduleItem in modularVehicleS.CreateModuleItems())
757 {
758 if (!modularCar.TryAddModule(moduleItem))
759 {
760 PrintError($"Module item '{moduleItem.info.shortname}' in '{vehicleType}' cannot be attached to the vehicle");
761 moduleItem.Remove();
762 }
763 }
764 }
765
766 private void AddItemsToVehicleEngine(ModularCar modularCar, ModularVehicleS modularVehicleS, string vehicleType)
767 {
768 if (modularCar == null || modularCar.IsDestroyed) return;
769 foreach (var moduleEntity in modularCar.AttachedModuleEntities)
770 {
771 var vehicleModuleEngine = moduleEntity as VehicleModuleEngine;
772 if (vehicleModuleEngine != null)
773 {
774 var engineInventory = vehicleModuleEngine.GetContainer()?.inventory;
775 if (engineInventory != null)
776 {
777 foreach (var engineItem in modularVehicleS.CreateEngineItems())
778 {
779 bool moved = false;
780 for (int i = 0; i < engineInventory.capacity; i++)
781 {
782 if (engineItem.MoveToContainer(engineInventory, i, false))
783 {
784 moved = true;
785 break;
786 }
787 }
788 if (!moved)
789 {
790 PrintError($"Engine item '{engineItem.info.shortname}' in '{vehicleType}' cannot be move to the vehicle engine inventory");
791 engineItem.Remove();
792 }
793 }
794 }
795 }
796 }
797 }
798
799 #endregion VehicleModules
800
801 #region Drop
802
803 private static bool CanDropInventory(BaseVehicleS baseVehicleS)
804 {
805 var inventoryVehicle = baseVehicleS as IInventoryVehicle;
806 return inventoryVehicle != null && inventoryVehicle.dropInventoryOnRecall;
807 }
808
809 private void DropVehicleInventoryItems(BasePlayer player, string vehicleType, BaseEntity entity, BaseVehicleS baseVehicleS)
810 {
811 DroppedItemContainer droppedItemContainer = null;
812 NormalVehicleType normalVehicleType;
813 if (Enum.TryParse(vehicleType, out normalVehicleType))
814 {
815 switch (normalVehicleType)
816 {
817 case NormalVehicleType.Sedan:
818 case NormalVehicleType.MiniCopter:
819 case NormalVehicleType.TransportHelicopter:
820 case NormalVehicleType.Chinook:
821 case NormalVehicleType.WorkCart:
822 case NormalVehicleType.MagnetCrane:
823 return;
824
825 case NormalVehicleType.Rowboat:
826 case NormalVehicleType.RHIB:
827 {
828 var storageContainer = (entity as MotorRowboat)?.storageUnitInstance.Get(true) as StorageContainer;
829 droppedItemContainer = storageContainer?.inventory?.Drop(PREFAB_ITEM_DROP, entity.GetDropPosition(),
830 entity.transform.rotation);
831 }
832 break;
833
834 case NormalVehicleType.HotAirBalloon:
835 {
836 var storageContainer = (entity as HotAirBalloon)?.storageUnitInstance.Get(true) as StorageContainer;
837 droppedItemContainer = storageContainer?.inventory?.Drop(PREFAB_ITEM_DROP, entity.GetDropPosition(),
838 entity.transform.rotation);
839 }
840 break;
841
842 case NormalVehicleType.RidableHorse:
843 {
844 droppedItemContainer = (entity as RidableHorse)?.inventory?.Drop(PREFAB_ITEM_DROP, entity.GetDropPosition(),
845 entity.transform.rotation);
846 }
847 break;
848 }
849 }
850 else
851 {
852 var modularCar = entity as ModularCar;
853 if (modularCar == null) return;
854 foreach (var moduleEntity in modularCar.AttachedModuleEntities)
855 {
856 if (moduleEntity is VehicleModuleEngine) continue;
857 var moduleStorage = moduleEntity as VehicleModuleStorage;
858 if (moduleStorage != null)
859 {
860 droppedItemContainer = moduleStorage.GetContainer()?.inventory?.Drop(PREFAB_ITEM_DROP, entity.GetDropPosition(), entity.transform.rotation);
861 }
862 }
863 }
864 if (droppedItemContainer != null)
865 {
866 Print(player, Lang("VehicleInventoryDropped", player.UserIDString, baseVehicleS.displayName));
867 }
868 }
869
870 #endregion Drop
871
872 #region TryPay
873
874 private bool TryPay(BasePlayer player, Dictionary<string, PriceInfo> prices, out string missingResources)
875 {
876 if (permission.UserHasPermission(player.UserIDString, PERMISSION_BYPASS_COST))
877 {
878 missingResources = null;
879 return true;
880 }
881
882 if (!CanPay(player, prices, out missingResources))
883 {
884 return false;
885 }
886
887 var collect = Pool.GetList<Item>();
888 foreach (var entry in prices)
889 {
890 if (entry.Value.amount <= 0) continue;
891 var itemDefinition = ItemManager.FindItemDefinition(entry.Key);
892 if (itemDefinition != null)
893 {
894 player.inventory.Take(collect, itemDefinition.itemid, entry.Value.amount);
895 player.Command("note.inv", itemDefinition.itemid, -entry.Value.amount);
896 continue;
897 }
898 switch (entry.Key.ToLower())
899 {
900 case "economics":
901 Economics?.Call("Withdraw", player.userID, (double)entry.Value.amount);
902 continue;
903
904 case "serverrewards":
905 ServerRewards?.Call("TakePoints", player.userID, entry.Value.amount);
906 continue;
907 }
908 }
909
910 foreach (var item in collect)
911 {
912 item.Remove();
913 }
914 Pool.FreeList(ref collect);
915 missingResources = null;
916 return true;
917 }
918
919 private readonly Hash<string, int> missingDictionary = new Hash<string, int>();
920
921 private bool CanPay(BasePlayer player, Dictionary<string, PriceInfo> prices, out string missingResources)
922 {
923 missingDictionary.Clear();
924 var language = RustTranslationAPI != null ? lang.GetLanguage(player.UserIDString) : null;
925 foreach (var entry in prices)
926 {
927 if (entry.Value.amount <= 0) continue;
928 int missingAmount;
929 var itemDefinition = ItemManager.FindItemDefinition(entry.Key);
930 if (itemDefinition != null) missingAmount = entry.Value.amount - player.inventory.GetAmount(itemDefinition.itemid);
931 else missingAmount = CheckBalance(entry.Key, entry.Value.amount, player.userID);
932 if (missingAmount <= 0) continue;
933 var displayName = GetItemDisplayName(language, entry.Key, entry.Value.displayName);
934 missingDictionary[displayName] += missingAmount;
935 }
936 if (missingDictionary.Count > 0)
937 {
938 StringBuilder stringBuilder = Pool.Get<StringBuilder>();
939 foreach (var entry in missingDictionary)
940 {
941 stringBuilder.AppendLine($"* {Lang("PriceFormat", player.UserIDString, entry.Key, entry.Value)}");
942 }
943 missingResources = stringBuilder.ToString();
944 stringBuilder.Clear();
945 Pool.Free(ref stringBuilder);
946 return false;
947 }
948 missingResources = null;
949 return true;
950 }
951
952 private int CheckBalance(string key, int price, ulong playerID)
953 {
954 switch (key.ToLower())
955 {
956 case "economics":
957 var balance = Economics?.Call("Balance", playerID);
958 if (balance is double)
959 {
960 var n = price - (double)balance;
961 return n <= 0 ? 0 : (int)Math.Ceiling(n);
962 }
963 return price;
964
965 case "serverrewards":
966 var points = ServerRewards?.Call("CheckPoints", playerID);
967 if (points is int)
968 {
969 var n = price - (int)points;
970 return n <= 0 ? 0 : n;
971 }
972 return price;
973
974 default:
975 PrintError($"Unknown Currency Type '{key}'");
976 return price;
977 }
978 }
979
980 #endregion TryPay
981
982 #region AreFriends
983
984 private bool AreFriends(ulong playerID, ulong friendID)
985 {
986 if (playerID == friendID) return true;
987 if (configData.globalS.useTeams && SameTeam(playerID, friendID)) return true;
988 if (configData.globalS.useFriends && HasFriend(playerID, friendID)) return true;
989 if (configData.globalS.useClans && SameClan(playerID, friendID)) return true;
990 return false;
991 }
992
993 private static bool SameTeam(ulong playerID, ulong friendID)
994 {
995 if (!RelationshipManager.TeamsEnabled()) return false;
996 var playerTeam = RelationshipManager.ServerInstance.FindPlayersTeam(playerID);
997 if (playerTeam == null) return false;
998 var friendTeam = RelationshipManager.ServerInstance.FindPlayersTeam(friendID);
999 if (friendTeam == null) return false;
1000 return playerTeam == friendTeam;
1001 }
1002
1003 private bool HasFriend(ulong playerID, ulong friendID)
1004 {
1005 if (Friends == null) return false;
1006 return (bool)Friends.Call("HasFriend", playerID, friendID);
1007 }
1008
1009 private bool SameClan(ulong playerID, ulong friendID)
1010 {
1011 if (Clans == null) return false;
1012 //Clans
1013 var isMember = Clans.Call("IsClanMember", playerID.ToString(), friendID.ToString());
1014 if (isMember != null) return (bool)isMember;
1015 //Rust:IO Clans
1016 var playerClan = Clans.Call("GetClanOf", playerID);
1017 if (playerClan == null) return false;
1018 var friendClan = Clans.Call("GetClanOf", friendID);
1019 if (friendClan == null) return false;
1020 return (string)playerClan == (string)friendClan;
1021 }
1022
1023 #endregion AreFriends
1024
1025 #region PlayerIsBlocked
1026
1027 private bool IsPlayerBlocked(BasePlayer player)
1028 {
1029 if (NoEscape == null) return false;
1030 if (configData.globalS.useRaidBlocker && IsRaidBlocked(player.UserIDString))
1031 {
1032 Print(player, Lang("RaidBlocked", player.UserIDString));
1033 return true;
1034 }
1035 if (configData.globalS.useCombatBlocker && IsCombatBlocked(player.UserIDString))
1036 {
1037 Print(player, Lang("CombatBlocked", player.UserIDString));
1038 return true;
1039 }
1040 return false;
1041 }
1042
1043 private bool IsRaidBlocked(string playerID) => (bool)NoEscape.Call("IsRaidBlocked", playerID);
1044
1045 private bool IsCombatBlocked(string playerID) => (bool)NoEscape.Call("IsCombatBlocked", playerID);
1046
1047 #endregion PlayerIsBlocked
1048
1049 #region GetBaseVehicleS
1050
1051 private BaseVehicleS GetBaseVehicleS(string vehicleName)
1052 {
1053 BaseVehicleS baseVehicleS;
1054 return allBaseVehicleSettings.TryGetValue(vehicleName, out baseVehicleS) ? baseVehicleS : null;
1055 }
1056
1057 private BaseVehicleS GetBaseVehicleS(NormalVehicleType normalVehicleType)
1058 {
1059 switch (normalVehicleType)
1060 {
1061 case NormalVehicleType.Rowboat: return configData.normalVehicleS.rowboatS;
1062 case NormalVehicleType.RHIB: return configData.normalVehicleS.rhibS;
1063 case NormalVehicleType.Sedan: return configData.normalVehicleS.sedanS;
1064 case NormalVehicleType.HotAirBalloon: return configData.normalVehicleS.hotAirBalloonS;
1065 case NormalVehicleType.MiniCopter: return configData.normalVehicleS.miniCopterS;
1066 case NormalVehicleType.TransportHelicopter: return configData.normalVehicleS.transportHelicopterS;
1067 case NormalVehicleType.Chinook: return configData.normalVehicleS.chinookS;
1068 case NormalVehicleType.RidableHorse: return configData.normalVehicleS.ridableHorseS;
1069 case NormalVehicleType.WorkCart: return configData.normalVehicleS.workCartS;
1070 case NormalVehicleType.MagnetCrane: return configData.normalVehicleS.magnetCraneS;
1071 default: return null;
1072 }
1073 }
1074
1075 #endregion GetBaseVehicleS
1076
1077 #region HasVehiclePermission
1078
1079 private bool CanViewVehicleInfo(BasePlayer player, string vehicleType, BaseVehicleS baseVehicleS)
1080 {
1081 if (baseVehicleS.purchasable && baseVehicleS.commands.Count > 0)
1082 {
1083 return HasVehiclePermission(player, vehicleType, baseVehicleS);
1084 }
1085 return false;
1086 }
1087
1088 private bool HasVehiclePermission(BasePlayer player, string vehicleType, BaseVehicleS baseVehicleS = null)
1089 {
1090 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicleType);
1091 if (!baseVehicleS.usePermission || string.IsNullOrEmpty(baseVehicleS.permission)) return true;
1092 return permission.UserHasPermission(player.UserIDString, PERMISSION_ALL) ||
1093 permission.UserHasPermission(player.UserIDString, baseVehicleS.permission);
1094 }
1095
1096 #endregion HasVehiclePermission
1097
1098 #region GetCooldown
1099
1100 private double GetSpawnCooldown(BasePlayer player, BaseVehicleS baseVehicleS)
1101 {
1102 double cooldown = baseVehicleS.spawnCooldown;
1103 foreach (var entry in baseVehicleS.cooldownPermissions)
1104 {
1105 if (cooldown > entry.Value.spawnCooldown && permission.UserHasPermission(player.UserIDString, entry.Key))
1106 {
1107 cooldown = entry.Value.spawnCooldown;
1108 }
1109 }
1110 return cooldown;
1111 }
1112
1113 private double GetRecallCooldown(BasePlayer player, BaseVehicleS baseVehicleS)
1114 {
1115 double cooldown = baseVehicleS.recallCooldown;
1116 foreach (var entry in baseVehicleS.cooldownPermissions)
1117 {
1118 if (cooldown > entry.Value.recallCooldown && permission.UserHasPermission(player.UserIDString, entry.Key))
1119 {
1120 cooldown = entry.Value.recallCooldown;
1121 }
1122 }
1123 return cooldown;
1124 }
1125
1126 #endregion GetCooldown
1127
1128 #region ClaimVehicle
1129
1130 private bool TryClaimVehicle(BasePlayer player, BaseEntity entity, NormalVehicleType normalVehicleType)
1131 {
1132 return TryClaimVehicle(player, entity, normalVehicleType.ToString());
1133 }
1134
1135 private bool TryClaimVehicle(BasePlayer player, BaseEntity entity, string vehicleType)
1136 {
1137 Vehicle vehicle;
1138 if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle))
1139 {
1140 if (!configData.globalS.autoUnlockFromVendor)
1141 {
1142 return false;
1143 }
1144 storedData.AddVehicleLicense(player.userID, vehicleType);
1145 vehicle = storedData.GetVehicleLicense(player.userID, vehicleType);
1146 }
1147 if (vehicle.entity == null || vehicle.entity.IsDestroyed)
1148 {
1149 entity.OwnerID = player.userID;
1150 SetupVehicleEntity(entity, vehicle, player, vehicleType, false);
1151 return true;
1152 }
1153 return false;
1154 }
1155
1156 #endregion ClaimVehicle
1157
1158 #region Helpers
1159
1160 private static bool GetDismountPosition(BaseVehicle baseVehicle, BasePlayer player, out Vector3 result)
1161 {
1162 var parentVehicle = baseVehicle.VehicleParent();
1163 if (parentVehicle != null)
1164 {
1165 return GetDismountPosition(parentVehicle, player, out result);
1166 }
1167 var list = Pool.GetList<Vector3>();
1168 foreach (var transform in baseVehicle.dismountPositions)
1169 {
1170 var visualCheckOrigin = transform.position + Vector3.up * 0.6f;
1171 if (baseVehicle.ValidDismountPosition(transform.position, visualCheckOrigin))
1172 {
1173 list.Add(transform.position);
1174 }
1175 }
1176 if (list.Count == 0)
1177 {
1178 result = Vector3.zero;
1179 Pool.FreeList(ref list);
1180 return false;
1181 }
1182 Vector3 pos = player.transform.position;
1183 list.Sort((a, b) => Vector3.Distance(a, pos).CompareTo(Vector3.Distance(b, pos)));
1184 result = list[0];
1185 Pool.FreeList(ref list);
1186 return true;
1187 }
1188
1189 private static string GetVehiclePrefab(string vehicleType, BaseVehicleS baseVehicleS)
1190 {
1191 NormalVehicleType normalVehicleType;
1192 if (Enum.TryParse(vehicleType, out normalVehicleType))
1193 {
1194 switch (normalVehicleType)
1195 {
1196 case NormalVehicleType.Rowboat: return PREFAB_ROWBOAT;
1197 case NormalVehicleType.RHIB: return PREFAB_RHIB;
1198 case NormalVehicleType.Sedan: return PREFAB_SEDAN;
1199 case NormalVehicleType.HotAirBalloon: return PREFAB_HOTAIRBALLOON;
1200 case NormalVehicleType.MiniCopter: return PREFAB_MINICOPTER;
1201 case NormalVehicleType.TransportHelicopter: return PREFAB_TRANSPORTCOPTER;
1202 case NormalVehicleType.Chinook: return PREFAB_CHINOOK;
1203 case NormalVehicleType.RidableHorse: return PREFAB_RIDABLEHORSE;
1204 case NormalVehicleType.WorkCart: return PREFAB_WORKCART;
1205 case NormalVehicleType.MagnetCrane: return PREFAB_MAGNET_CRANE;
1206 }
1207 }
1208 else
1209 {
1210 var modularVehicleS = baseVehicleS as ModularVehicleS;
1211 if (modularVehicleS != null)
1212 {
1213 switch (modularVehicleS.chassisType)
1214 {
1215 case ChassisType.Small: return PREFAB_CHASSIS_SMALL;
1216 case ChassisType.Medium: return PREFAB_CHASSIS_MEDIUM;
1217 case ChassisType.Large: return PREFAB_CHASSIS_LARGE;
1218 }
1219 }
1220 }
1221
1222 return null;
1223 }
1224
1225 private static bool VehicleAnyMounted(BaseEntity entity)
1226 {
1227 var baseVehicle = entity as BaseVehicle;
1228 if (baseVehicle != null && baseVehicle.AnyMounted())
1229 {
1230 return true;
1231 }
1232
1233 return entity.GetComponentsInChildren<BasePlayer>()?.Length > 0;
1234 }
1235
1236 private static void DismountAllPlayers(BaseEntity entity)
1237 {
1238 var baseVehicle = entity as BaseVehicle;
1239 if (baseVehicle != null)
1240 {
1241 //(vehicle as BaseVehicle).DismountAllPlayers();
1242 foreach (var mountPointInfo in baseVehicle.mountPoints)
1243 {
1244 if (mountPointInfo.mountable != null)
1245 {
1246 var mounted = mountPointInfo.mountable.GetMounted();
1247 if (mounted != null)
1248 {
1249 mountPointInfo.mountable.DismountPlayer(mounted);
1250 }
1251 }
1252 }
1253 }
1254 var players = entity.GetComponentsInChildren<BasePlayer>();
1255 foreach (var player in players)
1256 {
1257 player.SetParent(null, true, true);
1258 }
1259 }
1260
1261 private static Vector3 GetGroundPositionLookingAt(BasePlayer player, float distance, bool needUp = true)
1262 {
1263 RaycastHit hitInfo;
1264 var headRay = player.eyes.HeadRay();
1265 if (Physics.Raycast(headRay, out hitInfo, distance, LAYER_GROUND))
1266 {
1267 return hitInfo.point;
1268 }
1269 return GetGroundPosition(headRay.origin + headRay.direction * distance, needUp);
1270 }
1271
1272 private static Vector3 GetGroundPosition(Vector3 position, bool needUp = true)
1273 {
1274 RaycastHit hitInfo;
1275 position.y = Physics.Raycast(needUp ? position + Vector3.up * 200 : position, Vector3.down, out hitInfo, needUp ? 400f : 50f, LAYER_GROUND)
1276 ? hitInfo.point.y
1277 : TerrainMeta.HeightMap.GetHeight(position);
1278 return position;
1279 }
1280
1281 private static bool IsInWater(Vector3 position)
1282 {
1283 var colliders = Pool.GetList<Collider>();
1284 Vis.Colliders(position, 0.5f, colliders);
1285 var flag = colliders.Any(x => x.gameObject.layer == (int)Rust.Layer.Water);
1286 Pool.FreeList(ref colliders);
1287 return flag;
1288 //return WaterLevel.Test(lookingAt);
1289 }
1290
1291 private static void MoveToPosition(BasePlayer player, Vector3 position)
1292 {
1293 player.Teleport(position);
1294 player.ForceUpdateTriggers();
1295 //if (player.HasParent()) player.SetParent(null, true, true);
1296 player.SendNetworkUpdateImmediate();
1297 }
1298
1299 #endregion Helpers
1300
1301 #endregion Methods
1302
1303 #region API
1304
1305 private bool IsLicensedVehicle(BaseEntity entity)
1306 {
1307 return vehiclesCache.ContainsKey(entity);
1308 }
1309
1310 private BaseEntity GetLicensedVehicle(ulong playerID, string license)
1311 {
1312 return storedData.GetVehicleLicense(playerID, license)?.entity;
1313 }
1314
1315 private bool HasVehicleLicense(ulong playerID, string license)
1316 {
1317 return storedData.HasVehicleLicense(playerID, license);
1318 }
1319
1320 private bool RemoveVehicleLicense(ulong playerID, string license)
1321 {
1322 return storedData.RemoveVehicleLicense(playerID, license);
1323 }
1324
1325 private bool AddVehicleLicense(ulong playerID, string license)
1326 {
1327 return storedData.AddVehicleLicense(playerID, license);
1328 }
1329
1330 private List<string> GetVehicleLicenses(ulong playerID)
1331 {
1332 return storedData.GetVehicleLicenseNames(playerID);
1333 }
1334
1335 private void PurchaseAllVehicles(ulong playerID)
1336 {
1337 storedData.PurchaseAllVehicles(playerID);
1338 }
1339
1340 #endregion API
1341
1342 #region Commands
1343
1344 #region Universal Command
1345
1346 private void CmdUniversal(BasePlayer player, string command, string[] args)
1347 {
1348 if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE))
1349 {
1350 Print(player, Lang("NotAllowed", player.UserIDString));
1351 return;
1352 }
1353
1354 string vehicleType;
1355 if (IsValidOption(player, command, out vehicleType))
1356 {
1357 var bypassCooldown = args.Length >= 1 && IsValidBypassCooldownOption(args[0]);
1358 HandleUniversalCmd(player, vehicleType, bypassCooldown, command);
1359 }
1360 }
1361
1362 private void HandleUniversalCmd(BasePlayer player, string vehicleType, bool bypassCooldown, string command)
1363 {
1364 Vehicle vehicle;
1365 if (storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle))
1366 {
1367 string reason; Vector3 position = Vector3.zero; Quaternion rotation = Quaternion.identity;
1368 if (vehicle.entity != null && !vehicle.entity.IsDestroyed)
1369 {
1370 //recall
1371 if (CanRecall(player, vehicle, vehicleType, bypassCooldown, out reason, ref position, ref rotation, command: command))
1372 {
1373 RecallVehicle(player, vehicle, vehicleType, position, rotation);
1374 return;
1375 }
1376 }
1377 else
1378 {
1379 //spawn
1380 if (CanSpawn(player, vehicle, vehicleType, bypassCooldown, out reason, ref position, ref rotation, command: command))
1381 {
1382 SpawnVehicle(player, vehicle, vehicleType, position, rotation);
1383 return;
1384 }
1385 }
1386 Print(player, reason);
1387 return;
1388 }
1389 //buy
1390 BuyVehicle(player, vehicleType);
1391 }
1392
1393 #endregion Universal Command
1394
1395 #region Custom Kill Command
1396
1397 private void CmdCustomKill(BasePlayer player, string command, string[] args)
1398 {
1399 if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE))
1400 {
1401 Print(player, Lang("NotAllowed", player.UserIDString));
1402 return;
1403 }
1404 command = command.Remove(0, configData.chatS.customKillCommandPrefix.Length);
1405 HandleKillCmd(player, command);
1406 }
1407
1408 #endregion Custom Kill Command
1409
1410 #region Help Command
1411
1412 private void CmdLicenseHelp(BasePlayer player, string command, string[] args)
1413 {
1414 StringBuilder stringBuilder = Pool.Get<StringBuilder>();
1415 stringBuilder.AppendLine(Lang("Help", player.UserIDString));
1416 stringBuilder.AppendLine(Lang("HelpLicence1", player.UserIDString, configData.chatS.buyCommand));
1417 stringBuilder.AppendLine(Lang("HelpLicence2", player.UserIDString, configData.chatS.spawnCommand));
1418 stringBuilder.AppendLine(Lang("HelpLicence3", player.UserIDString, configData.chatS.recallCommand));
1419 stringBuilder.AppendLine(Lang("HelpLicence4", player.UserIDString, configData.chatS.killCommand));
1420
1421 foreach (var entry in allBaseVehicleSettings)
1422 {
1423 if (CanViewVehicleInfo(player, entry.Key, entry.Value))
1424 {
1425 if (configData.chatS.useUniversalCommand)
1426 {
1427 var firstCmd = entry.Value.commands[0];
1428 stringBuilder.AppendLine(Lang("HelpLicence5", player.UserIDString, firstCmd, entry.Value.displayName));
1429 }
1430 //if (!string.IsNullOrEmpty(configData.chatS.customKillCommandPrefix))
1431 //{
1432 // stringBuilder.AppendLine(Lang("HelpLicence6", player.UserIDString, configData.chatS.customKillCommandPrefix + firstCmd, entry.Value.displayName));
1433 //}
1434 }
1435 }
1436 Print(player, stringBuilder.ToString());
1437 stringBuilder.Clear();
1438 Pool.Free(ref stringBuilder);
1439 }
1440
1441 #endregion Help Command
1442
1443 #region Remove Command
1444
1445 [ConsoleCommand("vl.remove")]
1446 private void CCmdRemoveVehicle(ConsoleSystem.Arg arg)
1447 {
1448 if (arg.IsAdmin && arg.Args != null && arg.Args.Length == 2)
1449 {
1450 var option = arg.Args[0];
1451 string vehicleType;
1452 if (!IsValidVehicleType(option, out vehicleType))
1453 {
1454 Print(arg, $"{option} is not a valid vehicle type");
1455 return;
1456 }
1457 switch (arg.Args[1].ToLower())
1458 {
1459 case "*":
1460 case "all":
1461 {
1462 storedData.RemoveLicenseForAllPlayers(vehicleType);
1463 var vehicleName = GetBaseVehicleS(vehicleType).displayName;
1464 Print(arg, $"You successfully removed the vehicle({vehicleName}) of all players");
1465 }
1466 return;
1467
1468 default:
1469 {
1470 var target = RustCore.FindPlayer(arg.Args[1]);
1471 if (target == null)
1472 {
1473 Print(arg, $"Player '{arg.Args[1]}' not found");
1474 return;
1475 }
1476
1477 var vehicleName = GetBaseVehicleS(vehicleType).displayName;
1478 if (RemoveVehicleLicense(target.userID, vehicleType))
1479 {
1480 Print(arg, $"You successfully removed the vehicle({vehicleName}) of {target.displayName}");
1481 return;
1482 }
1483
1484 Print(arg, $"{target.displayName} has not purchased vehicle({vehicleName}) and cannot be removed");
1485 }
1486 return;
1487 }
1488 }
1489 }
1490
1491 [ConsoleCommand("vl.cleardata")]
1492 private void CCmdClearVehicle(ConsoleSystem.Arg arg)
1493 {
1494 if (arg.IsAdmin)
1495 {
1496 foreach (var vehicle in vehiclesCache.Keys.ToArray())
1497 {
1498 vehicle.Kill(BaseNetworkable.DestroyMode.Gib);
1499 }
1500 vehiclesCache.Clear();
1501 ClearData();
1502 Print(arg, "You successfully cleaned up all vehicle data");
1503 }
1504 }
1505
1506 #endregion Remove Command
1507
1508 #region Buy Command
1509
1510 [ConsoleCommand("vl.buy")]
1511 private void CCmdBuyVehicle(ConsoleSystem.Arg arg)
1512 {
1513 if (arg.IsAdmin && arg.Args != null && arg.Args.Length == 2)
1514 {
1515 var option = arg.Args[0];
1516 string vehicleType;
1517 if (!IsValidVehicleType(option, out vehicleType))
1518 {
1519 Print(arg, $"{option} is not a valid vehicle type");
1520 return;
1521 }
1522 switch (arg.Args[1].ToLower())
1523 {
1524 case "*":
1525 case "all":
1526 {
1527 storedData.AddLicenseForAllPlayers(vehicleType);
1528 var vehicleName = GetBaseVehicleS(vehicleType).displayName;
1529 Print(arg, $"You successfully purchased the vehicle({vehicleName}) for all players");
1530 }
1531 return;
1532
1533 default:
1534 {
1535 var target = RustCore.FindPlayer(arg.Args[1]);
1536 if (target == null)
1537 {
1538 Print(arg, $"Player '{arg.Args[1]}' not found");
1539 return;
1540 }
1541
1542 var vehicleName = GetBaseVehicleS(vehicleType).displayName;
1543 if (AddVehicleLicense(target.userID, vehicleType))
1544 {
1545 Print(arg, $"You successfully purchased the vehicle({vehicleName}) for {target.displayName}");
1546 return;
1547 }
1548
1549 Print(arg, $"{target.displayName} has purchased vehicle({vehicleName})");
1550 }
1551 return;
1552 }
1553 }
1554 var player = arg.Player();
1555 if (player != null) CmdBuyVehicle(player, string.Empty, arg.Args);
1556 else Print(arg, $"The server console cannot use the '{arg.cmd.FullName}' command");
1557 }
1558
1559 private void CmdBuyVehicle(BasePlayer player, string command, string[] args)
1560 {
1561 if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE))
1562 {
1563 Print(player, Lang("NotAllowed", player.UserIDString));
1564 return;
1565 }
1566 if (args == null || args.Length < 1)
1567 {
1568 StringBuilder stringBuilder = Pool.Get<StringBuilder>();
1569 stringBuilder.AppendLine(Lang("Help", player.UserIDString));
1570 foreach (var entry in allBaseVehicleSettings)
1571 {
1572 if (CanViewVehicleInfo(player, entry.Key, entry.Value))
1573 {
1574 var firstCmd = entry.Value.commands[0];
1575 if (entry.Value.purchasePrices.Count > 0)
1576 {
1577 var prices = FormatPriceInfo(player, entry.Value.purchasePrices);
1578 stringBuilder.AppendLine(Lang("HelpBuyPrice", player.UserIDString, configData.chatS.buyCommand, firstCmd, entry.Value.displayName, prices));
1579 }
1580 else
1581 {
1582 stringBuilder.AppendLine(Lang("HelpBuy", player.UserIDString, configData.chatS.buyCommand, firstCmd, entry.Value.displayName));
1583 }
1584 }
1585 }
1586 Print(player, stringBuilder.ToString());
1587 stringBuilder.Clear();
1588 Pool.Free(ref stringBuilder);
1589 return;
1590 }
1591 string vehicleType;
1592 if (IsValidOption(player, args[0], out vehicleType))
1593 {
1594 BuyVehicle(player, vehicleType);
1595 }
1596 }
1597
1598 private bool BuyVehicle(BasePlayer player, string vehicleType)
1599 {
1600 var baseVehicleS = GetBaseVehicleS(vehicleType);
1601 if (!baseVehicleS.purchasable)
1602 {
1603 Print(player, Lang("VehicleCannotBeBought", player.UserIDString, baseVehicleS.displayName));
1604 return false;
1605 }
1606 var vehicles = storedData.GetPlayerVehicles(player.userID, false);
1607 if (vehicles.ContainsKey(vehicleType))
1608 {
1609 Print(player, Lang("VehicleAlreadyPurchased", player.UserIDString, baseVehicleS.displayName));
1610 return false;
1611 }
1612 string missingResources;
1613 if (baseVehicleS.purchasePrices.Count > 0 && !TryPay(player, baseVehicleS.purchasePrices, out missingResources))
1614 {
1615 Print(player, Lang("NoResourcesToPurchaseVehicle", player.UserIDString, baseVehicleS.displayName, missingResources));
1616 return false;
1617 }
1618 vehicles.Add(vehicleType, new Vehicle());
1619 SaveData();
1620 Print(player, Lang("VehiclePurchased", player.UserIDString, baseVehicleS.displayName, configData.chatS.spawnCommand));
1621 return true;
1622 }
1623
1624 #endregion Buy Command
1625
1626 #region Spawn Command
1627
1628 [ConsoleCommand("vl.spawn")]
1629 private void CCmdSpawnVehicle(ConsoleSystem.Arg arg)
1630 {
1631 var player = arg.Player();
1632 if (player == null) Print(arg, $"The server console cannot use the '{arg.cmd.FullName}' command");
1633 else CmdSpawnVehicle(player, string.Empty, arg.Args);
1634 }
1635
1636 private void CmdSpawnVehicle(BasePlayer player, string command, string[] args)
1637 {
1638 if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE))
1639 {
1640 Print(player, Lang("NotAllowed", player.UserIDString));
1641 return;
1642 }
1643 if (args == null || args.Length < 1)
1644 {
1645 StringBuilder stringBuilder = Pool.Get<StringBuilder>();
1646 stringBuilder.AppendLine(Lang("Help", player.UserIDString));
1647 foreach (var entry in allBaseVehicleSettings)
1648 {
1649 if (CanViewVehicleInfo(player, entry.Key, entry.Value))
1650 {
1651 var firstCmd = entry.Value.commands[0];
1652 if (entry.Value.spawnPrices.Count > 0)
1653 {
1654 var prices = FormatPriceInfo(player, entry.Value.spawnPrices);
1655 stringBuilder.AppendLine(Lang("HelpSpawnPrice", player.UserIDString, configData.chatS.spawnCommand, firstCmd, entry.Value.displayName, prices));
1656 }
1657 else
1658 {
1659 stringBuilder.AppendLine(Lang("HelpSpawn", player.UserIDString, configData.chatS.spawnCommand, firstCmd, entry.Value.displayName));
1660 }
1661 }
1662 }
1663 Print(player, stringBuilder.ToString());
1664 stringBuilder.Clear();
1665 Pool.Free(ref stringBuilder);
1666 return;
1667 }
1668 string vehicleType;
1669 if (IsValidOption(player, args[0], out vehicleType))
1670 {
1671 var bypassCooldown = args.Length > 1 && IsValidBypassCooldownOption(args[1]);
1672 SpawnVehicle(player, vehicleType, bypassCooldown, command + " " + args[0]);
1673 }
1674 }
1675
1676 private bool SpawnVehicle(BasePlayer player, string vehicleType, bool bypassCooldown, string command)
1677 {
1678 var baseVehicleS = GetBaseVehicleS(vehicleType);
1679 Vehicle vehicle;
1680 if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle))
1681 {
1682 Print(player, Lang("VehicleNotYetPurchased", player.UserIDString, baseVehicleS.displayName, configData.chatS.buyCommand));
1683 return false;
1684 }
1685 if (vehicle.entity != null && !vehicle.entity.IsDestroyed)
1686 {
1687 Print(player, Lang("AlreadyVehicleOut", player.UserIDString, baseVehicleS.displayName, configData.chatS.recallCommand));
1688 return false;
1689 }
1690 string reason; Vector3 position = Vector3.zero; Quaternion rotation = Quaternion.identity;
1691 if (CanSpawn(player, vehicle, vehicleType, bypassCooldown, out reason, ref position, ref rotation, baseVehicleS, command))
1692 {
1693 SpawnVehicle(player, vehicle, vehicleType, position, rotation, baseVehicleS);
1694 return false;
1695 }
1696 Print(player, reason);
1697 return true;
1698 }
1699
1700 private bool CanSpawn(BasePlayer player, Vehicle vehicle, string vehicleType, bool bypassCooldown, out string reason, ref Vector3 position, ref Quaternion rotation, BaseVehicleS baseVehicleS = null, string command = "")
1701 {
1702 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicleType);
1703 if (configData.globalS.limitVehicles > 0)
1704 {
1705 var activeVehicles = storedData.ActiveVehiclesCount(player.userID);
1706 if (activeVehicles >= configData.globalS.limitVehicles)
1707 {
1708 reason = Lang("VehiclesLimit", player.UserIDString, configData.globalS.limitVehicles);
1709 return false;
1710 }
1711 }
1712 if (!CanPlayerAction(player, vehicleType, baseVehicleS, out reason, ref position, ref rotation))
1713 {
1714 return false;
1715 }
1716 var obj = Interface.CallHook("CanLicensedVehicleSpawn", player, vehicleType, position, rotation);
1717 if (obj != null)
1718 {
1719 var s = obj as string;
1720 reason = s ?? Lang("SpawnWasBlocked", player.UserIDString, baseVehicleS.displayName);
1721 return false;
1722 }
1723
1724#if DEBUG
1725 if (player.IsAdmin)
1726 {
1727 reason = null;
1728 return true;
1729 }
1730#endif
1731 if (!CheckCooldown(player, vehicle, baseVehicleS, bypassCooldown, out reason, true, command))
1732 {
1733 return false;
1734 }
1735
1736 string missingResources;
1737 if (baseVehicleS.spawnPrices.Count > 0 && !TryPay(player, baseVehicleS.spawnPrices, out missingResources))
1738 {
1739 reason = Lang("NoResourcesToSpawnVehicle", player.UserIDString, baseVehicleS.displayName, missingResources);
1740 return false;
1741 }
1742 reason = null;
1743 return true;
1744 }
1745
1746 private void SpawnVehicle(BasePlayer player, Vehicle vehicle, string vehicleType, Vector3 position, Quaternion rotation, BaseVehicleS baseVehicleS = null)
1747 {
1748 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicleType);
1749 var prefab = GetVehiclePrefab(vehicleType, baseVehicleS);
1750 if (string.IsNullOrEmpty(prefab)) return;
1751 var entity = GameManager.server.CreateEntity(prefab, position, rotation);
1752 if (entity == null) return;
1753 entity.enableSaving = configData.globalS.storeVehicle;
1754 entity.OwnerID = player.userID;
1755 entity.Spawn();
1756 if (entity != null && !entity.IsDestroyed)
1757 {
1758 SetupVehicleEntity(entity, vehicle, player, vehicleType, baseVehicleS: baseVehicleS);
1759 }
1760 else
1761 {
1762 Print(player, Lang("NotSpawnedOrRecalled", player.UserIDString, baseVehicleS.displayName));
1763 return;
1764 }
1765
1766 Interface.CallHook("OnLicensedVehicleSpawned", entity, player, vehicleType);
1767 Print(player, Lang("VehicleSpawned", player.UserIDString, baseVehicleS.displayName));
1768 }
1769
1770 private void SetupVehicleEntity(BaseEntity entity, Vehicle vehicle, BasePlayer player, string vehicleType, bool giveFuel = true, BaseVehicleS baseVehicleS = null)
1771 {
1772 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicleType);
1773 if (giveFuel) TryGiveFuel(entity, vehicleType, baseVehicleS as IFuelVehicle);
1774 if (baseVehicleS.maxHealth > 0 && Math.Abs(baseVehicleS.maxHealth - entity.MaxHealth()) > 0f)
1775 {
1776 (entity as BaseCombatEntity)?.InitializeHealth(baseVehicleS.maxHealth, baseVehicleS.maxHealth);
1777 }
1778
1779 var modularCar = entity as ModularCar;
1780 if (modularCar != null)
1781 {
1782 var modularVehicleS = baseVehicleS as ModularVehicleS;
1783 if (modularVehicleS != null)
1784 {
1785 if (modularVehicleS.ModuleItems.Any())
1786 {
1787 AttacheVehicleModules(modularCar, modularVehicleS, vehicleType);
1788 }
1789 if (modularVehicleS.EngineItems.Any())
1790 {
1791 NextTick(() => AddItemsToVehicleEngine(modularCar, modularVehicleS, vehicleType));
1792 }
1793 }
1794 }
1795 else
1796 {
1797 var helicopterVehicle = entity as BaseHelicopterVehicle;
1798 if (helicopterVehicle != null)
1799 {
1800 if (configData.globalS.noServerGibs) helicopterVehicle.serverGibs.guid = string.Empty;
1801 if (configData.globalS.noFireBall) helicopterVehicle.fireBall.guid = string.Empty;
1802 if (configData.globalS.noMapMarker)
1803 {
1804 var ch47Helicopter = entity as CH47Helicopter;
1805 if (ch47Helicopter != null)
1806 {
1807 ch47Helicopter.mapMarkerInstance?.Kill();
1808 ch47Helicopter.mapMarkerEntityPrefab.guid = string.Empty;
1809 }
1810 }
1811 }
1812 }
1813
1814 if (configData.globalS.preventShattering)
1815 {
1816 var magnetLiftable = entity.GetComponent<MagnetLiftable>();
1817 if (magnetLiftable != null)
1818 {
1819 UnityEngine.Object.Destroy(magnetLiftable);
1820 }
1821 }
1822
1823 vehicle.playerID = player.userID;
1824 vehicle.vehicleType = vehicleType;
1825 vehicle.entity = entity;
1826 vehicle.entityID = entity.net.ID;
1827 vehicle.lastDismount = vehicle.lastRecall = TimeEx.currentTimestamp;
1828 vehiclesCache.Add(entity, vehicle);
1829 }
1830
1831 #endregion Spawn Command
1832
1833 #region Recall Command
1834
1835 [ConsoleCommand("vl.recall")]
1836 private void CCmdRecallVehicle(ConsoleSystem.Arg arg)
1837 {
1838 var player = arg.Player();
1839 if (player == null) Print(arg, $"The server console cannot use the '{arg.cmd.FullName}' command");
1840 else CmdRecallVehicle(player, string.Empty, arg.Args);
1841 }
1842
1843 private void CmdRecallVehicle(BasePlayer player, string command, string[] args)
1844 {
1845 if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE))
1846 {
1847 Print(player, Lang("NotAllowed", player.UserIDString));
1848 return;
1849 }
1850 if (args == null || args.Length < 1)
1851 {
1852 StringBuilder stringBuilder = Pool.Get<StringBuilder>();
1853 stringBuilder.AppendLine(Lang("Help", player.UserIDString));
1854 foreach (var entry in allBaseVehicleSettings)
1855 {
1856 if (CanViewVehicleInfo(player, entry.Key, entry.Value))
1857 {
1858 var firstCmd = entry.Value.commands[0];
1859 if (entry.Value.recallPrices.Count > 0)
1860 {
1861 var prices = FormatPriceInfo(player, entry.Value.recallPrices);
1862 stringBuilder.AppendLine(Lang("HelpRecallPrice", player.UserIDString, configData.chatS.recallCommand, firstCmd, entry.Value.displayName, prices));
1863 }
1864 else
1865 {
1866 stringBuilder.AppendLine(Lang("HelpRecall", player.UserIDString, configData.chatS.recallCommand, firstCmd, entry.Value.displayName));
1867 }
1868 }
1869 }
1870 Print(player, stringBuilder.ToString());
1871 stringBuilder.Clear();
1872 Pool.Free(ref stringBuilder);
1873 return;
1874 }
1875 string vehicleType;
1876 if (IsValidOption(player, args[0], out vehicleType))
1877 {
1878 var bypassCooldown = args.Length > 1 && IsValidBypassCooldownOption(args[1]);
1879 RecallVehicle(player, vehicleType, bypassCooldown, command + " " + args[0]);
1880 }
1881 }
1882
1883 private bool RecallVehicle(BasePlayer player, string vehicleType, bool bypassCooldown, string command)
1884 {
1885 var baseVehicleS = GetBaseVehicleS(vehicleType);
1886 Vehicle vehicle;
1887 if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle))
1888 {
1889 Print(player, Lang("VehicleNotYetPurchased", player.UserIDString, baseVehicleS.displayName, configData.chatS.buyCommand));
1890 return false;
1891 }
1892 if (vehicle.entity != null && !vehicle.entity.IsDestroyed)
1893 {
1894 string reason; Vector3 position = Vector3.zero; Quaternion rotation = Quaternion.identity;
1895 if (CanRecall(player, vehicle, vehicleType, bypassCooldown, out reason, ref position, ref rotation, baseVehicleS, command))
1896 {
1897 RecallVehicle(player, vehicle, vehicleType, position, rotation, baseVehicleS);
1898 return true;
1899 }
1900 Print(player, reason);
1901 return false;
1902 }
1903 Print(player, Lang("VehicleNotOut", player.UserIDString, baseVehicleS.displayName, configData.chatS.spawnCommand));
1904 return false;
1905 }
1906
1907 private bool CanRecall(BasePlayer player, Vehicle vehicle, string vehicleType, bool bypassCooldown, out string reason, ref Vector3 position, ref Quaternion rotation, BaseVehicleS baseVehicleS = null, string command = "")
1908 {
1909 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicleType);
1910 if (baseVehicleS.recallMaxDistance > 0 && Vector3.Distance(player.transform.position, vehicle.entity.transform.position) > baseVehicleS.recallMaxDistance)
1911 {
1912 reason = Lang("RecallTooFar", player.UserIDString, baseVehicleS.recallMaxDistance, baseVehicleS.displayName);
1913 return false;
1914 }
1915 if (configData.globalS.anyMountedRecall && VehicleAnyMounted(vehicle.entity))
1916 {
1917 reason = Lang("PlayerMountedOnVehicle", player.UserIDString, baseVehicleS.displayName);
1918 return false;
1919 }
1920 if (!CanPlayerAction(player, vehicleType, baseVehicleS, out reason, ref position, ref rotation))
1921 {
1922 return false;
1923 }
1924
1925 var obj = Interface.CallHook("CanLicensedVehicleRecall", vehicle.entity, player, vehicleType, position, rotation);
1926 if (obj != null)
1927 {
1928 var s = obj as string;
1929 reason = s ?? Lang("RecallWasBlocked", player.UserIDString, baseVehicleS.displayName);
1930 return false;
1931 }
1932#if DEBUG
1933 if (player.IsAdmin)
1934 {
1935 reason = null;
1936 return true;
1937 }
1938#endif
1939 if (!CheckCooldown(player, vehicle, baseVehicleS, bypassCooldown, out reason, false, command))
1940 {
1941 return false;
1942 }
1943 string missingResources;
1944 if (baseVehicleS.recallPrices.Count > 0 && !TryPay(player, baseVehicleS.recallPrices, out missingResources))
1945 {
1946 reason = Lang("NoResourcesToRecallVehicle", player.UserIDString, baseVehicleS.displayName, missingResources);
1947 return false;
1948 }
1949 reason = null;
1950 return true;
1951 }
1952
1953 private MethodInfo frontWheelSplineDistSetMethod = typeof(BaseTrain).GetMethod("set_FrontWheelSplineDist", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
1954
1955 private void RecallVehicle(BasePlayer player, Vehicle vehicle, string vehicleType, Vector3 position, Quaternion rotation, BaseVehicleS baseVehicleS = null)
1956 {
1957 if (baseVehicleS == null) baseVehicleS = GetBaseVehicleS(vehicleType);
1958 var entity = vehicle.entity;
1959 if (configData.globalS.dismountAllPlayersRecall)
1960 {
1961 DismountAllPlayers(entity);
1962 }
1963 if (CanDropInventory(baseVehicleS))
1964 {
1965 DropVehicleInventoryItems(player, vehicle.vehicleType, entity, baseVehicleS);
1966 }
1967 if (entity.HasParent()) entity.SetParent(null, true, true);
1968 if (entity is ModularCar)
1969 {
1970 var modularCarGarages = Pool.GetList<ModularCarGarage>();
1971 Vis.Entities(entity.transform.position, 3f, modularCarGarages, Rust.Layers.Mask.Deployed | Rust.Layers.Mask.Default);
1972 var modularCarGarage = modularCarGarages.FirstOrDefault(x => x.carOccupant == entity);
1973 Pool.FreeList(ref modularCarGarages);
1974 if (modularCarGarage != null)
1975 {
1976 modularCarGarage.enabled = false;
1977 modularCarGarage.ReleaseOccupant();
1978 modularCarGarage.Invoke(() => modularCarGarage.enabled = true, 0.25f);
1979 }
1980 }
1981
1982 vehicle.OnRecall();
1983 var entityTransform = entity.transform;
1984 entityTransform.position = position;
1985 entityTransform.rotation = rotation;
1986 entityTransform.hasChanged = true;
1987
1988 var ridableHorse = entity as RidableHorse;
1989 if (ridableHorse != null)
1990 {
1991 ridableHorse.TryLeaveHitch();
1992 ridableHorse.DropToGround(ridableHorse.transform.position, true);//ridableHorse.UpdateDropToGroundForDuration(2f);
1993 }
1994 var trainEngine = entity as TrainEngine;
1995 if (trainEngine != null)
1996 {
1997 trainEngine.initialSpawnTime = Time.time - 1f;
1998 float distResult;
1999 TrainTrackSpline splineResult;
2000 if (TrainTrackSpline.TryFindTrackNearby(trainEngine.GetFrontWheelPos(), 2f, out splineResult, out distResult) && splineResult.HasClearTrackSpaceNear(trainEngine))
2001 {
2002 //trainEngine.FrontWheelSplineDist = distResult;
2003 frontWheelSplineDistSetMethod?.Invoke(trainEngine, new object[] { distResult });
2004 trainEngine.SetTheRestFromFrontWheelData(ref splineResult, trainEngine.FrontTrackSection.GetPosition(trainEngine.FrontWheelSplineDist));
2005 trainEngine.FrontTrackSection = splineResult;
2006 trainEngine.Invoke(() =>
2007 {
2008 trainEngine.FixedUpdateMoveTrain(Time.fixedDeltaTime);
2009 }, 0.25f);
2010 }
2011 else
2012 {
2013 trainEngine.Kill();
2014 }
2015 }
2016 if (entity == null || entity.IsDestroyed)
2017 {
2018 Print(player, Lang("NotSpawnedOrRecalled", player.UserIDString, baseVehicleS.displayName));
2019 return;
2020 }
2021
2022 Interface.CallHook("OnLicensedVehicleRecalled", entity, player, vehicleType);
2023 Print(player, Lang("VehicleRecalled", player.UserIDString, baseVehicleS.displayName));
2024 }
2025
2026 #endregion Recall Command
2027
2028 #region Kill Command
2029
2030 [ConsoleCommand("vl.kill")]
2031 private void CCmdKillVehicle(ConsoleSystem.Arg arg)
2032 {
2033 var player = arg.Player();
2034 if (player == null) Print(arg, $"The server console cannot use the '{arg.cmd.FullName}' command");
2035 else CmdKillVehicle(player, string.Empty, arg.Args);
2036 }
2037
2038 private void CmdKillVehicle(BasePlayer player, string command, string[] args)
2039 {
2040 if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE))
2041 {
2042 Print(player, Lang("NotAllowed", player.UserIDString));
2043 return;
2044 }
2045 if (args == null || args.Length < 1)
2046 {
2047 StringBuilder stringBuilder = Pool.Get<StringBuilder>();
2048 stringBuilder.AppendLine(Lang("Help", player.UserIDString));
2049 foreach (var entry in allBaseVehicleSettings)
2050 {
2051 if (CanViewVehicleInfo(player, entry.Key, entry.Value))
2052 {
2053 var firstCmd = entry.Value.commands[0];
2054 if (!string.IsNullOrEmpty(configData.chatS.customKillCommandPrefix))
2055 {
2056 stringBuilder.AppendLine(Lang("HelpKillCustom", player.UserIDString, configData.chatS.killCommand, firstCmd, configData.chatS.customKillCommandPrefix + firstCmd, entry.Value.displayName));
2057 }
2058 else
2059 {
2060 stringBuilder.AppendLine(Lang("HelpKill", player.UserIDString, configData.chatS.killCommand, firstCmd, entry.Value.displayName));
2061 }
2062 }
2063 }
2064 Print(player, stringBuilder.ToString());
2065 stringBuilder.Clear();
2066 Pool.Free(ref stringBuilder);
2067 return;
2068 }
2069
2070 HandleKillCmd(player, args[0]);
2071 }
2072
2073 private void HandleKillCmd(BasePlayer player, string option)
2074 {
2075 string vehicleType;
2076 if (IsValidOption(player, option, out vehicleType))
2077 {
2078 KillVehicle(player, vehicleType);
2079 }
2080 }
2081
2082 private bool KillVehicle(BasePlayer player, string vehicleType)
2083 {
2084 var baseVehicleS = GetBaseVehicleS(vehicleType);
2085 Vehicle vehicle;
2086 if (!storedData.IsVehiclePurchased(player.userID, vehicleType, out vehicle))
2087 {
2088 Print(player, Lang("VehicleNotYetPurchased", player.UserIDString, baseVehicleS.displayName, configData.chatS.buyCommand));
2089 return false;
2090 }
2091 if (vehicle.entity != null && !vehicle.entity.IsDestroyed)
2092 {
2093 if (!CanKill(player, vehicle, baseVehicleS))
2094 {
2095 return false;
2096 }
2097 vehicle.entity.Kill(BaseNetworkable.DestroyMode.Gib);
2098 Print(player, Lang("VehicleKilled", player.UserIDString, baseVehicleS.displayName));
2099 return true;
2100 }
2101 Print(player, Lang("VehicleNotOut", player.UserIDString, baseVehicleS.displayName, configData.chatS.spawnCommand));
2102 return false;
2103 }
2104
2105 private bool CanKill(BasePlayer player, Vehicle vehicle, BaseVehicleS baseVehicleS)
2106 {
2107 if (configData.globalS.anyMountedKill && VehicleAnyMounted(vehicle.entity))
2108 {
2109 Print(player, Lang("PlayerMountedOnVehicle", player.UserIDString, baseVehicleS.displayName));
2110 return false;
2111 }
2112 if (baseVehicleS.killMaxDistance > 0 && Vector3.Distance(player.transform.position, vehicle.entity.transform.position) > baseVehicleS.killMaxDistance)
2113 {
2114 Print(player, Lang("KillTooFar", player.UserIDString, baseVehicleS.killMaxDistance, baseVehicleS.displayName));
2115 return false;
2116 }
2117
2118 return true;
2119 }
2120
2121 #endregion Kill Command
2122
2123 #region Command Helpers
2124
2125 private bool IsValidBypassCooldownOption(string option)
2126 {
2127 return !string.IsNullOrEmpty(configData.chatS.bypassCooldownCommand) && string.Equals(option, configData.chatS.bypassCooldownCommand, StringComparison.OrdinalIgnoreCase);
2128 }
2129
2130 private bool IsValidOption(BasePlayer player, string option, out string vehicleType)
2131 {
2132 if (!commandToVehicleType.TryGetValue(option, out vehicleType))
2133 {
2134 Print(player, Lang("OptionNotFound", player.UserIDString, option));
2135 return false;
2136 }
2137 if (!HasVehiclePermission(player, vehicleType))
2138 {
2139 Print(player, Lang("NotAllowed", player.UserIDString));
2140 vehicleType = null;
2141 return false;
2142 }
2143 if (IsPlayerBlocked(player))
2144 {
2145 vehicleType = null;
2146 return false;
2147 }
2148 return true;
2149 }
2150
2151 private bool IsValidVehicleType(string option, out string vehicleType)
2152 {
2153 foreach (var entry in allBaseVehicleSettings)
2154 {
2155 if (string.Equals(entry.Key, option, StringComparison.OrdinalIgnoreCase))
2156 {
2157 vehicleType = entry.Key;
2158 return true;
2159 }
2160 }
2161
2162 vehicleType = null;
2163 return false;
2164 }
2165
2166 private string FormatPriceInfo(BasePlayer player, Dictionary<string, PriceInfo> prices)
2167 {
2168 var language = RustTranslationAPI != null ? lang.GetLanguage(player.UserIDString) : null;
2169 return string.Join(", ",
2170 from p in prices
2171 select Lang("PriceFormat", player.UserIDString, GetItemDisplayName(language, p.Key, p.Value.displayName), p.Value.amount));
2172 }
2173
2174 private bool CanPlayerAction(BasePlayer player, string vehicleType, BaseVehicleS baseVehicleS, out string reason, ref Vector3 position, ref Quaternion rotation, BaseEntity entity = null)
2175 {
2176 if (configData.globalS.preventBuildingBlocked && player.IsBuildingBlocked())
2177 {
2178 reason = Lang("BuildingBlocked", player.UserIDString, baseVehicleS.displayName);
2179 return false;
2180 }
2181 if (configData.globalS.preventSafeZone && player.InSafeZone())
2182 {
2183 reason = Lang("PlayerInSafeZone", player.UserIDString, baseVehicleS.displayName);
2184 return false;
2185 }
2186 if (configData.globalS.preventMountedOrParented && HasMountedOrParented(player, vehicleType))
2187 {
2188 reason = Lang("MountedOrParented", player.UserIDString, baseVehicleS.displayName);
2189 return false;
2190 }
2191 Vector3 lookingAt = Vector3.zero;
2192 var isWorkCart = vehicleType == nameof(NormalVehicleType.WorkCart);
2193 var checkWater = vehicleType == nameof(NormalVehicleType.Rowboat) || vehicleType == nameof(NormalVehicleType.RHIB);
2194 if (!CheckPosition(player, baseVehicleS, checkWater, isWorkCart, out reason, ref lookingAt, entity))
2195 {
2196 return false;
2197 }
2198 FindVehicleSpawnPositionAndRotation(player, baseVehicleS, checkWater, isWorkCart, vehicleType, lookingAt, out position, out rotation);
2199 reason = null;
2200 return true;
2201 }
2202
2203 private bool HasMountedOrParented(BasePlayer player, string vehicleType)
2204 {
2205 if (player.GetMountedVehicle() != null) return true;
2206 var parentEntity = player.GetParentEntity();
2207 if (parentEntity != null)
2208 {
2209 if (configData.globalS.spawnLookingAt && LandOnCargoShip != null && parentEntity is CargoShip &&
2210 (vehicleType == nameof(NormalVehicleType.MiniCopter) ||
2211 vehicleType == nameof(NormalVehicleType.TransportHelicopter)))
2212 {
2213 return false;
2214 }
2215 return true;
2216 }
2217 return false;
2218 }
2219
2220 private bool CheckCooldown(BasePlayer player, Vehicle vehicle, BaseVehicleS baseVehicleS, bool bypassCooldown, out string reason, bool isSpawnCooldown, string command = "")
2221 {
2222 var cooldown = isSpawnCooldown ? GetSpawnCooldown(player, baseVehicleS) : GetRecallCooldown(player, baseVehicleS);
2223 if (cooldown > 0)
2224 {
2225 var timeLeft = Math.Ceiling(cooldown - (TimeEx.currentTimestamp - (isSpawnCooldown ? vehicle.lastDeath : vehicle.lastRecall)));
2226 if (timeLeft > 0)
2227 {
2228 var bypassPrices = isSpawnCooldown
2229 ? baseVehicleS.bypassSpawnCooldownPrices
2230 : baseVehicleS.bypassRecallCooldownPrices;
2231 if (bypassCooldown && bypassPrices.Count > 0)
2232 {
2233 string missingResources;
2234 if (!TryPay(player, bypassPrices, out missingResources))
2235 {
2236 reason = Lang(isSpawnCooldown ? "NoResourcesToSpawnVehicleBypass" : "NoResourcesToRecallVehicleBypass", player.UserIDString, baseVehicleS.displayName, missingResources);
2237 return false;
2238 }
2239
2240 if (isSpawnCooldown) vehicle.lastDeath = 0;
2241 else vehicle.lastRecall = 0;
2242 }
2243 else
2244 {
2245 if (string.IsNullOrEmpty(configData.chatS.bypassCooldownCommand) || bypassPrices.Count <= 0)
2246 {
2247 reason = Lang(isSpawnCooldown ? "VehicleOnSpawnCooldown" : "VehicleOnRecallCooldown", player.UserIDString, timeLeft, baseVehicleS.displayName);
2248 }
2249 else
2250 {
2251 reason = Lang(isSpawnCooldown ? "VehicleOnSpawnCooldownPay" : "VehicleOnRecallCooldownPay",
2252 player.UserIDString, timeLeft, baseVehicleS.displayName,
2253 command + " " + configData.chatS.bypassCooldownCommand,
2254 FormatPriceInfo(player,
2255 isSpawnCooldown
2256 ? baseVehicleS.bypassSpawnCooldownPrices
2257 : baseVehicleS.bypassRecallCooldownPrices));
2258 }
2259 return false;
2260 }
2261 }
2262 }
2263 reason = null;
2264 return true;
2265 }
2266
2267 private bool CheckPosition(BasePlayer player, BaseVehicleS baseVehicleS, bool checkWater, bool isWorkCart, out string reason, ref Vector3 lookingAt, BaseEntity entity = null)
2268 {
2269 if (checkWater || configData.globalS.spawnLookingAt || isWorkCart)
2270 {
2271 lookingAt = GetGroundPositionLookingAt(player, baseVehicleS.distance, !isWorkCart);
2272 if (checkWater && !IsInWater(lookingAt))
2273 {
2274 reason = Lang("NotLookingAtWater", player.UserIDString, baseVehicleS.displayName);
2275 return false;
2276 }
2277 if (configData.globalS.spawnLookingAt && baseVehicleS.minDistanceForPlayers > 0)
2278 {
2279 var nearbyPlayers = Pool.GetList<BasePlayer>();
2280 Vis.Entities(lookingAt, baseVehicleS.minDistanceForPlayers, nearbyPlayers, Rust.Layers.Mask.Player_Server);
2281 bool flag = nearbyPlayers.Any(x => x.userID.IsSteamId() && x != player);
2282 Pool.FreeList(ref nearbyPlayers);
2283 if (flag)
2284 {
2285 reason = Lang("PlayersOnNearby", player.UserIDString, baseVehicleS.displayName);
2286 return false;
2287 }
2288 }
2289
2290 if (isWorkCart)
2291 {
2292 float distResult;
2293 TrainTrackSpline splineResult;
2294 if (!TrainTrackSpline.TryFindTrackNearby(lookingAt, baseVehicleS.distance, out splineResult, out distResult))
2295 {
2296 reason = Lang("TooFarTrainTrack", player.UserIDString);
2297 return false;
2298 }
2299 //splineResult.HasClearTrackSpaceNear(entity as TrainEngine)
2300 var position = splineResult.GetPosition(distResult);
2301 if (!HasClearTrackSpaceNear(splineResult, position, entity as TrainTrackSpline.ITrainTrackUser))
2302 {
2303 reason = Lang("TooCloseTrainBarricadeOrWorkCart", player.UserIDString);
2304 return false;
2305 }
2306 lookingAt = position;
2307 reason = null;
2308 return true;
2309 }
2310 }
2311 reason = null;
2312 return true;
2313 }
2314
2315 private void FindVehicleSpawnPositionAndRotation(BasePlayer player, BaseVehicleS baseVehicleS, bool checkWater, bool isWorkCart, string vehicleType, Vector3 lookingAt, out Vector3 spawnPos, out Quaternion spawnRot)
2316 {
2317 if (isWorkCart)
2318 {
2319 spawnPos = lookingAt;
2320 var rotation = player.eyes.HeadForward().WithY(0);
2321 spawnRot = rotation != Vector3.zero ? Quaternion.LookRotation(rotation) : Quaternion.identity;
2322 return;
2323 }
2324 if (configData.globalS.spawnLookingAt)
2325 {
2326 bool needGetGround = true;
2327 spawnPos = lookingAt == Vector3.zero ? GetGroundPositionLookingAt(player, baseVehicleS.distance) : lookingAt;
2328 if (checkWater)
2329 {
2330 RaycastHit hit;
2331 if (Physics.Raycast(spawnPos, Vector3.up, out hit, 100, LAYER_GROUND) && hit.GetEntity() is StabilityEntity)
2332 {
2333 //At the dock
2334 needGetGround = false;
2335 }
2336 }
2337 else
2338 {
2339 var buildingBlocks = Pool.GetList<BuildingBlock>();
2340 Vis.Entities(spawnPos, 2f, buildingBlocks, Rust.Layers.Mask.Construction);
2341 if (buildingBlocks.Count > 0)
2342 {
2343 var pos = spawnPos;
2344 var closestBuildingBlock = buildingBlocks
2345 .Where(x => !x.ShortPrefabName.Contains("wall"))
2346 .OrderBy(x => (x.transform.position - pos).magnitude).FirstOrDefault();
2347 if (closestBuildingBlock != null)
2348 {
2349 var worldSpaceBounds = closestBuildingBlock.WorldSpaceBounds();
2350 spawnPos = worldSpaceBounds.position;
2351 spawnPos.y += worldSpaceBounds.extents.y;
2352 needGetGround = false;
2353 }
2354 }
2355 Pool.FreeList(ref buildingBlocks);
2356 }
2357 if (needGetGround)
2358 {
2359 spawnPos = GetGroundPosition(spawnPos);
2360 }
2361 }
2362 else
2363 {
2364 var minDistance = Mathf.Min(baseVehicleS.minDistanceForPlayers, 2.5f);
2365 var distance = Mathf.Max(baseVehicleS.distance, minDistance);
2366 spawnPos = player.transform.position;
2367 var nearbyPlayers = Pool.GetList<BasePlayer>();
2368 var sourcePos = checkWater ? (lookingAt == Vector3.zero ? GetGroundPositionLookingAt(player, baseVehicleS.distance) : lookingAt) : spawnPos;
2369 for (int i = 0; i < 100; i++)
2370 {
2371 spawnPos.x = sourcePos.x + UnityEngine.Random.Range(minDistance, distance) * (UnityEngine.Random.value >= 0.5f ? 1 : -1);
2372 spawnPos.z = sourcePos.z + UnityEngine.Random.Range(minDistance, distance) * (UnityEngine.Random.value >= 0.5f ? 1 : -1);
2373 spawnPos = GetGroundPosition(spawnPos);
2374 nearbyPlayers.Clear();
2375 Vis.Entities(spawnPos, minDistance, nearbyPlayers, Rust.Layers.Mask.Player_Server);
2376 if (!nearbyPlayers.Any(x => x.userID.IsSteamId()))
2377 {
2378 break;
2379 }
2380 }
2381 Pool.FreeList(ref nearbyPlayers);
2382 }
2383
2384 var normalized = (spawnPos - player.transform.position).normalized;
2385 var angle = normalized != Vector3.zero ? Quaternion.LookRotation(normalized).eulerAngles.y : UnityEngine.Random.Range(0f, 360f);
2386 var rot = vehicleType == nameof(NormalVehicleType.HotAirBalloon) ? 180f : 90f;
2387 spawnRot = Quaternion.Euler(Vector3.up * (angle + rot));
2388 if (vehicleType != nameof(NormalVehicleType.RidableHorse)) spawnPos += Vector3.up * 0.3f;
2389 }
2390
2391 #region HasClearTrackSpace
2392
2393 public bool HasClearTrackSpaceNear(TrainTrackSpline trainTrackSpline, Vector3 position, TrainTrackSpline.ITrainTrackUser asker)
2394 {
2395 if (!HasClearTrackSpace(trainTrackSpline, position, asker))
2396 {
2397 return false;
2398 }
2399 if (trainTrackSpline.HasNextTrack)
2400 {
2401 foreach (var nextTrack in trainTrackSpline.nextTracks)
2402 {
2403 if (!HasClearTrackSpace(nextTrack.track, position, asker))
2404 {
2405 return false;
2406 }
2407 }
2408 }
2409 if (trainTrackSpline.HasPrevTrack)
2410 {
2411 foreach (var prevTrack in trainTrackSpline.prevTracks)
2412 {
2413 if (!HasClearTrackSpace(prevTrack.track, position, asker))
2414 {
2415 return false;
2416 }
2417 }
2418 }
2419 return true;
2420 }
2421
2422 private bool HasClearTrackSpace(TrainTrackSpline trainTrackSpline, Vector3 position, TrainTrackSpline.ITrainTrackUser asker)
2423 {
2424 foreach (var trackUser in trainTrackSpline.trackUsers)
2425 {
2426 if (trackUser != asker && Vector3.SqrMagnitude(trackUser.Position - position) < 144f)
2427 {
2428 return false;
2429 }
2430 }
2431 return true;
2432 }
2433
2434 #endregion HasClearTrackSpace
2435
2436 #endregion Command Helpers
2437
2438 #endregion Commands
2439
2440 #region ConfigurationFile
2441
2442 public ConfigData configData { get; private set; }
2443
2444 public class ConfigData
2445 {
2446 [JsonProperty(PropertyName = "Settings")]
2447 public GlobalSettings globalS = new GlobalSettings();
2448
2449 [JsonProperty(PropertyName = "Chat Settings")]
2450 public ChatSettings chatS = new ChatSettings();
2451
2452 [JsonProperty(PropertyName = "Normal Vehicle Settings")]
2453 public NormalVehicleSettings normalVehicleS = new NormalVehicleSettings();
2454
2455 [JsonProperty(PropertyName = "Modular Vehicle Settings", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2456 public Dictionary<string, ModularVehicleS> modularCarS = new Dictionary<string, ModularVehicleS>
2457 {
2458 ["SmallCar"] = new ModularVehicleS
2459 {
2460 purchasable = true,
2461 displayName = "Small Modular Car",
2462 distance = 5,
2463 minDistanceForPlayers = 3,
2464 usePermission = true,
2465 permission = "vehiclelicence.smallmodularcar",
2466 commands = new List<string> { "small", "smallcar" },
2467 purchasePrices = new Dictionary<string, PriceInfo>
2468 {
2469 ["scrap"] = new PriceInfo { amount = 1600, displayName = "Scrap" }
2470 },
2471 spawnPrices = new Dictionary<string, PriceInfo>
2472 {
2473 ["metal.refined"] = new PriceInfo { amount = 10, displayName = "High Quality Metal" }
2474 },
2475 recallPrices = new Dictionary<string, PriceInfo>
2476 {
2477 ["scrap"] = new PriceInfo { amount = 5, displayName = "Scrap" }
2478 },
2479 spawnCooldown = 7200,
2480 recallCooldown = 30,
2481 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2482 {
2483 ["vehiclelicence.vip"] = new CooldownPermissionS
2484 {
2485 spawnCooldown = 3600,
2486 recallCooldown = 10,
2487 }
2488 },
2489 chassisType = ChassisType.Small,
2490 moduleItems = new List<ModuleItem>
2491 {
2492 new ModuleItem
2493 {
2494 shortName = "vehicle.1mod.cockpit.with.engine" ,healthPercentage = 50f
2495 },
2496 new ModuleItem
2497 {
2498 shortName = "vehicle.1mod.storage" ,healthPercentage = 50f
2499 },
2500 },
2501 engineItems = new List<EngineItem>
2502 {
2503 new EngineItem
2504 {
2505 shortName = "carburetor1",conditionPercentage = 20f
2506 },
2507 new EngineItem
2508 {
2509 shortName = "crankshaft1",conditionPercentage = 20f
2510 },
2511 new EngineItem
2512 {
2513 shortName = "piston1",conditionPercentage = 20f
2514 },
2515 new EngineItem
2516 {
2517 shortName = "sparkplug1",conditionPercentage = 20f
2518 },
2519 new EngineItem
2520 {
2521 shortName = "valve1",conditionPercentage = 20f
2522 }
2523 }
2524 },
2525 ["MediumCar"] = new ModularVehicleS
2526 {
2527 purchasable = true,
2528 displayName = "Medium Modular Car",
2529 distance = 5,
2530 minDistanceForPlayers = 3,
2531 usePermission = true,
2532 permission = "vehiclelicence.mediumodularcar",
2533 commands = new List<string> { "medium", "mediumcar" },
2534 purchasePrices = new Dictionary<string, PriceInfo>
2535 {
2536 ["scrap"] = new PriceInfo { amount = 2400, displayName = "Scrap" }
2537 },
2538 spawnPrices = new Dictionary<string, PriceInfo>
2539 {
2540 ["metal.refined"] = new PriceInfo { amount = 50, displayName = "High Quality Metal" }
2541 },
2542 recallPrices = new Dictionary<string, PriceInfo>
2543 {
2544 ["scrap"] = new PriceInfo { amount = 8, displayName = "Scrap" }
2545 },
2546 spawnCooldown = 9000,
2547 recallCooldown = 30,
2548 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2549 {
2550 ["vehiclelicence.vip"] = new CooldownPermissionS
2551 {
2552 spawnCooldown = 4500,
2553 recallCooldown = 10,
2554 }
2555 },
2556 chassisType = ChassisType.Medium,
2557 moduleItems = new List<ModuleItem>
2558 {
2559 new ModuleItem
2560 {
2561 shortName = "vehicle.1mod.cockpit.with.engine" ,healthPercentage = 50f
2562 },
2563 new ModuleItem
2564 {
2565 shortName = "vehicle.1mod.rear.seats" ,healthPercentage = 50f
2566 },
2567 new ModuleItem
2568 {
2569 shortName = "vehicle.1mod.flatbed" ,healthPercentage = 50f
2570 },
2571 },
2572 engineItems = new List<EngineItem>
2573 {
2574 new EngineItem
2575 {
2576 shortName = "carburetor2",conditionPercentage = 20f
2577 },
2578 new EngineItem
2579 {
2580 shortName = "crankshaft2",conditionPercentage = 20f
2581 },
2582 new EngineItem
2583 {
2584 shortName = "piston2",conditionPercentage = 20f
2585 },
2586 new EngineItem
2587 {
2588 shortName = "sparkplug2",conditionPercentage = 20f
2589 },
2590 new EngineItem
2591 {
2592 shortName = "valve2",conditionPercentage = 20f
2593 }
2594 }
2595 },
2596 ["LargeCar"] = new ModularVehicleS
2597 {
2598 purchasable = true,
2599 displayName = "Large Modular Car",
2600 distance = 6,
2601 minDistanceForPlayers = 3,
2602 usePermission = true,
2603 permission = "vehiclelicence.largemodularcar",
2604 commands = new List<string> { "large", "largecar" },
2605 purchasePrices = new Dictionary<string, PriceInfo>
2606 {
2607 ["scrap"] = new PriceInfo { amount = 3000, displayName = "Scrap" }
2608 },
2609 spawnPrices = new Dictionary<string, PriceInfo>
2610 {
2611 ["metal.refined"] = new PriceInfo { amount = 100, displayName = "High Quality Metal" }
2612 },
2613 recallPrices = new Dictionary<string, PriceInfo>
2614 {
2615 ["scrap"] = new PriceInfo { amount = 10, displayName = "Scrap" }
2616 },
2617 spawnCooldown = 10800,
2618 recallCooldown = 30,
2619 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2620 {
2621 ["vehiclelicence.vip"] = new CooldownPermissionS
2622 {
2623 spawnCooldown = 5400,
2624 recallCooldown = 10,
2625 }
2626 },
2627 chassisType = ChassisType.Large,
2628 moduleItems = new List<ModuleItem>
2629 {
2630 new ModuleItem
2631 {
2632 shortName = "vehicle.1mod.engine",healthPercentage = 50f
2633 },
2634 new ModuleItem
2635 {
2636 shortName = "vehicle.1mod.cockpit.armored",healthPercentage = 50f
2637 },
2638 new ModuleItem
2639 {
2640 shortName = "vehicle.1mod.passengers.armored",healthPercentage = 50f
2641 },
2642 new ModuleItem
2643 {
2644 shortName = "vehicle.1mod.storage",healthPercentage = 50f
2645 },
2646 },
2647 engineItems = new List<EngineItem>
2648 {
2649 new EngineItem
2650 {
2651 shortName = "carburetor3",conditionPercentage = 10f
2652 },
2653 new EngineItem
2654 {
2655 shortName = "crankshaft3",conditionPercentage = 10f
2656 },
2657 new EngineItem
2658 {
2659 shortName = "piston3",conditionPercentage = 10f
2660 },
2661 new EngineItem
2662 {
2663 shortName = "piston3",conditionPercentage = 10f
2664 },
2665 new EngineItem
2666 {
2667 shortName = "sparkplug3",conditionPercentage = 10f
2668 },
2669 new EngineItem
2670 {
2671 shortName = "sparkplug3",conditionPercentage = 10f
2672 },
2673 new EngineItem
2674 {
2675 shortName = "valve3",conditionPercentage = 10f
2676 },
2677 new EngineItem
2678 {
2679 shortName = "valve3",conditionPercentage = 10f
2680 }
2681 }
2682 },
2683 };
2684
2685 [JsonProperty(PropertyName = "Version")]
2686 public VersionNumber version;
2687 }
2688
2689 public class ChatSettings
2690 {
2691 [JsonProperty(PropertyName = "Use Universal Chat Command")]
2692 public bool useUniversalCommand = true;
2693
2694 [JsonProperty(PropertyName = "Help Chat Command")]
2695 public string helpCommand = "license";
2696
2697 [JsonProperty(PropertyName = "Buy Chat Command")]
2698 public string buyCommand = "buy";
2699
2700 [JsonProperty(PropertyName = "Spawn Chat Command")]
2701 public string spawnCommand = "spawn";
2702
2703 [JsonProperty(PropertyName = "Recall Chat Command")]
2704 public string recallCommand = "recall";
2705
2706 [JsonProperty(PropertyName = "Kill Chat Command")]
2707 public string killCommand = "kill";
2708
2709 [JsonProperty(PropertyName = "Custom Kill Chat Command Prefix")]
2710 public string customKillCommandPrefix = "no";
2711
2712 [JsonProperty(PropertyName = "Bypass Cooldown Command")]
2713 public string bypassCooldownCommand = "pay";
2714
2715 [JsonProperty(PropertyName = "Chat Prefix")]
2716 public string prefix = "<color=#00FFFF>[VehicleLicense]</color>: ";
2717
2718 [JsonProperty(PropertyName = "Chat SteamID Icon")]
2719 public ulong steamIDIcon = 76561198924840872;
2720 }
2721
2722 public class GlobalSettings
2723 {
2724 [JsonProperty(PropertyName = "Store Vehicle On Plugin Unloaded / Server Restart")]
2725 public bool storeVehicle = true;
2726
2727 [JsonProperty(PropertyName = "Clear Vehicle Data On Map Wipe")]
2728 public bool clearVehicleOnWipe;
2729
2730 [JsonProperty(PropertyName = "Interval to check vehicle for wipe (Seconds)")]
2731 public float checkVehiclesInterval = 300;
2732
2733 [JsonProperty(PropertyName = "Spawn vehicle in the direction you are looking at")]
2734 public bool spawnLookingAt = true;
2735
2736 [JsonProperty(PropertyName = "Automatically claim vehicles purchased from vehicle vendors")]
2737 public bool autoClaimFromVendor;
2738
2739 [JsonProperty(PropertyName = "Vehicle vendor purchases will unlock the license for the player")]
2740 public bool autoUnlockFromVendor;
2741
2742 [JsonProperty(PropertyName = "Limit the number of vehicles at a time")]
2743 public int limitVehicles;
2744
2745 [JsonProperty(PropertyName = "Prevent vehicles from damaging players")]
2746 public bool preventDamagePlayer = true;
2747
2748 [JsonProperty(PropertyName = "Prevent vehicles from shattering")]
2749 public bool preventShattering = true;
2750
2751 [JsonProperty(PropertyName = "Prevent vehicles from spawning or recalling in safe zone")]
2752 public bool preventSafeZone = true;
2753
2754 [JsonProperty(PropertyName = "Prevent vehicles from spawning or recalling when the player are building blocked")]
2755 public bool preventBuildingBlocked = true;
2756
2757 [JsonProperty(PropertyName = "Prevent vehicles from spawning or recalling when the player is mounted or parented")]
2758 public bool preventMountedOrParented = true;
2759
2760 [JsonProperty(PropertyName = "Check if any player mounted when recalling a vehicle")]
2761 public bool anyMountedRecall = true;
2762
2763 [JsonProperty(PropertyName = "Check if any player mounted when killing a vehicle")]
2764 public bool anyMountedKill;
2765
2766 [JsonProperty(PropertyName = "Dismount all players when a vehicle is recalled")]
2767 public bool dismountAllPlayersRecall = true;
2768
2769 [JsonProperty(PropertyName = "Prevent other players from mounting vehicle")]
2770 public bool preventMounting = true;
2771
2772 [JsonProperty(PropertyName = "Prevent mounting on driver's seat only")]
2773 public bool preventDriverSeat = true;
2774
2775 [JsonProperty(PropertyName = "Prevent other players from looting fuel container and inventory")]
2776 public bool preventLooting = true;
2777
2778 [JsonProperty(PropertyName = "Use Teams")]
2779 public bool useTeams;
2780
2781 [JsonProperty(PropertyName = "Use Clans")]
2782 public bool useClans = true;
2783
2784 [JsonProperty(PropertyName = "Use Friends")]
2785 public bool useFriends = true;
2786
2787 [JsonProperty(PropertyName = "Vehicle No Decay")]
2788 public bool noDecay;
2789
2790 [JsonProperty(PropertyName = "Vehicle No Fire Ball")]
2791 public bool noFireBall = true;
2792
2793 [JsonProperty(PropertyName = "Vehicle No Server Gibs")]
2794 public bool noServerGibs = true;
2795
2796 [JsonProperty(PropertyName = "Chinook No Map Marker")]
2797 public bool noMapMarker = true;
2798
2799 [JsonProperty(PropertyName = "Use Raid Blocker (Need NoEscape Plugin)")]
2800 public bool useRaidBlocker;
2801
2802 [JsonProperty(PropertyName = "Use Combat Blocker (Need NoEscape Plugin)")]
2803 public bool useCombatBlocker;
2804 }
2805
2806 public class NormalVehicleSettings
2807 {
2808 [JsonProperty(PropertyName = "Sedan Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2809 public BaseVehicleS sedanS = new BaseVehicleS
2810 {
2811 purchasable = true,
2812 displayName = "Sedan",
2813 distance = 5,
2814 minDistanceForPlayers = 3,
2815 usePermission = true,
2816 permission = "vehiclelicence.sedan",
2817 commands = new List<string> { "car", "sedan" },
2818 purchasePrices = new Dictionary<string, PriceInfo>
2819 {
2820 ["scrap"] = new PriceInfo { amount = 300, displayName = "Scrap" }
2821 },
2822 spawnCooldown = 300,
2823 recallCooldown = 30,
2824 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2825 {
2826 ["vehiclelicence.vip"] = new CooldownPermissionS
2827 {
2828 spawnCooldown = 150,
2829 recallCooldown = 10,
2830 }
2831 },
2832 };
2833
2834 [JsonProperty(PropertyName = "Chinook Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2835 public BaseVehicleS chinookS = new BaseVehicleS
2836 {
2837 purchasable = true,
2838 displayName = "Chinook",
2839 distance = 15,
2840 minDistanceForPlayers = 6,
2841 usePermission = true,
2842 permission = "vehiclelicence.chinook",
2843 commands = new List<string> { "ch47", "chinook" },
2844 purchasePrices = new Dictionary<string, PriceInfo>
2845 {
2846 ["scrap"] = new PriceInfo { amount = 3000, displayName = "Scrap" }
2847 },
2848 spawnCooldown = 3000,
2849 recallCooldown = 30,
2850 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2851 {
2852 ["vehiclelicence.vip"] = new CooldownPermissionS
2853 {
2854 spawnCooldown = 1500,
2855 recallCooldown = 10,
2856 }
2857 },
2858 };
2859
2860 [JsonProperty(PropertyName = "Rowboat Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2861 public InvFuelVehicleS rowboatS = new InvFuelVehicleS
2862 {
2863 purchasable = true,
2864 displayName = "Row Boat",
2865 distance = 5,
2866 minDistanceForPlayers = 2,
2867 usePermission = true,
2868 permission = "vehiclelicence.rowboat",
2869 commands = new List<string> { "row", "rowboat" },
2870 purchasePrices = new Dictionary<string, PriceInfo>
2871 {
2872 ["scrap"] = new PriceInfo { amount = 500, displayName = "Scrap" }
2873 },
2874 spawnCooldown = 300,
2875 recallCooldown = 30,
2876 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2877 {
2878 ["vehiclelicence.vip"] = new CooldownPermissionS
2879 {
2880 spawnCooldown = 150,
2881 recallCooldown = 10,
2882 }
2883 },
2884 };
2885
2886 [JsonProperty(PropertyName = "RHIB Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2887 public InvFuelVehicleS rhibS = new InvFuelVehicleS
2888 {
2889 purchasable = true,
2890 displayName = "Rigid Hulled Inflatable Boat",
2891 distance = 10,
2892 minDistanceForPlayers = 3,
2893 usePermission = true,
2894 permission = "vehiclelicence.rhib",
2895 commands = new List<string> { "rhib" },
2896 purchasePrices = new Dictionary<string, PriceInfo>
2897 {
2898 ["scrap"] = new PriceInfo { amount = 1000, displayName = "Scrap" }
2899 },
2900 spawnCooldown = 450,
2901 recallCooldown = 30,
2902 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2903 {
2904 ["vehiclelicence.vip"] = new CooldownPermissionS
2905 {
2906 spawnCooldown = 225,
2907 recallCooldown = 10,
2908 }
2909 },
2910 };
2911
2912 [JsonProperty(PropertyName = "Hot Air Balloon Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2913 public InvFuelVehicleS hotAirBalloonS = new InvFuelVehicleS
2914 {
2915 purchasable = true,
2916 displayName = "Hot Air Balloon",
2917 distance = 20,
2918 minDistanceForPlayers = 5,
2919 usePermission = true,
2920 permission = "vehiclelicence.hotairballoon",
2921 commands = new List<string> { "hab", "hotairballoon" },
2922 purchasePrices = new Dictionary<string, PriceInfo>
2923 {
2924 ["scrap"] = new PriceInfo { amount = 500, displayName = "Scrap" }
2925 },
2926 spawnCooldown = 900,
2927 recallCooldown = 30,
2928 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2929 {
2930 ["vehiclelicence.vip"] = new CooldownPermissionS
2931 {
2932 spawnCooldown = 450,
2933 recallCooldown = 10,
2934 }
2935 },
2936 };
2937
2938 [JsonProperty(PropertyName = "Ridable Horse Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2939 public InventoryVehicleS ridableHorseS = new InventoryVehicleS
2940 {
2941 purchasable = true,
2942 displayName = "Ridable Horse",
2943 distance = 6,
2944 minDistanceForPlayers = 1,
2945 usePermission = true,
2946 permission = "vehiclelicence.ridablehorse",
2947 commands = new List<string> { "horse", "ridablehorse" },
2948 purchasePrices = new Dictionary<string, PriceInfo>
2949 {
2950 ["scrap"] = new PriceInfo { amount = 700, displayName = "Scrap" }
2951 },
2952 spawnCooldown = 3000,
2953 recallCooldown = 30,
2954 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2955 {
2956 ["vehiclelicence.vip"] = new CooldownPermissionS
2957 {
2958 spawnCooldown = 1500,
2959 recallCooldown = 10,
2960 }
2961 },
2962 };
2963
2964 [JsonProperty(PropertyName = "Mini Copter Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2965 public FuelVehicleS miniCopterS = new FuelVehicleS
2966 {
2967 purchasable = true,
2968 displayName = "Mini Copter",
2969 distance = 8,
2970 minDistanceForPlayers = 2,
2971 usePermission = true,
2972 permission = "vehiclelicence.minicopter",
2973 commands = new List<string> { "mini", "minicopter" },
2974 purchasePrices = new Dictionary<string, PriceInfo>
2975 {
2976 ["scrap"] = new PriceInfo { amount = 4000, displayName = "Scrap" }
2977 },
2978 spawnCooldown = 1800,
2979 recallCooldown = 30,
2980 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
2981 {
2982 ["vehiclelicence.vip"] = new CooldownPermissionS
2983 {
2984 spawnCooldown = 900,
2985 recallCooldown = 10,
2986 }
2987 },
2988 };
2989
2990 [JsonProperty(PropertyName = "Transport Helicopter Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
2991 public FuelVehicleS transportHelicopterS = new FuelVehicleS
2992 {
2993 purchasable = true,
2994 displayName = "Transport Copter",
2995 distance = 10,
2996 minDistanceForPlayers = 4,
2997 usePermission = true,
2998 permission = "vehiclelicence.transportcopter",
2999 commands = new List<string>
3000 {
3001 "tcop", "transportcopter"
3002 },
3003 purchasePrices = new Dictionary<string, PriceInfo>
3004 {
3005 ["scrap"] = new PriceInfo { amount = 5000, displayName = "Scrap" }
3006 },
3007 spawnCooldown = 2400,
3008 recallCooldown = 30,
3009 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
3010 {
3011 ["vehiclelicence.vip"] = new CooldownPermissionS
3012 {
3013 spawnCooldown = 1200,
3014 recallCooldown = 10,
3015 }
3016 },
3017 };
3018
3019 [JsonProperty(PropertyName = "Work Cart Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
3020 public FuelVehicleS workCartS = new FuelVehicleS
3021 {
3022 purchasable = true,
3023 displayName = "Work Cart",
3024 distance = 12,
3025 minDistanceForPlayers = 6,
3026 usePermission = true,
3027 permission = "vehiclelicence.workcart",
3028 commands = new List<string>
3029 {
3030 "cart", "workcart"
3031 },
3032 purchasePrices = new Dictionary<string, PriceInfo>
3033 {
3034 ["scrap"] = new PriceInfo { amount = 2000, displayName = "Scrap" }
3035 },
3036 spawnCooldown = 1800,
3037 recallCooldown = 30,
3038 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
3039 {
3040 ["vehiclelicence.vip"] = new CooldownPermissionS
3041 {
3042 spawnCooldown = 900,
3043 recallCooldown = 10,
3044 }
3045 },
3046 };
3047
3048 [JsonProperty(PropertyName = "Magnet Crane Vehicle", ObjectCreationHandling = ObjectCreationHandling.Replace)]
3049 public FuelVehicleS magnetCraneS = new FuelVehicleS
3050 {
3051 purchasable = true,
3052 displayName = "Magnet Crane",
3053 distance = 16,
3054 minDistanceForPlayers = 8,
3055 usePermission = true,
3056 permission = "vehiclelicence.magnetcrane",
3057 commands = new List<string>
3058 {
3059 "crane", "magnetcrane"
3060 },
3061 purchasePrices = new Dictionary<string, PriceInfo>
3062 {
3063 ["scrap"] = new PriceInfo { amount = 2000, displayName = "Scrap" }
3064 },
3065 spawnCooldown = 600,
3066 recallCooldown = 30,
3067 cooldownPermissions = new Dictionary<string, CooldownPermissionS>
3068 {
3069 ["vehiclelicence.vip"] = new CooldownPermissionS
3070 {
3071 spawnCooldown = 300,
3072 recallCooldown = 10,
3073 }
3074 },
3075 };
3076 }
3077
3078 public class BaseVehicleS
3079 {
3080 [JsonProperty(PropertyName = "Purchasable")]
3081 public bool purchasable;
3082
3083 [JsonProperty(PropertyName = "Display Name")]
3084 public string displayName;
3085
3086 [JsonProperty(PropertyName = "Use Permission")]
3087 public bool usePermission;
3088
3089 [JsonProperty(PropertyName = "Permission")]
3090 public string permission;
3091
3092 [JsonProperty(PropertyName = "Distance To Spawn")]
3093 public float distance;
3094
3095 [JsonProperty(PropertyName = "Time Before Vehicle Wipe (Seconds)")]
3096 public double wipeTime;
3097
3098 [JsonProperty(PropertyName = "Maximum Health")]
3099 public float maxHealth;
3100
3101 [JsonProperty(PropertyName = "Can Recall Maximum Distance")]
3102 public float recallMaxDistance;
3103
3104 [JsonProperty(PropertyName = "Can Kill Maximum Distance")]
3105 public float killMaxDistance;
3106
3107 [JsonProperty(PropertyName = "Minimum distance from player to recall or spawn")]
3108 public float minDistanceForPlayers = 3f;
3109
3110 [JsonProperty(PropertyName = "Remove License Once Crashed")]
3111 public bool removeLicenseOnceCrash;
3112
3113 [JsonProperty(PropertyName = "Purchase Prices")]
3114 public Dictionary<string, PriceInfo> purchasePrices = new Dictionary<string, PriceInfo>();
3115
3116 [JsonProperty(PropertyName = "Spawn Prices")]
3117 public Dictionary<string, PriceInfo> spawnPrices = new Dictionary<string, PriceInfo>();
3118
3119 [JsonProperty(PropertyName = "Recall Prices")]
3120 public Dictionary<string, PriceInfo> recallPrices = new Dictionary<string, PriceInfo>();
3121
3122 [JsonProperty(PropertyName = "Recall Cooldown Bypass Prices")]
3123 public Dictionary<string, PriceInfo> bypassRecallCooldownPrices = new Dictionary<string, PriceInfo>();
3124
3125 [JsonProperty(PropertyName = "Spawn Cooldown Bypass Prices")]
3126 public Dictionary<string, PriceInfo> bypassSpawnCooldownPrices = new Dictionary<string, PriceInfo>();
3127
3128 [JsonProperty(PropertyName = "Commands")]
3129 public List<string> commands = new List<string>();
3130
3131 [JsonProperty(PropertyName = "Spawn Cooldown (Seconds)")]
3132 public double spawnCooldown;
3133
3134 [JsonProperty(PropertyName = "Recall Cooldown (Seconds)")]
3135 public double recallCooldown;
3136
3137 [JsonProperty(PropertyName = "Cooldown Permissions")]
3138 public Dictionary<string, CooldownPermissionS> cooldownPermissions = new Dictionary<string, CooldownPermissionS>();
3139 }
3140
3141 public class FuelVehicleS : BaseVehicleS, IFuelVehicle
3142 {
3143 public int spawnFuelAmount { get; set; }
3144 public bool refundFuelOnKill { get; set; } = true;
3145 public bool refundFuelOnCrash { get; set; } = true;
3146 }
3147
3148 public class InventoryVehicleS : BaseVehicleS, IInventoryVehicle
3149 {
3150 public bool refundInventoryOnKill { get; set; } = true;
3151 public bool refundInventoryOnCrash { get; set; } = true;
3152 public bool dropInventoryOnRecall { get; set; }
3153 }
3154
3155 public class InvFuelVehicleS : BaseVehicleS, IFuelVehicle, IInventoryVehicle
3156 {
3157 public int spawnFuelAmount { get; set; }
3158 public bool refundFuelOnKill { get; set; } = true;
3159 public bool refundFuelOnCrash { get; set; } = true;
3160 public bool refundInventoryOnKill { get; set; } = true;
3161 public bool refundInventoryOnCrash { get; set; } = true;
3162 public bool dropInventoryOnRecall { get; set; }
3163 }
3164
3165 public class ModularVehicleS : InvFuelVehicleS, IModularVehicle
3166 {
3167 public bool refundEngineOnKill { get; set; } = true;
3168 public bool refundEngineOnCrash { get; set; } = true;
3169 public bool refundModuleOnKill { get; set; } = true;
3170 public bool refundModuleOnCrash { get; set; } = true;
3171
3172 [JsonConverter(typeof(StringEnumConverter))]
3173 [JsonProperty(PropertyName = "Chassis Type (Small, Medium, Large)", Order = 40)]
3174 public ChassisType chassisType = ChassisType.Small;
3175
3176 [JsonProperty(PropertyName = "Vehicle Module Items", Order = 41)]
3177 public List<ModuleItem> moduleItems = new List<ModuleItem>();
3178
3179 [JsonProperty(PropertyName = "Vehicle Engine Items", Order = 42)]
3180 public List<EngineItem> engineItems = new List<EngineItem>();
3181
3182 #region ModuleItems
3183
3184 [JsonIgnore] private List<ModuleItem> _validModuleItems;
3185
3186 [JsonIgnore]
3187 public IEnumerable<ModuleItem> ModuleItems
3188 {
3189 get
3190 {
3191 if (_validModuleItems == null)
3192 {
3193 _validModuleItems = new List<ModuleItem>();
3194 foreach (var modularItem in moduleItems)
3195 {
3196 var itemDefinition = ItemManager.FindItemDefinition(modularItem.shortName);
3197 if (itemDefinition != null)
3198 {
3199 var itemModVehicleModule = itemDefinition.GetComponent<ItemModVehicleModule>();
3200 if (itemModVehicleModule == null || !itemModVehicleModule.entityPrefab.isValid)
3201 {
3202 Instance.PrintError($"'{modularItem}' is not a valid vehicle module");
3203 continue;
3204 }
3205 _validModuleItems.Add(modularItem);
3206 }
3207 }
3208 }
3209 return _validModuleItems;
3210 }
3211 }
3212
3213 public IEnumerable<Item> CreateModuleItems()
3214 {
3215 foreach (var moduleItem in ModuleItems)
3216 {
3217 var item = ItemManager.CreateByName(moduleItem.shortName);
3218 if (item != null)
3219 {
3220 item.condition = item.maxCondition * (moduleItem.healthPercentage / 100f);
3221 item.MarkDirty();
3222 yield return item;
3223 }
3224 }
3225 }
3226
3227 #endregion ModuleItems
3228
3229 #region EngineItems
3230
3231 [JsonIgnore] private List<EngineItem> _validEngineItems;
3232
3233 [JsonIgnore]
3234 public IEnumerable<EngineItem> EngineItems
3235 {
3236 get
3237 {
3238 if (_validEngineItems == null)
3239 {
3240 _validEngineItems = new List<EngineItem>();
3241 foreach (var modularItem in engineItems)
3242 {
3243 var itemDefinition = ItemManager.FindItemDefinition(modularItem.shortName);
3244 if (itemDefinition != null)
3245 {
3246 var itemModEngineItem = itemDefinition.GetComponent<ItemModEngineItem>();
3247 if (itemModEngineItem == null)
3248 {
3249 Instance.PrintError($"'{modularItem}' is not a valid engine item");
3250 continue;
3251 }
3252 _validEngineItems.Add(modularItem);
3253 }
3254 }
3255 }
3256 return _validEngineItems;
3257 }
3258 }
3259
3260 public IEnumerable<Item> CreateEngineItems()
3261 {
3262 foreach (var engineItem in EngineItems)
3263 {
3264 var item = ItemManager.CreateByName(engineItem.shortName);
3265 if (item != null)
3266 {
3267 item.condition = item.maxCondition * (engineItem.conditionPercentage / 100f);
3268 item.MarkDirty();
3269 yield return item;
3270 }
3271 }
3272 }
3273
3274 #endregion EngineItems
3275 }
3276
3277 #region Interfaces
3278
3279 public interface IFuelVehicle
3280 {
3281 [JsonProperty(PropertyName = "Amount Of Fuel To Spawn", Order = 23)] int spawnFuelAmount { get; set; }
3282 [JsonProperty(PropertyName = "Refund Fuel On Kill", Order = 26)] bool refundFuelOnKill { get; set; }
3283 [JsonProperty(PropertyName = "Refund Fuel On Crash", Order = 27)] bool refundFuelOnCrash { get; set; }
3284 }
3285
3286 public interface IInventoryVehicle
3287 {
3288 [JsonProperty(PropertyName = "Refund Inventory On Kill", Order = 28)] bool refundInventoryOnKill { get; set; }
3289 [JsonProperty(PropertyName = "Refund Inventory On Crash", Order = 29)] bool refundInventoryOnCrash { get; set; }
3290 [JsonProperty(PropertyName = "Drop Inventory Items When Vehicle Recall", Order = 39)] bool dropInventoryOnRecall { get; set; }
3291 }
3292
3293 public interface IModularVehicle
3294 {
3295 [JsonProperty(PropertyName = "Refund Engine Items On Kill", Order = 35)] bool refundEngineOnKill { get; set; }
3296 [JsonProperty(PropertyName = "Refund Engine Items On Crash", Order = 36)] bool refundEngineOnCrash { get; set; }
3297 [JsonProperty(PropertyName = "Refund Module Items On Kill", Order = 37)] bool refundModuleOnKill { get; set; }
3298 [JsonProperty(PropertyName = "Refund Module Items On Crash", Order = 38)] bool refundModuleOnCrash { get; set; }
3299 }
3300
3301 #endregion Interfaces
3302
3303 #region Structs
3304
3305 public struct CooldownPermissionS
3306 {
3307 public double spawnCooldown;
3308 public double recallCooldown;
3309 }
3310
3311 public struct ModuleItem
3312 {
3313 public string shortName;
3314 public float healthPercentage;
3315 }
3316
3317 public struct EngineItem
3318 {
3319 public string shortName;
3320 public float conditionPercentage;
3321 }
3322
3323 public struct PriceInfo
3324 {
3325 public int amount;
3326 public string displayName;
3327 }
3328
3329 #endregion Structs
3330
3331 protected override void LoadConfig()
3332 {
3333 base.LoadConfig();
3334 try
3335 {
3336 configData = Config.ReadObject<ConfigData>();
3337 if (configData == null)
3338 {
3339 LoadDefaultConfig();
3340 }
3341 else
3342 {
3343 UpdateConfigValues();
3344 }
3345 }
3346 catch (Exception ex)
3347 {
3348 PrintError($"The configuration file is corrupted. \n{ex}");
3349 LoadDefaultConfig();
3350 }
3351 SaveConfig();
3352 }
3353
3354 protected override void LoadDefaultConfig()
3355 {
3356 PrintWarning("Creating a new configuration file");
3357 configData = new ConfigData();
3358 configData.version = Version;
3359 }
3360
3361 protected override void SaveConfig() => Config.WriteObject(configData);
3362
3363 private void UpdateConfigValues()
3364 {
3365 if (configData.version < Version)
3366 {
3367 if (configData.version <= default(VersionNumber))
3368 {
3369 string prefix, prefixColor;
3370 if (GetConfigValue(out prefix, "Chat Settings", "Chat Prefix") && GetConfigValue(out prefixColor, "Chat Settings", "Chat Prefix Color"))
3371 {
3372 configData.chatS.prefix = $"<color={prefixColor}>{prefix}</color>: ";
3373 }
3374 }
3375 if (configData.version <= new VersionNumber(1, 7, 3))
3376 {
3377 configData.normalVehicleS.sedanS.minDistanceForPlayers = 3f;
3378 configData.normalVehicleS.chinookS.minDistanceForPlayers = 5f;
3379 configData.normalVehicleS.rowboatS.minDistanceForPlayers = 2f;
3380 configData.normalVehicleS.rhibS.minDistanceForPlayers = 3f;
3381 configData.normalVehicleS.hotAirBalloonS.minDistanceForPlayers = 4f;
3382 configData.normalVehicleS.ridableHorseS.minDistanceForPlayers = 1f;
3383 configData.normalVehicleS.miniCopterS.minDistanceForPlayers = 2f;
3384 configData.normalVehicleS.transportHelicopterS.minDistanceForPlayers = 4f;
3385 foreach (var entry in configData.modularCarS)
3386 {
3387 switch (entry.Value.chassisType)
3388 {
3389 case ChassisType.Small:
3390 entry.Value.minDistanceForPlayers = 2f;
3391 break;
3392
3393 case ChassisType.Medium:
3394 entry.Value.minDistanceForPlayers = 2.5f;
3395 break;
3396
3397 case ChassisType.Large:
3398 entry.Value.minDistanceForPlayers = 3f;
3399 break;
3400
3401 default: continue;
3402 }
3403 }
3404 }
3405 configData.version = Version;
3406 }
3407 }
3408
3409 private bool GetConfigValue<T>(out T value, params string[] path)
3410 {
3411 var configValue = Config.Get(path);
3412 if (configValue == null)
3413 {
3414 value = default(T);
3415 return false;
3416 }
3417 value = Config.ConvertValue<T>(configValue);
3418 return true;
3419 }
3420
3421 #endregion ConfigurationFile
3422
3423 #region DataFile
3424
3425 public StoredData storedData { get; private set; }
3426
3427 public class StoredData
3428 {
3429 public readonly Dictionary<ulong, Dictionary<string, Vehicle>> playerData = new Dictionary<ulong, Dictionary<string, Vehicle>>();
3430
3431 public int ActiveVehiclesCount(ulong playerID)
3432 {
3433 Dictionary<string, Vehicle> vehicles;
3434 if (!playerData.TryGetValue(playerID, out vehicles))
3435 {
3436 return 0;
3437 }
3438
3439 int count = 0;
3440 foreach (var vehicle in vehicles.Values)
3441 {
3442 if (vehicle.entity != null && !vehicle.entity.IsDestroyed)
3443 {
3444 count++;
3445 }
3446 }
3447 return count;
3448 }
3449
3450 public Dictionary<string, Vehicle> GetPlayerVehicles(ulong playerID, bool readOnly = true)
3451 {
3452 Dictionary<string, Vehicle> vehicles;
3453 if (!playerData.TryGetValue(playerID, out vehicles))
3454 {
3455 if (!readOnly)
3456 {
3457 vehicles = new Dictionary<string, Vehicle>();
3458 playerData.Add(playerID, vehicles);
3459 return vehicles;
3460 }
3461 return null;
3462 }
3463 return vehicles;
3464 }
3465
3466 public bool IsVehiclePurchased(ulong playerID, string vehicleType, out Vehicle vehicle)
3467 {
3468 vehicle = GetVehicleLicense(playerID, vehicleType);
3469 if (vehicle == null)
3470 {
3471 return false;
3472 }
3473 return true;
3474 }
3475
3476 public Vehicle GetVehicleLicense(ulong playerID, string vehicleType)
3477 {
3478 Dictionary<string, Vehicle> vehicles;
3479 if (!playerData.TryGetValue(playerID, out vehicles))
3480 {
3481 return null;
3482 }
3483 Vehicle vehicle;
3484 if (!vehicles.TryGetValue(vehicleType, out vehicle))
3485 {
3486 return null;
3487 }
3488 return vehicle;
3489 }
3490
3491 public bool HasVehicleLicense(ulong playerID, string vehicleType)
3492 {
3493 Dictionary<string, Vehicle> vehicles;
3494 if (!playerData.TryGetValue(playerID, out vehicles))
3495 {
3496 return false;
3497 }
3498 return vehicles.ContainsKey(vehicleType);
3499 }
3500
3501 public bool AddVehicleLicense(ulong playerID, string vehicleType)
3502 {
3503 Dictionary<string, Vehicle> vehicles;
3504 if (!playerData.TryGetValue(playerID, out vehicles))
3505 {
3506 vehicles = new Dictionary<string, Vehicle>();
3507 playerData.Add(playerID, vehicles);
3508 }
3509 if (vehicles.ContainsKey(vehicleType))
3510 {
3511 return false;
3512 }
3513 vehicles.Add(vehicleType, new Vehicle());
3514 Instance.SaveData();
3515 return true;
3516 }
3517
3518 public bool RemoveVehicleLicense(ulong playerID, string vehicleType)
3519 {
3520 Dictionary<string, Vehicle> vehicles;
3521 if (!playerData.TryGetValue(playerID, out vehicles))
3522 {
3523 return false;
3524 }
3525
3526 if (!vehicles.Remove(vehicleType))
3527 {
3528 return false;
3529 }
3530 Instance.SaveData();
3531 return true;
3532 }
3533
3534 public List<string> GetVehicleLicenseNames(ulong playerID)
3535 {
3536 Dictionary<string, Vehicle> vehicles;
3537 if (!playerData.TryGetValue(playerID, out vehicles))
3538 {
3539 return new List<string>();
3540 }
3541 return vehicles.Keys.ToList();
3542 }
3543
3544 public void PurchaseAllVehicles(ulong playerID)
3545 {
3546 bool changed = false;
3547 Dictionary<string, Vehicle> vehicles;
3548 if (!playerData.TryGetValue(playerID, out vehicles))
3549 {
3550 vehicles = new Dictionary<string, Vehicle>();
3551 playerData.Add(playerID, vehicles);
3552 }
3553 foreach (var vehicleType in Instance.allBaseVehicleSettings.Keys)
3554 {
3555 if (!vehicles.ContainsKey(vehicleType))
3556 {
3557 vehicles.Add(vehicleType, new Vehicle());
3558 changed = true;
3559 }
3560 }
3561 if (changed) Instance.SaveData();
3562 }
3563
3564 public void AddLicenseForAllPlayers(string vehicleType)
3565 {
3566 foreach (var entry in playerData)
3567 {
3568 if (!entry.Value.ContainsKey(vehicleType))
3569 {
3570 entry.Value.Add(vehicleType, new Vehicle());
3571 }
3572 }
3573 }
3574
3575 public void RemoveLicenseForAllPlayers(string vehicleType)
3576 {
3577 foreach (var entry in playerData)
3578 {
3579 entry.Value.Remove(vehicleType);
3580 }
3581 }
3582
3583 public void ResetPlayerData()
3584 {
3585 foreach (var vehicleEntries in playerData)
3586 {
3587 foreach (var vehicleEntry in vehicleEntries.Value)
3588 {
3589 vehicleEntry.Value.Reset();
3590 }
3591 }
3592 }
3593 }
3594
3595 public class Vehicle
3596 {
3597 public uint entityID;
3598 public double lastDeath;
3599 [JsonIgnore] public ulong playerID;
3600 [JsonIgnore] public BaseEntity entity;
3601 [JsonIgnore] public string vehicleType;
3602 [JsonIgnore] public double lastRecall;
3603 [JsonIgnore] public double lastDismount;
3604
3605 public void OnDismount() => lastDismount = TimeEx.currentTimestamp;
3606
3607 public void OnRecall() => lastRecall = TimeEx.currentTimestamp;
3608
3609 public void OnDeath()
3610 {
3611 entity = null;
3612 entityID = 0;
3613 lastDeath = TimeEx.currentTimestamp;
3614 }
3615
3616 public void Reset()
3617 {
3618 entityID = 0;
3619 lastDeath = 0;
3620 }
3621 }
3622
3623 private void LoadData()
3624 {
3625 try
3626 {
3627 storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
3628 }
3629 catch
3630 {
3631 storedData = null;
3632 }
3633 finally
3634 {
3635 if (storedData == null)
3636 {
3637 ClearData();
3638 }
3639 }
3640 }
3641
3642 private void ClearData()
3643 {
3644 storedData = new StoredData();
3645 SaveData();
3646 }
3647
3648 private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
3649
3650 private void OnNewSave(string filename)
3651 {
3652 if (configData.globalS.clearVehicleOnWipe)
3653 {
3654 ClearData();
3655 }
3656 else
3657 {
3658 storedData.ResetPlayerData();
3659 SaveData();
3660 }
3661 }
3662
3663 #endregion DataFile
3664
3665 #region LanguageFile
3666
3667 private void Print(BasePlayer player, string message)
3668 {
3669 Player.Message(player, message, configData.chatS.prefix, configData.chatS.steamIDIcon);
3670 }
3671
3672 private void Print(ConsoleSystem.Arg arg, string message)
3673 {
3674 var player = arg.Player();
3675 if (player == null) Puts(message);
3676 else PrintToConsole(player, message);
3677 }
3678
3679 private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);
3680
3681 protected override void LoadDefaultMessages()
3682 {
3683 lang.RegisterMessages(new Dictionary<string, string>
3684 {
3685 ["Help"] = "These are the available commands:",
3686 ["HelpLicence1"] = "<color=#4DFF4D>/{0}</color> -- To buy a vehicle",
3687 ["HelpLicence2"] = "<color=#4DFF4D>/{0}</color> -- To spawn a vehicle",
3688 ["HelpLicence3"] = "<color=#4DFF4D>/{0}</color> -- To recall a vehicle",
3689 ["HelpLicence4"] = "<color=#4DFF4D>/{0}</color> -- To kill a vehicle",
3690 ["HelpLicence5"] = "<color=#4DFF4D>/{0}</color> -- To buy, spawn or recall a <color=#009EFF>{1}</color>",
3691 //["HelpLicence6"] = "<color=#4DFF4D>/{0}</color> -- To kill a <color=#009EFF>{1}</color>",
3692
3693 ["PriceFormat"] = "<color=#FF1919>{0}</color> x{1}",
3694 ["HelpBuy"] = "<color=#4DFF4D>/{0} {1}</color> -- To buy a <color=#009EFF>{2}</color>",
3695 ["HelpBuyPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- To buy a <color=#009EFF>{2}</color>. Price: {3}",
3696 ["HelpSpawn"] = "<color=#4DFF4D>/{0} {1}</color> -- To spawn a <color=#009EFF>{2}</color>",
3697 ["HelpSpawnPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- To spawn a <color=#009EFF>{2}</color>. Price: {3}",
3698 ["HelpRecall"] = "<color=#4DFF4D>/{0} {1}</color> -- To recall a <color=#009EFF>{2}</color>",
3699 ["HelpRecallPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- To recall a <color=#009EFF>{2}</color>. Price: {3}",
3700 ["HelpKill"] = "<color=#4DFF4D>/{0} {1}</color> -- To kill a <color=#009EFF>{2}</color>",
3701 ["HelpKillCustom"] = "<color=#4DFF4D>/{0} {1}</color> or <color=#4DFF4D>/{2}</color> -- To kill a <color=#009EFF>{3}</color>",
3702
3703 ["NotAllowed"] = "You do not have permission to use this command.",
3704 ["RaidBlocked"] = "<color=#FF1919>You may not do that while raid blocked</color>.",
3705 ["CombatBlocked"] = "<color=#FF1919>You may not do that while combat blocked</color>.",
3706 ["OptionNotFound"] = "This <color=#009EFF>{0}</color> option doesn't exist.",
3707 ["VehiclePurchased"] = "You have purchased a <color=#009EFF>{0}</color>, type <color=#4DFF4D>/{1}</color> for more information.",
3708 ["VehicleAlreadyPurchased"] = "You have already purchased <color=#009EFF>{0}</color>.",
3709 ["VehicleCannotBeBought"] = "<color=#009EFF>{0}</color> is unpurchasable",
3710 ["VehicleNotOut"] = "<color=#009EFF>{0}</color> is not out, type <color=#4DFF4D>/{1}</color> for more information.",
3711 ["AlreadyVehicleOut"] = "You already have a <color=#009EFF>{0}</color> outside, type <color=#4DFF4D>/{1}</color> for more information.",
3712 ["VehicleNotYetPurchased"] = "You have not yet purchased a <color=#009EFF>{0}</color>, type <color=#4DFF4D>/{1}</color> for more information.",
3713 ["VehicleSpawned"] = "You spawned your <color=#009EFF>{0}</color>.",
3714 ["VehicleRecalled"] = "You recalled your <color=#009EFF>{0}</color>.",
3715 ["VehicleKilled"] = "You killed your <color=#009EFF>{0}</color>.",
3716 ["VehicleOnSpawnCooldown"] = "You must wait <color=#FF1919>{0}</color> seconds before you can spawn your <color=#009EFF>{1}</color>.",
3717 ["VehicleOnRecallCooldown"] = "You must wait <color=#FF1919>{0}</color> seconds before you can recall your <color=#009EFF>{1}</color>.",
3718 ["VehicleOnSpawnCooldownPay"] = "You must wait <color=#FF1919>{0}</color> seconds before you can spawn your <color=#009EFF>{1}</color>. You can bypass this cooldown by using the <color=#FF1919>/{2}</color> command to pay <color=#009EFF>{3}</color>",
3719 ["VehicleOnRecallCooldownPay"] = "You must wait <color=#FF1919>{0}</color> seconds before you can recall your <color=#009EFF>{1}</color>. You can bypass this cooldown by using the <color=#FF1919>/{2}</color> command to pay <color=#009EFF>{3}</color>",
3720 ["NotLookingAtWater"] = "You must be looking at water to spawn or recall a <color=#009EFF>{0}</color>.",
3721 ["BuildingBlocked"] = "You can't spawn a <color=#009EFF>{0}</color> if you don't have the building privileges.",
3722 ["RefundedVehicleItems"] = "Your <color=#009EFF>{0}</color> vehicle items was refunded to your inventory.",
3723 ["PlayerMountedOnVehicle"] = "It cannot be recalled or killed when players mounted on your <color=#009EFF>{0}</color>.",
3724 ["PlayerInSafeZone"] = "You cannot spawn or recall your <color=#009EFF>{0}</color> in the safe zone.",
3725 ["VehicleInventoryDropped"] = "Your <color=#009EFF>{0}</color> vehicle inventory cannot be recalled, it have dropped to the ground.",
3726 ["NoResourcesToPurchaseVehicle"] = "You don't have enough resources to buy a <color=#009EFF>{0}</color>. You are missing: \n{1}",
3727 ["NoResourcesToSpawnVehicle"] = "You don't have enough resources to spawn a <color=#009EFF>{0}</color>. You are missing: \n{1}",
3728 ["NoResourcesToSpawnVehicleBypass"] = "You don't have enough resources to bypass the cooldown to spawn a <color=#009EFF>{0}</color>. You are missing: \n{1}",
3729 ["NoResourcesToRecallVehicle"] = "You don't have enough resources to recall a <color=#009EFF>{0}</color>. You are missing: \n{1}",
3730 ["NoResourcesToRecallVehicleBypass"] = "You don't have enough resources to bypass the cooldown to recall a <color=#009EFF>{0}</color>. You are missing: \n{1}",
3731 ["MountedOrParented"] = "You cannot spawn or recall a <color=#009EFF>{0}</color> when mounted or parented.",
3732 ["RecallTooFar"] = "You must be within <color=#FF1919>{0}</color> meters of <color=#009EFF>{1}</color> to recall.",
3733 ["KillTooFar"] = "You must be within <color=#FF1919>{0}</color> meters of <color=#009EFF>{1}</color> to kill.",
3734 ["PlayersOnNearby"] = "You cannot spawn or recall a <color=#009EFF>{0}</color> when there are players near the position you are looking at.",
3735 ["RecallWasBlocked"] = "An external plugin blocked you from recalling a <color=#009EFF>{0}</color>.",
3736 ["SpawnWasBlocked"] = "An external plugin blocked you from spawning a <color=#009EFF>{0}</color>.",
3737 ["VehiclesLimit"] = "You can have up to <color=#009EFF>{0}</color> vehicles at a time.",
3738 ["TooFarTrainTrack"] = "You are too far from the train track.",
3739 ["TooCloseTrainBarricadeOrWorkCart"] = "You are too close to the train barricade or work cart.",
3740 ["NotSpawnedOrRecalled"] = "For some reason, your <color=#009EFF>{0}</color> vehicle was not spawned/recalled",
3741
3742 ["CantUse"] = "Sorry! This {0} belongs to {1}.You cannot use it."
3743 }, this);
3744 lang.RegisterMessages(new Dictionary<string, string>
3745 {
3746 ["Help"] = "可用命令列表:",
3747 ["HelpLicence1"] = "<color=#4DFF4D>/{0}</color> -- 购买一辆载具",
3748 ["HelpLicence2"] = "<color=#4DFF4D>/{0}</color> -- 生成一辆载具",
3749 ["HelpLicence3"] = "<color=#4DFF4D>/{0}</color> -- 召回一辆载具",
3750 ["HelpLicence4"] = "<color=#4DFF4D>/{0}</color> -- 摧毁一辆载具",
3751 ["HelpLicence5"] = "<color=#4DFF4D>/{0}</color> -- 购买,生成,召回一辆 <color=#009EFF>{1}</color>",
3752 //["HelpLicence6"] = "<color=#4DFF4D>/{0}</color> -- 摧毁一辆 <color=#009EFF>{1}</color>",
3753
3754 ["PriceFormat"] = "<color=#FF1919>{0}</color> x{1}",
3755 ["HelpBuy"] = "<color=#4DFF4D>/{0} {1}</color> -- 购买一辆 <color=#009EFF>{2}</color>",
3756 ["HelpBuyPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- 购买一辆 <color=#009EFF>{2}</color>,价格: {3}",
3757 ["HelpSpawn"] = "<color=#4DFF4D>/{0} {1}</color> -- 生成一辆 <color=#009EFF>{2}</color>",
3758 ["HelpSpawnPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- 生成一辆 <color=#009EFF>{2}</color>,价格: {3}",
3759 ["HelpRecall"] = "<color=#4DFF4D>/{0} {1}</color> -- 召回一辆 <color=#009EFF>{2}</color>",
3760 ["HelpRecallPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- 召回一辆 <color=#009EFF>{2}</color>,价格: {3}",
3761 ["HelpKill"] = "<color=#4DFF4D>/{0} {1}</color> -- 摧毁一辆 <color=#009EFF>{2}</color>",
3762 ["HelpKillCustom"] = "<color=#4DFF4D>/{0} {1}</color> 或者 <color=#4DFF4D>/{2}</color> -- 摧毁一辆 <color=#009EFF>{3}</color>",
3763
3764 ["NotAllowed"] = "您没有权限使用该命令",
3765 ["RaidBlocked"] = "<color=#FF1919>您被突袭阻止了,不能使用该命令</color>",
3766 ["CombatBlocked"] = "<color=#FF1919>您被战斗阻止了,不能使用该命令</color>",
3767 ["OptionNotFound"] = "选项 <color=#009EFF>{0}</color> 不存在",
3768 ["VehiclePurchased"] = "您购买了 <color=#009EFF>{0}</color>, 输入 <color=#4DFF4D>/{1}</color> 了解更多信息",
3769 ["VehicleAlreadyPurchased"] = "您已经购买了 <color=#009EFF>{0}</color>",
3770 ["VehicleCannotBeBought"] = "<color=#009EFF>{0}</color> 是不可购买的",
3771 ["VehicleNotOut"] = "您还没有生成您的 <color=#009EFF>{0}</color>, 输入 <color=#4DFF4D>/{1}</color> 了解更多信息",
3772 ["AlreadyVehicleOut"] = "您已经生成了您的 <color=#009EFF>{0}</color>, 输入 <color=#4DFF4D>/{1}</color> 了解更多信息",
3773 ["VehicleNotYetPurchased"] = "您还没有购买 <color=#009EFF>{0}</color>, 输入 <color=#4DFF4D>/{1}</color> 了解更多信息",
3774 ["VehicleSpawned"] = "您生成了您的 <color=#009EFF>{0}</color>",
3775 ["VehicleRecalled"] = "您召回了您的 <color=#009EFF>{0}</color>",
3776 ["VehicleKilled"] = "您摧毁了您的 <color=#009EFF>{0}</color>",
3777 ["VehicleOnSpawnCooldown"] = "您必须等待 <color=#FF1919>{0}</color> 秒,才能生成您的 <color=#009EFF>{1}</color>",
3778 ["VehicleOnRecallCooldown"] = "您必须等待 <color=#FF1919>{0}</color> 秒,才能召回您的 <color=#009EFF>{1}</color>",
3779 ["VehicleOnSpawnCooldownPay"] = "您必须等待 <color=#FF1919>{0}</color> 秒,才能生成您的 <color=#009EFF>{1}</color>。你可以使用 <color=#FF1919>/{2}</color> 命令支付 <color=#009EFF>{3}</color> 来绕过这个冷却时间",
3780 ["VehicleOnRecallCooldownPay"] = "您必须等待 <color=#FF1919>{0}</color> 秒,才能召回您的 <color=#009EFF>{1}</color>。你可以使用 <color=#FF1919>/{2}</color> 命令支付 <color=#009EFF>{3}</color> 来绕过这个冷却时间",
3781 ["NotLookingAtWater"] = "您必须看着水面才能生成您的 <color=#009EFF>{0}</color>",
3782 ["BuildingBlocked"] = "您没有领地柜权限,无法生成您的 <color=#009EFF>{0}</color>",
3783 ["RefundedVehicleItems"] = "您的 <color=#009EFF>{0}</color> 载具物品已经归还回您的库存",
3784 ["PlayerMountedOnVehicle"] = "您的 <color=#009EFF>{0}</color> 上坐着玩家,无法被召回或摧毁",
3785 ["PlayerInSafeZone"] = "您不能在安全区域内生成或召回您的 <color=#009EFF>{0}</color>",
3786 ["VehicleInventoryDropped"] = "您的 <color=#009EFF>{0}</color> 载具物品不能召回,它已经掉落在地上了",
3787 ["NoResourcesToPurchaseVehicle"] = "您没有足够的资源购买 <color=#009EFF>{0}</color>,还需要: \n{1}",
3788 ["NoResourcesToSpawnVehicle"] = "您没有足够的资源生成 <color=#009EFF>{0}</color>,还需要: \n{1}",
3789 ["NoResourcesToSpawnVehicleBypass"] = "您没有足够的资源绕过冷却时间来生成 <color=#009EFF>{0}</color>,还需要: \n{1}",
3790 ["NoResourcesToRecallVehicle"] = "您没有足够的资源召回 <color=#009EFF>{0}</color>,还需要: \n{1}",
3791 ["NoResourcesToRecallVehicleBypass"] = "您没有足够的资源绕过冷却时间来召回 <color=#009EFF>{0}</color>,还需要: \n{1}",
3792 ["MountedOrParented"] = "当您坐着或者在附着在实体上时无法生成或召回 <color=#009EFF>{0}</color>",
3793 ["RecallTooFar"] = "您必须在 <color=#FF1919>{0}</color> 米内才能召回您的 <color=#009EFF>{1}</color>",
3794 ["KillTooFar"] = "您必须在 <color=#FF1919>{0}</color> 米内才能摧毁您的 <color=#009EFF>{1}</color>",
3795 ["PlayersOnNearby"] = "您正在看着的位置附近有玩家时无法生成或召回 <color=#009EFF>{0}</color>",
3796 ["RecallWasBlocked"] = "有其他插件阻止您召回 <color=#009EFF>{0}</color>.",
3797 ["SpawnWasBlocked"] = "有其他插件阻止您生成 <color=#009EFF>{0}</color>.",
3798 ["VehiclesLimit"] = "您在同一时间内最多可以拥有 <color=#009EFF>{0}</color> 辆载具",
3799 ["TooFarTrainTrack"] = "您距离地铁轨道太远了",
3800 ["TooCloseTrainBarricadeOrWorkCart"] = "您距离地轨障碍物或其它地铁太近了",
3801 ["NotSpawnedOrRecalled"] = "由于某些原因,您的 <color=#009EFF>{0}</color> 载具无法生成或召回",
3802
3803 ["CantUse"] = "您不能使用它,这个 {0} 属于 {1}"
3804 }, this, "zh-CN");
3805 lang.RegisterMessages(new Dictionary<string, string>
3806 {
3807 ["Help"] = "Список доступных команд:",
3808 ["HelpLicence1"] = "<color=#4DFF4D>/{0}</color> -- Купить транспорт",
3809 ["HelpLicence2"] = "<color=#4DFF4D>/{0}</color> -- Создать транспорт",
3810 ["HelpLicence3"] = "<color=#4DFF4D>/{0}</color> -- Вызвать транспорт",
3811 ["HelpLicence4"] = "<color=#4DFF4D>/{0}</color> -- Уничтожить транспорт",
3812 ["HelpLicence5"] = "<color=#4DFF4D>/{0}</color> -- Купить, создать, или вызвать <color=#009EFF>{1}</color>",
3813 //["HelpLicence6"] = "<color=#4DFF4D>/{0}</color> -- Уничтожить <color=#009EFF>{1}</color>",
3814
3815 ["PriceFormat"] = "<color=#FF1919>{0}</color> x{1}",
3816 ["HelpBuy"] = "<color=#4DFF4D>/{0} {1}</color> -- Купить <color=#009EFF>{2}</color>.",
3817 ["HelpBuyPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- Купить <color=#009EFF>{2}</color>. Цена: {3}",
3818 ["HelpSpawn"] = "<color=#4DFF4D>/{0} {1}</color> -- Создать <color=#009EFF>{2}</color>",
3819 ["HelpSpawnPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- Вызывать <color=#009EFF>{2}</color>. Цена: {3}",
3820 ["HelpRecall"] = "<color=#4DFF4D>/{0} {1}</color> -- Вызвать <color=#009EFF>{2}</color>",
3821 ["HelpRecallPrice"] = "<color=#4DFF4D>/{0} {1}</color> -- Вызвать <color=#009EFF>{2}</color>. Цена: {3}",
3822 ["HelpKill"] = "<color=#4DFF4D>/{0} {1}</color> -- Уничтожить <color=#009EFF>{2}</color>",
3823 ["HelpKillCustom"] = "<color=#4DFF4D>/{0} {1}</color> или же <color=#4DFF4D>/{2}</color> -- Уничтожить <color=#009EFF>{3}</color>",
3824
3825 ["NotAllowed"] = "У вас нет разрешения для использования данной команды.",
3826 ["RaidBlocked"] = "<color=#FF1919>Вы не можете это сделать из-за блокировки (рейд)</color>.",
3827 ["CombatBlocked"] = "<color=#FF1919>Вы не можете это сделать из-за блокировки (бой)</color>.",
3828 ["OptionNotFound"] = "Опция <color=#009EFF>{0}</color> не существует.",
3829 ["VehiclePurchased"] = "Вы приобрели <color=#009EFF>{0}</color>, напишите <color=#4DFF4D>/{1}</color> для получения дополнительной информации.",
3830 ["VehicleAlreadyPurchased"] = "Вы уже приобрели <color=#009EFF>{0}</color>.",
3831 ["VehicleCannotBeBought"] = "<color=#009EFF>{0}</color> приобрести невозможно",
3832 ["VehicleNotOut"] = "<color=#009EFF>{0}</color> отсутствует. Напишите <color=#4DFF4D>/{1}</color> для получения дополнительной информации.",
3833 ["AlreadyVehicleOut"] = "У вас уже есть <color=#009EFF>{0}</color>, напишите <color=#4DFF4D>/{1}</color> для получения дополнительной информации.",
3834 ["VehicleNotYetPurchased"] = "Вы ещё не приобрели <color=#009EFF>{0}</color>. Напишите <color=#4DFF4D>/{1}</color> для получения дополнительной информации.",
3835 ["VehicleSpawned"] = "Вы создали ваш <color=#009EFF>{0}</color>.",
3836 ["VehicleRecalled"] = "Вы вызвали ваш <color=#009EFF>{0}</color>.",
3837 ["VehicleKilled"] = "Вы уничтожили ваш <color=#009EFF>{0}</color>.",
3838 ["VehicleOnSpawnCooldown"] = "Вам необходимо подождать <color=#FF1919>{0}</color> секунд прежде, чем создать свой <color=#009EFF>{1}</color>.",
3839 ["VehicleOnRecallCooldown"] = "Вам необходимо подождать <color=#FF1919>{0}</color> секунд прежде, чем вызвать свой <color=#009EFF>{1}</color>.",
3840 ["VehicleOnSpawnCooldownPay"] = "Вам необходимо подождать <color=#FF1919>{0}</color> секунд прежде, чем создать свой <color=#009EFF>{1}</color>. Вы можете обойти это время восстановления, используя команду <color=#FF1919>/{2}</color>, чтобы заплатить <color=#009EFF>{3}</color>",
3841 ["VehicleOnRecallCooldownPay"] = "Вам необходимо подождать <color=#FF1919>{0}</color> секунд прежде, чем вызвать свой <color=#009EFF>{1}</color>. Вы можете обойти это время восстановления, используя команду <color=#FF1919>/{2}</color>, чтобы заплатить <color=#009EFF>{3}</color>",
3842 ["NotLookingAtWater"] = "Вы должны смотреть на воду, чтобы создать или вызвать <color=#009EFF>{0}</color>.",
3843 ["BuildingBlocked"] = "Вы не можете создать <color=#009EFF>{0}</color> если отсутствует право строительства.",
3844 ["RefundedVehicleItems"] = "Запчасти от вашего <color=#009EFF>{0}</color> были возвращены в ваш инвентарь.",
3845 ["PlayerMountedOnVehicle"] = "Нельзя вызвать, когда игрок находится в вашем <color=#009EFF>{0}</color>.",
3846 ["PlayerInSafeZone"] = "Вы не можете создать, или вызвать ваш <color=#009EFF>{0}</color> в безопасной зоне.",
3847 ["VehicleInventoryDropped"] = "Инвентарь из вашего <color=#009EFF>{0}</color> не может быть вызван, он выброшен на землю.",
3848 ["NoResourcesToPurchaseVehicle"] = "У вас недостаточно ресурсов для покупки <color=#009EFF>{0}</color>. Вам не хватает: \n{1}",
3849 ["NoResourcesToSpawnVehicle"] = "У вас недостаточно ресурсов для покупки <color=#009EFF>{0}</color>. Вам не хватает: \n{1}",
3850 ["NoResourcesToSpawnVehicleBypass"] = "У вас недостаточно ресурсов для покупки <color=#009EFF>{0}</color>. Вам не хватает: \n{1}",
3851 ["NoResourcesToRecallVehicle"] = "У вас недостаточно ресурсов для покупки <color=#009EFF>{0}</color>. Вам не хватает: \n{1}",
3852 ["NoResourcesToRecallVehicleBypass"] = "У вас недостаточно ресурсов для покупки <color=#009EFF>{0}</color>. Вам не хватает: \n{1}",
3853 ["MountedOrParented"] = "Вы не можете создать <color=#009EFF>{0}</color> когда сидите или привязаны к объекту.",
3854 ["RecallTooFar"] = "Вы должны быть в пределах <color=#FF1919>{0}</color> метров от <color=#009EFF>{1}</color>, чтобы вызывать.",
3855 ["KillTooFar"] = "Вы должны быть в пределах <color=#FF1919>{0}</color> метров от <color=#009EFF>{1}</color>, уничтожить.",
3856 ["PlayersOnNearby"] = "Вы не можете создать <color=#009EFF>{0}</color> когда рядом с той позицией, на которую вы смотрите, есть игроки.",
3857 ["RecallWasBlocked"] = "Внешний плагин заблокировал вам вызвать <color=#009EFF>{0}</color>.",
3858 ["SpawnWasBlocked"] = "Внешний плагин заблокировал вам создать <color=#009EFF>{0}</color>.",
3859 ["VehiclesLimit"] = "У вас может быть до <color=#009EFF>{0}</color> автомобилей одновременно",
3860 ["TooFarTrainTrack"] = "Вы слишком далеко от железнодорожных путей",
3861 ["TooCloseTrainBarricadeOrWorkCart"] = "Вы слишком близко к железнодорожной баррикаде или рабочей тележке",
3862 ["NotSpawnedOrRecalled"] = "По какой-то причине ваш <color=#009EFF>{0}</color> автомобилей не был вызван / отозван",
3863
3864 ["CantUse"] = "Простите! Этот {0} принадлежит {1}. Вы не можете его использовать."
3865 }, this, "ru");
3866 }
3867
3868 #endregion LanguageFile
3869 }
3870}