using System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AskaEnemyScaling.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("AskaEnemyScaling.Core")] [assembly: AssemblyTitle("AskaEnemyScaling.Core")] [assembly: AssemblyVersion("1.0.0.0")] namespace AskaEnemyScaling.Core; [Serializable] public sealed class ScalingAxis { public float Peak; public float Shape; public float Overdrive; public float Cap; public bool IsNoOp { get { if (Peak <= 1f) { return Overdrive <= 0f; } return false; } } public ScalingAxis() : this(1f, 1f, 0f, 1f) { } public ScalingAxis(float peak, float shape, float overdrive, float cap) { Peak = peak; Shape = shape; Overdrive = overdrive; Cap = cap; } public float Evaluate(float progress) { if (float.IsNaN(progress) || progress <= 0f) { return 1f; } float num = ((progress < 1f) ? progress : 1f); float num2 = ((progress > 1f) ? (progress - 1f) : 0f); float num3 = ((Shape > 0f) ? Shape : 1f); float num4 = (float)Math.Pow(num, num3); float num5 = 1f + (Peak - 1f) * num4 + num2 * Overdrive; if (num5 < 1f) { num5 = 1f; } float num6 = ((Cap < 1f) ? 1f : Cap); if (num5 > num6) { num5 = num6; } return num5; } public ScalingAxis Clone() { return new ScalingAxis(Peak, Shape, Overdrive, Cap); } } public readonly struct ProgressionSnapshot { public readonly int DaysPassed; public readonly int VillagerCount; public ProgressionSnapshot(int daysPassed, int villagerCount) { DaysPassed = daysPassed; VillagerCount = villagerCount; } } public readonly struct EnemyMultipliers { public readonly float Health; public readonly float Damage; public readonly float SpawnCount; public static readonly EnemyMultipliers None = new EnemyMultipliers(1f, 1f, 1f); public bool IsNoOp { get { if (Health <= 1f && Damage <= 1f) { return SpawnCount <= 1f; } return false; } } public EnemyMultipliers(float health, float damage, float spawnCount) { Health = health; Damage = damage; SpawnCount = spawnCount; } public EnemyMultipliers Dampen(float factor) { if (factor >= 1f) { return this; } if (factor < 0f) { factor = 0f; } return new EnemyMultipliers(1f + (Health - 1f) * factor, 1f + (Damage - 1f) * factor, 1f + (SpawnCount - 1f) * factor); } public override string ToString() { return $"HP x{Health:0.00} DMG x{Damage:0.00} COUNT x{SpawnCount:0.00}"; } } public static class ScalingCalculator { public static float NormaliseDriver(float value, float start, float full) { float num = full - start; if (num <= 0f) { if (!(value >= start)) { return 0f; } return 1f; } float num2 = (value - start) / num; if (!(num2 > 0f)) { return 0f; } return num2; } public static float ComputeProgress(ScalingProfile profile, ProgressionSnapshot snapshot) { float num = NormaliseDriver(snapshot.DaysPassed, profile.DayStart, profile.DayFull); float num2 = NormaliseDriver(snapshot.VillagerCount, profile.VillagerStart, profile.VillagerFull); switch (profile.Combine) { case CombineMode.DaysOnly: return num; case CombineMode.VillagersOnly: return num2; case CombineMode.Average: return (num + num2) * 0.5f; case CombineMode.Weighted: { float num3 = profile.DayWeight + profile.VillagerWeight; if (num3 <= 0f) { return 0f; } return (num * profile.DayWeight + num2 * profile.VillagerWeight) / num3; } default: if (!(num > num2)) { return num2; } return num; } } public static EnemyMultipliers Compute(ScalingProfile profile, ProgressionSnapshot snapshot) { if (profile == null || profile.IsNoOp) { return EnemyMultipliers.None; } float progress = ComputeProgress(profile, snapshot); return new EnemyMultipliers(profile.Health.Evaluate(progress), profile.Damage.Evaluate(progress), profile.SpawnCount.Evaluate(progress)); } public static int ScaleCount(int vanillaCount, float multiplier, int absoluteCeiling = 0) { if (vanillaCount <= 0 || multiplier <= 1f) { return vanillaCount; } int num = (int)Math.Round((double)vanillaCount * (double)multiplier, MidpointRounding.AwayFromZero); if (num < vanillaCount) { num = vanillaCount; } if (absoluteCeiling > 0 && num > absoluteCeiling) { num = absoluteCeiling; } return num; } } public enum PresetId { Standard, RisingTide, Ragnarok, Custom } public static class ScalingPresets { public static ScalingProfile Standard() { return new ScalingProfile { Name = "Standard", Enabled = false, DayStart = 10, DayFull = 175, VillagerStart = 5, VillagerFull = 125, Combine = CombineMode.Max, Health = new ScalingAxis(1f, 1f, 0f, 1f), Damage = new ScalingAxis(1f, 1f, 0f, 1f), SpawnCount = new ScalingAxis(1f, 1f, 0f, 1f), Categories = EnemyCategories.Default }; } public static ScalingProfile RisingTide() { return new ScalingProfile { Name = "Rising Tide", Enabled = true, DayStart = 15, DayFull = 180, VillagerStart = 8, VillagerFull = 120, Combine = CombineMode.Max, Health = new ScalingAxis(2f, 1.3f, 0.35f, 3.5f), Damage = new ScalingAxis(1.5f, 1.3f, 0.2f, 2.25f), SpawnCount = new ScalingAxis(1.35f, 1.2f, 0.15f, 2f), BossDampening = 0.5f, ConcurrentEnemyCeiling = 1.4f, Categories = EnemyCategories.Default }; } public static ScalingProfile Ragnarok() { return new ScalingProfile { Name = "Ragnarok", Enabled = true, DayStart = 10, DayFull = 175, VillagerStart = 5, VillagerFull = 125, Combine = CombineMode.Max, Health = new ScalingAxis(4f, 1.15f, 1.2f, 9f), Damage = new ScalingAxis(2.5f, 1.15f, 0.55f, 5f), SpawnCount = new ScalingAxis(2f, 1.1f, 0.45f, 3.5f), BossDampening = 0.5f, ConcurrentEnemyCeiling = 1.6f, Categories = EnemyCategories.Default }; } public static ScalingProfile Get(PresetId id) { return id switch { PresetId.RisingTide => RisingTide(), PresetId.Ragnarok => Ragnarok(), _ => Standard(), }; } } public enum CombineMode { Max, Average, Weighted, DaysOnly, VillagersOnly } [Flags] public enum EnemyCategories { None = 0, Invasion = 1, Roaming = 2, CaveAndDen = 4, Bosses = 8, Wildlife = 0x10, Default = 0xF } [Serializable] public sealed class ScalingProfile { public string Name = "Custom"; public int DayStart = 10; public int DayFull = 175; public int VillagerStart = 5; public int VillagerFull = 125; public CombineMode Combine; public float DayWeight = 0.5f; public float VillagerWeight = 0.5f; public ScalingAxis Health = new ScalingAxis(); public ScalingAxis Damage = new ScalingAxis(); public ScalingAxis SpawnCount = new ScalingAxis(); public float BossDampening = 0.5f; public float ConcurrentEnemyCeiling = 1.6f; public EnemyCategories Categories = EnemyCategories.Default; public bool Enabled = true; public bool IsNoOp { get { if (Enabled) { if (Health.IsNoOp && Damage.IsNoOp) { return SpawnCount.IsNoOp; } return false; } return true; } } public bool Affects(EnemyCategories category) { return (Categories & category) != 0; } public ScalingProfile Clone() { return new ScalingProfile { Name = Name, DayStart = DayStart, DayFull = DayFull, VillagerStart = VillagerStart, VillagerFull = VillagerFull, Combine = Combine, DayWeight = DayWeight, VillagerWeight = VillagerWeight, Health = Health.Clone(), Damage = Damage.Clone(), SpawnCount = SpawnCount.Clone(), BossDampening = BossDampening, ConcurrentEnemyCeiling = ConcurrentEnemyCeiling, Categories = Categories, Enabled = Enabled }; } }