using System; using System.Collections.Generic; using System.Linq; using BepInEx.Configuration; using HarmonyLib; namespace BanquetForCyka { public interface IFieldModifier { void CaptureOriginal(); void Apply(); void Restore(); void LogValue(string prefix); } /// /// Represents a field that can be multiplied by a configurable value /// /// The type of the field value /// The type of the multiplier public class FieldConfiguration { private string _fieldName; private ConfigEntry _defaultMultiplier; private Func> _conditionalMultiplier; private Func _applyCondition; public string FieldName { get { return _fieldName; } set { _fieldName = value; } } public ConfigEntry DefaultMultiplier { get { return _defaultMultiplier; } set { _defaultMultiplier = value; } } public Func> ConditionalMultiplier { get { return _conditionalMultiplier; } set { _conditionalMultiplier = value; } } public Func ApplyCondition { get { return _applyCondition; } set { _applyCondition = value; } } public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier) { _fieldName = fieldName; _defaultMultiplier = defaultMultiplier; } public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier, Func> conditionalMultiplier) { _fieldName = fieldName; _defaultMultiplier = defaultMultiplier; _conditionalMultiplier = conditionalMultiplier; } public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier, Func applyCondition) { _fieldName = fieldName; _defaultMultiplier = defaultMultiplier; _applyCondition = applyCondition; } public FieldConfiguration(string fieldName, ConfigEntry defaultMultiplier, Func> conditionalMultiplier, Func applyCondition) { _fieldName = fieldName; _defaultMultiplier = defaultMultiplier; _conditionalMultiplier = conditionalMultiplier; _applyCondition = applyCondition; } public ConfigEntry GetMultiplier(object __instance) { if (_conditionalMultiplier == null) { return _defaultMultiplier; } return _conditionalMultiplier(__instance); } public bool ShouldApply(object __instance) { if (_applyCondition == null) { return true; } return _applyCondition(__instance); } } public class MultipliedField : IFieldModifier { private readonly string _fieldName; private readonly ConfigEntry _multiplier; private readonly Traverse _parentTraverse; private readonly Func _applyCondition; private TField _originalValue; public string FieldName { get { return _fieldName; } } public MultipliedField(string fieldName, ConfigEntry multiplier, Traverse parentTraverse, Func applyCondition = null) { _fieldName = fieldName; _multiplier = multiplier; _parentTraverse = parentTraverse; _applyCondition = applyCondition; 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 { if (_applyCondition != null && !_applyCondition(_parentTraverse.GetValue())) { if (Main.debug.Value) Console.WriteLine("Skipping {0}: condition not met", _fieldName); return; } 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 _value; private Func> _conditionalValue; private Func _applyCondition; public string FieldName { get { return _fieldName; } set { _fieldName = value; } } public ConfigEntry Value { get { return _value; } set { _value = value; } } public Func> ConditionalValue { get { return _conditionalValue; } set { _conditionalValue = value; } } public Func ApplyCondition { get { return _applyCondition; } set { _applyCondition = value; } } public BooleanFieldConfiguration(string fieldName, ConfigEntry value) { _fieldName = fieldName; _value = value; } public BooleanFieldConfiguration(string fieldName, ConfigEntry value, Func> conditionalValue) { _fieldName = fieldName; _value = value; _conditionalValue = conditionalValue; } public BooleanFieldConfiguration(string fieldName, ConfigEntry value, Func applyCondition) { _fieldName = fieldName; _value = value; _applyCondition = applyCondition; } public BooleanFieldConfiguration(string fieldName, ConfigEntry value, Func> conditionalValue, Func applyCondition) { _fieldName = fieldName; _value = value; _conditionalValue = conditionalValue; _applyCondition = applyCondition; } public ConfigEntry GetValue(object __instance) { if (_conditionalValue == null) { return _value; } return _conditionalValue(__instance); } public bool ShouldApply(object __instance) { if (_applyCondition == null) { return true; } return _applyCondition(__instance); } } public class BooleanField : IFieldModifier { private readonly string _fieldName; private readonly ConfigEntry _value; private readonly Traverse _parentTraverse; private readonly Func _applyCondition; private bool _originalValue; public string FieldName { get { return _fieldName; } } public BooleanField(string fieldName, ConfigEntry value, Traverse parentTraverse, Func applyCondition = null) { _fieldName = fieldName; _value = value; _parentTraverse = parentTraverse; _applyCondition = applyCondition; 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 (_applyCondition != null && !_applyCondition(_parentTraverse.GetValue())) { if (Main.debug.Value) Console.WriteLine("Skipping {0}: condition not met", _fieldName); return; } if (_value.Value) { if (Main.debug.Value) Console.WriteLine("Applying to {0}: forcing to true", _fieldName); SetValue(true); } else { if (Main.debug.Value) Console.WriteLine("Applying to {0}: leaving as {1} (config is false)", _fieldName, GetValue()); } } 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}, config: {5})", prefix, _parentTraverse, _fieldName, currentValue, _originalValue, _value.Value); } } /// /// Represents an object with multiple fields that can be multiplied /// /// The type of the object being managed public class MultipliedObject { private readonly T _instance; private readonly Traverse _objectTraverse; private readonly Dictionary _fields; public MultipliedObject(T __instance) { _instance = __instance; _objectTraverse = Traverse.Create(__instance); _fields = new Dictionary(); } public void AddField(FieldConfiguration config) { var multiplier = config.GetMultiplier(_instance); _fields[config.FieldName] = new MultipliedField(config.FieldName, multiplier, _objectTraverse, config.ShouldApply); } public void AddBooleanField(BooleanFieldConfiguration config) { var value = config.GetValue(_instance); _fields[config.FieldName] = new BooleanField(config.FieldName, value, _objectTraverse, config.ShouldApply); } public void CaptureFrom() { foreach (var field in _fields.Values) { field.CaptureOriginal(); } } public void ApplyTo(IEnumerable fieldNames = null) { IEnumerable fieldsToApply = fieldNames ?? _fields.Keys; foreach (var fieldName in fieldsToApply.Where(name => _fields.ContainsKey(name))) _fields[fieldName].Apply(); } public void RestoreTo(IEnumerable fieldNames = null) { IEnumerable 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); } } } /// /// Manages a collection of objects whose fields can be multiplied /// /// The type of objects being managed public class MultipliedObjectManager { private readonly Dictionary> _managedObjects; private readonly Action> _configureObject; public MultipliedObjectManager(Action> configureObject) { if (configureObject == null) throw new ArgumentNullException("configureObject"); _configureObject = configureObject; _managedObjects = new Dictionary>(); } private void SafeRemove(T __instance) { if (__instance == null) return; try { _managedObjects.Remove(__instance); } catch (Exception e) { Console.WriteLine("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(__instance); _configureObject(multipliedObject); multipliedObject.CaptureFrom(); _managedObjects.Add(__instance, multipliedObject); multipliedObject.LogValues("Patching"); ApplyTo(__instance); multipliedObject.LogValues("Patched"); } catch (Exception e) { Console.WriteLine("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 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("Error restoring values: {0}", e); } SafeRemove(__instance); } } catch (Exception e) { Console.WriteLine("Error in OnObjectDetached: {0}", e); } } public void ApplyAll(IEnumerable 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("Error applying to __instance: {0}", e); } } } public void ApplyTo(T __instance, IEnumerable fieldNames = null) { if (Main.debug.Value) Console.WriteLine("Applying {0}", typeof(T).Name); if (__instance == null) return; try { MultipliedObject obj; if (_managedObjects.TryGetValue(__instance, out obj)) obj.ApplyTo(fieldNames); } catch (Exception e) { Console.WriteLine("Error in ApplyTo: {0}", e); } } public void RestoreTo(T __instance, IEnumerable fieldNames = null) { if (Main.debug.Value) Console.WriteLine("Restoring {0}", typeof(T).Name); if (__instance == null) return; try { MultipliedObject obj; if (_managedObjects.TryGetValue(__instance, out obj)) obj.RestoreTo(fieldNames); } catch (Exception e) { Console.WriteLine("Error in RestoreTo: {0}", e); } } } }