C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Oxide.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using Rust.Modular;
namespace Oxide.Plugins
{
[Info("No Engine Parts", "WhiteThunder", "1.0.0")]
[Description("Allows modular cars to be driven without engine parts.")]
internal class NoEngineParts : CovalencePlugin
{
#region Fields
private const string PermissionPresetPrefix = "noengineparts.preset";
private Configuration pluginConfig;
#endregion
#region Hooks
private void Init()
{
foreach (var preset in pluginConfig.presetsRequiringPermission)
if (!string.IsNullOrWhiteSpace(preset.name))
permission.RegisterPermission(GetPresetPermission(preset.name), this);
}
private void OnEntityMounted(ModularCarSeat seat)
{
var car = seat.associatedSeatingModule.Vehicle as ModularCar;
if (car == null)
return;
// Only refresh engine loadout if engine cannot be started, else handle in OnEngineStarted
if (car.HasDriver() && !car.HasAnyWorkingEngines())
RefreshCarEngineLoadouts(car);
}
private void OnEngineStarted(ModularCar car) =>
RefreshCarEngineLoadouts(car);
private object OnEngineLoadoutRefresh(EngineStorage engineStorage)
{
var enginePreset = DetermineEnginePresetForStorage(engineStorage);
if (enginePreset == null)
return null;
if (TryRefreshEngineLoadout(engineStorage, enginePreset))
return false;
return null;
}
#endregion
#region Helper Methods
private bool OverrideLoadoutWasBlocked(EngineStorage engineStorage)
{
object hookResult = Interface.CallHook("OnEngineLoadoutOverride", engineStorage);
return hookResult is bool && (bool)hookResult == false;
}
private string GetPresetPermission(string presetName) => $"{PermissionPresetPrefix}.{presetName}";
private void RefreshCarEngineLoadouts(ModularCar car)
{
var enginePreset = DetermineEnginePresetForOwner(car.OwnerID);
if (enginePreset == null)
return;
foreach (var module in car.AttachedModuleEntities)
{
var engineModule = module as VehicleModuleEngine;
if (engineModule == null)
continue;
var engineStorage = engineModule.GetContainer() as EngineStorage;
if (engineStorage == null)
continue;
TryRefreshEngineLoadout(engineStorage, enginePreset);
}
}
private bool TryRefreshEngineLoadout(EngineStorage engineStorage, EnginePreset enginePreset)
{
if (OverrideLoadoutWasBlocked(engineStorage))
return false;
var acceleration = 0f;
var topSpeed = 0f;
var fuelEconomy = 0f;
for (var slot = 0; slot < engineStorage.inventory.capacity; slot++)
{
var engineItemType = engineStorage.slotTypes[slot];
var item = engineStorage.inventory.GetSlot(slot);
var itemValue = 0f;
if (item != null && !item.isBroken)
{
var component = item.info.GetComponent<ItemModEngineItem>();
if (component != null)
itemValue = item.amount * engineStorage.GetTierValue(component.tier);
}
if (engineItemType.BoostsAcceleration())
acceleration += Math.Max(itemValue, enginePreset.acceleration);
if (engineItemType.BoostsFuelEconomy())
fuelEconomy += Math.Max(itemValue, enginePreset.fuelEconomy);
if (engineItemType.BoostsTopSpeed())
topSpeed += Math.Max(itemValue, enginePreset.topSpeed);
}
engineStorage.isUsable = acceleration > 0 && topSpeed > 0 && fuelEconomy > 0;
engineStorage.accelerationBoostPercent = acceleration / engineStorage.accelerationBoostSlots;
engineStorage.fuelEconomyBoostPercent = fuelEconomy / engineStorage.fuelEconomyBoostSlots;
engineStorage.topSpeedBoostPercent = topSpeed / engineStorage.topSpeedBoostSlots;
engineStorage.SendNetworkUpdate();
engineStorage.GetEngineModule()?.RefreshPerformanceStats(engineStorage);
return true;
}
#endregion
#region Configuration
private EnginePreset DetermineEnginePresetForStorage(EngineStorage engineStorage)
{
var car = engineStorage.GetEngineModule()?.Vehicle as ModularCar;
if (car == null)
return pluginConfig.defaultPreset;
return DetermineEnginePresetForOwner(car.OwnerID);
}
private EnginePreset DetermineEnginePresetForOwner(ulong ownerId)
{
if (ownerId == 0 || pluginConfig.presetsRequiringPermission == null)
return pluginConfig.defaultPreset;
var ownerIdString = ownerId.ToString();
for (var i = pluginConfig.presetsRequiringPermission.Length - 1; i >= 0; i--)
{
var preset = pluginConfig.presetsRequiringPermission[i];
if (!string.IsNullOrWhiteSpace(preset.name) &&
permission.UserHasPermission(ownerIdString, GetPresetPermission(preset.name)))
{
return preset;
}
}
return pluginConfig.defaultPreset;
}
private Configuration GetDefaultConfig() => new Configuration();
internal class Configuration : SerializableConfiguration
{
[JsonProperty("DefaultPreset")]
public EnginePreset defaultPreset = new EnginePreset()
{
acceleration = 0.3f,
topSpeed = 0.3f,
fuelEconomy = 0.3f,
};
[JsonProperty("PresetsRequiringPermission")]
public EnginePreset[] presetsRequiringPermission = new EnginePreset[]
{
new EnginePreset ()
{
name = "tier1",
acceleration = 0.6f,
topSpeed = 0.6f,
fuelEconomy = 0.6f,
},
new EnginePreset ()
{
name = "tier2",
acceleration = 0.8f,
topSpeed = 0.8f,
fuelEconomy = 0.8f,
},
new EnginePreset ()
{
name = "tier3",
acceleration = 1,
topSpeed = 1,
fuelEconomy = 1,
},
new EnginePreset ()
{
name = "tier4",
acceleration = 2,
topSpeed = 2,
fuelEconomy = 2,
},
new EnginePreset()
{
name = "tier5",
acceleration = 3,
topSpeed = 3,
fuelEconomy = 3,
},
new EnginePreset()
{
name = "tier6",
acceleration = 4,
topSpeed = 4,
fuelEconomy = 4,
},
};
}
internal class EnginePreset
{
[JsonProperty("Name", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string name;
[JsonProperty("Acceleration")]
public float acceleration;
[JsonProperty("TopSpeed")]
public float topSpeed;
[JsonProperty("FuelEconomy")]
public float fuelEconomy;
}
#endregion
#region Configuration Boilerplate
internal class SerializableConfiguration
{
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
}
internal 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
}
}