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