using SoulHarvest.Common;
using SoulHarvest.Tiles;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;
namespace SoulHarvest.Items;
public class DeathNecklace : ModItem, IDeathAltarUpgradeable
{
public const int MaxCoreLevel = 10;
public const int MaxAbsorbAllLevel = 10;
public const float RadiusGrowthPerLevel = 42f;
private static readonly DeathAltarUpgradeType[] UpgradeTypes =
[
DeathAltarUpgradeType.NecklaceRadius,
DeathAltarUpgradeType.NecklaceAbsorbAll,
DeathAltarUpgradeType.NecklaceSovereignty
];
public int RadiusLevel { get; private set; } = 1;
public int AbsorbAllLevel { get; private set; } = 1;
public bool SovereigntyUnlocked { get; private set; }
// Serialized only so existing necklace items can migrate their combat tree
// into the new character-owned DeathDomainProgression.
internal int LegacyFrequencyLevel { get; private set; } = 1;
internal int LegacyDamageLevel { get; private set; } = 1;
internal int LegacyVolleyLevel { get; private set; } = 1;
internal int LegacyLifeStealLevel { get; private set; }
public float DomainRadius => 160f + (RadiusLevel - 1) * RadiusGrowthPerLevel;
public float DomainAttractionRadius => DomainRadius + 320f + (AbsorbAllLevel - 1) * 32f;
public float DomainAttractionStrength => 0.035f + (AbsorbAllLevel - 1) * 0.0075f;
public float DomainAttractionMaximumSpeed => 0.75f + (AbsorbAllLevel - 1) * 0.09f;
public float DomainRepulsionStrength => 0.055f + (AbsorbAllLevel - 1) * 0.01f;
public float DomainRepulsionMaximumSpeed => 1.1f + (AbsorbAllLevel - 1) * 0.12f;
// Reaching the radius cap only maximizes the finite field. Full-screen
// coverage is a runtime state granted by the final Death Descends node.
public bool DeathDescentUnlocked => SovereigntyUnlocked && AllCoreStatsMaxed;
public bool DeathRequiemUnlocked => DeathDescentUnlocked;
public int MasteryScore => RadiusLevel + AbsorbAllLevel + (SovereigntyUnlocked ? 30 : 0);
public IReadOnlyList<DeathAltarUpgradeType> AltarUpgradeTypes => UpgradeTypes;
protected override bool CloneNewInstances => true;
public override void PreReforge()
{
DeathAltarReforgePreservation.Capture(this);
}
public override void PostReforge()
{
DeathAltarReforgePreservation.Restore(this);
}
public override void SetDefaults()
{
Item.width = 30;
Item.height = 38;
Item.accessory = true;
Item.value = Item.sellPrice(gold: 8);
Item.rare = ItemRarityID.LightRed;
}
public override void UpdateAccessory(Player player, bool hideVisual)
{
MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
modPlayer.MergeLegacyDeathNecklaceProgression(this);
modPlayer.SetDeathNecklace(this);
if (!Main.dedServ && SovereigntyUnlocked && Main.rand.NextBool(10))
{
Dust soul = Dust.NewDustPerfect(
player.Center + Main.rand.NextVector2Circular(24f, 34f),
DustID.DungeonSpirit,
-player.velocity * 0.04f,
70,
new Color(105, 225, 245),
0.65f);
soul.noGravity = true;
}
}
public override void UpdateInventory(Player player)
{
player.GetModPlayer<MyPlayer>().MergeLegacyDeathNecklaceProgression(this);
}
public override void ModifyTooltips(List<TooltipLine> tooltips)
{
string key = DeathDescentUnlocked ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled";
tooltips.Add(new TooltipLine(Mod, "DeathDomainStats", Language.GetTextValue(
"Mods.SoulHarvest.UI.NecklaceStatsV2",
DomainRadius / 16f,
AbsorbAllLevel,
Language.GetTextValue(key))) { OverrideColor = new Color(110, 220, 240) });
tooltips.Add(new TooltipLine(Mod, "DeathDomainToggle", Language.GetTextValue("Mods.SoulHarvest.UI.DeathDomainToggleHint")));
tooltips.Add(new TooltipLine(Mod, "SoulHarvestAltarHint", Language.GetTextValue("Mods.SoulHarvest.UI.AltarHint")));
}
public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType type)
{
return type switch
{
DeathAltarUpgradeType.NecklaceRadius => RadiusLevel >= MaxCoreLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence((RadiusLevel + 1) / 2),
DeathAltarUpgradeType.NecklaceAbsorbAll => AbsorbAllLevel >= MaxAbsorbAllLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence((AbsorbAllLevel + 1) / 2),
DeathAltarUpgradeType.NecklaceSovereignty => SovereigntyUnlocked || !AllCoreStatsMaxed ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(30),
_ => DeathAltarPrice.Unavailable
};
}
public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType type)
{
if (!GetUpgradePrice(type).IsAvailable)
return false;
switch (type)
{
case DeathAltarUpgradeType.NecklaceRadius:
RadiusLevel++;
break;
case DeathAltarUpgradeType.NecklaceAbsorbAll:
AbsorbAllLevel++;
break;
case DeathAltarUpgradeType.NecklaceSovereignty:
SovereigntyUnlocked = true;
break;
default:
return false;
}
return true;
}
public string GetCurrentUpgradeValue(DeathAltarUpgradeType type) => type switch
{
DeathAltarUpgradeType.NecklaceRadius => $"{DomainRadius / 16f:0.#} tiles",
DeathAltarUpgradeType.NecklaceAbsorbAll => $"Lv. {AbsorbAllLevel}",
DeathAltarUpgradeType.NecklaceSovereignty => ToggleValue(SovereigntyUnlocked),
_ => string.Empty
};
public string GetNextUpgradeValue(DeathAltarUpgradeType type) => type switch
{
DeathAltarUpgradeType.NecklaceRadius => $"{(DomainRadius + RadiusGrowthPerLevel) / 16f:0.#} tiles",
DeathAltarUpgradeType.NecklaceAbsorbAll => $"Lv. {AbsorbAllLevel + 1}",
DeathAltarUpgradeType.NecklaceSovereignty => Language.GetTextValue("Mods.SoulHarvest.UI.DeathDescent"),
_ => string.Empty
};
public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType type)
{
if (type == DeathAltarUpgradeType.NecklaceSovereignty && !SovereigntyUnlocked && !AllCoreStatsMaxed)
return DeathAltarUnavailableReason.RequiresNecklaceMastery;
return DeathAltarUnavailableReason.Maxed;
}
public override void SaveData(TagCompound tag)
{
tag[nameof(RadiusLevel)] = RadiusLevel;
tag[nameof(AbsorbAllLevel)] = AbsorbAllLevel;
tag["FrequencyLevel"] = LegacyFrequencyLevel;
tag["DamageLevel"] = LegacyDamageLevel;
tag["VolleyLevel"] = LegacyVolleyLevel;
tag["LifeStealLevel"] = LegacyLifeStealLevel;
tag[nameof(SovereigntyUnlocked)] = SovereigntyUnlocked;
}
public override void LoadData(TagCompound tag)
{
RadiusLevel = Math.Clamp(tag.GetInt(nameof(RadiusLevel)), 1, MaxCoreLevel);
SovereigntyUnlocked = tag.GetBool(nameof(SovereigntyUnlocked));
LegacyFrequencyLevel = Math.Clamp(tag.GetInt("FrequencyLevel"), 1, DeathDomainProgression.MaxCoreLevel);
LegacyDamageLevel = Math.Clamp(tag.GetInt("DamageLevel"), 1, DeathDomainProgression.MaxCoreLevel);
LegacyVolleyLevel = Math.Clamp(tag.GetInt("VolleyLevel"), 1, DeathDomainProgression.MaxVolleyLevel);
LegacyLifeStealLevel = Math.Clamp(tag.GetInt("LifeStealLevel"), 0, DeathDomainProgression.MaxLifeStealLevel);
AbsorbAllLevel = tag.ContainsKey(nameof(AbsorbAllLevel))
? Math.Clamp(tag.GetInt(nameof(AbsorbAllLevel)), 1, MaxAbsorbAllLevel)
: SovereigntyUnlocked
? MaxAbsorbAllLevel
: 1;
}
public override void NetSend(BinaryWriter writer)
{
writer.Write((byte)RadiusLevel);
writer.Write((byte)AbsorbAllLevel);
writer.Write((byte)LegacyFrequencyLevel);
writer.Write((byte)LegacyDamageLevel);
writer.Write((byte)LegacyVolleyLevel);
writer.Write((byte)LegacyLifeStealLevel);
writer.Write(SovereigntyUnlocked);
}
public override void NetReceive(BinaryReader reader)
{
RadiusLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxCoreLevel);
AbsorbAllLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxAbsorbAllLevel);
LegacyFrequencyLevel = Math.Clamp((int)reader.ReadByte(), 1, DeathDomainProgression.MaxCoreLevel);
LegacyDamageLevel = Math.Clamp((int)reader.ReadByte(), 1, DeathDomainProgression.MaxCoreLevel);
LegacyVolleyLevel = Math.Clamp((int)reader.ReadByte(), 1, DeathDomainProgression.MaxVolleyLevel);
LegacyLifeStealLevel = Math.Clamp((int)reader.ReadByte(), 0, DeathDomainProgression.MaxLifeStealLevel);
SovereigntyUnlocked = reader.ReadBoolean();
}
public override void AddRecipes()
{
CreateRecipe()
.AddIngredient(ItemID.PanicNecklace)
.AddIngredient(ItemID.ObsidianRose)
.AddIngredient<Soul>(3)
.AddTile(ModContent.TileType<DeathAltarTile>())
.Register();
}
private bool AllCoreStatsMaxed => RadiusLevel >= MaxCoreLevel
&& AbsorbAllLevel >= MaxAbsorbAllLevel;
private static string ToggleValue(bool enabled) => Language.GetTextValue(
enabled ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled");
}
using SoulHarvest.Common;
using SoulHarvest.Tiles;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;
namespace SoulHarvest.Items;
public class DeathNecklace : ModItem, IDeathAltarUpgradeable
{
public const int MaxCoreLevel = 10;
public const int MaxAbsorbAllLevel = 10;
public const float RadiusGrowthPerLevel = 42f;
private static readonly DeathAltarUpgradeType[] UpgradeTypes =
[
DeathAltarUpgradeType.NecklaceRadius,
DeathAltarUpgradeType.NecklaceAbsorbAll,
DeathAltarUpgradeType.NecklaceSovereignty
];
public int RadiusLevel { get; private set; } = 1;
public int AbsorbAllLevel { get; private set; } = 1;
public bool SovereigntyUnlocked { get; private set; }
// Serialized only so existing necklace items can migrate their combat tree
// into the new character-owned DeathDomainProgression.
internal int LegacyFrequencyLevel { get; private set; } = 1;
internal int LegacyDamageLevel { get; private set; } = 1;
internal int LegacyVolleyLevel { get; private set; } = 1;
internal int LegacyLifeStealLevel { get; private set; }
public float DomainRadius => 160f + (RadiusLevel - 1) * RadiusGrowthPerLevel;
public float DomainAttractionRadius => DomainRadius + 320f + (AbsorbAllLevel - 1) * 32f;
public float DomainAttractionStrength => 0.035f + (AbsorbAllLevel - 1) * 0.0075f;
public float DomainAttractionMaximumSpeed => 0.75f + (AbsorbAllLevel - 1) * 0.09f;
public float DomainRepulsionStrength => 0.055f + (AbsorbAllLevel - 1) * 0.01f;
public float DomainRepulsionMaximumSpeed => 1.1f + (AbsorbAllLevel - 1) * 0.12f;
// Reaching the radius cap only maximizes the finite field. Full-screen
// coverage is a runtime state granted by the final Death Descends node.
public bool DeathDescentUnlocked => SovereigntyUnlocked && AllCoreStatsMaxed;
public bool DeathRequiemUnlocked => DeathDescentUnlocked;
public int MasteryScore => RadiusLevel + AbsorbAllLevel + (SovereigntyUnlocked ? 30 : 0);
public IReadOnlyList<DeathAltarUpgradeType> AltarUpgradeTypes => UpgradeTypes;
protected override bool CloneNewInstances => true;
public override void PreReforge()
{
DeathAltarReforgePreservation.Capture(this);
}
public override void PostReforge()
{
DeathAltarReforgePreservation.Restore(this);
}
public override void SetDefaults()
{
Item.width = 30;
Item.height = 38;
Item.accessory = true;
Item.value = Item.sellPrice(gold: 8);
Item.rare = ItemRarityID.LightRed;
}
public override void UpdateAccessory(Player player, bool hideVisual)
{
MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
modPlayer.MergeLegacyDeathNecklaceProgression(this);
modPlayer.SetDeathNecklace(this);
if (!Main.dedServ && SovereigntyUnlocked && Main.rand.NextBool(10))
{
Dust soul = Dust.NewDustPerfect(
player.Center + Main.rand.NextVector2Circular(24f, 34f),
DustID.DungeonSpirit,
-player.velocity * 0.04f,
70,
new Color(105, 225, 245),
0.65f);
soul.noGravity = true;
}
}
public override void UpdateInventory(Player player)
{
player.GetModPlayer<MyPlayer>().MergeLegacyDeathNecklaceProgression(this);
}
public override void ModifyTooltips(List<TooltipLine> tooltips)
{
string key = DeathDescentUnlocked ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled";
tooltips.Add(new TooltipLine(Mod, "DeathDomainStats", Language.GetTextValue(
"Mods.SoulHarvest.UI.NecklaceStatsV2",
DomainRadius / 16f,
AbsorbAllLevel,
Language.GetTextValue(key))) { OverrideColor = new Color(110, 220, 240) });
tooltips.Add(new TooltipLine(Mod, "DeathDomainToggle", Language.GetTextValue("Mods.SoulHarvest.UI.DeathDomainToggleHint")));
tooltips.Add(new TooltipLine(Mod, "SoulHarvestAltarHint", Language.GetTextValue("Mods.SoulHarvest.UI.AltarHint")));
}
public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType type)
{
return type switch
{
DeathAltarUpgradeType.NecklaceRadius => RadiusLevel >= MaxCoreLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence((RadiusLevel + 1) / 2),
DeathAltarUpgradeType.NecklaceAbsorbAll => AbsorbAllLevel >= MaxAbsorbAllLevel ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence((AbsorbAllLevel + 1) / 2),
DeathAltarUpgradeType.NecklaceSovereignty => SovereigntyUnlocked || !AllCoreStatsMaxed ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(30),
_ => DeathAltarPrice.Unavailable
};
}
public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType type)
{
if (!GetUpgradePrice(type).IsAvailable)
return false;
switch (type)
{
case DeathAltarUpgradeType.NecklaceRadius:
RadiusLevel++;
break;
case DeathAltarUpgradeType.NecklaceAbsorbAll:
AbsorbAllLevel++;
break;
case DeathAltarUpgradeType.NecklaceSovereignty:
SovereigntyUnlocked = true;
break;
default:
return false;
}
return true;
}
public string GetCurrentUpgradeValue(DeathAltarUpgradeType type) => type switch
{
DeathAltarUpgradeType.NecklaceRadius => $"{DomainRadius / 16f:0.#} tiles",
DeathAltarUpgradeType.NecklaceAbsorbAll => $"Lv. {AbsorbAllLevel}",
DeathAltarUpgradeType.NecklaceSovereignty => ToggleValue(SovereigntyUnlocked),
_ => string.Empty
};
public string GetNextUpgradeValue(DeathAltarUpgradeType type) => type switch
{
DeathAltarUpgradeType.NecklaceRadius => $"{(DomainRadius + RadiusGrowthPerLevel) / 16f:0.#} tiles",
DeathAltarUpgradeType.NecklaceAbsorbAll => $"Lv. {AbsorbAllLevel + 1}",
DeathAltarUpgradeType.NecklaceSovereignty => Language.GetTextValue("Mods.SoulHarvest.UI.DeathDescent"),
_ => string.Empty
};
public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType type)
{
if (type == DeathAltarUpgradeType.NecklaceSovereignty && !SovereigntyUnlocked && !AllCoreStatsMaxed)
return DeathAltarUnavailableReason.RequiresNecklaceMastery;
return DeathAltarUnavailableReason.Maxed;
}
public override void SaveData(TagCompound tag)
{
tag[nameof(RadiusLevel)] = RadiusLevel;
tag[nameof(AbsorbAllLevel)] = AbsorbAllLevel;
tag["FrequencyLevel"] = LegacyFrequencyLevel;
tag["DamageLevel"] = LegacyDamageLevel;
tag["VolleyLevel"] = LegacyVolleyLevel;
tag["LifeStealLevel"] = LegacyLifeStealLevel;
tag[nameof(SovereigntyUnlocked)] = SovereigntyUnlocked;
}
public override void LoadData(TagCompound tag)
{
RadiusLevel = Math.Clamp(tag.GetInt(nameof(RadiusLevel)), 1, MaxCoreLevel);
SovereigntyUnlocked = tag.GetBool(nameof(SovereigntyUnlocked));
LegacyFrequencyLevel = Math.Clamp(tag.GetInt("FrequencyLevel"), 1, DeathDomainProgression.MaxCoreLevel);
LegacyDamageLevel = Math.Clamp(tag.GetInt("DamageLevel"), 1, DeathDomainProgression.MaxCoreLevel);
LegacyVolleyLevel = Math.Clamp(tag.GetInt("VolleyLevel"), 1, DeathDomainProgression.MaxVolleyLevel);
LegacyLifeStealLevel = Math.Clamp(tag.GetInt("LifeStealLevel"), 0, DeathDomainProgression.MaxLifeStealLevel);
AbsorbAllLevel = tag.ContainsKey(nameof(AbsorbAllLevel))
? Math.Clamp(tag.GetInt(nameof(AbsorbAllLevel)), 1, MaxAbsorbAllLevel)
: SovereigntyUnlocked
? MaxAbsorbAllLevel
: 1;
}
public override void NetSend(BinaryWriter writer)
{
writer.Write((byte)RadiusLevel);
writer.Write((byte)AbsorbAllLevel);
writer.Write((byte)LegacyFrequencyLevel);
writer.Write((byte)LegacyDamageLevel);
writer.Write((byte)LegacyVolleyLevel);
writer.Write((byte)LegacyLifeStealLevel);
writer.Write(SovereigntyUnlocked);
}
public override void NetReceive(BinaryReader reader)
{
RadiusLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxCoreLevel);
AbsorbAllLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxAbsorbAllLevel);
LegacyFrequencyLevel = Math.Clamp((int)reader.ReadByte(), 1, DeathDomainProgression.MaxCoreLevel);
LegacyDamageLevel = Math.Clamp((int)reader.ReadByte(), 1, DeathDomainProgression.MaxCoreLevel);
LegacyVolleyLevel = Math.Clamp((int)reader.ReadByte(), 1, DeathDomainProgression.MaxVolleyLevel);
LegacyLifeStealLevel = Math.Clamp((int)reader.ReadByte(), 0, DeathDomainProgression.MaxLifeStealLevel);
SovereigntyUnlocked = reader.ReadBoolean();
}
public override void AddRecipes()
{
CreateRecipe()
.AddIngredient(ItemID.PanicNecklace)
.AddIngredient(ItemID.ObsidianRose)
.AddIngredient<Soul>(3)
.AddTile(ModContent.TileType<DeathAltarTile>())
.Register();
}
private bool AllCoreStatsMaxed => RadiusLevel >= MaxCoreLevel
&& AbsorbAllLevel >= MaxAbsorbAllLevel;
private static string ToggleValue(bool enabled) => Language.GetTextValue(
enabled ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled");
}