using SoulHarvest.Common;
using SoulHarvest.Tiles;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Terraria;
using Terraria.GameContent;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;
namespace SoulHarvest.Items;
[AutoloadEquip(EquipType.Body)]
public class DeathRobe : ModItem, IDeathAltarUpgradeable
{
public const int MaxRobeLevel = 10;
private static readonly int[] DefenseByLevel = [12, 20, 35, 55, 85, 130, 220, 400, 1000, 9999];
private static readonly int[] MinionsByLevel = [1, 2, 3, 4, 6, 10, 16, 26, 42, 60, 80];
private static readonly int[] TurretsByLevel = [1, 2, 3, 4, 5, 7, 9, 12, 15, 18, 20];
private static readonly DeathAltarUpgradeType[] UpgradeTypes =
[
DeathAltarUpgradeType.RobeDefense,
DeathAltarUpgradeType.RobeMaxLife,
DeathAltarUpgradeType.RobeLifeRegen,
DeathAltarUpgradeType.RobeMobility,
DeathAltarUpgradeType.RobeMinions,
DeathAltarUpgradeType.RobeTurrets,
DeathAltarUpgradeType.RobePlacementRange,
DeathAltarUpgradeType.RobeKnockbackImmunity,
DeathAltarUpgradeType.RobeFallDamageImmunity,
DeathAltarUpgradeType.RobeZeroMana,
DeathAltarUpgradeType.RobeLavaImmunity,
DeathAltarUpgradeType.RobeImmuneOnFire,
DeathAltarUpgradeType.RobeImmunePoisoned,
DeathAltarUpgradeType.RobeImmuneConfused,
DeathAltarUpgradeType.RobeImmuneBleeding,
DeathAltarUpgradeType.RobeImmuneSlow,
DeathAltarUpgradeType.RobeImmuneWeak,
DeathAltarUpgradeType.RobeImmuneSilenced,
DeathAltarUpgradeType.RobeImmuneDarkness,
DeathAltarUpgradeType.RobeImmuneCursed,
DeathAltarUpgradeType.RobeImmuneFrostburn,
DeathAltarUpgradeType.RobeImmuneFrozen,
DeathAltarUpgradeType.RobeImmuneChaosState,
DeathAltarUpgradeType.RobeImmuneWet,
DeathAltarUpgradeType.RobeImmuneBrokenArmor,
DeathAltarUpgradeType.RobeImmuneSuffocation
];
public int DefenseLevel { get; private set; } = 1;
public int MaxLifeLevel { get; private set; }
public int LifeRegenLevel { get; private set; }
public int MobilityLevel { get; private set; }
public int MinionLevel { get; private set; }
public int TurretLevel { get; private set; }
public int PlacementRangeLevel { get; private set; }
public bool KnockbackImmunityUnlocked { get; private set; }
public bool FallDamageImmunityUnlocked { get; private set; }
public bool ZeroManaUnlocked { get; private set; }
public bool LavaImmunityUnlocked { get; private set; }
public ushort DebuffImmunityMask { get; private set; }
public int DefenseValue => DefenseByLevel[DefenseLevel - 1];
public int MaxLifeBonus => MaxLifeLevel * 50;
public int LifeRegenBonus => LifeRegenLevel * 5;
public int MinionCapacity => MinionsByLevel[MinionLevel];
public int TurretCapacity => TurretsByLevel[TurretLevel];
public int PlacementRangeBonus => PlacementRangeLevel * 10;
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 SetStaticDefaults()
{
ArmorIDs.Body.Sets.HidesTopSkin[Item.bodySlot] = true;
// These sprites came directly from the 1.3 mod. A 1.4 body equip normally treats
// DeathRobe_Body as a 360x224 composite sheet, but this asset is the legacy 40x1120
// format with separate arm frames. Keep the original framing and bind each legacy
// layer explicitly so the robe uses the same player-relative positions as before.
FieldInfo? framingField = typeof(ArmorIDs.Body.Sets).GetField(
"UsesNewFramingCode",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (framingField?.GetValue(null) is bool[] usesNewFramingCode)
usesNewFramingCode[Item.bodySlot] = false;
if (!Main.dedServ)
{
var bodyTexture = ModContent.Request<Texture2D>($"{Texture}_{EquipType.Body}");
TextureAssets.ArmorBody[Item.bodySlot] = bodyTexture;
TextureAssets.FemaleBody[Item.bodySlot] = bodyTexture;
TextureAssets.ArmorArm[Item.bodySlot] = ModContent.Request<Texture2D>($"{Texture}_Arms");
}
}
public override void SetDefaults()
{
Item.width = 18;
Item.height = 18;
Item.value = Item.sellPrice(gold: 10);
Item.rare = ItemRarityID.LightRed;
ApplyDynamicStats();
}
public override void UpdateInventory(Player player) => ApplyDynamicStats();
public override void UpdateEquip(Player player)
{
player.statLifeMax2 += MaxLifeBonus;
player.lifeRegen += LifeRegenBonus;
if (MobilityLevel > 0)
{
float progress = MobilityLevel / 10f;
player.maxRunSpeed = Math.Max(player.maxRunSpeed, MathHelper.Lerp(3f, 4f, progress));
player.runAcceleration = Math.Max(player.runAcceleration, MathHelper.Lerp(0.08f, 0.7f, progress));
player.runSlowdown = Math.Max(player.runSlowdown, MathHelper.Lerp(0.08f, 1f, progress));
}
if (MinionLevel > 0)
player.maxMinions = Math.Max(player.maxMinions, MinionCapacity);
if (TurretLevel > 0)
player.maxTurrets = Math.Max(player.maxTurrets, TurretCapacity);
player.blockRange += PlacementRangeBonus;
player.noKnockback |= KnockbackImmunityUnlocked;
player.noFallDmg |= FallDamageImmunityUnlocked;
if (ZeroManaUnlocked)
player.manaCost = 0f;
if (LavaImmunityUnlocked)
{
player.lavaImmune = true;
player.fireWalk = true;
}
foreach (DeathAltarUpgradeType type in UpgradeTypes)
{
if (IsDebuffUpgrade(type) && IsDebuffImmune(type))
player.buffImmune[GetDebuffId(type)] = true;
}
if (Main.rand.NextBool(4))
{
Dust dust = Dust.NewDustDirect(player.position, player.width, player.height, DustID.BatScepter, Alpha: 100, Scale: 1f);
dust.noGravity = true;
}
}
public override void ModifyTooltips(List<TooltipLine> tooltips)
{
string stats = Language.GetTextValue(
"Mods.SoulHarvest.UI.RobeStats",
DefenseValue,
MaxLifeBonus,
LifeRegenBonus,
CountDebuffImmunities(),
15,
ToggleValue(LavaImmunityUnlocked));
tooltips.Add(new TooltipLine(Mod, "DeathRobeStats", stats) { OverrideColor = new Color(170, 95, 230) });
tooltips.Add(new TooltipLine(Mod, "SoulHarvestAltarHint", Language.GetTextValue("Mods.SoulHarvest.UI.AltarHint")));
}
public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType upgradeType)
{
if (IsDebuffUpgrade(upgradeType))
return IsDebuffImmune(upgradeType) ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(1);
return upgradeType switch
{
DeathAltarUpgradeType.RobeDefense => DefenseLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPrice(DefenseLevel, 40),
DeathAltarUpgradeType.RobeMaxLife => MaxLifeLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MaxLifeLevel, 30),
DeathAltarUpgradeType.RobeLifeRegen => LifeRegenLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(LifeRegenLevel, 35),
DeathAltarUpgradeType.RobeMobility => MobilityLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MobilityLevel, 30),
DeathAltarUpgradeType.RobeMinions => MinionLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MinionLevel, 50),
DeathAltarUpgradeType.RobeTurrets => TurretLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(TurretLevel, 50),
DeathAltarUpgradeType.RobePlacementRange => PlacementRangeLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(PlacementRangeLevel, 20),
DeathAltarUpgradeType.RobeKnockbackImmunity => KnockbackImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(8),
DeathAltarUpgradeType.RobeFallDamageImmunity => FallDamageImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(5),
DeathAltarUpgradeType.RobeZeroMana => ZeroManaUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(15),
DeathAltarUpgradeType.RobeLavaImmunity => LavaImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(8),
_ => DeathAltarPrice.Unavailable
};
}
public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType upgradeType)
{
if (!GetUpgradePrice(upgradeType).IsAvailable)
return false;
if (IsDebuffUpgrade(upgradeType))
{
DebuffImmunityMask |= (ushort)(1 << GetDebuffBit(upgradeType));
return true;
}
switch (upgradeType)
{
case DeathAltarUpgradeType.RobeDefense:
DefenseLevel++;
ApplyDynamicStats();
break;
case DeathAltarUpgradeType.RobeMaxLife:
MaxLifeLevel++;
break;
case DeathAltarUpgradeType.RobeLifeRegen:
LifeRegenLevel++;
break;
case DeathAltarUpgradeType.RobeMobility:
MobilityLevel++;
break;
case DeathAltarUpgradeType.RobeMinions:
MinionLevel++;
break;
case DeathAltarUpgradeType.RobeTurrets:
TurretLevel++;
break;
case DeathAltarUpgradeType.RobePlacementRange:
PlacementRangeLevel++;
break;
case DeathAltarUpgradeType.RobeKnockbackImmunity:
KnockbackImmunityUnlocked = true;
break;
case DeathAltarUpgradeType.RobeFallDamageImmunity:
FallDamageImmunityUnlocked = true;
break;
case DeathAltarUpgradeType.RobeZeroMana:
ZeroManaUnlocked = true;
break;
case DeathAltarUpgradeType.RobeLavaImmunity:
LavaImmunityUnlocked = true;
break;
default:
return false;
}
return true;
}
public string GetCurrentUpgradeValue(DeathAltarUpgradeType upgradeType)
{
if (IsDebuffUpgrade(upgradeType))
return Language.GetTextValue(IsDebuffImmune(upgradeType) ? "Mods.SoulHarvest.UI.Immune" : "Mods.SoulHarvest.UI.NotImmune");
return upgradeType switch
{
DeathAltarUpgradeType.RobeDefense => DefenseValue.ToString(),
DeathAltarUpgradeType.RobeMaxLife => $"+{MaxLifeBonus}",
DeathAltarUpgradeType.RobeLifeRegen => $"+{LifeRegenBonus}",
DeathAltarUpgradeType.RobeMobility => $"Lv.{MobilityLevel}",
DeathAltarUpgradeType.RobeMinions => MinionCapacity.ToString(),
DeathAltarUpgradeType.RobeTurrets => TurretCapacity.ToString(),
DeathAltarUpgradeType.RobePlacementRange => $"+{PlacementRangeBonus}",
DeathAltarUpgradeType.RobeKnockbackImmunity => ToggleValue(KnockbackImmunityUnlocked),
DeathAltarUpgradeType.RobeFallDamageImmunity => ToggleValue(FallDamageImmunityUnlocked),
DeathAltarUpgradeType.RobeZeroMana => ToggleValue(ZeroManaUnlocked),
DeathAltarUpgradeType.RobeLavaImmunity => ToggleValue(LavaImmunityUnlocked),
_ => string.Empty
};
}
public string GetNextUpgradeValue(DeathAltarUpgradeType upgradeType)
{
if (IsDebuffUpgrade(upgradeType))
return Language.GetTextValue("Mods.SoulHarvest.UI.Immune");
return upgradeType switch
{
DeathAltarUpgradeType.RobeDefense => DefenseByLevel[Math.Min(DefenseLevel, MaxRobeLevel - 1)].ToString(),
DeathAltarUpgradeType.RobeMaxLife => $"+{(MaxLifeLevel + 1) * 50}",
DeathAltarUpgradeType.RobeLifeRegen => $"+{(LifeRegenLevel + 1) * 5}",
DeathAltarUpgradeType.RobeMobility => $"Lv.{MobilityLevel + 1}",
DeathAltarUpgradeType.RobeMinions => MinionsByLevel[Math.Min(MinionLevel + 1, MaxRobeLevel)].ToString(),
DeathAltarUpgradeType.RobeTurrets => TurretsByLevel[Math.Min(TurretLevel + 1, MaxRobeLevel)].ToString(),
DeathAltarUpgradeType.RobePlacementRange => $"+{(PlacementRangeLevel + 1) * 10}",
DeathAltarUpgradeType.RobeKnockbackImmunity or DeathAltarUpgradeType.RobeFallDamageImmunity or DeathAltarUpgradeType.RobeZeroMana or DeathAltarUpgradeType.RobeLavaImmunity
=> Language.GetTextValue("Mods.SoulHarvest.UI.Enabled"),
_ => string.Empty
};
}
public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType upgradeType)
{
return DeathAltarUnavailableReason.Maxed;
}
public override void SaveData(TagCompound tag)
{
tag[nameof(DefenseLevel)] = DefenseLevel;
tag[nameof(MaxLifeLevel)] = MaxLifeLevel;
tag[nameof(LifeRegenLevel)] = LifeRegenLevel;
tag[nameof(MobilityLevel)] = MobilityLevel;
tag[nameof(MinionLevel)] = MinionLevel;
tag[nameof(TurretLevel)] = TurretLevel;
tag[nameof(PlacementRangeLevel)] = PlacementRangeLevel;
tag[nameof(KnockbackImmunityUnlocked)] = KnockbackImmunityUnlocked;
tag[nameof(FallDamageImmunityUnlocked)] = FallDamageImmunityUnlocked;
tag[nameof(ZeroManaUnlocked)] = ZeroManaUnlocked;
tag[nameof(LavaImmunityUnlocked)] = LavaImmunityUnlocked;
tag[nameof(DebuffImmunityMask)] = (int)DebuffImmunityMask;
}
public override void LoadData(TagCompound tag)
{
DefenseLevel = Math.Clamp(tag.GetInt(nameof(DefenseLevel)), 1, MaxRobeLevel);
MaxLifeLevel = Math.Clamp(tag.GetInt(nameof(MaxLifeLevel)), 0, MaxRobeLevel);
LifeRegenLevel = Math.Clamp(tag.GetInt(nameof(LifeRegenLevel)), 0, MaxRobeLevel);
MobilityLevel = Math.Clamp(tag.GetInt(nameof(MobilityLevel)), 0, MaxRobeLevel);
MinionLevel = Math.Clamp(tag.GetInt(nameof(MinionLevel)), 0, MaxRobeLevel);
TurretLevel = Math.Clamp(tag.GetInt(nameof(TurretLevel)), 0, MaxRobeLevel);
PlacementRangeLevel = Math.Clamp(tag.GetInt(nameof(PlacementRangeLevel)), 0, MaxRobeLevel);
KnockbackImmunityUnlocked = tag.GetBool(nameof(KnockbackImmunityUnlocked));
FallDamageImmunityUnlocked = tag.GetBool(nameof(FallDamageImmunityUnlocked));
ZeroManaUnlocked = tag.GetBool(nameof(ZeroManaUnlocked));
LavaImmunityUnlocked = tag.GetBool(nameof(LavaImmunityUnlocked));
DebuffImmunityMask = (ushort)Math.Clamp(tag.GetInt(nameof(DebuffImmunityMask)), 0, ushort.MaxValue);
ApplyDynamicStats();
}
public override void NetSend(BinaryWriter writer)
{
writer.Write((byte)DefenseLevel);
writer.Write((byte)MaxLifeLevel);
writer.Write((byte)LifeRegenLevel);
writer.Write((byte)MobilityLevel);
writer.Write((byte)MinionLevel);
writer.Write((byte)TurretLevel);
writer.Write((byte)PlacementRangeLevel);
writer.Write(KnockbackImmunityUnlocked);
writer.Write(FallDamageImmunityUnlocked);
writer.Write(ZeroManaUnlocked);
writer.Write(LavaImmunityUnlocked);
writer.Write(DebuffImmunityMask);
}
public override void NetReceive(BinaryReader reader)
{
DefenseLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxRobeLevel);
MaxLifeLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
LifeRegenLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
MobilityLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
MinionLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
TurretLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
PlacementRangeLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
KnockbackImmunityUnlocked = reader.ReadBoolean();
FallDamageImmunityUnlocked = reader.ReadBoolean();
ZeroManaUnlocked = reader.ReadBoolean();
LavaImmunityUnlocked = reader.ReadBoolean();
DebuffImmunityMask = reader.ReadUInt16();
ApplyDynamicStats();
}
public override void AddRecipes()
{
CreateRecipe()
.AddIngredient(ItemID.Silk, 20)
.AddIngredient<Soul>()
.AddTile(ModContent.TileType<DeathAltarTile>())
.Register();
}
private void ApplyDynamicStats()
{
Item.defense = DefenseValue;
}
private bool IsDebuffImmune(DeathAltarUpgradeType type)
{
return (DebuffImmunityMask & (1 << GetDebuffBit(type))) != 0;
}
private int CountDebuffImmunities()
{
int count = 0;
ushort mask = DebuffImmunityMask;
while (mask != 0)
{
count += mask & 1;
mask >>= 1;
}
return count;
}
private static bool IsDebuffUpgrade(DeathAltarUpgradeType type)
{
return type >= DeathAltarUpgradeType.RobeImmuneOnFire && type <= DeathAltarUpgradeType.RobeImmuneSuffocation;
}
private static int GetDebuffBit(DeathAltarUpgradeType type)
{
return (int)type - (int)DeathAltarUpgradeType.RobeImmuneOnFire;
}
private static int GetDebuffId(DeathAltarUpgradeType type)
{
return type switch
{
DeathAltarUpgradeType.RobeImmuneOnFire => BuffID.OnFire,
DeathAltarUpgradeType.RobeImmunePoisoned => BuffID.Poisoned,
DeathAltarUpgradeType.RobeImmuneConfused => BuffID.Confused,
DeathAltarUpgradeType.RobeImmuneBleeding => BuffID.Bleeding,
DeathAltarUpgradeType.RobeImmuneSlow => BuffID.Slow,
DeathAltarUpgradeType.RobeImmuneWeak => BuffID.Weak,
DeathAltarUpgradeType.RobeImmuneSilenced => BuffID.Silenced,
DeathAltarUpgradeType.RobeImmuneDarkness => BuffID.Darkness,
DeathAltarUpgradeType.RobeImmuneCursed => BuffID.Cursed,
DeathAltarUpgradeType.RobeImmuneFrostburn => BuffID.Frostburn,
DeathAltarUpgradeType.RobeImmuneFrozen => BuffID.Frozen,
DeathAltarUpgradeType.RobeImmuneChaosState => BuffID.ChaosState,
DeathAltarUpgradeType.RobeImmuneWet => BuffID.Wet,
DeathAltarUpgradeType.RobeImmuneBrokenArmor => BuffID.BrokenArmor,
DeathAltarUpgradeType.RobeImmuneSuffocation => BuffID.Suffocation,
_ => 0
};
}
private static DeathAltarPrice LevelPrice(int currentLevel, int soulMultiplier)
{
return currentLevel < 6
? DeathAltarPrice.Souls(currentLevel * soulMultiplier)
: DeathAltarPrice.Essence(currentLevel * 2);
}
private static DeathAltarPrice LevelPriceFromZero(int currentLevel, int soulMultiplier)
{
return currentLevel < 5
? DeathAltarPrice.Souls((currentLevel + 1) * soulMultiplier)
: DeathAltarPrice.Essence((currentLevel + 1) * 2);
}
private static string ToggleValue(bool enabled)
{
return Language.GetTextValue(enabled ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled");
}
}
using SoulHarvest.Common;
using SoulHarvest.Tiles;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Terraria;
using Terraria.GameContent;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;
namespace SoulHarvest.Items;
[AutoloadEquip(EquipType.Body)]
public class DeathRobe : ModItem, IDeathAltarUpgradeable
{
public const int MaxRobeLevel = 10;
private static readonly int[] DefenseByLevel = [12, 20, 35, 55, 85, 130, 220, 400, 1000, 9999];
private static readonly int[] MinionsByLevel = [1, 2, 3, 4, 6, 10, 16, 26, 42, 60, 80];
private static readonly int[] TurretsByLevel = [1, 2, 3, 4, 5, 7, 9, 12, 15, 18, 20];
private static readonly DeathAltarUpgradeType[] UpgradeTypes =
[
DeathAltarUpgradeType.RobeDefense,
DeathAltarUpgradeType.RobeMaxLife,
DeathAltarUpgradeType.RobeLifeRegen,
DeathAltarUpgradeType.RobeMobility,
DeathAltarUpgradeType.RobeMinions,
DeathAltarUpgradeType.RobeTurrets,
DeathAltarUpgradeType.RobePlacementRange,
DeathAltarUpgradeType.RobeKnockbackImmunity,
DeathAltarUpgradeType.RobeFallDamageImmunity,
DeathAltarUpgradeType.RobeZeroMana,
DeathAltarUpgradeType.RobeLavaImmunity,
DeathAltarUpgradeType.RobeImmuneOnFire,
DeathAltarUpgradeType.RobeImmunePoisoned,
DeathAltarUpgradeType.RobeImmuneConfused,
DeathAltarUpgradeType.RobeImmuneBleeding,
DeathAltarUpgradeType.RobeImmuneSlow,
DeathAltarUpgradeType.RobeImmuneWeak,
DeathAltarUpgradeType.RobeImmuneSilenced,
DeathAltarUpgradeType.RobeImmuneDarkness,
DeathAltarUpgradeType.RobeImmuneCursed,
DeathAltarUpgradeType.RobeImmuneFrostburn,
DeathAltarUpgradeType.RobeImmuneFrozen,
DeathAltarUpgradeType.RobeImmuneChaosState,
DeathAltarUpgradeType.RobeImmuneWet,
DeathAltarUpgradeType.RobeImmuneBrokenArmor,
DeathAltarUpgradeType.RobeImmuneSuffocation
];
public int DefenseLevel { get; private set; } = 1;
public int MaxLifeLevel { get; private set; }
public int LifeRegenLevel { get; private set; }
public int MobilityLevel { get; private set; }
public int MinionLevel { get; private set; }
public int TurretLevel { get; private set; }
public int PlacementRangeLevel { get; private set; }
public bool KnockbackImmunityUnlocked { get; private set; }
public bool FallDamageImmunityUnlocked { get; private set; }
public bool ZeroManaUnlocked { get; private set; }
public bool LavaImmunityUnlocked { get; private set; }
public ushort DebuffImmunityMask { get; private set; }
public int DefenseValue => DefenseByLevel[DefenseLevel - 1];
public int MaxLifeBonus => MaxLifeLevel * 50;
public int LifeRegenBonus => LifeRegenLevel * 5;
public int MinionCapacity => MinionsByLevel[MinionLevel];
public int TurretCapacity => TurretsByLevel[TurretLevel];
public int PlacementRangeBonus => PlacementRangeLevel * 10;
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 SetStaticDefaults()
{
ArmorIDs.Body.Sets.HidesTopSkin[Item.bodySlot] = true;
// These sprites came directly from the 1.3 mod. A 1.4 body equip normally treats
// DeathRobe_Body as a 360x224 composite sheet, but this asset is the legacy 40x1120
// format with separate arm frames. Keep the original framing and bind each legacy
// layer explicitly so the robe uses the same player-relative positions as before.
FieldInfo? framingField = typeof(ArmorIDs.Body.Sets).GetField(
"UsesNewFramingCode",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (framingField?.GetValue(null) is bool[] usesNewFramingCode)
usesNewFramingCode[Item.bodySlot] = false;
if (!Main.dedServ)
{
var bodyTexture = ModContent.Request<Texture2D>($"{Texture}_{EquipType.Body}");
TextureAssets.ArmorBody[Item.bodySlot] = bodyTexture;
TextureAssets.FemaleBody[Item.bodySlot] = bodyTexture;
TextureAssets.ArmorArm[Item.bodySlot] = ModContent.Request<Texture2D>($"{Texture}_Arms");
}
}
public override void SetDefaults()
{
Item.width = 18;
Item.height = 18;
Item.value = Item.sellPrice(gold: 10);
Item.rare = ItemRarityID.LightRed;
ApplyDynamicStats();
}
public override void UpdateInventory(Player player) => ApplyDynamicStats();
public override void UpdateEquip(Player player)
{
player.statLifeMax2 += MaxLifeBonus;
player.lifeRegen += LifeRegenBonus;
if (MobilityLevel > 0)
{
float progress = MobilityLevel / 10f;
player.maxRunSpeed = Math.Max(player.maxRunSpeed, MathHelper.Lerp(3f, 4f, progress));
player.runAcceleration = Math.Max(player.runAcceleration, MathHelper.Lerp(0.08f, 0.7f, progress));
player.runSlowdown = Math.Max(player.runSlowdown, MathHelper.Lerp(0.08f, 1f, progress));
}
if (MinionLevel > 0)
player.maxMinions = Math.Max(player.maxMinions, MinionCapacity);
if (TurretLevel > 0)
player.maxTurrets = Math.Max(player.maxTurrets, TurretCapacity);
player.blockRange += PlacementRangeBonus;
player.noKnockback |= KnockbackImmunityUnlocked;
player.noFallDmg |= FallDamageImmunityUnlocked;
if (ZeroManaUnlocked)
player.manaCost = 0f;
if (LavaImmunityUnlocked)
{
player.lavaImmune = true;
player.fireWalk = true;
}
foreach (DeathAltarUpgradeType type in UpgradeTypes)
{
if (IsDebuffUpgrade(type) && IsDebuffImmune(type))
player.buffImmune[GetDebuffId(type)] = true;
}
if (Main.rand.NextBool(4))
{
Dust dust = Dust.NewDustDirect(player.position, player.width, player.height, DustID.BatScepter, Alpha: 100, Scale: 1f);
dust.noGravity = true;
}
}
public override void ModifyTooltips(List<TooltipLine> tooltips)
{
string stats = Language.GetTextValue(
"Mods.SoulHarvest.UI.RobeStats",
DefenseValue,
MaxLifeBonus,
LifeRegenBonus,
CountDebuffImmunities(),
15,
ToggleValue(LavaImmunityUnlocked));
tooltips.Add(new TooltipLine(Mod, "DeathRobeStats", stats) { OverrideColor = new Color(170, 95, 230) });
tooltips.Add(new TooltipLine(Mod, "SoulHarvestAltarHint", Language.GetTextValue("Mods.SoulHarvest.UI.AltarHint")));
}
public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType upgradeType)
{
if (IsDebuffUpgrade(upgradeType))
return IsDebuffImmune(upgradeType) ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(1);
return upgradeType switch
{
DeathAltarUpgradeType.RobeDefense => DefenseLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPrice(DefenseLevel, 40),
DeathAltarUpgradeType.RobeMaxLife => MaxLifeLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MaxLifeLevel, 30),
DeathAltarUpgradeType.RobeLifeRegen => LifeRegenLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(LifeRegenLevel, 35),
DeathAltarUpgradeType.RobeMobility => MobilityLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MobilityLevel, 30),
DeathAltarUpgradeType.RobeMinions => MinionLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MinionLevel, 50),
DeathAltarUpgradeType.RobeTurrets => TurretLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(TurretLevel, 50),
DeathAltarUpgradeType.RobePlacementRange => PlacementRangeLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(PlacementRangeLevel, 20),
DeathAltarUpgradeType.RobeKnockbackImmunity => KnockbackImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(8),
DeathAltarUpgradeType.RobeFallDamageImmunity => FallDamageImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(5),
DeathAltarUpgradeType.RobeZeroMana => ZeroManaUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(15),
DeathAltarUpgradeType.RobeLavaImmunity => LavaImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(8),
_ => DeathAltarPrice.Unavailable
};
}
public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType upgradeType)
{
if (!GetUpgradePrice(upgradeType).IsAvailable)
return false;
if (IsDebuffUpgrade(upgradeType))
{
DebuffImmunityMask |= (ushort)(1 << GetDebuffBit(upgradeType));
return true;
}
switch (upgradeType)
{
case DeathAltarUpgradeType.RobeDefense:
DefenseLevel++;
ApplyDynamicStats();
break;
case DeathAltarUpgradeType.RobeMaxLife:
MaxLifeLevel++;
break;
case DeathAltarUpgradeType.RobeLifeRegen:
LifeRegenLevel++;
break;
case DeathAltarUpgradeType.RobeMobility:
MobilityLevel++;
break;
case DeathAltarUpgradeType.RobeMinions:
MinionLevel++;
break;
case DeathAltarUpgradeType.RobeTurrets:
TurretLevel++;
break;
case DeathAltarUpgradeType.RobePlacementRange:
PlacementRangeLevel++;
break;
case DeathAltarUpgradeType.RobeKnockbackImmunity:
KnockbackImmunityUnlocked = true;
break;
case DeathAltarUpgradeType.RobeFallDamageImmunity:
FallDamageImmunityUnlocked = true;
break;
case DeathAltarUpgradeType.RobeZeroMana:
ZeroManaUnlocked = true;
break;
case DeathAltarUpgradeType.RobeLavaImmunity:
LavaImmunityUnlocked = true;
break;
default:
return false;
}
return true;
}
public string GetCurrentUpgradeValue(DeathAltarUpgradeType upgradeType)
{
if (IsDebuffUpgrade(upgradeType))
return Language.GetTextValue(IsDebuffImmune(upgradeType) ? "Mods.SoulHarvest.UI.Immune" : "Mods.SoulHarvest.UI.NotImmune");
return upgradeType switch
{
DeathAltarUpgradeType.RobeDefense => DefenseValue.ToString(),
DeathAltarUpgradeType.RobeMaxLife => $"+{MaxLifeBonus}",
DeathAltarUpgradeType.RobeLifeRegen => $"+{LifeRegenBonus}",
DeathAltarUpgradeType.RobeMobility => $"Lv.{MobilityLevel}",
DeathAltarUpgradeType.RobeMinions => MinionCapacity.ToString(),
DeathAltarUpgradeType.RobeTurrets => TurretCapacity.ToString(),
DeathAltarUpgradeType.RobePlacementRange => $"+{PlacementRangeBonus}",
DeathAltarUpgradeType.RobeKnockbackImmunity => ToggleValue(KnockbackImmunityUnlocked),
DeathAltarUpgradeType.RobeFallDamageImmunity => ToggleValue(FallDamageImmunityUnlocked),
DeathAltarUpgradeType.RobeZeroMana => ToggleValue(ZeroManaUnlocked),
DeathAltarUpgradeType.RobeLavaImmunity => ToggleValue(LavaImmunityUnlocked),
_ => string.Empty
};
}
public string GetNextUpgradeValue(DeathAltarUpgradeType upgradeType)
{
if (IsDebuffUpgrade(upgradeType))
return Language.GetTextValue("Mods.SoulHarvest.UI.Immune");
return upgradeType switch
{
DeathAltarUpgradeType.RobeDefense => DefenseByLevel[Math.Min(DefenseLevel, MaxRobeLevel - 1)].ToString(),
DeathAltarUpgradeType.RobeMaxLife => $"+{(MaxLifeLevel + 1) * 50}",
DeathAltarUpgradeType.RobeLifeRegen => $"+{(LifeRegenLevel + 1) * 5}",
DeathAltarUpgradeType.RobeMobility => $"Lv.{MobilityLevel + 1}",
DeathAltarUpgradeType.RobeMinions => MinionsByLevel[Math.Min(MinionLevel + 1, MaxRobeLevel)].ToString(),
DeathAltarUpgradeType.RobeTurrets => TurretsByLevel[Math.Min(TurretLevel + 1, MaxRobeLevel)].ToString(),
DeathAltarUpgradeType.RobePlacementRange => $"+{(PlacementRangeLevel + 1) * 10}",
DeathAltarUpgradeType.RobeKnockbackImmunity or DeathAltarUpgradeType.RobeFallDamageImmunity or DeathAltarUpgradeType.RobeZeroMana or DeathAltarUpgradeType.RobeLavaImmunity
=> Language.GetTextValue("Mods.SoulHarvest.UI.Enabled"),
_ => string.Empty
};
}
public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType upgradeType)
{
return DeathAltarUnavailableReason.Maxed;
}
public override void SaveData(TagCompound tag)
{
tag[nameof(DefenseLevel)] = DefenseLevel;
tag[nameof(MaxLifeLevel)] = MaxLifeLevel;
tag[nameof(LifeRegenLevel)] = LifeRegenLevel;
tag[nameof(MobilityLevel)] = MobilityLevel;
tag[nameof(MinionLevel)] = MinionLevel;
tag[nameof(TurretLevel)] = TurretLevel;
tag[nameof(PlacementRangeLevel)] = PlacementRangeLevel;
tag[nameof(KnockbackImmunityUnlocked)] = KnockbackImmunityUnlocked;
tag[nameof(FallDamageImmunityUnlocked)] = FallDamageImmunityUnlocked;
tag[nameof(ZeroManaUnlocked)] = ZeroManaUnlocked;
tag[nameof(LavaImmunityUnlocked)] = LavaImmunityUnlocked;
tag[nameof(DebuffImmunityMask)] = (int)DebuffImmunityMask;
}
public override void LoadData(TagCompound tag)
{
DefenseLevel = Math.Clamp(tag.GetInt(nameof(DefenseLevel)), 1, MaxRobeLevel);
MaxLifeLevel = Math.Clamp(tag.GetInt(nameof(MaxLifeLevel)), 0, MaxRobeLevel);
LifeRegenLevel = Math.Clamp(tag.GetInt(nameof(LifeRegenLevel)), 0, MaxRobeLevel);
MobilityLevel = Math.Clamp(tag.GetInt(nameof(MobilityLevel)), 0, MaxRobeLevel);
MinionLevel = Math.Clamp(tag.GetInt(nameof(MinionLevel)), 0, MaxRobeLevel);
TurretLevel = Math.Clamp(tag.GetInt(nameof(TurretLevel)), 0, MaxRobeLevel);
PlacementRangeLevel = Math.Clamp(tag.GetInt(nameof(PlacementRangeLevel)), 0, MaxRobeLevel);
KnockbackImmunityUnlocked = tag.GetBool(nameof(KnockbackImmunityUnlocked));
FallDamageImmunityUnlocked = tag.GetBool(nameof(FallDamageImmunityUnlocked));
ZeroManaUnlocked = tag.GetBool(nameof(ZeroManaUnlocked));
LavaImmunityUnlocked = tag.GetBool(nameof(LavaImmunityUnlocked));
DebuffImmunityMask = (ushort)Math.Clamp(tag.GetInt(nameof(DebuffImmunityMask)), 0, ushort.MaxValue);
ApplyDynamicStats();
}
public override void NetSend(BinaryWriter writer)
{
writer.Write((byte)DefenseLevel);
writer.Write((byte)MaxLifeLevel);
writer.Write((byte)LifeRegenLevel);
writer.Write((byte)MobilityLevel);
writer.Write((byte)MinionLevel);
writer.Write((byte)TurretLevel);
writer.Write((byte)PlacementRangeLevel);
writer.Write(KnockbackImmunityUnlocked);
writer.Write(FallDamageImmunityUnlocked);
writer.Write(ZeroManaUnlocked);
writer.Write(LavaImmunityUnlocked);
writer.Write(DebuffImmunityMask);
}
public override void NetReceive(BinaryReader reader)
{
DefenseLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxRobeLevel);
MaxLifeLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
LifeRegenLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
MobilityLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
MinionLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
TurretLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
PlacementRangeLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
KnockbackImmunityUnlocked = reader.ReadBoolean();
FallDamageImmunityUnlocked = reader.ReadBoolean();
ZeroManaUnlocked = reader.ReadBoolean();
LavaImmunityUnlocked = reader.ReadBoolean();
DebuffImmunityMask = reader.ReadUInt16();
ApplyDynamicStats();
}
public override void AddRecipes()
{
CreateRecipe()
.AddIngredient(ItemID.Silk, 20)
.AddIngredient<Soul>()
.AddTile(ModContent.TileType<DeathAltarTile>())
.Register();
}
private void ApplyDynamicStats()
{
Item.defense = DefenseValue;
}
private bool IsDebuffImmune(DeathAltarUpgradeType type)
{
return (DebuffImmunityMask & (1 << GetDebuffBit(type))) != 0;
}
private int CountDebuffImmunities()
{
int count = 0;
ushort mask = DebuffImmunityMask;
while (mask != 0)
{
count += mask & 1;
mask >>= 1;
}
return count;
}
private static bool IsDebuffUpgrade(DeathAltarUpgradeType type)
{
return type >= DeathAltarUpgradeType.RobeImmuneOnFire && type <= DeathAltarUpgradeType.RobeImmuneSuffocation;
}
private static int GetDebuffBit(DeathAltarUpgradeType type)
{
return (int)type - (int)DeathAltarUpgradeType.RobeImmuneOnFire;
}
private static int GetDebuffId(DeathAltarUpgradeType type)
{
return type switch
{
DeathAltarUpgradeType.RobeImmuneOnFire => BuffID.OnFire,
DeathAltarUpgradeType.RobeImmunePoisoned => BuffID.Poisoned,
DeathAltarUpgradeType.RobeImmuneConfused => BuffID.Confused,
DeathAltarUpgradeType.RobeImmuneBleeding => BuffID.Bleeding,
DeathAltarUpgradeType.RobeImmuneSlow => BuffID.Slow,
DeathAltarUpgradeType.RobeImmuneWeak => BuffID.Weak,
DeathAltarUpgradeType.RobeImmuneSilenced => BuffID.Silenced,
DeathAltarUpgradeType.RobeImmuneDarkness => BuffID.Darkness,
DeathAltarUpgradeType.RobeImmuneCursed => BuffID.Cursed,
DeathAltarUpgradeType.RobeImmuneFrostburn => BuffID.Frostburn,
DeathAltarUpgradeType.RobeImmuneFrozen => BuffID.Frozen,
DeathAltarUpgradeType.RobeImmuneChaosState => BuffID.ChaosState,
DeathAltarUpgradeType.RobeImmuneWet => BuffID.Wet,
DeathAltarUpgradeType.RobeImmuneBrokenArmor => BuffID.BrokenArmor,
DeathAltarUpgradeType.RobeImmuneSuffocation => BuffID.Suffocation,
_ => 0
};
}
private static DeathAltarPrice LevelPrice(int currentLevel, int soulMultiplier)
{
return currentLevel < 6
? DeathAltarPrice.Souls(currentLevel * soulMultiplier)
: DeathAltarPrice.Essence(currentLevel * 2);
}
private static DeathAltarPrice LevelPriceFromZero(int currentLevel, int soulMultiplier)
{
return currentLevel < 5
? DeathAltarPrice.Souls((currentLevel + 1) * soulMultiplier)
: DeathAltarPrice.Essence((currentLevel + 1) * 2);
}
private static string ToggleValue(bool enabled)
{
return Language.GetTextValue(enabled ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled");
}
}