454 lines
17 KiB
C#
454 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using BepInEx.Configuration;
|
|
using HarmonyLib;
|
|
|
|
namespace TerraTech {
|
|
public interface IFieldModifier {
|
|
void CaptureOriginal();
|
|
void Apply();
|
|
void Restore();
|
|
void LogValue(string prefix);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a field that can be multiplied by a configurable value
|
|
/// </summary>
|
|
/// <typeparam name="TField">The type of the field value</typeparam>
|
|
/// <typeparam name="TMul">The type of the multiplier</typeparam>
|
|
public class FieldConfiguration<TField, TMul> {
|
|
private string _fieldName;
|
|
private ConfigEntry<TMul> _defaultMultiplier;
|
|
private Func<object, ConfigEntry<TMul>> _conditionalMultiplier;
|
|
|
|
public string FieldName {
|
|
get { return _fieldName; }
|
|
set { _fieldName = value; }
|
|
}
|
|
|
|
public ConfigEntry<TMul> DefaultMultiplier {
|
|
get { return _defaultMultiplier; }
|
|
set { _defaultMultiplier = value; }
|
|
}
|
|
|
|
public Func<object, ConfigEntry<TMul>> ConditionalMultiplier {
|
|
get { return _conditionalMultiplier; }
|
|
set { _conditionalMultiplier = value; }
|
|
}
|
|
|
|
public FieldConfiguration(string fieldName, ConfigEntry<TMul> defaultMultiplier) {
|
|
_fieldName = fieldName;
|
|
_defaultMultiplier = defaultMultiplier;
|
|
}
|
|
|
|
public FieldConfiguration(string fieldName, ConfigEntry<TMul> defaultMultiplier,
|
|
Func<object, ConfigEntry<TMul>> conditionalMultiplier) {
|
|
_fieldName = fieldName;
|
|
_defaultMultiplier = defaultMultiplier;
|
|
_conditionalMultiplier = conditionalMultiplier;
|
|
}
|
|
|
|
public ConfigEntry<TMul> GetMultiplier(object instance) {
|
|
if (_conditionalMultiplier == null) {
|
|
return _defaultMultiplier;
|
|
}
|
|
return _conditionalMultiplier(instance);
|
|
}
|
|
}
|
|
|
|
public class MultipliedField<TField, TMul> : IFieldModifier {
|
|
private readonly string _fieldName;
|
|
private readonly ConfigEntry<TMul> _multiplier;
|
|
private readonly Traverse _parentTraverse;
|
|
private TField _originalValue;
|
|
|
|
public string FieldName {
|
|
get { return _fieldName; }
|
|
}
|
|
|
|
public MultipliedField(string fieldName, ConfigEntry<TMul> multiplier, Traverse parentTraverse) {
|
|
_fieldName = fieldName;
|
|
_multiplier = multiplier;
|
|
_parentTraverse = parentTraverse;
|
|
|
|
if (!parentTraverse.Field(fieldName).FieldExists()) {
|
|
throw new ArgumentException(
|
|
string.Format("Field {0} does not exist on {1}", fieldName, parentTraverse));
|
|
}
|
|
|
|
// Verify TField is a numeric type
|
|
if (!IsNumericType(typeof(TField))) {
|
|
throw new ArgumentException(
|
|
string.Format("Field type {0} must be a numeric type", typeof(TField).Name));
|
|
}
|
|
|
|
// Verify TMul is a numeric type
|
|
if (!IsNumericType(typeof(TMul))) {
|
|
throw new ArgumentException(
|
|
string.Format("Multiplier type {0} must be a numeric type", typeof(TMul).Name));
|
|
}
|
|
}
|
|
|
|
private static bool IsNumericType(Type type) {
|
|
return type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort) ||
|
|
type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) ||
|
|
type == typeof(float) || type == typeof(double) || type == typeof(decimal);
|
|
}
|
|
|
|
private TField MultiplyValues(TField fieldValue, TMul multiplierValue) {
|
|
// Convert both to double for the multiplication
|
|
double fieldDouble = Convert.ToDouble(fieldValue);
|
|
double multiplierDouble = Convert.ToDouble(multiplierValue);
|
|
double result = fieldDouble * multiplierDouble;
|
|
// Convert back to TField
|
|
return (TField)Convert.ChangeType(result, typeof(TField));
|
|
}
|
|
|
|
public TField GetValue() {
|
|
var value = _parentTraverse.Field(_fieldName).GetValue();
|
|
if (value == null)
|
|
throw new InvalidOperationException(string.Format("Field {0} returned null", _fieldName));
|
|
return (TField)value;
|
|
}
|
|
|
|
public void SetValue(TField value) {
|
|
_parentTraverse.Field(_fieldName).SetValue(value);
|
|
var verifyValue = GetValue();
|
|
if (!verifyValue.Equals(value))
|
|
throw new InvalidOperationException(
|
|
string.Format("Field {0} set to {1} but read back as {2}", _fieldName, value, verifyValue));
|
|
}
|
|
|
|
public void CaptureOriginal() {
|
|
_originalValue = GetValue();
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Captured original value for {0}: {1}", _fieldName, _originalValue);
|
|
}
|
|
|
|
public void Apply() {
|
|
try {
|
|
var newValue = MultiplyValues(_originalValue, _multiplier.Value);
|
|
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Applying to {0}: {1} * {2} = {3}", _fieldName, _originalValue, _multiplier.Value,
|
|
newValue);
|
|
|
|
SetValue(newValue);
|
|
} catch (Exception e) {
|
|
throw new InvalidOperationException(string.Format("Failed to apply multiplication to {0}", _fieldName),
|
|
e);
|
|
}
|
|
}
|
|
|
|
public void Restore() {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Restoring {0} to original value: {1}", _fieldName, _originalValue);
|
|
SetValue(_originalValue);
|
|
}
|
|
|
|
public void LogValue(string prefix) {
|
|
if (!Main.debug.Value)
|
|
return;
|
|
var currentValue = GetValue();
|
|
Console.WriteLine("{0} {1}; {2}: {3} (original: {4}, multiplier: {5})", prefix, _parentTraverse, _fieldName,
|
|
currentValue, _originalValue, _multiplier.Value);
|
|
}
|
|
}
|
|
|
|
public class BooleanFieldConfiguration {
|
|
private string _fieldName;
|
|
private ConfigEntry<bool> _value;
|
|
private Func<object, ConfigEntry<bool>> _conditionalValue;
|
|
|
|
public string FieldName {
|
|
get { return _fieldName; }
|
|
set { _fieldName = value; }
|
|
}
|
|
|
|
public ConfigEntry<bool> Value {
|
|
get { return _value; }
|
|
set { _value = value; }
|
|
}
|
|
|
|
public Func<object, ConfigEntry<bool>> ConditionalValue {
|
|
get { return _conditionalValue; }
|
|
set { _conditionalValue = value; }
|
|
}
|
|
|
|
public BooleanFieldConfiguration(string fieldName, ConfigEntry<bool> value) {
|
|
_fieldName = fieldName;
|
|
_value = value;
|
|
}
|
|
|
|
public BooleanFieldConfiguration(string fieldName, ConfigEntry<bool> value,
|
|
Func<object, ConfigEntry<bool>> conditionalValue) {
|
|
_fieldName = fieldName;
|
|
_value = value;
|
|
_conditionalValue = conditionalValue;
|
|
}
|
|
|
|
public ConfigEntry<bool> GetValue(object instance) {
|
|
if (_conditionalValue == null) {
|
|
return _value;
|
|
}
|
|
return _conditionalValue(instance);
|
|
}
|
|
}
|
|
|
|
public class BooleanField : IFieldModifier {
|
|
private readonly string _fieldName;
|
|
private readonly ConfigEntry<bool> _value;
|
|
private readonly Traverse _parentTraverse;
|
|
private bool _originalValue;
|
|
|
|
public string FieldName {
|
|
get { return _fieldName; }
|
|
}
|
|
|
|
public BooleanField(string fieldName, ConfigEntry<bool> value, Traverse parentTraverse) {
|
|
_fieldName = fieldName;
|
|
_value = value;
|
|
_parentTraverse = parentTraverse;
|
|
|
|
if (!parentTraverse.Field(fieldName).FieldExists()) {
|
|
throw new ArgumentException(
|
|
string.Format("Field {0} does not exist on {1}", fieldName, parentTraverse));
|
|
}
|
|
}
|
|
|
|
public bool GetValue() {
|
|
var value = _parentTraverse.Field(_fieldName).GetValue();
|
|
if (value == null)
|
|
throw new InvalidOperationException(string.Format("Field {0} returned null", _fieldName));
|
|
return (bool)value;
|
|
}
|
|
|
|
public void SetValue(bool value) {
|
|
_parentTraverse.Field(_fieldName).SetValue(value);
|
|
var verifyValue = GetValue();
|
|
if (verifyValue != value)
|
|
throw new InvalidOperationException(
|
|
string.Format("Field {0} set to {1} but read back as {2}", _fieldName, value, verifyValue));
|
|
}
|
|
|
|
public void CaptureOriginal() {
|
|
_originalValue = GetValue();
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Captured original value for {0}: {1}", _fieldName, _originalValue);
|
|
}
|
|
|
|
public void Apply() {
|
|
try {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Applying to {0}: setting to {1}", _fieldName, _value.Value);
|
|
|
|
SetValue(_value.Value);
|
|
} catch (Exception e) {
|
|
throw new InvalidOperationException(string.Format("Failed to apply value to {0}", _fieldName), e);
|
|
}
|
|
}
|
|
|
|
public void Restore() {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Restoring {0} to original value: {1}", _fieldName, _originalValue);
|
|
SetValue(_originalValue);
|
|
}
|
|
|
|
public void LogValue(string prefix) {
|
|
if (!Main.debug.Value)
|
|
return;
|
|
var currentValue = GetValue();
|
|
Console.WriteLine("{0} {1}; {2}: {3} (original: {4}, value: {5})", prefix, _parentTraverse, _fieldName,
|
|
currentValue, _originalValue, _value.Value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents an object with multiple fields that can be multiplied
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the object being managed</typeparam>
|
|
public class MultipliedObject<T> {
|
|
private readonly T _instance;
|
|
private readonly Traverse _objectTraverse;
|
|
private readonly Dictionary<string, IFieldModifier> _fields;
|
|
|
|
public MultipliedObject(T instance) {
|
|
_instance = instance;
|
|
_objectTraverse = Traverse.Create(instance);
|
|
_fields = new Dictionary<string, IFieldModifier>();
|
|
}
|
|
|
|
public void AddField<TField, TMul>(FieldConfiguration<TField, TMul> config) {
|
|
var multiplier = config.GetMultiplier(_instance);
|
|
_fields[config.FieldName] =
|
|
new MultipliedField<TField, TMul>(config.FieldName, multiplier, _objectTraverse);
|
|
}
|
|
|
|
public void AddBooleanField(BooleanFieldConfiguration config) {
|
|
var value = config.GetValue(_instance);
|
|
_fields[config.FieldName] = new BooleanField(config.FieldName, value, _objectTraverse);
|
|
}
|
|
|
|
public void CaptureFrom() {
|
|
foreach (var field in _fields.Values) {
|
|
field.CaptureOriginal();
|
|
}
|
|
}
|
|
|
|
public void ApplyTo(IEnumerable<string> fieldNames = null) {
|
|
IEnumerable<string> fieldsToApply = fieldNames ?? _fields.Keys;
|
|
foreach (var fieldName in fieldsToApply.Where(name => _fields.ContainsKey(name))) {
|
|
_fields[fieldName].Apply();
|
|
}
|
|
}
|
|
|
|
public void RestoreTo(IEnumerable<string> fieldNames = null) {
|
|
IEnumerable<string> fieldsToRestore = fieldNames ?? _fields.Keys;
|
|
foreach (var fieldName in fieldsToRestore.Where(name => _fields.ContainsKey(name))) {
|
|
_fields[fieldName].Restore();
|
|
}
|
|
}
|
|
|
|
public void LogValues(string prefix) {
|
|
if (!Main.debug.Value)
|
|
return;
|
|
foreach (var field in _fields.Values) {
|
|
field.LogValue(prefix);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manages a collection of objects whose fields can be multiplied
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of objects being managed</typeparam>
|
|
public class MultipliedObjectManager<T> {
|
|
private readonly Dictionary<T, MultipliedObject<T>> _managedObjects;
|
|
private readonly Action<MultipliedObject<T>> _configureObject;
|
|
|
|
public MultipliedObjectManager(Action<MultipliedObject<T>> configureObject) {
|
|
if (configureObject == null)
|
|
throw new ArgumentNullException("configureObject");
|
|
|
|
_configureObject = configureObject;
|
|
_managedObjects = new Dictionary<T, MultipliedObject<T>>();
|
|
}
|
|
|
|
private void SafeRemove(T instance) {
|
|
if (instance == null)
|
|
return;
|
|
|
|
try {
|
|
_managedObjects.Remove(instance);
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error removing instance from _managedObjects: {0}", e));
|
|
}
|
|
}
|
|
|
|
public void OnObjectAttached(T instance) {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("{0}.OnAttached", typeof(T).Name);
|
|
|
|
if (instance == null) {
|
|
Console.WriteLine("Attempted to attach null instance");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (_managedObjects.ContainsKey(instance)) {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("{0} already managed, skipping", typeof(T).Name);
|
|
return;
|
|
}
|
|
|
|
var multipliedObject = new MultipliedObject<T>(instance);
|
|
_configureObject(multipliedObject);
|
|
multipliedObject.CaptureFrom();
|
|
_managedObjects.Add(instance, multipliedObject);
|
|
multipliedObject.LogValues("Patching");
|
|
|
|
ApplyTo(instance);
|
|
multipliedObject.LogValues("Patched");
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error in OnObjectAttached: {0}", e));
|
|
}
|
|
}
|
|
|
|
public void OnObjectDetached(T instance) {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("{0}.OnDetaching", typeof(T).Name);
|
|
if (instance == null) {
|
|
Console.WriteLine("Attempted to detach null instance");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
MultipliedObject<T> multipliedObject;
|
|
if (_managedObjects.TryGetValue(instance, out multipliedObject)) {
|
|
if (Main.debug.Value)
|
|
multipliedObject.LogValues("Restoring");
|
|
|
|
try {
|
|
RestoreTo(instance);
|
|
multipliedObject.LogValues("Restored");
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error restoring values: {0}", e));
|
|
}
|
|
|
|
SafeRemove(instance);
|
|
}
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error in OnObjectDetached: {0}", e));
|
|
}
|
|
}
|
|
|
|
public void ApplyAll(IEnumerable<string> fieldNames = null) {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Modifying {0} {1}", _managedObjects.Count, typeof(T).Name);
|
|
|
|
// Make a copy of the keys to avoid modification during enumeration
|
|
var instances = _managedObjects.Keys.ToList();
|
|
|
|
foreach (var instance in instances) {
|
|
try {
|
|
RestoreTo(instance, fieldNames);
|
|
ApplyTo(instance, fieldNames);
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error applying to instance: {0}", e));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ApplyTo(T instance, IEnumerable<string> fieldNames = null) {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Applying {0}", typeof(T).Name);
|
|
if (instance == null)
|
|
return;
|
|
|
|
try {
|
|
MultipliedObject<T> obj;
|
|
if (_managedObjects.TryGetValue(instance, out obj))
|
|
obj.ApplyTo(fieldNames);
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error in ApplyTo: {0}", e));
|
|
}
|
|
}
|
|
|
|
public void RestoreTo(T instance, IEnumerable<string> fieldNames = null) {
|
|
if (Main.debug.Value)
|
|
Console.WriteLine("Restoring {0}", typeof(T).Name);
|
|
if (instance == null)
|
|
return;
|
|
|
|
try {
|
|
MultipliedObject<T> obj;
|
|
if (_managedObjects.TryGetValue(instance, out obj))
|
|
obj.RestoreTo(fieldNames);
|
|
} catch (Exception e) {
|
|
Console.WriteLine(String.Format("Error in RestoreTo: {0}", e));
|
|
}
|
|
}
|
|
}
|
|
}
|