C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust.Modular;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Oxide.Plugins {
[Info("Auto Engine Parts", "WhiteThunder", "1.0.0")]
[Description("Ensures modular car engines always have engine parts which players cannot remove.")]
internal class AutoEngineParts : CovalencePlugin {
#region Fields
[PluginReference]
private Plugin EnginePartsDurability;
private const string PermissionTier1 = "autoengineparts.tier1";
private const string PermissionTier2 = "autoengineparts.tier2";
private const string PermissionTier3 = "autoengineparts.tier3";
private const float VanillaInternalDamageMultiplier = 0.5f;
private Configuration _pluginConfig;
#endregion
#region Hooks
private void Init() {
permission.RegisterPermission(PermissionTier1, this);
permission.RegisterPermission(PermissionTier2, this);
permission.RegisterPermission(PermissionTier3, this);
Unsubscribe(nameof(OnEntitySpawned));
}
private void OnServerInitialized() {
foreach (var entity in BaseNetworkable.serverEntities) MaybeUpdateEngineModule(entity as BaseVehicleModule, dropExistingParts : true);
Subscribe(nameof(OnEntitySpawned));
}
private void Unload() {
foreach (var entity in BaseNetworkable.serverEntities) {
var car = entity as ModularCar;
if (car == null) continue;
foreach (var module in car.AttachedModuleEntities) {
var engineStorage = GetEngineStorage(module);
if (engineStorage == null) continue;
MaybeUpdateEngineStorage(engineStorage, 0, dropExistingParts : false);
}
}
}
private object OnVehicleModuleMove(VehicleModuleEngine module) {
var engineStorage = GetEngineStorage(module);
if (engineStorage == null) return null;
if (!IsLocked(engineStorage)) return null;
// Return true to force the module to be moved even though it probably has items.// The items will be removed in the OnEntityKill hook.return true;
}
private void OnEntityKill(VehicleModuleEngine module) {
var engineStorage = GetEngineStorage(module);
if (engineStorage == null) return;
if (!IsLocked(engineStorage)) return;
// Kill the inventory to remove all items.engineStorage.inventory.Kill();
}
private void OnEntitySpawned(VehicleModuleEngine module) {
// Delaying is necessary since the storage container is created immediately after this hook is called.NextTick(() => MaybeUpdateEngineModule(module, dropExistingParts: false));
}
private object OnItemLock(Item item) {
var car = item.parent?.entityOwner as ModularCar;
if (car == null) return null;
var engineStorage = GetEngineStorage(car.GetModuleForItem(item));
if (engineStorage == null) return null;
if (!IsLocked(engineStorage)) return null;
// Return true (standard) to cancel default behavior.// This prevents the checkered appearance and allows the item to be dropped.return true;
}
// This hook is exposed by Engine Parts Durability (EnginePartsDurability).private object OnEngineDamageMultiplierChange(EngineStorage engineStorage, float desiredMultiplier)
{
// The inventory being locked indicates that this plugin is likely controlling the multiplier.if (engineStorage.inventory.IsLocked())
return false;
return null;
}
// This hook is exposed by plugin: Claim Vehicle Ownership (ClaimVehicle).private void OnVehicleOwnershipChanged(ModularCar car)
{
var enginePartsTier = GetOwnerEnginePartsTier(car.OwnerID);
foreach (var module in car.AttachedModuleEntities) {
var engineStorage = GetEngineStorage(module);
if (engineStorage != null) MaybeUpdateEngineStorage(engineStorage, enginePartsTier, dropExistingParts : true);
}
}
#endregion
#region Helper Methods
private static bool UpdateEngineStorageWasBlocked(EngineStorage engineStorage, int tier) {
object hookResult = Interface.CallHook("OnEngineStorageFill", engineStorage, tier);
return hookResult is bool && (bool)hookResult == false;
}
private static ulong GetCarOwnerId(BaseVehicleModule module) {
var car = module.Vehicle as ModularCar;
return car == null ? 0 : car.OwnerID;
}
private static EngineStorage GetEngineStorage(BaseVehicleModule module) {
var engineModule = module as VehicleModuleEngine;
if (engineModule == null) return null;
return engineModule.GetContainer() as EngineStorage;
}
private static bool IsLocked(EngineStorage engineStorage) => engineStorage.inventory.IsLocked();
private static void RemoveAllEngineParts(EngineStorage engineStorage) {
for (var slot = 0; slot < engineStorage.inventory.capacity; slot++) {
var item = engineStorage.inventory.GetSlot(slot);
if (item != null) {
item.RemoveFromContainer();
item.Remove();
}
}
}
private static bool TryAddEngineItem(EngineStorage engineStorage, int slot, int tier) {
ItemModEngineItem output;
if (!engineStorage.allEngineItems.TryGetItem(tier, engineStorage.slotTypes[slot], out output)) return false;
var component = output.GetComponent<ItemDefinition>();
var item = ItemManager.Create(component);
if (item == null) return false;
if (!item.MoveToContainer(engineStorage.inventory, slot, allowStack : false)) {
item.Remove();
return false;
}
return true;
}
private static int Clamp(int x, int min, int max) => Math.Max(min, Math.Min(x, max));
private void MaybeUpdateEngineModule(BaseVehicleModule module, bool dropExistingParts) {
var engineStorage = GetEngineStorage(module);
if (engineStorage == null) return;
var enginePartsTier = GetOwnerEnginePartsTier(GetCarOwnerId(module));
MaybeUpdateEngineStorage(engineStorage, enginePartsTier, dropExistingParts);
}
private void MaybeUpdateEngineStorage(EngineStorage engineStorage, int enginePartsTier, bool dropExistingParts) {
if (!UpdateEngineStorageWasBlocked(engineStorage, enginePartsTier)) UpdateEngineStorage(engineStorage, enginePartsTier, dropExistingParts);
}
private void UpdateEngineStorage(EngineStorage engineStorage, int desiredTier, bool dropExistingParts) {
var inventory = engineStorage.inventory;
var wasLocked = inventory.IsLocked();
var hasEngineParts = !inventory.IsEmpty();
// If the storage was already locked, we assume the parts were free so we can safely delete them.// This may be less efficient than keeping/repairing the parts if they match the desired tier, but this keeps it simple.if (hasEngineParts && wasLocked)
RemoveAllEngineParts(engineStorage);
if (desiredTier <= 0) {
if (wasLocked) inventory.SetLocked(false);
ResetInternalDamageMultiplier(engineStorage);
return;
}
if (hasEngineParts && !wasLocked) {
// The existing engine parts must be removed to add new ones.if (dropExistingParts)
{
// Drop existing engine parts because players may have worked hard for them.engineStorage.DropItems();
}
else RemoveAllEngineParts(engineStorage);
}
// This must be set first because adding items will trigger the OnItemLock hook which needs to see this flag.inventory.SetLocked(true);
var inventoryFilled = true;
for (var slot = 0; slot < inventory.capacity; slot++) {
if (!TryAddEngineItem(engineStorage, slot, desiredTier)) inventoryFilled = false;
}
if (!inventoryFilled) {
// Something went wrong when filling the inventory, so unlock it to allow parts to be added.inventory.SetLocked(false);
return;
}
// Prevent engine parts from taking damage since the player has no way to repair them while the inventory is locked.engineStorage.internalDamageMultiplier = 0;
}
private void ResetInternalDamageMultiplier(EngineStorage engineStorage) {
if (EnginePartsDurability != null) EnginePartsDurability.Call("API_RefreshMultiplier", engineStorage);
else engineStorage.internalDamageMultiplier = VanillaInternalDamageMultiplier;
}
private int GetOwnerEnginePartsTier(ulong ownerId) {
var defaultTier = _pluginConfig.DefaultEnginePartsTier;
if (ownerId == 0) return defaultTier;
var ownerIdString = ownerId.ToString();
if (permission.UserHasPermission(ownerIdString, PermissionTier3)) return 3;
else if (permission.UserHasPermission(ownerIdString, PermissionTier2)) return Math.Max(2, defaultTier);
else if (permission.UserHasPermission(ownerIdString, PermissionTier1)) return Math.Max(1, defaultTier);
else return defaultTier;
}
#endregion
#region Configuration
private class Configuration : SerializableConfiguration {
private int _defaultEnginePartsTier = 0;
[JsonProperty("DefaultEnginePartsTier")]
public int DefaultEnginePartsTier {
get {
return _defaultEnginePartsTier;
}
set {
_defaultEnginePartsTier = Clamp(value, 0, 3);
}
}
}
private Configuration GetDefaultConfig() => new Configuration();
#endregion
#region Configuration Boilerplate
private class SerializableConfiguration {
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
}
private static class JsonHelper {
public static object Deserialize(string json) => ToObject(JToken.Parse(json));
private static object ToObject(JToken token) {
switch (token.Type) {
case JTokenType.Object : return token.Children<JProperty>().ToDictionary(prop => prop.Name, prop => ToObject(prop.Value));
case JTokenType.Array : return token.Select(ToObject).ToList();
default : return ((JValue)token).Value;
}
}
}
private bool MaybeUpdateConfig(SerializableConfiguration config) {
var currentWithDefaults = config.ToDictionary();
var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value);
return MaybeUpdateConfigDict(currentWithDefaults, currentRaw);
}
private bool MaybeUpdateConfigDict(Dictionary<string, object> currentWithDefaults, Dictionary<string, object> currentRaw) {
bool changed = false;
foreach (var key in currentWithDefaults.Keys) {
object currentRawValue;
if (currentRaw.TryGetValue(key, out currentRawValue)) {
var defaultDictValue = currentWithDefaults[key] as Dictionary<string, object>;
var currentDictValue = currentRawValue as Dictionary<string, object>;
if (defaultDictValue != null) {
if (currentDictValue == null) {
currentRaw[key] = currentWithDefaults[key];
changed = true;
}
else if (MaybeUpdateConfigDict(defaultDictValue, currentDictValue)) changed = true;
}
}
else {
currentRaw[key] = currentWithDefaults[key];
changed = true;
}
}
return changed;
}
protected override void LoadDefaultConfig() => _pluginConfig = GetDefaultConfig();
protected override void LoadConfig() {
base.LoadConfig();
try {
_pluginConfig = Config.ReadObject<Configuration>();
if (_pluginConfig == null) {
throw new JsonException();
}
if (MaybeUpdateConfig(_pluginConfig)) {
LogWarning("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch {
LogWarning($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig() {
Log($"Configuration changes saved to {Name}.json");
Config.WriteObject(_pluginConfig, true);
}
#endregion
}
}