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 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($"{Texture}_{EquipType.Body}"); TextureAssets.ArmorBody[Item.bodySlot] = bodyTexture; TextureAssets.FemaleBody[Item.bodySlot] = bodyTexture; TextureAssets.ArmorArm[Item.bodySlot] = ModContent.Request($"{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 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() .AddTile(ModContent.TileType()) .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"); } }