Decompiled source of Enemies Balancer v0.0.1
OutwardEnemiesBalancer.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Xml; using System.Xml.Serialization; using BepInEx; using BepInEx.Logging; using HarmonyLib; using OutwardEnemiesBalancer.Balancing; using OutwardEnemiesBalancer.Balancing.Internal; using OutwardEnemiesBalancer.Balancing.Serializable; using OutwardEnemiesBalancer.Events; using OutwardEnemiesBalancer.Managers; using OutwardEnemiesBalancer.Utility.Data; using OutwardEnemiesBalancer.Utility.Enums; using OutwardEnemiesBalancer.Utility.Extensions; using OutwardEnemiesBalancer.Utility.Helpers.Static; using OutwardModsCommunicator.EventBus; using OutwardModsCommunicator.Managers; using SideLoader; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("OutwardEnemiesBalancer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("OutwardEnemiesBalancer")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("c5450fe0-edcf-483f-b9ea-4b1ef9d36da7")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace OutwardEnemiesBalancer { public class BalancingRule { public string id; public string enemyID; public string enemyName; public AreaFamily areaFamily; public AreaEnum? area; public Factions? faction; public bool isBoss; public bool isBossPawn; public bool isStoryBoss; public bool isUniqueArenaBoss; public bool isUniqueEnemy; public List<string> exceptNames; public List<string> exceptIds; public Dictionary<string, float?> statModifications; public ValueModifierType modifierType = ValueModifierType.Scale; public BalancingRule(string id = null) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(id)) { UID val = UID.Generate(); this.id = ((UID)(ref val)).Value; } else { this.id = id; } statModifications = new Dictionary<string, float?>(); } public bool Matches(Character character) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null) { return false; } UID uID; if (!string.IsNullOrEmpty(enemyID)) { uID = character.UID; return ((UID)(ref uID)).Value == enemyID; } if (!string.IsNullOrEmpty(enemyName) && !character.Name.Equals(enemyName, StringComparison.OrdinalIgnoreCase)) { return false; } if (exceptIds != null) { List<string> list = exceptIds; uID = character.UID; if (list.Contains(((UID)(ref uID)).Value)) { return false; } } if (exceptNames != null && exceptNames.Contains(character.Name)) { return false; } if (faction.HasValue && character.Faction != faction.Value) { return false; } if (area.HasValue) { Area currentArea = AreaManager.Instance.CurrentArea; Area val = AreaManager.Instance.GetArea(area.Value); if (currentArea == null || val == null || currentArea.ID != val.ID) { return false; } } if (areaFamily != null && !AreaFamiliesHelpers.DoesAreaFamilyMatch(areaFamily)) { return false; } if (isBoss) { if (!BossRegistryManager.Instance.IsBoss(character)) { return false; } return true; } if (BossRegistryManager.Instance.IsBoss(character)) { return false; } if (isUniqueArenaBoss) { if (!UniqueArenaBossesHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (BossRegistryManager.Instance.IsBossOfCategory(character, BossCategories.Arena)) { return false; } if (isStoryBoss) { if (!StoryBossesHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (BossRegistryManager.Instance.IsBossOfCategory(character, BossCategories.Story)) { return false; } if (isBossPawn) { if (!BossPawnsHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (BossRegistryManager.Instance.IsBossOfCategory(character, BossCategories.Pawn)) { return false; } UniqueEnemies result5; if (isUniqueEnemy) { if (!UniqueEnemiesHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (UniqueEnemiesHelper.Enemies.TryGetEnum(character, out result5)) { return false; } return true; } } public class FactionRule { public string id; public string enemyID; public string enemyName; public AreaFamily areaFamily; public AreaEnum? area; public Factions? targetFaction; public Factions newFaction; public bool isBoss; public bool isBossPawn; public bool isStoryBoss; public bool isUniqueArenaBoss; public bool isUniqueEnemy; public List<string> exceptNames; public List<string> exceptIds; public FactionRule(string id = null) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(id)) { UID val = UID.Generate(); this.id = ((UID)(ref val)).Value; } else { this.id = id; } exceptNames = new List<string>(); exceptIds = new List<string>(); } public bool Matches(Character character) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null) { return false; } UID uID; if (!string.IsNullOrEmpty(enemyID)) { uID = character.UID; return ((UID)(ref uID)).Value == enemyID; } if (!string.IsNullOrEmpty(enemyName) && !character.Name.Equals(enemyName, StringComparison.OrdinalIgnoreCase)) { return false; } if (exceptIds != null) { List<string> list = exceptIds; uID = character.UID; if (list.Contains(((UID)(ref uID)).Value)) { return false; } } if (exceptNames != null && exceptNames.Contains(character.Name)) { return false; } if (targetFaction.HasValue && character.Faction != targetFaction.Value) { return false; } if (area.HasValue) { Area currentArea = AreaManager.Instance.CurrentArea; Area val = AreaManager.Instance.GetArea(area.Value); if (currentArea == null || val == null || currentArea.ID != val.ID) { return false; } } if (areaFamily != null && !AreaFamiliesHelpers.DoesAreaFamilyMatch(areaFamily)) { return false; } if (isBoss) { if (!BossRegistryManager.Instance.IsBoss(character)) { return false; } return true; } if (BossRegistryManager.Instance.IsBoss(character)) { return false; } if (isUniqueArenaBoss) { if (!UniqueArenaBossesHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (BossRegistryManager.Instance.IsBossOfCategory(character, BossCategories.Arena)) { return false; } if (isStoryBoss) { if (!StoryBossesHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (BossRegistryManager.Instance.IsBossOfCategory(character, BossCategories.Story)) { return false; } if (isBossPawn) { if (!BossPawnsHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (BossRegistryManager.Instance.IsBossOfCategory(character, BossCategories.Pawn)) { return false; } UniqueEnemies result5; if (isUniqueEnemy) { if (!UniqueEnemiesHelper.Enemies.TryGetEnum(character, out var _)) { return false; } } else if (UniqueEnemiesHelper.Enemies.TryGetEnum(character, out result5)) { return false; } return true; } } [BepInPlugin("gymmed.enemies_balancer", "Enemies Balancer", "0.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class OutwardEnemiesBalancer : BaseUnityPlugin { [HarmonyPatch(typeof(ResourcesPrefabManager), "Load")] public class ResourcesPrefabManager_Load { private static void Postfix(ResourcesPrefabManager __instance) { BalancingRulesSerializer.Instance.LoadPlayerBalanceRules(); BalancingRulesSerializer.Instance.LoadFactionRules(PathsManager.DefaultBalanceRulesPath); } } public const string GUID = "gymmed.enemies_balancer"; public const string NAME = "Enemies Balancer"; public const string VERSION = "0.0.1"; public const string EVENTS_LISTENER_GUID = "gymmed.enemies_balancer_*"; public static string prefix = "[Enemies-Balancer]"; internal static ManualLogSource Log; internal void Awake() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; LogMessage("Hello world from Enemies Balancer 0.0.1!"); new Harmony("gymmed.enemies_balancer").PatchAll(); PathsManager.Initialize(); EventBusRegister.RegisterEvents(); EventBusSubscriber.AddSubscribers(); SL.OnSceneLoaded += OnSceneLoaded; } private void OnSceneLoaded() { try { CharacterBalancerManager.Instance.ApplyBalancingRules(); FactionBalancerManager.Instance.ApplyFactionRules(); } catch (Exception ex) { LogMessage("Error applying rules: " + ex.Message); } } internal void Update() { } public static void LogMessage(string message) { Log.LogMessage((object)(prefix + " " + message)); } public static void LogSL(string message) { SL.Log(prefix + " " + message); } public static string GetProjectLocation() { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } } } namespace OutwardEnemiesBalancer.Utility.Helpers { public abstract class EnemyEnumHelperBase<T> where T : Enum { protected abstract Dictionary<T, string> EnemyNames { get; } public T GetEnumFromCharacter(Character character) { if ((Object)(object)character == (Object)null) { return default(T); } foreach (KeyValuePair<T, string> enemyName in EnemyNames) { if (character.Name.Equals(enemyName.Value, StringComparison.OrdinalIgnoreCase)) { return enemyName.Key; } } return default(T); } public string GetEnemyName(T enemy) { if (!EnemyNames.TryGetValue(enemy, out var value)) { return enemy.ToString(); } return value; } } } namespace OutwardEnemiesBalancer.Utility.Helpers.Static { public static class AreaFamiliesHelpers { public static AreaFamily GetAreaFamilyByKeyWord(string keyword) { AreaFamily[] areaFamilies = AreaManager.AreaFamilies; foreach (AreaFamily val in areaFamilies) { string[] familyKeywords = val.FamilyKeywords; for (int j = 0; j < familyKeywords.Length; j++) { if (familyKeywords[j].Equals(keyword, StringComparison.OrdinalIgnoreCase)) { return val; } } } return null; } public static AreaFamily GetAreaFamilyByName(string name) { AreaFamily[] areaFamilies = AreaManager.AreaFamilies; foreach (AreaFamily val in areaFamilies) { if (val.FamilyName.Equals(name, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } public static bool DoesAreaFamilyMatch(AreaFamily family) { AreaFamily activeAreaFamily = GetActiveAreaFamily(); if (activeAreaFamily == null || family == null) { return false; } if (activeAreaFamily.FamilyName == family.FamilyName) { return true; } return false; } public static AreaFamily GetActiveAreaFamily() { AreaFamily[] areaFamilies = AreaManager.AreaFamilies; foreach (AreaFamily val in areaFamilies) { string[] familyKeywords = val.FamilyKeywords; foreach (string value in familyKeywords) { if (SceneManagerHelper.ActiveSceneName.Contains(value)) { return val; } } } return null; } } public static class AreaHelpers { public static bool IsAreaInAreaFamily(AreaEnum area) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) AreaFamily[] areaFamilies = AreaManager.AreaFamilies; for (int i = 0; i < areaFamilies.Length; i++) { string[] familyKeywords = areaFamilies[i].FamilyKeywords; foreach (string value in familyKeywords) { if (AreaManager.Instance.GetArea(area).SceneName.Contains(value)) { return true; } } } return false; } public static AreaEnum? GetAreaEnumFromAreaDefaultName(string areaName) { Area[] areas = AreaManager.Instance.Areas; foreach (Area val in areas) { if (val.DefaultName == areaName) { return (AreaEnum)val.ID; } } return null; } public static AreaEnum? GetAreaEnumFromAreaName(string areaName) { Area[] areas = AreaManager.Instance.Areas; foreach (Area val in areas) { if (val.GetName() == areaName) { return (AreaEnum)val.ID; } } return null; } public static AreaEnum GetAreaEnumFromArea(Area area) { return (AreaEnum)area.ID; } public static List<AreaEnum> GetAreasFromEnumDictionary<T>(Dictionary<T, string> locations) where T : Enum { //IL_0045: Unknown result type (might be due to invalid IL or missing references) List<AreaEnum> list = new List<AreaEnum>(); foreach (KeyValuePair<T, string> location in locations) { Area[] areas = AreaManager.Instance.Areas; foreach (Area val in areas) { if (val.GetName() == location.Value) { list.Add(GetAreaEnumFromArea(val)); } } } return list; } public static HashSet<AreaEnum> GetUniqueAreasFromEnumDictionary<T>(Dictionary<T, string> locations) where T : Enum { //IL_0045: Unknown result type (might be due to invalid IL or missing references) HashSet<AreaEnum> hashSet = new HashSet<AreaEnum>(); foreach (KeyValuePair<T, string> location in locations) { Area[] areas = AreaManager.Instance.Areas; foreach (Area val in areas) { if (val.GetName() == location.Value) { hashSet.Add(GetAreaEnumFromArea(val)); } } } return hashSet; } } public static class BalancingRuleHelpers { public static bool TryToFillRuleWithId(BalancingRule rule, EventPayload payload) { string text = payload.Get<string>(EnemyBalanceParamsHelper.Get(EnemyBalanceParams.BalanceRuleId).key, (string)null); if (!string.IsNullOrEmpty(text)) { rule.id = text; return true; } return false; } public static bool TryToFillRuleWithEnemyId(BalancingRule rule, EventPayload payload, bool enforceNonEmpty = false) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.EnemyId); string text = payload.Get<string>(tuple.Item1, (string)null); if (string.IsNullOrEmpty(text)) { if (enforceNonEmpty) { OutwardEnemiesBalancer.LogSL("BalancingRuleHelpers@TryToFillRuleWithEnemyId didn't receive " + tuple.Item1 + "!"); } return false; } rule.enemyID = text; return true; } public static bool TryToFillRuleWithEnemyName(BalancingRule rule, EventPayload payload, bool enforceNonEmpty = false) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.EnemyName); string text = payload.Get<string>(tuple.Item1, (string)null); if (string.IsNullOrEmpty(text)) { if (enforceNonEmpty) { OutwardEnemiesBalancer.LogSL("BalancingRuleHelpers@TryToFillRuleWithEnemyName didn't receive " + tuple.Item1 + "!"); } return false; } rule.enemyName = text; return true; } public static bool FillRuleWithEnvironmentConditions(BalancingRule rule, EventPayload payload) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.AreaFamily); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Faction); (string, Type, string) tuple3 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.AreaEnum); rule.areaFamily = payload.Get<AreaFamily>(tuple.Item1, (AreaFamily)null); rule.faction = EventPayloadEnumHelper.GetEnum<Factions>(payload, tuple2.Item1, (Factions?)null); rule.area = EventPayloadEnumHelper.GetEnum<AreaEnum>(payload, tuple3.Item1, (AreaEnum?)null); if (rule.areaFamily == null && !rule.faction.HasValue) { return rule.area.HasValue; } return true; } public static bool FillRuleForStrongEnemyTypes(BalancingRule rule, EventPayload payload) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForBosses); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForBossesPawns); (string, Type, string) tuple3 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForStoryBosses); (string, Type, string) tuple4 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForUniqueArenaBosses); (string, Type, string) tuple5 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForUniqueEnemies); rule.isBoss = payload.Get<bool>(tuple.Item1, false); rule.isBossPawn = payload.Get<bool>(tuple2.Item1, false); rule.isStoryBoss = payload.Get<bool>(tuple3.Item1, false); rule.isUniqueArenaBoss = payload.Get<bool>(tuple4.Item1, false); rule.isUniqueEnemy = payload.Get<bool>(tuple5.Item1, false); if (!rule.isBoss && !rule.isBossPawn && !rule.isStoryBoss && !rule.isUniqueArenaBoss) { return rule.isUniqueEnemy; } return true; } public static bool FillRuleWithExceptions(BalancingRule rule, EventPayload payload) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptIds); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptNames); rule.exceptIds = payload.Get<List<string>>(tuple.Item1, (List<string>)null); rule.exceptNames = payload.Get<List<string>>(tuple2.Item1, (List<string>)null); if (rule.exceptIds == null) { return rule.exceptNames != null; } return true; } public static bool FillRuleWithIdExceptions(BalancingRule rule, EventPayload payload) { rule.exceptIds = payload.Get<List<string>>(EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptIds).key, (List<string>)null); return rule.exceptIds != null; } public static void FillRuleWithStatModifications(BalancingRule rule, EventPayload payload) { ValueModifierType? @enum = payload.GetEnum<ValueModifierType>(EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ModifierType).key); if (@enum.HasValue) { rule.modifierType = @enum.Value; } (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.StatModifications); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.StatType); (string, Type, string) tuple3 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Value); Dictionary<string, float?> dictionary = payload.Get<Dictionary<string, float?>>(tuple.Item1, (Dictionary<string, float?>)null); if (dictionary != null) { foreach (KeyValuePair<string, float?> item in dictionary) { rule.statModifications[item.Key] = item.Value; } } string text = payload.Get<string>(tuple2.Item1, (string)null); float? num = payload.Get<float?>(tuple3.Item1, (float?)null); if (!string.IsNullOrEmpty(text) && num.HasValue) { rule.statModifications[text] = num.Value; } } public static void FillRuleWithStatModificationsFromVitalStats(BalancingRule rule, EventPayload payload) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MaxHealth); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MaxStamina); (string, Type, string) tuple3 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MaxMana); (string, Type, string) tuple4 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.HealthRegen); (string, Type, string) tuple5 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.StaminaRegen); (string, Type, string) tuple6 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ManaRegen); AddStatMod(rule, payload, EnemyBalanceStatType.MaxHealth.ToString(), tuple.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.MaxStamina.ToString(), tuple2.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.MaxMana.ToString(), tuple3.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.HealthRegen.ToString(), tuple4.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.StaminaRegen.ToString(), tuple5.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.ManaRegen.ToString(), tuple6.Item1); } private static void AddStatMod(BalancingRule rule, EventPayload payload, string statName, string paramKey) { float? num = payload.Get<float?>(paramKey, (float?)null); if (num.HasValue) { rule.statModifications[statName] = num.Value; } } public static void FillRuleWithStatModificationsFromEnvironmentalStats(BalancingRule rule, EventPayload payload) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ColdProtection); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.HeatProtection); (string, Type, string) tuple3 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.CorruptionResistance); (string, Type, string) tuple4 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Waterproof); AddStatMod(rule, payload, EnemyBalanceStatType.ColdProtection.ToString(), tuple.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.HeatProtection.ToString(), tuple2.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.CorruptionProtection.ToString(), tuple3.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.Waterproof.ToString(), tuple4.Item1); } public static void FillRuleWithStatModificationsFromCombatStats(BalancingRule rule, EventPayload payload) { (string, Type, string) tuple = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Impact); (string, Type, string) tuple2 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ImpactResistance); (string, Type, string) tuple3 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MovementSpeed); (string, Type, string) tuple4 = EnemyBalanceParamsHelper.Get(EnemyBalanceParams.AttackSpeed); AddStatMod(rule, payload, EnemyBalanceStatType.Impact.ToString(), tuple.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.ImpactResistance.ToString(), tuple2.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.MovementSpeed.ToString(), tuple3.Item1); AddStatMod(rule, payload, EnemyBalanceStatType.AttackSpeed.ToString(), tuple4.Item1); } public static bool ValidateAndAppendRule(BalancingRule rule) { if (string.IsNullOrEmpty(rule.enemyID) && string.IsNullOrEmpty(rule.enemyName) && rule.areaFamily == null && !rule.faction.HasValue && !rule.area.HasValue && !rule.isBoss && !rule.isBossPawn && !rule.isStoryBoss && !rule.isUniqueArenaBoss && !rule.isUniqueEnemy) { OutwardEnemiesBalancer.LogSL("BalancingRuleHelpers@ValidateAndAppendRule: Rule has no targeting criteria!"); return false; } if (rule.statModifications.Count == 0) { OutwardEnemiesBalancer.LogSL("BalancingRuleHelpers@ValidateAndAppendRule: Rule has no stat modifications!"); return false; } BalancingRuleRegistryManager.Instance.AppendBalancingRule(rule); return true; } } public static class EventPayloadEnumHelper { public static T? GetEnum<T>(this EventPayload payload, string key, T? defaultValue = null) where T : struct, Enum { if (!((Dictionary<string, object>)(object)payload).TryGetValue(key, out object value)) { return defaultValue; } if (!TryConvertToEnum<T>(value, out var result)) { return defaultValue; } return result; } public static T GetEnum<T>(this EventPayload payload, string key, T defaultValue) where T : struct, Enum { if (!((Dictionary<string, object>)(object)payload).TryGetValue(key, out object value)) { return defaultValue; } if (!TryConvertToEnum<T>(value, out var result)) { return defaultValue; } return result; } public static bool TryGetEnum<T>(this EventPayload payload, string key, out T result) where T : struct, Enum { result = default(T); if (!((Dictionary<string, object>)(object)payload).TryGetValue(key, out object value)) { return false; } return TryConvertToEnum<T>(value, out result); } private static bool TryConvertToEnum<T>(object value, out T result) where T : struct, Enum { result = default(T); if (value is T val) { if (!Enum.IsDefined(typeof(T), val)) { OutwardEnemiesBalancer.LogSL($"EventPayloadEnumHelper: Value '{val}' is not a valid {typeof(T).Name} enum value."); return false; } result = val; return true; } if (value is string text) { if (Enum.TryParse<T>(text, ignoreCase: true, out var result2)) { if (!Enum.IsDefined(typeof(T), result2)) { OutwardEnemiesBalancer.LogSL("EventPayloadEnumHelper: Value '" + text + "' is not a valid " + typeof(T).Name + " enum value."); return false; } result = result2; return true; } OutwardEnemiesBalancer.LogSL("EventPayloadEnumHelper: Could not parse '" + text + "' to " + typeof(T).Name + " enum."); return false; } OutwardEnemiesBalancer.LogSL("EventPayloadEnumHelper: Cannot convert " + (value?.GetType().Name ?? "null") + " to " + typeof(T).Name + " enum."); return false; } } } namespace OutwardEnemiesBalancer.Utility.Extensions { public static class EnemyHelperExtensions { public static Dictionary<TEnum, TResult> ToDictionaryBySelector<TEnum, TResult>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict, Func<EnemyIdentificationGroupData, TResult> selector) where TEnum : Enum { return dict.ToDictionary((KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Key, (KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => selector(kvp.Value)); } public static List<TResult> GetAll<TResult, TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict, Func<EnemyIdentificationData, TResult> selector) where TEnum : Enum { return dict.SelectMany((KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Value.Enemies).Select(selector).Distinct() .ToList(); } public static List<string> GetAll<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.GetAll((EnemyIdentificationData e) => e.ID); } public static bool TryGetEnum<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict, Character character, out TEnum result) where TEnum : Enum { string enumIdentificator = BossRegistryManager.GetEnemyBossIdentificator(character); foreach (KeyValuePair<TEnum, EnemyIdentificationGroupData> item in dict) { if (item.Value.Matches(character, (EnemyIdentificationData idData, Character character) => enumIdentificator.Equals(BossRegistryManager.GetIdentificatorFromEnemyIdentification(idData)))) { result = item.Key; return true; } } result = default(TEnum); return false; } public static Dictionary<TEnum, string> GetFirstDisplayNameFromGroup<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.ToDictionary((KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Key, (KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Value.Enemies.First().DisplayName); } public static Dictionary<TEnum, string> GetFirstNameLocFromGroup<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.ToDictionary((KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Key, (KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Value.Enemies.First().LocKey); } public static Dictionary<TEnum, string> GetFirstWikiLocationsFromGroup<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.ToDictionary((KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Key, (KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Value.Enemies.First().WikiLocation); } public static Dictionary<TEnum, string> GetFirstGameLocationsFromGroup<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.ToDictionary((KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Key, (KeyValuePair<TEnum, EnemyIdentificationGroupData> kvp) => kvp.Value.Enemies.First().GameLocation); } public static List<string> GetAllDisplayNames<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.GetAll((EnemyIdentificationData e) => e.DisplayName); } public static List<string> GetAllIds<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.GetAll((EnemyIdentificationData e) => e.ID); } public static List<string> GetAllGameLocations<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.GetAll((EnemyIdentificationData e) => e.GameLocation); } public static List<string> GetAllWikiLocations<TEnum>(this Dictionary<TEnum, EnemyIdentificationGroupData> dict) where TEnum : Enum { return dict.GetAll((EnemyIdentificationData e) => e.WikiLocation); } } } namespace OutwardEnemiesBalancer.Utility.Enums { public enum BossCategories { Story, Arena, Pawn } public struct BossID { public BossCategories Category; public EnemyIdentificationGroupData EnemyData; public Enum EnumValue; public BossID(BossCategories category, EnemyIdentificationGroupData data, Enum enumValue = null) { Category = category; EnemyData = data; EnumValue = enumValue; } public override string ToString() { string text = ""; foreach (EnemyIdentificationData enemy in EnemyData.Enemies) { text = text + "Name " + enemy.DisplayName + " Id " + enemy.ID + " enum " + EnumValue.ToString(); } return $"{Category}:{text}"; } } public enum BossPawns { Elite_Krypteia_warrior, Elite_Krypteia_Witch, Elite_Obsidian_Elemental } public static class BossPawnsHelper { public static readonly Dictionary<BossPawns, EnemyIdentificationGroupData> Enemies = new Dictionary<BossPawns, EnemyIdentificationGroupData> { { BossPawns.Elite_Krypteia_warrior, new EnemyIdentificationGroupData("Balira", "KrypteiaGuard", "name_unpc_balira_01", "AbvgKMnPLUiffB6LzjaguQ", "Tower of Regrets Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { BossPawns.Elite_Krypteia_Witch, new EnemyIdentificationGroupData("Balira", "KrypteiaMage", "name_unpc_balira_01", "MfBjNPYsvkODdyLjYrlXXw", "Tower of Regrets Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { BossPawns.Elite_Obsidian_Elemental, new EnemyIdentificationGroupData(new EnemyIdentificationData("Obsidian Elemental", "ObsidianElemental", "Wildlife_ObsidianElemental", "RM13rq4JTEqbuANnncMCKA", "Burning Tree Arena", "Unknown Arena", "EmercarDungeonsBosses"), new EnemyIdentificationData("Obsidian Elemental (1)", "ObsidianElemental", "Wildlife_ObsidianElemental", "Qrq3e4nUpkS8CH3yd8J-ow", "Burning Tree Arena", "Unknown Arena", "EmercarDungeonsBosses")) } }; } public enum EnemyBalanceParams { BalanceRuleId, EnemyId, EnemyName, AreaEnum, AreaFamily, Faction, IsForBosses, IsForBossesPawns, IsForStoryBosses, IsForUniqueArenaBosses, IsForUniqueEnemies, ExceptIds, ExceptNames, StatModifications, ModifierType, StatType, Value, MinClamp, MaxClamp, MaxHealth, MaxStamina, MaxMana, HealthRegen, StaminaRegen, ManaRegen, DamageType, DamageValue, ResistanceValue, ProtectionValue, ColdProtection, HeatProtection, CorruptionResistance, Waterproof, Impact, ImpactResistance, MovementSpeed, AttackSpeed, LoadBalanceRulesXmlPath, StoreBalanceRulesXmlPath, NewFaction } public static class EnemyBalanceParamsHelper { private static readonly Dictionary<EnemyBalanceParams, (string key, Type type, string description)> _registry = new Dictionary<EnemyBalanceParams, (string, Type, string)> { [EnemyBalanceParams.BalanceRuleId] = ("balanceRuleId", typeof(string), "Optional. Unique identifier for the balancing rule."), [EnemyBalanceParams.EnemyId] = ("enemyId", typeof(string), "Optional. Specific enemy UID to target."), [EnemyBalanceParams.EnemyName] = ("enemyName", typeof(string), "Optional. Enemy display name to target."), [EnemyBalanceParams.AreaEnum] = ("area", typeof(AreaEnum?), "Optional. Specific area filter."), [EnemyBalanceParams.AreaFamily] = ("areaFamily", typeof(AreaFamily), "Optional. Area family (region) filter."), [EnemyBalanceParams.Faction] = ("faction", typeof(Factions?), "Optional. Faction filter."), [EnemyBalanceParams.IsForBosses] = ("isForBosses", typeof(bool), "Optional. Apply to all bosses."), [EnemyBalanceParams.IsForBossesPawns] = ("isForBossPawns", typeof(bool), "Optional. Apply to boss pawns."), [EnemyBalanceParams.IsForStoryBosses] = ("isForStoryBosses", typeof(bool), "Optional. Apply to story bosses."), [EnemyBalanceParams.IsForUniqueArenaBosses] = ("isForUniqueArenaBosses", typeof(bool), "Optional. Apply to unique arena bosses."), [EnemyBalanceParams.IsForUniqueEnemies] = ("isForUniqueEnemies", typeof(bool), "Optional. Apply to unique enemies."), [EnemyBalanceParams.ExceptIds] = ("listExceptIds", typeof(List<string>), "Optional. List of enemy UIDs to exclude."), [EnemyBalanceParams.ExceptNames] = ("listExceptNames", typeof(List<string>), "Optional. List of enemy names to exclude."), [EnemyBalanceParams.StatModifications] = ("statModifications", typeof(Dictionary<string, float?>), "Optional. Dictionary of stat name to value. Use with modifierType for scaling."), [EnemyBalanceParams.ModifierType] = ("modifierType", typeof(ValueModifierType), "Optional. Default Scale. How to modify: Direct, Scale, Add."), [EnemyBalanceParams.StatType] = ("statType", typeof(string), "Optional. Stat name (string) to modify. Use with value parameter."), [EnemyBalanceParams.Value] = ("value", typeof(float?), "Optional. Modification value for single stat."), [EnemyBalanceParams.MinClamp] = ("minClamp", typeof(float?), "Optional. Minimum clamp for the result."), [EnemyBalanceParams.MaxClamp] = ("maxClamp", typeof(float?), "Optional. Maximum clamp for the result."), [EnemyBalanceParams.MaxHealth] = ("maxHealth", typeof(float?), "Optional. Max health modification."), [EnemyBalanceParams.MaxStamina] = ("maxStamina", typeof(float?), "Optional. Max stamina modification."), [EnemyBalanceParams.MaxMana] = ("maxMana", typeof(float?), "Optional. Max mana modification."), [EnemyBalanceParams.HealthRegen] = ("healthRegen", typeof(float?), "Optional. Health regen modification."), [EnemyBalanceParams.StaminaRegen] = ("staminaRegen", typeof(float?), "Optional. Stamina regen modification."), [EnemyBalanceParams.ManaRegen] = ("manaRegen", typeof(float?), "Optional. Mana regen modification."), [EnemyBalanceParams.DamageType] = ("damageType", typeof(Types), "Optional. Damage type for grouped damage event."), [EnemyBalanceParams.DamageValue] = ("damageValue", typeof(float?), "Optional. Damage value for grouped damage event."), [EnemyBalanceParams.ResistanceValue] = ("resistanceValue", typeof(float?), "Optional. Resistance value for grouped damage event."), [EnemyBalanceParams.ProtectionValue] = ("protectionValue", typeof(float?), "Optional. Protection value for grouped damage event."), [EnemyBalanceParams.ColdProtection] = ("coldProtection", typeof(float?), "Optional. Cold protection modification."), [EnemyBalanceParams.HeatProtection] = ("heatProtection", typeof(float?), "Optional. Heat protection modification."), [EnemyBalanceParams.CorruptionResistance] = ("corruptionResistance", typeof(float?), "Optional. Corruption resistance modification."), [EnemyBalanceParams.Waterproof] = ("waterproof", typeof(float?), "Optional. Waterproof modification."), [EnemyBalanceParams.Impact] = ("impact", typeof(float?), "Optional. Impact damage modification."), [EnemyBalanceParams.ImpactResistance] = ("impactResistance", typeof(float?), "Optional. Impact resistance modification."), [EnemyBalanceParams.MovementSpeed] = ("movementSpeed", typeof(float?), "Optional. Movement speed modification."), [EnemyBalanceParams.AttackSpeed] = ("attackSpeed", typeof(float?), "Optional. Attack speed modification."), [EnemyBalanceParams.LoadBalanceRulesXmlPath] = ("filePath", typeof(string), "Required. Path to load balance rules XML."), [EnemyBalanceParams.StoreBalanceRulesXmlPath] = ("filePath", typeof(string), "Optional. Path to save balance rules XML."), [EnemyBalanceParams.NewFaction] = ("newFaction", typeof(Factions), "Required. The faction to change enemies to.") }; public static (string key, Type type, string description) Get(EnemyBalanceParams param) { return _registry[param]; } public static (string key, Type type, string description)[] Combine(params object[] items) { List<(string, Type, string)> list = new List<(string, Type, string)>(); foreach (object obj in items) { if (obj is (string, Type, string) item) { list.Add(item); continue; } if (obj is (string, Type, string)[] collection) { list.AddRange(collection); continue; } throw new ArgumentException("Unsupported item type: " + obj?.GetType().FullName); } return list.ToArray(); } } public enum StoryBosseses { Crimson_Avatar, Djinn, Forge_Master, Light_Mender, Plague_Doctor } public static class StoryBossesHelper { public static readonly Dictionary<StoryBosseses, EnemyIdentificationGroupData> Enemies = new Dictionary<StoryBosseses, EnemyIdentificationGroupData> { { StoryBosseses.Crimson_Avatar, new EnemyIdentificationGroupData("Burning Man", "CrimsonAvatar (1)", "Undead_BurningMan", "4eeggsSn2Eyah4IjjqvpYQ", "Scarlet Sanctuary", "Scarlet Sanctuary") }, { StoryBosseses.Djinn, new EnemyIdentificationGroupData("Gold Lich", "LichGold", "Undead_LichGold", "EwoPQ0iVwkK-XtNuaVPf3g", "Oil Refinery", "Oil Refinery") }, { StoryBosseses.Forge_Master, new EnemyIdentificationGroupData("Jade Lich", "LichRust", "Undead_LichJade", "shyc5M7b-UGVHBZsJMdP4Q", "Forgotten Research Laboratory", "Forgotten Research Laboratory") }, { StoryBosseses.Light_Mender, new EnemyIdentificationGroupData("Gold Lich", "LichGold", "Undead_LichGold", "EwoPQ0iVwkK-XtNuaVPf3g", "Spire of Light", "Spire of Light") }, { StoryBosseses.Plague_Doctor, new EnemyIdentificationGroupData("Jade Lich", "LichJade", "Undead_LichJade", "8sjFFBPMvkuJcrcyIYs-KA", "Dark Ziggurat", "Dark Ziggurat Interior") } }; } public enum UniqueArenaBosses { Ash_Giant_Highmonk, Brand_Squire, Breath_of_Darkness, Calixa_Boss, Concealed_Knight, Elite_Alpha_Tuanosaur, Elite_Ash_Giant, Elite_Beast_Golem, Elite_Boozu, Elite_Burning_Man, Elite_Crescent_Shark, Elite_Crimson_Avatar, Elite_Gargoyle_Alchemist, Elite_Gargoyle_Mage, Elite_Gargoyle_Warrior, Elite_Mantis_Shrimp, Elite_Sublime_Shell, Elite_Torcrab, Grandmother, Immaculate_Dreamer, Immaculates_Bird, Light_Mender, Plague_Doctor, Troglodyte_Queen } public static class UniqueArenaBossesHelper { public static readonly Dictionary<UniqueArenaBosses, EnemyIdentificationGroupData> Enemies = new Dictionary<UniqueArenaBosses, EnemyIdentificationGroupData> { { UniqueArenaBosses.Ash_Giant_Highmonk, new EnemyIdentificationGroupData("Ash Giant Priest", "EliteAshGiantPriest", "Giant_Priest", "UnIXpnDMzUSfBu4S-ZDgsA", "Giant's Village Arena (south)", "Unknown Arena", "HallowedDungeonsBosses") }, { UniqueArenaBosses.Brand_Squire, new EnemyIdentificationGroupData("Desert Bandit", "EliteBrandSquire", "Bandit_Desert_Basic", "sb0TOkOPS06jhp56AOYJCw", "Conflux Mountain Arena", "Unknown Arena", "ChersoneseDungeonsBosses") }, { UniqueArenaBosses.Breath_of_Darkness, new EnemyIdentificationGroupData("Breath of Darkness", "AncientDwellerDark", "Elite_Dweller", "JmeufMpL_E6eYnqCYP2r3w", "The Vault of Stone", "The Vault of Stone") }, { UniqueArenaBosses.Calixa_Boss, new EnemyIdentificationGroupData("Cyrene", "EliteCalixa", "Cyrene", "eCz766tEIEOWfK81om19wg", "Levant Arena", "Unknown Arena", "AbrassarDungeonsBosses") }, { UniqueArenaBosses.Concealed_Knight, new EnemyIdentificationGroupData("???", "NewBandit", "name_unpc_unknown_01", "XVuyIaCAVkatv89kId9Uqw", "Shipwreck (Castaway)", "CierzoTutorial", "CierzoTutorial") }, { UniqueArenaBosses.Elite_Alpha_Tuanosaur, new EnemyIdentificationGroupData("Alpha Tuanosaur", "EliteTuanosaurAlpha", "Wildlife_TuanosaurAlpha", "El8bA54i4E6vZraXsVZMow", "Ziggurat Passage Arena", "Unknown Arena", "HallowedDungeonsBosses") }, { UniqueArenaBosses.Elite_Ash_Giant, new EnemyIdentificationGroupData(new EnemyIdentificationData("Ash Giant", "EliteAshGiantPaf", "Giant_Guard", "3vXChaIK90qgq03PmsHFCg", "Unknown Arena", "Unknown Arena", "HallowedDungeonsBosses"), new EnemyIdentificationData("Ash Giant", "EliteAshGiantPif", "Giant_Guard", "851czvFVDUaB42CgVzfKdg", "Unknown Arena", "Unknown Arena", "HallowedDungeonsBosses"), new EnemyIdentificationData("Ash Giant", "EliteAshGiantPouf", "Giant_Guard", "kNmmOHZzKU-82F3OoX9NXw", "Unknown Arena", "Unknown Arena", "HallowedDungeonsBosses")) }, { UniqueArenaBosses.Elite_Beast_Golem, new EnemyIdentificationGroupData("Beast Golem", "EliteBeastGolem", "Golem_Beast", "n83g2QJhwUyUrN469WC4jA", "Parched Shipwrecks Arena", "Unknown Arena", "AbrassarDungeonsBosses") }, { UniqueArenaBosses.Elite_Boozu, new EnemyIdentificationGroupData("Blade Dancer", "BoozuProudBeast", "Wildlife_BladeDancer", "2Ef5z9OfYkev7M7Oi9GN-A", "Mana Lake Arena", "Unknown Arena", "AntiqueFieldDungeonsBosses") }, { UniqueArenaBosses.Elite_Burning_Man, new EnemyIdentificationGroupData("Burning Man", "EliteBurningMan", "Undead_BurningMan", "JmeufMpL_E6eYnqCYP2r3w", "Burning Tree Arena", "Unknown Arena", "EmercarDungeonsBosses") }, { UniqueArenaBosses.Elite_Crescent_Shark, new EnemyIdentificationGroupData(new EnemyIdentificationData("Crescent Shark", "EliteCrescentShark", "Wildlife_CrescentShark", "RM13rq4JTEqbuANnncMCKA", "Electric Lab Arena", "Unknown Arena", "AbrassarDungeonsBosses"), new EnemyIdentificationData("Crescent Shark", "EliteCrescentShark (1)", "Wildlife_CrescentShark", "ElDi5-rvqEqJKcXhEdgwBQ", "Electric Lab Arena", "Unknown Arena", "AbrassarDungeonsBosses"), new EnemyIdentificationData("Crescent Shark", "EliteCrescentShark (2)", "Wildlife_CrescentShark", "z3sfjJtqQEmUZ_S6g2RPIg", "Electric Lab Arena", "Unknown Arena", "AbrassarDungeonsBosses")) }, { UniqueArenaBosses.Elite_Crimson_Avatar, new EnemyIdentificationGroupData("Burning Man", "CrimsonAvatarElite (1)", "Undead_BurningMan", "JmeufMpL_E6eYnqCYP2r3w", "Vault of Stone Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { UniqueArenaBosses.Elite_Gargoyle_Alchemist, new EnemyIdentificationGroupData("Shell Horror", "GargoyleBossMelee (1)", "Horror_Shell", "k75QmVu5t0e_zIjHnUFbIQ", "New Sirocco Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { UniqueArenaBosses.Elite_Gargoyle_Mage, new EnemyIdentificationGroupData("Shell Horror", "GargoyleBossMelee (1)", "Horror_Shell", "AKBYHSSMJUaH9ddLiz_SZA", "New Sirocco Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { UniqueArenaBosses.Elite_Gargoyle_Warrior, new EnemyIdentificationGroupData("Shell Horror", "GargoyleBossMelee (1)", "Horror_Shell", "Z6yTTWK4u0GjDPfZ9X332A", "New Sirocco Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { UniqueArenaBosses.Elite_Mantis_Shrimp, new EnemyIdentificationGroupData("Mantis Shrimp", "EliteMantisShrimp", "Wildlife_Shrimp", "RM13rq4JTEqbuANnncMCKA", "Voltaic Hatchery Arena", "Unknown Arena", "ChersoneseDungeonsBosses") }, { UniqueArenaBosses.Elite_Sublime_Shell, new EnemyIdentificationGroupData("Nicolas", "CageArmorBoss (1)", "Nicolas", "X-dfltOoGUm7YlCE_Li1zQ", "Isolated Windmill Arena", "Unknown Arena", "AntiqueFieldDungeonsBosses") }, { UniqueArenaBosses.Elite_Torcrab, new EnemyIdentificationGroupData("Wildlife_Torcrab", "TorcrabGiant (1)", "Wildlife_Torcrab", "gQDvpLQh3kimgwMmvXJc4g", "River of Red Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { UniqueArenaBosses.Grandmother, new EnemyIdentificationGroupData("Ghost", "Grandmother", "Undead_Ghost", "7G5APgUksEGdQrBxKXr04g", "Tower of Regrets Arena", "Unknown Arena", "CalderaDungeonsBosses") }, { UniqueArenaBosses.Immaculate_Dreamer, new EnemyIdentificationGroupData("Immaculate", "EliteImmaculate", "Horror_Immaculate", "9jsiejBtHkOzeo4tOyyweg", "Cabal of Wind Temple Arena", "Unknown Arena", "EmercarDungeonsBosses") }, { UniqueArenaBosses.Immaculates_Bird, new EnemyIdentificationGroupData("Immaculate", "EliteSupremeShell (1)", "Horror_Immaculate", "JsyOv_Cwu0K0HlXyZInRQQ", "Immaculate's Camp Arena", "Unknown Arena", "AntiqueFieldDungeonsBosses") }, { UniqueArenaBosses.Light_Mender, new EnemyIdentificationGroupData("Gold Lich", "LichGold (1)", "Undead_LichGold", "v9mN1u1uMkaxsncBXhIM9A", "Spire of Light", "Unknown Arena", "EmercarDungeonsBosses") }, { UniqueArenaBosses.Plague_Doctor, new EnemyIdentificationGroupData("Jade Lich", "LichJade (1)", "Undead_LichJade", "GfWl16_MZ0uS7UYIKpS5Lg", "Dark Ziggurat", "Unknown Arena", "EmercarDungeonsBosses") }, { UniqueArenaBosses.Troglodyte_Queen, new EnemyIdentificationGroupData("Mana Troglodyte", "TroglodyteMana (1)", "Troglodyte_Mana", "pcQlY_whLUCC-FZel18VMg", "Blister Burrow Arena", "Unknown Arena", "ChersoneseDungeonsBosses") } }; } public enum UniqueEnemies { Accursed_Wendigo, Altered_Gargoyle, Ancestral_General, Ancestral_Soldier, Bloody_Alexis, Calygrey_Hero, Chromatic_Arcane_Elemental, Cracked_Gargoyle, Elemental_Parasite, Executioner_Bug, Ghost_of_Vanasse, Giant_Horror, Glacial_Tuanosaur, Golden_Matriarch, Grandmother_Medyse, Greater_Grotesque, Guardian_of_the_Compass, Kazite_Admiral, Lightning_Dancer, Liquid_Cooled_Golem, Luke_the_Pearlescent, Mad_Captains_Bones, Matriarch_Myrmitaur, Quartz_Elemental, Razorhorn_Stekosaur, Royal_Manticore, Rusted_Enforcer, Sandrose_Horror, She_Who_Speaks, That_Annoying_Troglodyte, The_Crusher, The_First_Cannibal, The_Last_Acolyte, Thunderbolt_Golem, Titanic_Guardian_Mk_7, Troglodyte_Archmage, Tyrant_of_the_Hive, Vile_Illuminator, Virulent_Hiveman, Volcanic_Gastrocin } public static class UniqueEnemiesHelper { public static readonly Dictionary<UniqueEnemies, EnemyIdentificationGroupData> Enemies = new Dictionary<UniqueEnemies, EnemyIdentificationGroupData> { { UniqueEnemies.Accursed_Wendigo, new EnemyIdentificationGroupData("Accursed Wendigo", "WendigoAccursed", "Unique_DefEd_Wendigo", "ElxncPIfuEuWkexSKkqFXg", "Corrupted Tombs", "Corrupted Tombs") }, { UniqueEnemies.Altered_Gargoyle, new EnemyIdentificationGroupData("Altered Gargoyle", "GargoyleAltered", "Unique_DefEd_AlteredGargoyle", "dhQVMNRU5kCIWsWRFYpDdw", "Ziggurat Passage", "Ziggurat Passage") }, { UniqueEnemies.Ancestral_General, new EnemyIdentificationGroupData("Ancestral General", "SkeletonBig (1)", "Unique_DefEd_AncestorBig", "XVuyIaCAVkatv89kId9Uqw", "Necropolis", "Necropolis") }, { UniqueEnemies.Ancestral_Soldier, new EnemyIdentificationGroupData("Ancestral Soldier", "SkeletonSmall (1)", "Unique_DefEd_AncestorSmall", "IwZYxBIQZkaXXMWT9HC5nA", "Necropolis", "Necropolis") }, { UniqueEnemies.Bloody_Alexis, new EnemyIdentificationGroupData("Bloody Alexis", "HumanArmoredThug", "Unique_DefEd_ArmoredThug", "VfYPPb4wcESdDVSiEq4UhA", "Undercity Passage", "Undercity Passage") }, { UniqueEnemies.Calygrey_Hero, new EnemyIdentificationGroupData("Calygrey Hero", "LionmanElite (1)", "Elite_Calygrey", "pMfhK69Stky7MvE9Ro0XMQ", "Steam Bath Tunnels", "Steam Bath Tunnels") }, { UniqueEnemies.Chromatic_Arcane_Elemental, new EnemyIdentificationGroupData("Chromatic Arcane Elemental", "ElementalChromatic", "Unique_DefEd_ChromaElemental", "RM13rq4JTEqbuANnncMCKA", "Compromised Mana Transfer Station", "Compromised Mana Transfer Station") }, { UniqueEnemies.Cracked_Gargoyle, new EnemyIdentificationGroupData("Cracked Gargoyle", "GargoyleCracked", "Unique_DefEd_CrackedGargoyle", "-McLNdZsNEa3itw-ny7YBw", "Ark of the Exiled", "Ark of the Exiled") }, { UniqueEnemies.Elemental_Parasite, new EnemyIdentificationGroupData("Crescent Shark", "ElementalParasite", "Wildlife_CrescentShark", "YDPy9S-An0-qGuvrJAM8yA", "Crumbling Loading Docks", "Crumbling Loading Docks") }, { UniqueEnemies.Executioner_Bug, new EnemyIdentificationGroupData("Executioner Bug", "ExecutionerBug", "Unique_DefEd_ExecutionerBug", "MWplmyrxokSXELL63oWcAg", "The Slide", "The Slide") }, { UniqueEnemies.Ghost_of_Vanasse, new EnemyIdentificationGroupData("Ghost of Vanasse", "GhostOfVanasse", "Undead_GhostVanasse", "R5UngwGS5EGWCH13toZO8Q", "Chersonese", "Chersonese") }, { UniqueEnemies.Giant_Horror, new EnemyIdentificationGroupData("Giant_Horror", "GiantHorror", "Giant_Horror", "SntMM-EzuE-ptgmqp4qkYQ", "Crumbling Loading Docks", "Crumbling Loading Docks") }, { UniqueEnemies.Glacial_Tuanosaur, new EnemyIdentificationGroupData("Glacial Tuanosaur", "TuanosaurIce", "Unique_DefEd_GlacialTuano", "1IKBT9DYc0yIkESwKtU40g", "Conflux Chambers", "Conflux Chambers") }, { UniqueEnemies.Golden_Matriarch, new EnemyIdentificationGroupData("Golden Matriarch", "SpecterMeleeMatriarch", "Unique_DefEd_GoldenMatriarch", "GI-aE4Ry7UOIyAYYk7emFg", "Voltaic Hatchery", "Voltaic Hatchery") }, { UniqueEnemies.Grandmother_Medyse, new EnemyIdentificationGroupData("Grandmother Medyse", "JellyFishMother", "Unique_DefEd_GrandmotherMedyse", "kp9R4kaoG02YfLdS9ROM4w", "Sulphuric Caverns", "Sulphuric Caverns") }, { UniqueEnemies.Greater_Grotesque, new EnemyIdentificationGroupData("Greater Grotesque", "ImmaculateHorrorGreater", "Unique_DefEd_GreaterGrotestue", "JmeufMpL_E6eYnqCYP2r3w", "Lost Golem Manufacturing Facility", "Lost Golem Manufacturing Facility") }, { UniqueEnemies.Guardian_of_the_Compass, new EnemyIdentificationGroupData("Guardian of the Compass", "GolemBoss", "Golem_Basic2", "BINT--E9xUaCgp4onBM54g", "The Walled Garden", "Abrassar") }, { UniqueEnemies.Kazite_Admiral, new EnemyIdentificationGroupData("Kazite Admiral", "HumanKaziteCaptain", "Unique_DefEd_KaziteCaptain", "XVuyIaCAVkatv89kId9Uqw", "Abandoned Living Quarters", "Abandoned Living Quarters") }, { UniqueEnemies.Lightning_Dancer, new EnemyIdentificationGroupData("Lightning Dancer", "BladeDancerLight", "Unique_DefEd_LightningDancer", "QRzc3AYY10CWyXOMgrIQTg", "Ancient Foundry", "Ancient Foundry") }, { UniqueEnemies.Liquid_Cooled_Golem, new EnemyIdentificationGroupData("Liquid-Cooled Golem", "GolemShieldedIce", "Unique_DefEd_LiquidGolem", "8ztut4_yiEmK0-NFLa-XNQ", "Destroyed Test Chambers", "Destroyed Test Chambers") }, { UniqueEnemies.Luke_the_Pearlescent, new EnemyIdentificationGroupData("Luke the Pearlescent", "NewBanditEquip_WhiteScavengerCaptainBoss_A (1)", "Bandit_Standard_Captain2", "XVuyIaCAVkatv89kId9Uqw", "Ruins of Old Levant", "Abrassar") }, { UniqueEnemies.Mad_Captains_Bones, new EnemyIdentificationGroupData("Mad Captain's Bones", "SkeletFighter", "Undead_Skeleton2", "JM_HjGXMlkq7a1Yb6gijgQ", "Pirates' Hideout", "Chersonese Misc. Dungeons") }, { UniqueEnemies.Matriarch_Myrmitaur, new EnemyIdentificationGroupData("Matriarch Myrmitaur", "MyrmElite (1)", "Elite_Myrm", "6sB4_5lOJU2bWuMHnOL4Ww", "Myrmitaur's Haven", "Myrmitaur's Haven") }, { UniqueEnemies.Quartz_Elemental, new EnemyIdentificationGroupData("Quartz Elemental", "ObsidianElementalQuartz", "Unique_DefEd_QuartzElemental", "LhhpSt8BO0aRN5mbeSuDrw", "The Grotto of Chalcedony", "The Grotto of Chalcedony") }, { UniqueEnemies.Razorhorn_Stekosaur, new EnemyIdentificationGroupData("Razorhorn Stekosaur", "SteakosaurBlack (1)", "Unique_DefEd_BlackSteko", "03dSXwJMRUuzGu8s3faATQ", "Reptilian Lair", "Reptilian Lair") }, { UniqueEnemies.Royal_Manticore, new EnemyIdentificationGroupData("The Royal Manticore", "RoyalManticore", "Wildlife_Manticore2", "RM13rq4JTEqbuANnncMCKA", "Enmerkar Forest", "Enmerkar Forest") }, { UniqueEnemies.Rusted_Enforcer, new EnemyIdentificationGroupData("Rusted Enforcer", "GolemRusted (1)", "Unique_DefEd_RustyGolem", "Ed2bzrgz5k-cRx3bUYTfmg", "Ghost Pass", "Ghost Pass") }, { UniqueEnemies.Sandrose_Horror, new EnemyIdentificationGroupData("Sandrose Horror", "ShelledHorrorBurning", "Unique_DefEd_SandroseHorror", "H7HoCKhBl0mC1j9UOECDrQ", "Sand Rose Cave", "Sand Rose Cave") }, { UniqueEnemies.She_Who_Speaks, new EnemyIdentificationGroupData("She Who Speaks", "AncientDwellerSpeak", "Unique_DefEd_BossDweller", "MBooN38mU0GPjQJGRuJ95g", "The Vault of Stone", "The Vault of Stone") }, { UniqueEnemies.That_Annoying_Troglodyte, new EnemyIdentificationGroupData("That Annoying Troglodyte", "TroglodyteAnnoying", "Unique_DefEd_AnnoyingTrog", "no-Z4ibpcEWbNntm_wRwZA", "Jade Quarry", "Jade Quarry") }, { UniqueEnemies.The_Crusher, new EnemyIdentificationGroupData("The Crusher", "HumanCrusher (1)", "Unique_DefEd_DesertCrusher", "AZL-EjXmhkOYB1obj0VkTw", "Ancestor's Resting Place", "Ancestor's Resting Place") }, { UniqueEnemies.The_First_Cannibal, new EnemyIdentificationGroupData("The First Cannibal", "WendigoCanibal", "Wildlife_Wendigo2", "wrYHXXh8J0KMhwoV8AC59w", "Face of the Ancients", "Face of the Ancients") }, { UniqueEnemies.The_Last_Acolyte, new EnemyIdentificationGroupData("The Last Acolyte", "HumanAcolyte (1)", "Unique_DefEd_LastAcolyte", "YeYzQP-gYUmSivlk5JCJew", "Stone Titan Caves", "Stone Titan Caves") }, { UniqueEnemies.Thunderbolt_Golem, new EnemyIdentificationGroupData("Thunderbolt Golem", "ForgeGolemLight (1)", "Unique_DefEd_ProtypeForgeGolem", "4qCAJzcAfEKNcl_c2k0r9g", "Electric Lab", "Electric Lab") }, { UniqueEnemies.Titanic_Guardian_Mk_7, new EnemyIdentificationGroupData(new EnemyIdentificationData("Jade Lich", "TitanGolemHalberd", "Undead_LichJade", "65aI6XT89kmHa1bwJz5PGQ", "Ruined Warehouse", "Ruined Warehouse"), new EnemyIdentificationData("Jade Lich", "TitanGolemHammer", "Undead_LichJade", "G_Q0oH1ttkWAZXCMuaAHjA", "Ruined Warehouse", "Ruined Warehouse"), new EnemyIdentificationData("Jade Lich", "TitanGolemSword", "Undead_LichJade", "wj3frikyIkqwVv7myrc5gw", "Ruined Warehouse", "Ruined Warehouse")) }, { UniqueEnemies.Troglodyte_Archmage, new EnemyIdentificationGroupData("Troglodyte Archmage", "TroglodyteArcMageDefEd (1)", "Unique_DefEd_TrogMage", "syKWNGT3QUO3nXxPt1WEcQ", "Blister Burrow", "Blister Burrow") }, { UniqueEnemies.Tyrant_of_the_Hive, new EnemyIdentificationGroupData("Tyrant of the Hive", "HiveLord1AID+", "Undead_Hivelord2", "yOo-iKN3-0mAtZ2pG16pyw", "Forest Hives", "Forest Hives") }, { UniqueEnemies.Vile_Illuminator, new EnemyIdentificationGroupData("Vile Illuminator", "IlluminatorHorrorVile", "Unique_DefEd_VileIlluminator", "l5ignQfsE0Cv4imB9DZJ5w", "Cabal of Wind Temple", "Cabal of Wind Temple") }, { UniqueEnemies.Virulent_Hiveman, new EnemyIdentificationGroupData("Virulent Hiveman", "HiveManVirulent", "Unique_DefEd_VirulentHiveman", "v1PnLFpcxEmm_IrZaP-eyg", "Ancient Hive", "Ancient Hive") }, { UniqueEnemies.Volcanic_Gastrocin, new EnemyIdentificationGroupData("Volcanic Gastrocin", "SlughellVolcanic", "Unique_DefEd_VolcanicSlug", "fEFTRdXp1kOWX-Z9OMAkBg", "The Eldest Brother", "The Eldest Brother") } }; } } namespace OutwardEnemiesBalancer.Utility.Data { public class EnemyIdentificationData { public string DisplayName; public string InternalName; public string LocKey; public string ID; public string WikiLocation; public string GameLocation; public string SceneName; public EnemyIdentificationData(string Name, string m_name, string m_nameLoc, string id, string wikiLocation, string gameLocation, string sceneName = "") { DisplayName = Name; InternalName = m_name; LocKey = m_nameLoc; ID = id; WikiLocation = wikiLocation; GameLocation = gameLocation; SceneName = sceneName; } public bool Matches(Character character, params Func<EnemyIdentificationData, Character, bool>[] comparers) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (comparers == null || comparers.Length == 0) { string iD = ID; UID uID = character.UID; return string.Equals(iD, ((UID)(ref uID)).Value, StringComparison.Ordinal); } return comparers.Any((Func<EnemyIdentificationData, Character, bool> c) => c(this, character)); } } public class EnemyIdentificationGroupData { public List<EnemyIdentificationData> Enemies { get; } public EnemyIdentificationGroupData(string displayName, string internalName, string locKey, string uid, string wikiLocation, string gameLocation, string sceneName = "") { Enemies = new List<EnemyIdentificationData> { new EnemyIdentificationData(displayName, internalName, locKey, uid, wikiLocation, gameLocation, sceneName) }; } public EnemyIdentificationGroupData(params EnemyIdentificationData[] entries) { Enemies = new List<EnemyIdentificationData>(); Enemies.AddRange(entries); } public bool Matches(Character character, params Func<EnemyIdentificationData, Character, bool>[] comparers) { return Enemies.Any((EnemyIdentificationData e) => e.Matches(character, comparers)); } public EnemyIdentificationData GetMatching(Character character) { return Enemies.FirstOrDefault((EnemyIdentificationData e) => e.Matches(character)); } } } namespace OutwardEnemiesBalancer.Managers { public class BalancingRuleRegistryManager { private static BalancingRuleRegistryManager _instance; public List<BalancingRule> balancingRules = new List<BalancingRule>(); public static BalancingRuleRegistryManager Instance { get { if (_instance == null) { _instance = new BalancingRuleRegistryManager(); } return _instance; } } private BalancingRuleRegistryManager() { } public void AppendBalancingRules(List<BalancingRule> rules) { foreach (BalancingRule rule in rules) { AppendBalancingRule(rule); } } public void AppendBalancingRule(BalancingRule rule) { BalancingRule balancingRule = balancingRules.FirstOrDefault((BalancingRule r) => r.id == rule.id); if (balancingRule != null) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Rule '" + rule.id + "' already exists. Merging stat modifications:"); stringBuilder.AppendLine(" Old mods: " + CountStats(balancingRule.statModifications)); stringBuilder.AppendLine(" New mods: " + CountStats(rule.statModifications)); foreach (KeyValuePair<string, float?> statModification in rule.statModifications) { float? value; string arg = ((!balancingRule.statModifications.TryGetValue(statModification.Key, out value)) ? "added" : "updated"); balancingRule.statModifications[statModification.Key] = statModification.Value; stringBuilder.AppendLine($" {arg}: {statModification.Key} = {statModification.Value}"); } OutwardEnemiesBalancer.LogSL(stringBuilder.ToString()); } else { balancingRules.Add(rule); EventBusPublisher.SendAppendBalancingRule(rule.id); } } private string CountStats(Dictionary<string, float?> stats) { if (stats == null) { return "0"; } return stats.Count.ToString(); } public void RemoveBalancingRuleById(string id) { foreach (BalancingRule item in balancingRules.Where((BalancingRule rule) => rule.id == id).ToList()) { RemoveBalancingRule(item); } } public void RemoveBalancingRule(BalancingRule rule) { balancingRules.Remove(rule); EventBusPublisher.SendRemoveBalancingRule(rule.id); } public List<BalancingRule> GetMatchingRules(Character character) { List<BalancingRule> list = new List<BalancingRule>(); foreach (BalancingRule balancingRule in balancingRules) { if (balancingRule.Matches(character)) { list.Add(balancingRule); } } return list; } } public class BalancingRulesSerializer { private static BalancingRulesSerializer _instance; public static BalancingRulesSerializer Instance { get { if (_instance == null) { _instance = new BalancingRulesSerializer(); } return _instance; } } private BalancingRulesSerializer() { } public BalancingRulesFile Load(string path) { try { if (!File.Exists(path)) { OutwardEnemiesBalancer.LogSL("BalancingRules file not found at: " + path); return null; } XmlSerializer xmlSerializer = new XmlSerializer(typeof(BalancingRulesFile)); using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read); return xmlSerializer.Deserialize(stream) as BalancingRulesFile; } catch (Exception ex) { OutwardEnemiesBalancer.LogSL("Failed to load BalancingRules file at '" + path + "': " + ex.Message); return null; } } public void LoadPlayerBalanceRules() { if (File.Exists(PathsManager.DefaultBalanceRulesPath)) { LoadBalanceRules(PathsManager.DefaultBalanceRulesPath); } } public void LoadBalanceRules(string path) { try { if (!File.Exists(path)) { OutwardEnemiesBalancer.LogSL("BalancingRulesSerializer@LoadBalanceRules file not found at: " + path); return; } BalancingRulesFile balancingRulesFile = Load(path); if (balancingRulesFile != null) { List<BalancingRule> balancingRules = GetBalancingRules(balancingRulesFile); BalancingRuleRegistryManager.Instance.AppendBalancingRules(balancingRules); } } catch (Exception ex) { OutwardEnemiesBalancer.LogSL("BalancingRulesSerializer@LoadBalanceRules failed loading '" + path + "': " + ex.Message); } } public void SaveBalanceRulesToXml(string filePath, List<BalancingRule> rules) { try { string directoryName = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } BalancingRulesFile o = BuildBalancingRulesFile(rules); XmlSerializer xmlSerializer = new XmlSerializer(typeof(BalancingRulesFile)); XmlWriterSettings settings = new XmlWriterSettings { Indent = true, NewLineOnAttributes = false }; using XmlWriter xmlWriter = XmlWriter.Create(filePath, settings); xmlSerializer.Serialize(xmlWriter, o); } catch (Exception ex) { OutwardEnemiesBalancer.LogSL("BalancingRulesSerializer@SaveBalanceRulesToXml failed saving '" + filePath + "': " + ex.Message); } } public List<BalancingRule> LoadBalanceRulesFromXmlSync(string path) { try { if (!File.Exists(path)) { OutwardEnemiesBalancer.LogSL("LoadBalanceRulesFromXmlSync file not found at: " + path); return null; } BalancingRulesFile balancingRulesFile = Load(path); if (balancingRulesFile == null) { return null; } return GetBalancingRules(balancingRulesFile); } catch (Exception ex) { OutwardEnemiesBalancer.LogSL("LoadBalanceRulesFromXmlSync failed loading '" + path + "': " + ex.Message); return null; } } public List<BalancingRule> GetBalancingRules(BalancingRulesFile file) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) List<BalancingRule> list = new List<BalancingRule>(); foreach (BalancingRuleSerializable rule in file.Rules) { BalancingRule balancingRule = new BalancingRule(rule.Id); balancingRule.enemyID = (string.IsNullOrEmpty(rule.EnemyID) ? null : rule.EnemyID); balancingRule.enemyName = rule.EnemyName ?? ""; balancingRule.areaFamily = AreaFamiliesHelpers.GetAreaFamilyByName(rule.AreaFamilyName); balancingRule.area = AreaHelpers.GetAreaEnumFromAreaName(rule.AreaName); if (!string.IsNullOrEmpty(rule.FactionName) && Enum.TryParse<Factions>(rule.FactionName, out Factions result)) { balancingRule.faction = result; } balancingRule.exceptIds = rule.ExceptIds; balancingRule.exceptNames = rule.ExceptNames; balancingRule.isBoss = rule.IsBoss; balancingRule.isBossPawn = rule.IsBossPawn; balancingRule.isStoryBoss = rule.IsStoryBoss; balancingRule.isUniqueArenaBoss = rule.IsUniqueArenaBoss; balancingRule.isUniqueEnemy = rule.IsUniqueEnemy; if (rule.StatModifications != null) { foreach (StatModificationSerializable statModification in rule.StatModifications) { balancingRule.statModifications[statModification.StatName] = statModification.Value; } } list.Add(balancingRule); } return list; } public BalancingRulesFile BuildBalancingRulesFile(List<BalancingRule> rules) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) BalancingRulesFile balancingRulesFile = new BalancingRulesFile { Rules = new List<BalancingRuleSerializable>() }; foreach (BalancingRule rule in rules) { BalancingRuleSerializable obj = new BalancingRuleSerializable { Id = (rule.id ?? null), EnemyID = (rule.enemyID ?? null), EnemyName = (rule.enemyName ?? ""), AreaFamilyName = (rule.areaFamily?.FamilyName ?? "") }; ref AreaEnum? area = ref rule.area; object obj2; if (!area.HasValue) { obj2 = null; } else { AreaEnum valueOrDefault = area.GetValueOrDefault(); obj2 = ((object)(AreaEnum)(ref valueOrDefault)).ToString(); } if (obj2 == null) { obj2 = ""; } obj.AreaName = (string)obj2; ref Factions? faction = ref rule.faction; object obj3; if (!faction.HasValue) { obj3 = null; } else { Factions valueOrDefault2 = faction.GetValueOrDefault(); obj3 = ((object)(Factions)(ref valueOrDefault2)).ToString(); } if (obj3 == null) { obj3 = ""; } obj.FactionName = (string)obj3; obj.ExceptIds = rule.exceptIds ?? null; obj.ExceptNames = rule.exceptNames ?? null; obj.IsBoss = rule.isBoss; obj.IsBossPawn = rule.isBossPawn; obj.IsStoryBoss = rule.isStoryBoss; obj.IsUniqueArenaBoss = rule.isUniqueArenaBoss; obj.IsUniqueEnemy = rule.isUniqueEnemy; obj.StatModifications = new List<StatModificationSerializable>(); BalancingRuleSerializable balancingRuleSerializable = obj; foreach (KeyValuePair<string, float?> statModification in rule.statModifications) { if (statModification.Value.HasValue) { balancingRuleSerializable.StatModifications.Add(new StatModificationSerializable { StatName = statModification.Key, Value = statModification.Value.Value }); } } balancingRulesFile.Rules.Add(balancingRuleSerializable); } return balancingRulesFile; } public void LoadFactionRules(string path) { try { if (!File.Exists(path)) { OutwardEnemiesBalancer.LogSL("FactionRules file not found at: " + path); return; } BalancingRulesFile balancingRulesFile = Load(path); if (balancingRulesFile != null && balancingRulesFile.FactionRules != null) { List<FactionRule> factionRules = GetFactionRules(balancingRulesFile); FactionRuleRegistryManager.Instance.AppendFactionRules(factionRules); } } catch (Exception ex) { OutwardEnemiesBalancer.LogSL("LoadFactionRules failed loading '" + path + "': " + ex.Message); } } public List<FactionRule> GetFactionRules(BalancingRulesFile file) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) List<FactionRule> list = new List<FactionRule>(); if (file.FactionRules == null) { return list; } foreach (FactionRuleSerializable factionRule2 in file.FactionRules) { FactionRule factionRule = new FactionRule(factionRule2.Id); factionRule.enemyID = (string.IsNullOrEmpty(factionRule2.EnemyID) ? null : factionRule2.EnemyID); factionRule.enemyName = factionRule2.EnemyName ?? ""; factionRule.areaFamily = AreaFamiliesHelpers.GetAreaFamilyByName(factionRule2.AreaFamilyName); factionRule.area = AreaHelpers.GetAreaEnumFromAreaName(factionRule2.AreaName); if (!string.IsNullOrEmpty(factionRule2.TargetFactionName) && Enum.TryParse<Factions>(factionRule2.TargetFactionName, out Factions result)) { factionRule.targetFaction = result; } if (!string.IsNullOrEmpty(factionRule2.NewFactionName) && Enum.TryParse<Factions>(factionRule2.NewFactionName, out Factions result2)) { factionRule.newFaction = result2; } factionRule.exceptIds = factionRule2.ExceptIds; factionRule.exceptNames = factionRule2.ExceptNames; factionRule.isBoss = factionRule2.IsBoss; factionRule.isBossPawn = factionRule2.IsBossPawn; factionRule.isStoryBoss = factionRule2.IsStoryBoss; factionRule.isUniqueArenaBoss = factionRule2.IsUniqueArenaBoss; factionRule.isUniqueEnemy = factionRule2.IsUniqueEnemy; list.Add(factionRule); } return list; } public void SaveFactionRulesToXml(string filePath, List<FactionRule> rules) { try { BalancingRulesFile o = BuildFactionRulesFile(rules); XmlSerializer xmlSerializer = new XmlSerializer(typeof(BalancingRulesFile)); XmlWriterSettings settings = new XmlWriterSettings { Indent = true, NewLineOnAttributes = false }; using XmlWriter xmlWriter = XmlWriter.Create(filePath, settings); xmlSerializer.Serialize(xmlWriter, o); } catch (Exception ex) { OutwardEnemiesBalancer.LogSL("SaveFactionRulesToXml failed saving '" + filePath + "': " + ex.Message); } } public BalancingRulesFile BuildFactionRulesFile(List<FactionRule> rules) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) BalancingRulesFile balancingRulesFile = new BalancingRulesFile { FactionRules = new List<FactionRuleSerializable>() }; foreach (FactionRule rule in rules) { FactionRuleSerializable obj = new FactionRuleSerializable { Id = (rule.id ?? null), EnemyID = (rule.enemyID ?? null), EnemyName = (rule.enemyName ?? ""), AreaFamilyName = (rule.areaFamily?.FamilyName ?? "") }; ref AreaEnum? area = ref rule.area; object obj2; if (!area.HasValue) { obj2 = null; } else { AreaEnum valueOrDefault = area.GetValueOrDefault(); obj2 = ((object)(AreaEnum)(ref valueOrDefault)).ToString(); } if (obj2 == null) { obj2 = ""; } obj.AreaName = (string)obj2; ref Factions? targetFaction = ref rule.targetFaction; object obj3; if (!targetFaction.HasValue) { obj3 = null; } else { Factions valueOrDefault2 = targetFaction.GetValueOrDefault(); obj3 = ((object)(Factions)(ref valueOrDefault2)).ToString(); } if (obj3 == null) { obj3 = ""; } obj.TargetFactionName = (string)obj3; obj.NewFactionName = ((object)(Factions)(ref rule.newFaction)).ToString(); obj.ExceptIds = rule.exceptIds ?? null; obj.ExceptNames = rule.exceptNames ?? null; obj.IsBoss = rule.isBoss; obj.IsBossPawn = rule.isBossPawn; obj.IsStoryBoss = rule.isStoryBoss; obj.IsUniqueArenaBoss = rule.isUniqueArenaBoss; obj.IsUniqueEnemy = rule.isUniqueEnemy; FactionRuleSerializable item = obj; balancingRulesFile.FactionRules.Add(item); } return balancingRulesFile; } } public class BossRegistryManager { private static BossRegistryManager _instance; private readonly Dictionary<string, BossID> bossLookup = new Dictionary<string, BossID>(StringComparer.Ordinal); public static BossRegistryManager Instance { get { if (_instance == null) { _instance = new BossRegistryManager(); } return _instance; } } private BossRegistryManager() { RegisterEnum(StoryBossesHelper.Enemies, BossCategories.Story); RegisterEnum(UniqueArenaBossesHelper.Enemies, BossCategories.Arena); RegisterEnum(BossPawnsHelper.Enemies, BossCategories.Pawn); } private void RegisterEnum<T>(Dictionary<T, EnemyIdentificationGroupData> mapping, BossCategories category) where T : Enum { foreach (KeyValuePair<T, EnemyIdentificationGroupData> item in mapping) { T key = item.Key; EnemyIdentificationGroupData value = item.Value; BossID value2 = new BossID(category, value, key); foreach (EnemyIdentificationData enemy in value.Enemies) { if (!string.IsNullOrWhiteSpace(enemy.ID)) { bossLookup[GetIdentificatorFromEnemyIdentification(enemy)] = value2; } } } } public bool TryGetBoss(string key, out BossID boss) { return bossLookup.TryGetValue(key, out boss); } public bool IsBoss(Character character) { return bossLookup.ContainsKey(GetEnemyBossIdentificator(character)); } public bool IsBossOfCategory(string key, BossCategories category) { if (TryGetBoss(key, out var boss)) { return boss.Category == category; } return false; } public bool IsBossOfCategory(Character character, BossCategories category) { if (TryGetBoss(GetEnemyBossIdentificator(character), out var boss)) { return boss.Category == category; } return false; } public IEnumerable<BossID> GetBossesOfCategory(BossCategories category) { return bossLookup.Values.Where((BossID b) => b.Category == category); } public static string GetEnemyBossIdentificator(Character character) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) Area currentArea = AreaManager.Instance.CurrentArea; string text = ((currentArea != null) ? currentArea.GetName() : null); UID uID; if (string.IsNullOrEmpty(text)) { uID = character.UID; return ((UID)(ref uID)).Value; } uID = character.UID; return ((UID)(ref uID)).Value + "_" + FixAreaNameForCode(text); } public static string GetIdentificatorFromEnemyIdentification(EnemyIdentificationData enemy) { return enemy.ID + "_" + FixAreaNameForCode(enemy.GameLocation); } public static string FixAreaNameForCode(string name) { return name.Trim().Replace(' ', '_'); } } public class CharacterBalancerManager { private static CharacterBalancerManager _instance; private static MethodInfo _getStatMethod; public static CharacterBalancerManager Instance { get { if (_instance == null) { _instance = new CharacterBalancerManager(); } return _instance; } } private CharacterBalancerManager() { } public void ApplyBalancingRules() { CharacterAI[] array = Object.FindObjectsOfType<CharacterAI>(); for (int i = 0; i < array.Length; i++) { Character character = ((CharacterControl)array[i]).Character; if ((Object)(object)character == (Object)null || !character.Alive) { continue; } List<BalancingRule> matchingRules = BalancingRuleRegistryManager.Instance.GetMatchingRules(character); if (matchingRules.Count <= 0) { continue; } foreach (BalancingRule item in matchingRules) { ApplyRuleToCharacter(character, item); } } } public void ApplyBalancingRule(BalancingRule rule) { CharacterAI[] array = Object.FindObjectsOfType<CharacterAI>(); for (int i = 0; i < array.Length; i++) { Character character = ((CharacterControl)array[i]).Character; if (!((Object)(object)character == (Object)null) && character.Alive && rule.Matches(character)) { ApplyRuleToCharacter(character, rule); } } } private int ApplyRuleToCharacter(Character character, BalancingRule rule) { if ((Object)(object)character.Stats == (Object)null || rule.statModifications == null || rule.statModifications.Count == 0) { return 0; } List<StatModification> list = StatModificationBuilder.BuildFromDictionary(rule.statModifications, rule.modifierType); int num = 0; foreach (StatModification item in list) { ApplyStatModification(character, item); num++; } return num; } private void ApplyStatModification(Character character, StatModification mod) { float statValue = GetStatValue(character.Stats, mod.StatType); float num = ApplyModification(statValue, mod); if (mod.MinClamp.HasValue) { num = Mathf.Max(num, mod.MinClamp.Value); } if (mod.MaxClamp.HasValue) { num = Mathf.Min(num, mod.MaxClamp.Value); } SetStatValue(character, mod.StatType, num); } private float ApplyModification(float currentValue, StatModification mod) { return mod.ModifierType switch { ValueModifierType.Direct => mod.Value, ValueModifierType.Scale => currentValue * mod.Value, ValueModifierType.Add => currentValue + mod.Value, _ => currentValue, }; } private StatType? MapToGameStatType(EnemyBalanceStatType statType) { return statType switch { EnemyBalanceStatType.MaxHealth => (StatType)0, EnemyBalanceStatType.MaxStamina => (StatType)3, EnemyBalanceStatType.MaxMana => (StatType)8, EnemyBalanceStatType.HealthRegen => (StatType)1, EnemyBalanceStatType.StaminaRegen => (StatType)4, EnemyBalanceStatType.ManaRegen => (StatType)9, EnemyBalanceStatType.Impact => (StatType)12, EnemyBalanceStatType.ImpactResistance => (StatType)43, EnemyBalanceStatType.MovementSpeed => (StatType)54, EnemyBalanceStatType.AttackSpeed => (StatType)56, EnemyBalanceStatType.SkillCooldownModifier => (StatType)68, EnemyBalanceStatType.DodgeInvulnerabilityModifier => (StatType)57, EnemyBalanceStatType.GlobalStatusResistance => (StatType)45, EnemyBalanceStatType.ColdProtection => (StatType)47, EnemyBalanceStatType.HeatProtection => (StatType)48, EnemyBalanceStatType.ColdRegenRate => (StatType)49, EnemyBalanceStatType.HeatRegenRate => (StatType)50, EnemyBalanceStatType.CorruptionProtection => (StatType)52, EnemyBalanceStatType.Waterproof => (StatType)51, _ => null, }; } private Stat GetGameStat(CharacterStats stats, EnemyBalanceStatType statType) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown StatType? val = MapToGameStatType(statType); if (!val.HasValue) { return null; } if (_getStatMethod == null) { _getStatMethod = typeof(CharacterStats).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetStat" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(StatType)); } if (_getStatMethod != null) { return (Stat)_getStatMethod.Invoke(stats, new object[1] { val.Value }); } return null; } private float GetStatValue(CharacterStats stats, EnemyBalanceStatType statType) { switch (statType) { case EnemyBalanceStatType.MaxHealth: return stats.BaseMaxHealth; case EnemyBalanceStatType.MaxStamina: return stats.MaxStamina; case EnemyBalanceStatType.MaxMana: return stats.BaseMaxMana; case EnemyBalanceStatType.HealthRegen: return stats.HealthRegen; case EnemyBalanceStatType.StaminaRegen: return stats.StaminaRegen; case EnemyBalanceStatType.ManaRegen: return stats.ManaRegen; case EnemyBalanceStatType.Impact: return stats.m_impactModifier.CurrentValue; case EnemyBalanceStatType.ImpactResistance: return stats.GetImpactResistance(); case EnemyBalanceStatType.MovementSpeed: return stats.MovementSpeed; case EnemyBalanceStatType.PhysicalDamage: return stats.m_damageTypesModifier[0].CurrentValue; case EnemyBalanceStatType.EtherealDamage: return stats.m_damageTypesModifier[1].CurrentValue; case EnemyBalanceStatType.DecayDamage: return stats.m_damageTypesModifier[2].CurrentValue; case EnemyBalanceStatType.ElectricDamage: return stats.m_damageTypesModifier[3].CurrentValue; case EnemyBalanceStatType.FrostDamage: return stats.m_damageTypesModifier[4].CurrentValue; case EnemyBalanceStatType.FireDamage: return stats.m_damageTypesModifier[5].CurrentValue; case EnemyBalanceStatType.PhysicalResistance: return stats.m_damageResistance[0].CurrentValue; case EnemyBalanceStatType.EtherealResistance: return stats.m_damageResistance[1].CurrentValue; case EnemyBalanceStatType.DecayResistance: return stats.m_damageResistance[2].CurrentValue; case EnemyBalanceStatType.ElectricResistance: return stats.m_damageResistance[3].CurrentValue; case EnemyBalanceStatType.FrostResistance: return stats.m_damageResistance[4].CurrentValue; case EnemyBalanceStatType.FireResistance: return stats.m_damageResistance[5].CurrentValue; case EnemyBalanceStatType.PhysicalProtection: return stats.m_damageProtection[0].CurrentValue; case EnemyBalanceStatType.EtherealProtection: return stats.m_damageProtection[1].CurrentValue; case EnemyBalanceStatType.DecayProtection: return stats.m_damageProtection[2].CurrentValue; case EnemyBalanceStatType.ElectricProtection: return stats.m_damageProtection[3].CurrentValue; case EnemyBalanceStatType.FrostProtection: return stats.m_damageProtection[4].CurrentValue; case EnemyBalanceStatType.FireProtection: return stats.m_damageProtection[5].CurrentValue; default: { Stat gameStat = GetGameStat(stats, statType); return (gameStat != null) ? gameStat.CurrentValue : 0f; } } } private void SetStatValue(Character character, EnemyBalanceStatType statType, float value) { CharacterStats stats = character.Stats; switch (statType) { case EnemyBalanceStatType.MaxHealth: { float num = Mathf.Max(value, 1f); float health = character.Health; float num2 = ((stats.BaseMaxHealth > 0f) ? (health / stats.BaseMaxHealth) : 1f); num2 = Mathf.Clamp(num2, 0f, 1f); stats.RefreshVitalMaxStat(false); stats.m_maxHealthStat.BaseValue = num; stats.RefreshVitalMaxStat(false); stats.SetHealth(num2 * num); break; } case EnemyBalanceStatType.MaxStamina: { float num5 = Mathf.Max(value, 1f); float stamina = character.Stamina; float num6 = Mathf.Clamp((stats.MaxStamina > 0f) ? (stamina / stats.MaxStamina) : 1f, 0f, 1f); stats.RefreshVitalMaxStat(false); stats.m_maxStamina.BaseValue = num5; stats.RefreshVitalMaxStat(false); float num7 = num6 * num5 - stamina; if (Mathf.Abs(num7) > 0.01f) { stats.AffectStamina(num7); } break; } case EnemyBalanceStatType.MaxMana: { float num3 = Mathf.Max(value, 1f); float currentMana = stats.CurrentMana; float num4 = ((stats.BaseMaxMana > 0f) ? (currentMana / stats.BaseMaxMana) : 1f); num4 = Mathf.Clamp(num4, 0f, 1f); stats.RefreshVitalMaxStat(false); stats.m_maxManaStat.BaseValue = num3; stats.RefreshVitalMaxStat(false); stats.SetMana(num4 * num3); break; } case EnemyBalanceStatType.HealthRegen: case EnemyBalanceStatType.StaminaRegen: case EnemyBalanceStatType.ManaRegen: case EnemyBalanceStatType.ColdProtection: case EnemyBalanceStatType.HeatProtection: case EnemyBalanceStatType.ColdRegenRate: case EnemyBalanceStatType.HeatRegenRate: case EnemyBalanceStatType.CorruptionProtection: case EnemyBalanceStatType.Waterproof: case EnemyBalanceStatType.Impact: case EnemyBalanceStatType.ImpactResistance: case EnemyBalanceStatType.MovementSpeed: case EnemyBalanceStatType.AttackSpeed: case EnemyBalanceStatType.SkillCooldownModifier: case EnemyBalanceStatType.DodgeInvulnerabilityModifier: case EnemyBalanceStatType.GlobalStatusResistance: { Stat gameStat = GetGameStat(stats, statType); if (gameStat != null) { gameStat.BaseValue = value; gameStat.Update(); } break; } case EnemyBalanceStatType.PhysicalDamage: stats.m_damageTypesModifier[0].BaseValue = value; stats.m_damageTypesModifier[0].Update(); break; case EnemyBalanceStatType.EtherealDamage: stats.m_damageTypesModifier[1].BaseValue = value; stats.m_damageTypesModifier[1].Update(); break; case EnemyBalanceStatType.DecayDamage: stats.m_damageTypesModifier[2].BaseValue = value; stats.m_damageTypesModifier[2].Update(); break; case EnemyBalanceStatType.ElectricDamage: stats.m_damageTypesModifier[3].BaseValue = value; stats.m_damageTypesModifier[3].Update(); break; case EnemyBalanceStatType.FrostDamage: stats.m_damageTypesModifier[4].BaseValue = value; stats.m_damageTypesModifier[4].Update(); break; case EnemyBalanceStatType.FireDamage: stats.m_damageTypesModifier[5].BaseValue = value; stats.m_damageTypesModifier[5].Update(); break; case EnemyBalanceStatType.PhysicalResistance: stats.m_damageResistance[0].BaseValue = value; stats.m_damageResistance[0].Update(); break; case EnemyBalanceStatType.EtherealResistance: stats.m_damageResistance[1].BaseValue = value; stats.m_damageResistance[1].Update(); break; case EnemyBalanceStatType.DecayResistance: stats.m_damageResistance[2].BaseValue = value; stats.m_damageResistance[2].Update(); break; case EnemyBalanceStatType.ElectricResistance: stats.m_damageResistance[3].BaseValue = value; stats.m_damageResistance[3].Update(); break; case EnemyBalanceStatType.FrostResistance: stats.m_damageResistance[4].BaseValue = value; stats.m_damageResistance[4].Update(); break; case EnemyBalanceStatType.FireResistance: stats.m_damageResistance[5].BaseValue = value; stats.m_damageResistance[5].Update(); break; case EnemyBalanceStatType.PhysicalProtection: stats.m_damageProtection[0].BaseValue = value; stats.m_damageProtection[0].Update(); break; case EnemyBalanceStatType.EtherealProtection: stats.m_damageProtection[1].BaseValue = value; stats.m_damageProtection[1].Update(); break; case EnemyBalanceStatType.DecayProtection: stats.m_damageProtection[2].BaseValue = value; stats.m_damageProtection[2].Update(); break; case EnemyBalanceStatType.ElectricProtection: stats.m_damageProtection[3].BaseValue = value; stats.m_damageProtection[3].Update(); break; case EnemyBalanceStatType.FrostProtection: stats.m_damageProtection[4].BaseValue = value; stats.m_damageProtection[4].Update(); break; case EnemyBalanceStatType.FireProtection: stats.m_damageProtection[5].BaseValue = value; stats.m_damageProtection[5].Update(); break; } if (statType == EnemyBalanceStatType.HealthRegen || statType == EnemyBalanceStatType.StaminaRegen || statType == EnemyBalanceStatType.ManaRegen || statType == EnemyBalanceStatType.ColdProtection || statType == EnemyBalanceStatType.HeatProtection || statType == EnemyBalanceStatType.ColdRegenRate || statType == EnemyBalanceStatType.HeatRegenRate || statType == EnemyBalanceStatType.CorruptionProtection || statType == EnemyBalanceStatType.Waterproof || statType == EnemyBalanceStatType.ImpactResistance) { stats.UpdateStats(); } if (statType == EnemyBalanceStatType.PhysicalResistance || statType == EnemyBalanceStatType.EtherealResistance || statType == EnemyBalanceStatType.DecayResistance || statType == EnemyBalanceStatType.ElectricResistance || statType == EnemyBalanceStatType.FrostResistance || statType == EnemyBalanceStatType.FireResistance || statType == EnemyBalanceStatType.PhysicalProtection || statType == EnemyBalanceStatType.EtherealProtection || statType == EnemyBalanceStatType.DecayProtection || statType == EnemyBalanceStatType.ElectricProtection || statType == EnemyBalanceStatType.FrostProtection || statType == EnemyBalanceStatType.FireProtection) { stats.UpdateStats(); } } } public class FactionBalancerManager { private static FactionBalancerManager _instance; public static FactionBalancerManager Instance { get { if (_instance == null) { _instance = new FactionBalancerManager(); } return _instance; } } private FactionBalancerManager() { } public void ApplyFactionRules() { CharacterAI[] array = Object.FindObjectsOfType<CharacterAI>(); for (int i = 0; i < array.Length; i++) { Character character = ((CharacterControl)array[i]).Character; if ((Object)(object)character == (Object)null || !character.Alive) { continue; } List<FactionRule> matchingRules = FactionRuleRegistryManager.Instance.GetMatchingRules(character); if (matchingRules.Count <= 0) { continue; } foreach (FactionRule item in matchingRules) { ApplyRuleToCharacter(character, item); } } } public void ApplyFactionRule(FactionRule rule) { CharacterAI[] array = Object.FindObjectsOfType<CharacterAI>(); for (int i = 0; i < array.Length; i++) { Character character = ((CharacterControl)array[i]).Character; if (!((Object)(object)character == (Object)null) && character.Alive && rule.Matches(character)) { ApplyRuleToCharacter(character, rule); } } } private void ApplyRuleToCharacter(Character character, FactionRule rule) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)character == (Object)null) && (int)rule.newFaction != 0) { _ = character.Faction; character.ChangeFaction(rule.newFaction, true); } } } public class FactionRuleRegistryManager { private static FactionRuleRegistryManager _instance; public List<FactionRule> factionRules = new List<FactionRule>(); public static FactionRuleRegistryManager Instance { get { if (_instance == null) { _instance = new FactionRuleRegistryManager(); } return _instance; } } private FactionRuleRegistryManager() { } public void AppendFactionRules(List<FactionRule> rules) { foreach (FactionRule rule in rules) { AppendFactionRule(rule); } } public void AppendFactionRule(FactionRule rule) { FactionRule factionRule = factionRules.FirstOrDefault((FactionRule r) => r.id == rule.id); if (factionRule != null) { OutwardEnemiesBalancer.LogSL("FactionRule '" + rule.id + "' already exists. Replacing."); factionRules.Remove(factionRule); } factionRules.Add(rule); EventBusPublisher.SendAppendFactionRule(rule.id); } public void RemoveFactionRuleById(string id) { foreach (FactionRule item in factionRules.Where((FactionRule rule) => rule.id == id).ToList()) { RemoveFactionRule(item); } } public void RemoveFactionRule(FactionRule rule) { factionRules.Remove(rule); EventBusPublisher.SendRemoveFactionRule(rule.id); } public List<FactionRule> GetMatchingRules(Character character) { List<FactionRule> list = new List<FactionRule>(); foreach (FactionRule factionRule in factionRules) { if (factionRule.Matches(character)) { list.Add(factionRule); } } return list; } public void Clear() { factionRules.Clear(); } } public static class PathsManager { public const string ConfigDirectoryName = "Enemies_Balancer"; public static readonly string ConfigPath; public static readonly string DefaultBalanceRulesPath; public static void Initialize() { if (!Directory.Exists(ConfigPath)) { Directory.CreateDirectory(ConfigPath); } } static PathsManager() { ConfigPath = Path.Combine(PathsManager.ConfigPath, "Enemies_Balancer"); DefaultBalanceRulesPath = Path.Combine(ConfigPath, "BalanceRules.xml"); Initialize(); } } } namespace OutwardEnemiesBalancer.Events { public static class EventBusPublisher { public const string Event_BalanceRuleAppended = "BalanceRuleAppended"; public const string Event_BalanceRuleRemoved = "BalanceRuleRemoved"; public const string Event_FactionRuleAppended = "FactionRuleAppended"; public const string Event_FactionRuleRemoved = "FactionRuleRemoved"; public static void SendAppendBalancingRule(string ruleId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown EventPayload val = new EventPayload(); val.Set("balanceRuleId", (object)ruleId); EventBus.Publish("gymmed.enemies_balancer", "BalanceRuleAppended", val); } public static void SendRemoveBalancingRule(string ruleId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown EventPayload val = new EventPayload(); val.Set("balanceRuleId", (object)ruleId); EventBus.Publish("gymmed.enemies_balancer", "BalanceRuleRemoved", val); } public static void SendAppendFactionRule(string ruleId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown EventPayload val = new EventPayload(); val.Set("factionRuleId", (object)ruleId); EventBus.Publish("gymmed.enemies_balancer", "FactionRuleAppended", val); } public static void SendRemoveFactionRule(string ruleId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown EventPayload val = new EventPayload(); val.Set("factionRuleId", (object)ruleId); EventBus.Publish("gymmed.enemies_balancer", "FactionRuleRemoved", val); } } public static class EventBusRegister { private static readonly (string key, Type type, string description)[] TargetingParams = new(string, Type, string)[5] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.EnemyId), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.EnemyName), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.AreaFamily), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Faction), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.AreaEnum) }; private static readonly (string key, Type type, string description)[] UniqueEnemyParams = new(string, Type, string)[5] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForBosses), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForBossesPawns), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForStoryBosses), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForUniqueArenaBosses), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.IsForUniqueEnemies) }; private static readonly (string key, Type type, string description)[] StatModParams = new(string, Type, string)[4] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.StatModifications), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ModifierType), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.StatType), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Value) }; private static readonly (string key, Type type, string description)[] ExceptionsParams = new(string, Type, string)[2] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptIds), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptNames) }; private static readonly (string key, Type type, string description)[] VitalStatsParams = new(string, Type, string)[7] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MaxHealth), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MaxStamina), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MaxMana), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.HealthRegen), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.StaminaRegen), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ManaRegen), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ModifierType) }; private static readonly (string key, Type type, string description)[] EnvironmentalStatsParams = new(string, Type, string)[5] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ColdProtection), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.HeatProtection), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.CorruptionResistance), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Waterproof), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ModifierType) }; private static readonly (string key, Type type, string description)[] CombatStatsParams = new(string, Type, string)[5] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.Impact), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ImpactResistance), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.MovementSpeed), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.AttackSpeed), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ModifierType) }; private static readonly (string key, Type type, string description)[] FactionRuleParams = new(string, Type, string)[3] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.NewFaction), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptIds), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptNames) }; public static void RegisterEvents() { EventBus.RegisterEvent("gymmed.enemies_balancer", "BalanceRuleAppended", "Published when a balancing rule is appended.", new(string, Type, string)[1] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.BalanceRuleId) }); EventBus.RegisterEvent("gymmed.enemies_balancer", "BalanceRuleRemoved", "Published when a balancing rule is removed.", new(string, Type, string)[1] { EnemyBalanceParamsHelper.Get(EnemyBalanceParams.BalanceRuleId) }); EventBus.RegisterEvent("gymmed.enemies_balancer_*", "AddBalanceRule", "Add a full balancing rule with all targeting options and stat modifications.", EnemyBalanceParamsHelper.Combine(EnemyBalanceParamsHelper.Get(EnemyBalanceParams.BalanceRuleId), TargetingParams, UniqueEnemyParams, StatModParams, ExceptionsParams)); EventBus.RegisterEvent("gymmed.enemies_balancer_*", "AddBalanceRuleByEnemyName", "Add balancing rule targeting by enemy name.", EnemyBalanceParamsHelper.Combine(EnemyBalanceParamsHelper.Get(EnemyBalanceParams.BalanceRuleId), EnemyBalanceParamsHelper.Get(EnemyBalanceParams.EnemyName), StatModParams, EnemyBalanceParamsHelper.Get(EnemyBalanceParams.ExceptIds))); EventBus.RegisterEvent("gymmed.enemies_balancer_*", "AddBalanceRuleByEnemyId", "Add balancing rule targeting by enem