using System;
using System.Collections.Generic;
using System.IO;
using Terraria;
using Terraria.Localization;
using Terraria.ModLoader.IO;
namespace SoulHarvest.Common;
/// <summary>
/// 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.
/// </summary>
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<DeathAltarUpgradeType> 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);
}
using System;
using System.Collections.Generic;
using System.IO;
using Terraria;
using Terraria.Localization;
using Terraria.ModLoader.IO;
namespace SoulHarvest.Common;
/// <summary>
/// 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.
/// </summary>
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<DeathAltarUpgradeType> 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);
}