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