using System; using System.Collections.Generic; using System.IO; using Terraria; using Terraria.Localization; using Terraria.ModLoader.IO; namespace SoulHarvest.Common; /// /// Character-owned combat progression shared by every weapon, wing and /// accessory that cuts open the Death Domain. It intentionally contains no /// domain toggle: opening the complete field remains a Death Necklace ability. /// public sealed class DeathDomainProgression : IDeathAltarUpgradeable { private const string SaveKey = "DeathDomainProgression"; public const int MaxCoreLevel = 10; public const int MaxVolleyLevel = 5; public const int MaxLifeStealLevel = 5; public const int MaxSoulRefinementLevel = 5; public const float SoulRefinementPerLevel = 0.10f; private static readonly DeathAltarUpgradeType[] UpgradeTypes = [ DeathAltarUpgradeType.DomainFrequency, DeathAltarUpgradeType.DomainDamage, DeathAltarUpgradeType.DomainVolley, DeathAltarUpgradeType.DomainLifeSteal, DeathAltarUpgradeType.DomainSoulRefinement ]; public int FrequencyLevel { get; private set; } = 1; public int DamageLevel { get; private set; } = 1; public int VolleyLevel { get; private set; } = 1; public int LifeStealLevel { get; private set; } public int SoulRefinementLevel { get; private set; } public int SpawnInterval => Math.Max(34, 100 - (FrequencyLevel - 1) * 7); public float NormalSlashLifeRatio => DamageLevel * 0.01f; public float BossSlashLifeRatio => DamageLevel * 0.001f; public int SlashCount => VolleyLevel; public float SoulRefinementBonus => SoulRefinementLevel * SoulRefinementPerLevel; public float VisualMastery => Math.Clamp( (FrequencyLevel + DamageLevel + VolleyLevel + LifeStealLevel + SoulRefinementLevel - 3f) / 32f, 0f, 1f); public IReadOnlyList AltarUpgradeTypes => UpgradeTypes; public void Reset() { FrequencyLevel = 1; DamageLevel = 1; VolleyLevel = 1; LifeStealLevel = 0; SoulRefinementLevel = 0; } public int ApplySoulRefinement(int baseAmount) { if (baseAmount <= 0 || SoulRefinementLevel <= 0) return Math.Max(0, baseAmount); int bonus = (int)Math.Floor(baseAmount * SoulRefinementBonus); return Math.Max(baseAmount, baseAmount + bonus); } public bool MergeLegacyNecklaceLevels(int frequency, int damage, int volley, int lifeSteal) { int oldFrequency = FrequencyLevel; int oldDamage = DamageLevel; int oldVolley = VolleyLevel; int oldLifeSteal = LifeStealLevel; FrequencyLevel = Math.Max(FrequencyLevel, Math.Clamp(frequency, 1, MaxCoreLevel)); DamageLevel = Math.Max(DamageLevel, Math.Clamp(damage, 1, MaxCoreLevel)); VolleyLevel = Math.Max(VolleyLevel, Math.Clamp(volley, 1, MaxVolleyLevel)); LifeStealLevel = Math.Max(LifeStealLevel, Math.Clamp(lifeSteal, 0, MaxLifeStealLevel)); return oldFrequency != FrequencyLevel || oldDamage != DamageLevel || oldVolley != VolleyLevel || oldLifeSteal != LifeStealLevel; } public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType type) => type switch { DeathAltarUpgradeType.DomainFrequency => FrequencyLevel >= MaxCoreLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence((FrequencyLevel + 1) / 2), DeathAltarUpgradeType.DomainDamage => DamageLevel >= MaxCoreLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(DamageLevel), DeathAltarUpgradeType.DomainVolley => VolleyLevel >= MaxVolleyLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(VolleyLevel * 3), DeathAltarUpgradeType.DomainLifeSteal => LifeStealLevel >= MaxLifeStealLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(2 + LifeStealLevel * 2), DeathAltarUpgradeType.DomainSoulRefinement => SoulRefinementLevel >= MaxSoulRefinementLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(4 + SoulRefinementLevel * 3), _ => DeathAltarPrice.Unavailable }; public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType type) { if (!GetUpgradePrice(type).IsAvailable) return false; switch (type) { case DeathAltarUpgradeType.DomainFrequency: FrequencyLevel++; break; case DeathAltarUpgradeType.DomainDamage: DamageLevel++; break; case DeathAltarUpgradeType.DomainVolley: VolleyLevel++; break; case DeathAltarUpgradeType.DomainLifeSteal: LifeStealLevel++; break; case DeathAltarUpgradeType.DomainSoulRefinement: SoulRefinementLevel++; break; default: return false; } return true; } public string GetCurrentUpgradeValue(DeathAltarUpgradeType type) => type switch { DeathAltarUpgradeType.DomainFrequency => $"{SpawnInterval / 60f:0.##}s", DeathAltarUpgradeType.DomainDamage => FormatSlashDamage(NormalSlashLifeRatio, BossSlashLifeRatio), DeathAltarUpgradeType.DomainVolley => $"×{SlashCount}", DeathAltarUpgradeType.DomainLifeSteal => $"{LifeStealLevel}%", DeathAltarUpgradeType.DomainSoulRefinement => $"+{SoulRefinementBonus * 100f:0}%", _ => string.Empty }; public string GetNextUpgradeValue(DeathAltarUpgradeType type) => type switch { DeathAltarUpgradeType.DomainFrequency => $"{Math.Max(34, SpawnInterval - 7) / 60f:0.##}s", DeathAltarUpgradeType.DomainDamage => FormatSlashDamage(NormalSlashLifeRatio + 0.01f, BossSlashLifeRatio + 0.001f), DeathAltarUpgradeType.DomainVolley => $"×{SlashCount + 1}", DeathAltarUpgradeType.DomainLifeSteal => $"{LifeStealLevel + 1}%", DeathAltarUpgradeType.DomainSoulRefinement => $"+{(SoulRefinementLevel + 1) * SoulRefinementPerLevel * 100f:0}%", _ => string.Empty }; public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType type) => DeathAltarUnavailableReason.Maxed; public void SaveData(TagCompound rootTag) { rootTag[SaveKey] = new TagCompound { [nameof(FrequencyLevel)] = FrequencyLevel, [nameof(DamageLevel)] = DamageLevel, [nameof(VolleyLevel)] = VolleyLevel, [nameof(LifeStealLevel)] = LifeStealLevel, [nameof(SoulRefinementLevel)] = SoulRefinementLevel }; } public void LoadData(TagCompound rootTag) { Reset(); if (!rootTag.ContainsKey(SaveKey)) return; TagCompound tag = rootTag.GetCompound(SaveKey); FrequencyLevel = Math.Clamp(tag.GetInt(nameof(FrequencyLevel)), 1, MaxCoreLevel); DamageLevel = Math.Clamp(tag.GetInt(nameof(DamageLevel)), 1, MaxCoreLevel); VolleyLevel = Math.Clamp(tag.GetInt(nameof(VolleyLevel)), 1, MaxVolleyLevel); LifeStealLevel = Math.Clamp(tag.GetInt(nameof(LifeStealLevel)), 0, MaxLifeStealLevel); SoulRefinementLevel = Math.Clamp(tag.GetInt(nameof(SoulRefinementLevel)), 0, MaxSoulRefinementLevel); } public void NetSend(BinaryWriter writer) { writer.Write((byte)FrequencyLevel); writer.Write((byte)DamageLevel); writer.Write((byte)VolleyLevel); writer.Write((byte)LifeStealLevel); writer.Write((byte)SoulRefinementLevel); } public void NetReceive(BinaryReader reader) { FrequencyLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxCoreLevel); DamageLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxCoreLevel); VolleyLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxVolleyLevel); LifeStealLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxLifeStealLevel); SoulRefinementLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxSoulRefinementLevel); } private static string FormatSlashDamage(float normalRatio, float bossRatio) => Language.GetTextValue("Mods.SoulHarvest.UI.NecklaceSlashDamage", normalRatio * 100f, bossRatio * 100f); }