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