XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/DeathMod

feat: add shared Fated skill tree and progression guide

5240732
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

21 个文件 +983 -382
Modified Common/DeathAltarUpgrade.cs +6 -1
@@ -14,6 +14,10 @@ public enum DeathAltarUpgradeType : byte
14 14 AttackSpeed,
15 15 Knockback,
16 16 HitCooldown,
17 FatedUnlock,
18 FatedMaxStacks,
19 FatedStacksPerHit,
20 FatedDuration,
17 21
18 22 WingFlightTime,
19 23 WingHorizontalSpeed,
@@ -58,7 +62,8 @@ public enum DeathAltarUnavailableReason : byte
58 62 {
59 63 Maxed,
60 64 RequiresProjectile,
61 RequiresWingMastery
65 RequiresWingMastery,
66 RequiresFatedUnlock
62 67 }
63 68
64 69 public readonly record struct DeathAltarPrice(int Amount, DeathAltarCurrency Currency)
Modified Common/MyGlobalNPC.cs +10 -9
@@ -15,13 +15,12 @@ namespace DeathMod.Common;
15 15 public class MyGlobalNPC : GlobalNPC
16 16 {
17 17 internal const int DeathMarkDuration = 60 * 60 * 10;
18 internal const int FatedDuration = 60 * 6;
19 internal const int MaxFatedStacks = 5;
18 internal const int AbsoluteMaximumFatedStacks = NormalSickle.MaxFatedStackCapacity;
20 19
21 20 public override bool InstancePerEntity => true;
22 21
23 22 private readonly bool[] sickleParticipants = new bool[Main.maxPlayers];
24 private readonly int[] fatedStackTimers = new int[MaxFatedStacks];
23 private readonly int[] fatedStackTimers = new int[AbsoluteMaximumFatedStacks];
25 24 private int lastSicklePlayer = -1;
26 25 private int fatedSecondTimer;
27 26
@@ -80,7 +79,7 @@ public class MyGlobalNPC : GlobalNPC
80 79
81 80 public override void ReceiveExtraAI(NPC npc, BitReader bitReader, BinaryReader binaryReader)
82 81 {
83 FatedStackCount = Math.Clamp((int)binaryReader.ReadByte(), 0, MaxFatedStacks);
82 FatedStackCount = Math.Clamp((int)binaryReader.ReadByte(), 0, AbsoluteMaximumFatedStacks);
84 83 }
85 84
86 85 public override void OnKill(NPC npc)
@@ -128,19 +127,21 @@ public class MyGlobalNPC : GlobalNPC
128 127 }
129 128 }
130 129
131 internal void AddFatedStacks(NPC npc, int amount)
130 internal void AddFatedStacks(NPC npc, int amount, int maximumStacks, int durationFrames)
132 131 {
133 132 if (amount <= 0 || Main.netMode == NetmodeID.MultiplayerClient || npc.friendly || npc.lifeMax <= 5)
134 133 return;
135 134
136 int stacksToAdd = Math.Min(amount, MaxFatedStacks - FatedStackCount);
135 maximumStacks = Math.Clamp(maximumStacks, 1, AbsoluteMaximumFatedStacks);
136 durationFrames = Math.Clamp(durationFrames, 60, ushort.MaxValue);
137 int stacksToAdd = Math.Min(amount, Math.Max(0, maximumStacks - FatedStackCount));
137 138 for (int index = 0; index < stacksToAdd; index++)
138 fatedStackTimers[FatedStackCount++] = FatedDuration;
139 fatedStackTimers[FatedStackCount++] = durationFrames;
139 140
140 141 for (int index = 0; index < FatedStackCount; index++)
141 fatedStackTimers[index] = Math.Max(fatedStackTimers[index], FatedDuration);
142 fatedStackTimers[index] = Math.Max(fatedStackTimers[index], durationFrames);
142 143
143 npc.AddBuff(ModContent.BuffType<FatedBuff>(), FatedDuration);
144 npc.AddBuff(ModContent.BuffType<FatedBuff>(), durationFrames);
144 145 npc.netUpdate = true;
145 146 }
146 147
Modified Common/MyGlobalProjectile.cs +14 -4
@@ -20,6 +20,8 @@ public class MyGlobalProjectile : GlobalProjectile
20 20 public int OnHitDebuffType { get; private set; }
21 21 public int OnHitDebuffDuration { get; private set; }
22 22 public int FatedStacksPerHit { get; private set; }
23 public int FatedMaximumStacks { get; private set; }
24 public int FatedDurationFrames { get; private set; }
23 25
24 26 public override void OnSpawn(Projectile projectile, IEntitySource source)
25 27 {
@@ -31,6 +33,8 @@ public class MyGlobalProjectile : GlobalProjectile
31 33 OnHitDebuffType = sickle.OnHitDebuffType;
32 34 OnHitDebuffDuration = sickle.OnHitDebuffDuration;
33 35 FatedStacksPerHit = sickle.FatedStacksPerHit;
36 FatedMaximumStacks = sickle.FatedMaximumStacks;
37 FatedDurationFrames = sickle.FatedDurationFrames;
34 38 }
35 39 else if (source is EntitySource_ItemUse_WithAmmo legacySource && legacySource.Item.ModItem is LegacyDeath)
36 40 {
@@ -46,6 +50,8 @@ public class MyGlobalProjectile : GlobalProjectile
46 50 OnHitDebuffType = parentGlobal.OnHitDebuffType;
47 51 OnHitDebuffDuration = parentGlobal.OnHitDebuffDuration;
48 52 FatedStacksPerHit = parentGlobal.FatedStacksPerHit;
53 FatedMaximumStacks = parentGlobal.FatedMaximumStacks;
54 FatedDurationFrames = parentGlobal.FatedDurationFrames;
49 55 }
50 56 else
51 57 {
@@ -71,6 +77,8 @@ public class MyGlobalProjectile : GlobalProjectile
71 77 binaryWriter.Write((ushort)OnHitDebuffType);
72 78 binaryWriter.Write((ushort)OnHitDebuffDuration);
73 79 binaryWriter.Write((byte)FatedStacksPerHit);
80 binaryWriter.Write((byte)FatedMaximumStacks);
81 binaryWriter.Write((ushort)FatedDurationFrames);
74 82 }
75 83
76 84 public override void ReceiveExtraAI(Projectile projectile, BitReader bitReader, BinaryReader binaryReader)
@@ -85,6 +93,8 @@ public class MyGlobalProjectile : GlobalProjectile
85 93 OnHitDebuffType = binaryReader.ReadUInt16();
86 94 OnHitDebuffDuration = binaryReader.ReadUInt16();
87 95 FatedStacksPerHit = binaryReader.ReadByte();
96 FatedMaximumStacks = binaryReader.ReadByte();
97 FatedDurationFrames = binaryReader.ReadUInt16();
88 98 projectile.ArmorPenetration = ArmorPenetrationLevel;
89 99 projectile.usesIDStaticNPCImmunity = false;
90 100 projectile.usesLocalNPCImmunity = true;
@@ -98,10 +108,10 @@ public class MyGlobalProjectile : GlobalProjectile
98 108
99 109 Main.player[projectile.owner].GetModPlayer<MyPlayer>().TryLifeSteal(damageDone, LifeStealLevel);
100 110 LifeStealVisuals.Spawn(target.Center, projectile.owner, LifeStealLevel);
101 ApplyStatusEffects(target, OnHitDebuffType, OnHitDebuffDuration, FatedStacksPerHit);
111 ApplyStatusEffects(target, OnHitDebuffType, OnHitDebuffDuration, FatedStacksPerHit, FatedMaximumStacks, FatedDurationFrames);
102 112 }
103 113
104 internal static void ApplyStatusEffects(NPC target, int debuffType, int debuffDuration, int fatedStacks)
114 internal static void ApplyStatusEffects(NPC target, int debuffType, int debuffDuration, int fatedStacks, int fatedMaximumStacks, int fatedDurationFrames)
105 115 {
106 116 if (Main.netMode == NetmodeID.MultiplayerClient || target.friendly || target.lifeMax <= 5)
107 117 return;
@@ -114,7 +124,7 @@ public class MyGlobalProjectile : GlobalProjectile
114 124 statusTarget.AddBuff(ModContent.BuffType<Buffs.DeathMarkBuff>(), MyGlobalNPC.DeathMarkDuration);
115 125 if (debuffType > 0 && debuffDuration > 0)
116 126 statusTarget.AddBuff(debuffType, debuffDuration);
117 if (fatedStacks > 0)
118 statusTarget.GetGlobalNPC<MyGlobalNPC>().AddFatedStacks(statusTarget, fatedStacks);
127 if (fatedStacks > 0 && fatedMaximumStacks > 0 && fatedDurationFrames > 0)
128 statusTarget.GetGlobalNPC<MyGlobalNPC>().AddFatedStacks(statusTarget, fatedStacks, fatedMaximumStacks, fatedDurationFrames);
119 129 }
120 130 }
Modified Common/MyPlayer.cs +43 -1
@@ -12,7 +12,7 @@ namespace DeathMod.Common;
12 12
13 13 public class MyPlayer : ModPlayer
14 14 {
15 public const int StartingSouls = 100;
15 public const int StartingSouls = 150;
16 16 public const int SoulsPerEssence = 100;
17 17 public const int BossSoulReward = 150;
18 18
@@ -119,6 +119,48 @@ public class MyPlayer : ModPlayer
119 119 return true;
120 120 }
121 121
122 public bool TryAbsorbEssence()
123 {
124 if (Main.netMode == NetmodeID.MultiplayerClient)
125 return false;
126
127 int essenceType = ModContent.ItemType<Soul>();
128 int changedSlot = -1;
129 for (int slot = 0; slot < 58; slot++)
130 {
131 Item item = Player.inventory[slot];
132 if (item.type != essenceType || item.stack <= 0)
133 continue;
134
135 item.stack--;
136 if (item.stack <= 0)
137 item.TurnToAir();
138 changedSlot = slot;
139 break;
140 }
141
142 if (changedSlot < 0)
143 {
144 DeathMod.SendOperationResult(Player, DeathMod.OperationResult.NotEnoughEssence);
145 return false;
146 }
147
148 AddSouls(SoulsPerEssence);
149 if (Main.netMode == NetmodeID.Server)
150 {
151 NetMessage.SendData(
152 MessageID.SyncEquipment,
153 -1,
154 -1,
155 null,
156 Player.whoAmI,
157 PlayerItemSlotID.Inventory0 + changedSlot,
158 Player.inventory[changedSlot].prefix);
159 }
160 DeathMod.SendOperationResult(Player, DeathMod.OperationResult.EssenceAbsorbed);
161 return true;
162 }
163
122 164 public void TryLifeSteal(int damageDone, int lifeStealLevel)
123 165 {
124 166 if (lifeStealLevel <= 0 || damageDone <= 0 || lifeStealCooldown > 0 || Player.whoAmI != Main.myPlayer)
Modified DeathMod.cs +7 -1
@@ -14,6 +14,7 @@ public class DeathMod : Mod
14 14 {
15 15 SyncSoulPlayer,
16 16 RequestExtractEssence,
17 RequestAbsorbEssence,
17 18 RequestAltarUpgrade,
18 19 OperationResult
19 20 }
@@ -21,6 +22,7 @@ public class DeathMod : Mod
21 22 internal enum OperationResult : byte
22 23 {
23 24 EssenceExtracted,
25 EssenceAbsorbed,
24 26 NotEnoughSouls,
25 27 UpgradeSucceeded,
26 28 NotEnoughEssence,
@@ -59,6 +61,10 @@ public class DeathMod : Mod
59 61 if (Main.netMode == NetmodeID.Server && whoAmI >= 0 && whoAmI < Main.maxPlayers && Main.player[whoAmI].active)
60 62 Main.player[whoAmI].GetModPlayer<MyPlayer>().TryExtractEssence();
61 63 break;
64 case MessageType.RequestAbsorbEssence:
65 if (Main.netMode == NetmodeID.Server && whoAmI >= 0 && whoAmI < Main.maxPlayers && Main.player[whoAmI].active)
66 Main.player[whoAmI].GetModPlayer<MyPlayer>().TryAbsorbEssence();
67 break;
62 68 case MessageType.RequestAltarUpgrade:
63 69 {
64 70 DeathAltarUpgradeType upgradeType = (DeathAltarUpgradeType)reader.ReadByte();
@@ -101,7 +107,7 @@ public class DeathMod : Mod
101 107 private static void ShowOperationResult(OperationResult result)
102 108 {
103 109 string key = $"Mods.DeathMod.Messages.{result}";
104 Color color = result is OperationResult.EssenceExtracted or OperationResult.UpgradeSucceeded
110 Color color = result is OperationResult.EssenceExtracted or OperationResult.EssenceAbsorbed or OperationResult.UpgradeSucceeded
105 111 ? new Color(90, 235, 255)
106 112 : new Color(255, 105, 130);
107 113 Main.NewText(Language.GetTextValue(key), color);
Modified Items/DeathSickle.cs +0 -1
@@ -16,7 +16,6 @@ public class DeathSickle : NormalSickle
16 16 public override bool UsesModernSwing => false;
17 17 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.None;
18 18 public override Color SickleColor => new(235, 35, 65);
19 public override int FatedStacksPerHit => 3;
20 19
21 20 public override void SetDefaults()
22 21 {
Modified Items/NormalSickle.cs +107 -16
@@ -28,12 +28,12 @@ public enum SickleAlternateAttackStyle : byte
28 28 {
29 29 None,
30 30 HarvestUppercut,
31 BoneRush,
31 BoneBarrage,
32 32 BloodCharge,
33 33 InfernalRush,
34 34 FrostCharge,
35 35 SoulCharge,
36 VoidStep
36 VoidRitual
37 37 }
38 38
39 39 public class NormalSickle : ModItem, IDeathAltarUpgradeable
@@ -48,6 +48,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
48 48 public const int MaxAttackSpeedLevel = 10;
49 49 public const int MaxKnockbackLevel = 10;
50 50 public const int MaxHitCooldownLevel = 10;
51 public const int MaxFatedStackCapacity = 10;
52 public const int MaxFatedStacksPerHitLevel = 5;
53 public const int MaxFatedDurationLevel = 10;
54 public const int BaseFatedDurationFrames = 60 * 3;
51 55
52 56 private static readonly DeathAltarUpgradeType[] LinearUpgradeTypes =
53 57 [
@@ -58,7 +62,11 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
58 62 DeathAltarUpgradeType.ProjectileCount,
59 63 DeathAltarUpgradeType.ArmorPenetration,
60 64 DeathAltarUpgradeType.HitCooldown,
61 DeathAltarUpgradeType.LifeSteal
65 DeathAltarUpgradeType.LifeSteal,
66 DeathAltarUpgradeType.FatedUnlock,
67 DeathAltarUpgradeType.FatedMaxStacks,
68 DeathAltarUpgradeType.FatedStacksPerHit,
69 DeathAltarUpgradeType.FatedDuration
62 70 ];
63 71
64 72 private static readonly DeathAltarUpgradeType[] BranchingUpgradeTypes =
@@ -71,7 +79,11 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
71 79 DeathAltarUpgradeType.ProjectileCount,
72 80 DeathAltarUpgradeType.ArmorPenetration,
73 81 DeathAltarUpgradeType.HitCooldown,
74 DeathAltarUpgradeType.LifeSteal
82 DeathAltarUpgradeType.LifeSteal,
83 DeathAltarUpgradeType.FatedUnlock,
84 DeathAltarUpgradeType.FatedMaxStacks,
85 DeathAltarUpgradeType.FatedStacksPerHit,
86 DeathAltarUpgradeType.FatedDuration
75 87 ];
76 88
77 89 public int DamageLevel { get; internal set; } = 1;
@@ -81,6 +93,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
81 93 public int AttackSpeedLevel { get; internal set; } = 1;
82 94 public int KnockbackLevel { get; internal set; } = 1;
83 95 public int HitCooldownLevel { get; internal set; } = 1;
96 public bool FatedUnlocked { get; internal set; }
97 public int FatedMaximumStacksLevel { get; internal set; } = 1;
98 public int FatedStacksPerHitLevel { get; internal set; } = 1;
99 public int FatedDurationLevel { get; internal set; } = 1;
84 100
85 101 public virtual int SickleTier => 0;
86 102 public virtual int BaseSickleDamage => 1;
@@ -106,7 +122,6 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
106 122 public virtual int AlternateCooldownFrames => 45;
107 123 public virtual int OnHitDebuffType => 0;
108 124 public virtual int OnHitDebuffDuration => 0;
109 public virtual int FatedStacksPerHit => 0;
110 125
111 126 public bool SupportsProjectileCount => AttackProjectileType > ProjectileID.None;
112 127 public bool HasAlternateAttack => AlternateAttackStyle != SickleAlternateAttackStyle.None;
@@ -119,6 +134,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
119 134 public float KnockbackBonus => (KnockbackLevel - 1) * 0.5f;
120 135 public float TotalBaseKnockback => BaseKnockback + KnockbackBonus;
121 136 public int HitCooldownFrames => BaseHitCooldownFrames - (HitCooldownLevel - 1);
137 public int FatedMaximumStacks => FatedUnlocked ? FatedMaximumStacksLevel : 0;
138 public int FatedStacksPerHit => FatedUnlocked ? FatedStacksPerHitLevel : 0;
139 public int FatedDurationFrames => FatedUnlocked ? BaseFatedDurationFrames + (FatedDurationLevel - 1) * 60 : 0;
122 140 public IReadOnlyList<DeathAltarUpgradeType> AltarUpgradeTypes => AlternateNextSickleType == ItemID.None
123 141 ? LinearUpgradeTypes
124 142 : BranchingUpgradeTypes;
@@ -221,7 +239,7 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
221 239 {
222 240 player.GetModPlayer<MyPlayer>().TryLifeSteal(damageDone, LifeStealLevel);
223 241 LifeStealVisuals.Spawn(target.Center, player.whoAmI, LifeStealLevel);
224 MyGlobalProjectile.ApplyStatusEffects(target, OnHitDebuffType, OnHitDebuffDuration, FatedStacksPerHit);
242 MyGlobalProjectile.ApplyStatusEffects(target, OnHitDebuffType, OnHitDebuffDuration, FatedStacksPerHit, FatedMaximumStacks, FatedDurationFrames);
225 243 }
226 244
227 245 public override void ModifyTooltips(List<TooltipLine> tooltips)
@@ -242,6 +260,13 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
242 260 {
243 261 OverrideColor = GetGradientColor()
244 262 });
263 string fatedStats = FatedUnlocked
264 ? Language.GetTextValue("Mods.DeathMod.UI.FatedStats", FatedMaximumStacks, FatedStacksPerHit, FatedDurationFrames / 60f)
265 : Language.GetTextValue("Mods.DeathMod.UI.FatedLocked");
266 tooltips.Add(new TooltipLine(Mod, "DeathModFatedStats", fatedStats)
267 {
268 OverrideColor = FatedUnlocked ? new Color(245, 75, 165) : new Color(135, 120, 150)
269 });
245 270 if (HasAlternateAttack)
246 271 {
247 272 string alternateHint = Language.GetTextValue($"Mods.DeathMod.UI.AlternateHints.{AlternateAttackStyle}");
@@ -279,13 +304,13 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
279 304 : 6 + DamageLevel - 30),
280 305 DeathAltarUpgradeType.ProjectileCount => !SupportsProjectileCount || ProjectileCount >= MaxProjectileCount
281 306 ? DeathAltarPrice.Unavailable
282 : ProjectileCount < 3 ? DeathAltarPrice.Souls(ProjectileCount * 50) : DeathAltarPrice.Essence(1 << ProjectileCount),
307 : DeathAltarPrice.Essence(1 << (ProjectileCount - 1)),
283 308 DeathAltarUpgradeType.ArmorPenetration => ArmorPenetrationLevel >= MaxArmorPenetrationLevel
284 309 ? DeathAltarPrice.Unavailable
285 : ArmorPenetrationLevel < 6 ? DeathAltarPrice.Souls(ArmorPenetrationLevel * 15) : DeathAltarPrice.Essence(1 + ArmorPenetrationLevel / 2),
310 : DeathAltarPrice.Essence(1 + (ArmorPenetrationLevel - 1) / 3),
286 311 DeathAltarUpgradeType.LifeSteal => LifeStealLevel >= MaxLifeStealLevel
287 312 ? DeathAltarPrice.Unavailable
288 : LifeStealLevel < 2 ? DeathAltarPrice.Souls((LifeStealLevel + 1) * 75) : DeathAltarPrice.Essence((LifeStealLevel + 1) * 3),
313 : DeathAltarPrice.Essence(1 + LifeStealLevel * 2),
289 314 DeathAltarUpgradeType.AttackSpeed => AttackSpeedLevel >= MaxAttackSpeedLevel
290 315 ? DeathAltarPrice.Unavailable
291 316 : AttackSpeedLevel < 6 ? DeathAltarPrice.Souls(AttackSpeedLevel * 25) : DeathAltarPrice.Essence(1 + AttackSpeedLevel / 2),
@@ -294,7 +319,19 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
294 319 : KnockbackLevel < 6 ? DeathAltarPrice.Souls(KnockbackLevel * 15) : DeathAltarPrice.Essence(1 + KnockbackLevel / 3),
295 320 DeathAltarUpgradeType.HitCooldown => !SupportsProjectileCount || HitCooldownLevel >= MaxHitCooldownLevel
296 321 ? DeathAltarPrice.Unavailable
297 : HitCooldownLevel < 6 ? DeathAltarPrice.Souls(HitCooldownLevel * 40) : DeathAltarPrice.Essence(HitCooldownLevel),
322 : DeathAltarPrice.Essence(1 + (HitCooldownLevel - 1) / 2),
323 DeathAltarUpgradeType.FatedUnlock => FatedUnlocked
324 ? DeathAltarPrice.Unavailable
325 : DeathAltarPrice.Essence(1),
326 DeathAltarUpgradeType.FatedMaxStacks => !FatedUnlocked || FatedMaximumStacksLevel >= MaxFatedStackCapacity
327 ? DeathAltarPrice.Unavailable
328 : DeathAltarPrice.Essence(FatedMaximumStacksLevel),
329 DeathAltarUpgradeType.FatedStacksPerHit => !FatedUnlocked || FatedStacksPerHitLevel >= MaxFatedStacksPerHitLevel
330 ? DeathAltarPrice.Unavailable
331 : DeathAltarPrice.Essence(FatedStacksPerHitLevel * 2),
332 DeathAltarUpgradeType.FatedDuration => !FatedUnlocked || FatedDurationLevel >= MaxFatedDurationLevel
333 ? DeathAltarPrice.Unavailable
334 : DeathAltarPrice.Essence(1 + (FatedDurationLevel - 1) / 2),
298 335 _ => DeathAltarPrice.Unavailable
299 336 };
300 337 }
@@ -318,6 +355,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
318 355 DeathAltarUpgradeType.ArmorPenetration => ArmorPenetrationLevel.ToString(),
319 356 DeathAltarUpgradeType.HitCooldown => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Frames", HitCooldownFrames),
320 357 DeathAltarUpgradeType.LifeSteal => $"{LifeStealLevel}%",
358 DeathAltarUpgradeType.FatedUnlock => Language.GetTextValue(FatedUnlocked ? "Mods.DeathMod.UI.Enabled" : "Mods.DeathMod.UI.Disabled"),
359 DeathAltarUpgradeType.FatedMaxStacks => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Stacks", FatedMaximumStacks),
360 DeathAltarUpgradeType.FatedStacksPerHit => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.StacksPerHit", FatedStacksPerHit),
361 DeathAltarUpgradeType.FatedDuration => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Seconds", FatedDurationFrames / 60f),
321 362 _ => string.Empty
322 363 };
323 364 }
@@ -335,15 +376,21 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
335 376 DeathAltarUpgradeType.ArmorPenetration => (ArmorPenetrationLevel + 1).ToString(),
336 377 DeathAltarUpgradeType.HitCooldown => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Frames", HitCooldownFrames - 1),
337 378 DeathAltarUpgradeType.LifeSteal => $"{LifeStealLevel + 1}%",
379 DeathAltarUpgradeType.FatedUnlock => Language.GetTextValue("Mods.DeathMod.UI.Enabled"),
380 DeathAltarUpgradeType.FatedMaxStacks => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Stacks", FatedMaximumStacksLevel + 1),
381 DeathAltarUpgradeType.FatedStacksPerHit => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.StacksPerHit", FatedStacksPerHitLevel + 1),
382 DeathAltarUpgradeType.FatedDuration => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Seconds", (BaseFatedDurationFrames + FatedDurationLevel * 60) / 60f),
338 383 _ => string.Empty
339 384 };
340 385 }
341 386
342 387 public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType upgradeType)
343 388 {
344 return upgradeType is DeathAltarUpgradeType.ProjectileCount or DeathAltarUpgradeType.HitCooldown && !SupportsProjectileCount
345 ? DeathAltarUnavailableReason.RequiresProjectile
346 : DeathAltarUnavailableReason.Maxed;
389 if ((upgradeType is DeathAltarUpgradeType.ProjectileCount or DeathAltarUpgradeType.HitCooldown) && !SupportsProjectileCount)
390 return DeathAltarUnavailableReason.RequiresProjectile;
391 if ((upgradeType is DeathAltarUpgradeType.FatedMaxStacks or DeathAltarUpgradeType.FatedStacksPerHit or DeathAltarUpgradeType.FatedDuration) && !FatedUnlocked)
392 return DeathAltarUnavailableReason.RequiresFatedUnlock;
393 return DeathAltarUnavailableReason.Maxed;
347 394 }
348 395
349 396 internal void SpawnAttackProjectiles(Player player, Projectile swingProjectile, float charge, bool heavySlash)
@@ -352,7 +399,12 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
352 399 return;
353 400
354 401 int count = Math.Clamp(ProjectileCount + (heavySlash ? ChargedProjectileBonus : 0), 1, MaxProjectileCount + ChargedProjectileBonus);
355 float spread = MathHelper.ToRadians(heavySlash ? 12f : 7f);
402 if (heavySlash && AlternateAttackStyle == SickleAlternateAttackStyle.BoneBarrage)
403 count = Math.Max(3, count);
404 else if (heavySlash && AlternateAttackStyle == SickleAlternateAttackStyle.VoidRitual)
405 count = Math.Max(4, count);
406
407 float spread = MathHelper.ToRadians(heavySlash && AlternateAttackStyle == SickleAlternateAttackStyle.BoneBarrage ? 18f : heavySlash ? 12f : 7f);
356 408 float speed = AttackProjectileSpeed * (heavySlash ? MathHelper.Lerp(1.1f, 1.35f, charge) : 1f);
357 409 int damage = Math.Max(1, (int)(swingProjectile.damage * ProjectileDamageFactor * (heavySlash ? MathHelper.Lerp(1.25f, 1.9f, charge) : 1f)));
358 410 Vector2 aim = swingProjectile.velocity.SafeNormalize(Vector2.UnitX * player.direction);
@@ -361,10 +413,13 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
361 413 for (int index = 0; index < count; index++)
362 414 {
363 415 float centeredIndex = index - (count - 1) / 2f;
364 Vector2 projectileVelocity = aim.RotatedBy(centeredIndex * spread) * speed;
416 Vector2 projectileDirection = heavySlash && AlternateAttackStyle == SickleAlternateAttackStyle.VoidRitual
417 ? aim.RotatedBy(index * MathHelper.TwoPi / count)
418 : aim.RotatedBy(centeredIndex * spread);
419 Vector2 projectileVelocity = projectileDirection * speed;
365 420 Projectile.NewProjectile(
366 421 source,
367 player.MountedCenter + aim * 28f,
422 player.MountedCenter + projectileDirection * 28f,
368 423 projectileVelocity,
369 424 AttackProjectileType,
370 425 damage,
@@ -404,6 +459,18 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
404 459 case DeathAltarUpgradeType.HitCooldown:
405 460 HitCooldownLevel++;
406 461 break;
462 case DeathAltarUpgradeType.FatedUnlock:
463 FatedUnlocked = true;
464 break;
465 case DeathAltarUpgradeType.FatedMaxStacks:
466 FatedMaximumStacksLevel++;
467 break;
468 case DeathAltarUpgradeType.FatedStacksPerHit:
469 FatedStacksPerHitLevel++;
470 break;
471 case DeathAltarUpgradeType.FatedDuration:
472 FatedDurationLevel++;
473 break;
407 474 default:
408 475 return false;
409 476 }
@@ -427,6 +494,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
427 494 int attackSpeedLevel = AttackSpeedLevel;
428 495 int knockbackLevel = KnockbackLevel;
429 496 int hitCooldownLevel = HitCooldownLevel;
497 bool fatedUnlocked = FatedUnlocked;
498 int fatedMaximumStacksLevel = FatedMaximumStacksLevel;
499 int fatedStacksPerHitLevel = FatedStacksPerHitLevel;
500 int fatedDurationLevel = FatedDurationLevel;
430 501 int prefix = item.prefix;
431 502 bool favorited = item.favorited;
432 503
@@ -444,6 +515,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
444 515 upgraded.AttackSpeedLevel = attackSpeedLevel;
445 516 upgraded.KnockbackLevel = knockbackLevel;
446 517 upgraded.HitCooldownLevel = hitCooldownLevel;
518 upgraded.FatedUnlocked = fatedUnlocked;
519 upgraded.FatedMaximumStacksLevel = fatedMaximumStacksLevel;
520 upgraded.FatedStacksPerHitLevel = fatedStacksPerHitLevel;
521 upgraded.FatedDurationLevel = fatedDurationLevel;
447 522 upgraded.ApplyDynamicStats();
448 523 item.favorited = favorited;
449 524 return true;
@@ -458,6 +533,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
458 533 tag[nameof(AttackSpeedLevel)] = AttackSpeedLevel;
459 534 tag[nameof(KnockbackLevel)] = KnockbackLevel;
460 535 tag[nameof(HitCooldownLevel)] = HitCooldownLevel;
536 tag[nameof(FatedUnlocked)] = FatedUnlocked;
537 tag[nameof(FatedMaximumStacksLevel)] = FatedMaximumStacksLevel;
538 tag[nameof(FatedStacksPerHitLevel)] = FatedStacksPerHitLevel;
539 tag[nameof(FatedDurationLevel)] = FatedDurationLevel;
461 540 }
462 541
463 542 public override void LoadData(TagCompound tag)
@@ -469,6 +548,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
469 548 AttackSpeedLevel = Math.Clamp(tag.GetInt(nameof(AttackSpeedLevel)), 1, MaxAttackSpeedLevel);
470 549 KnockbackLevel = Math.Clamp(tag.GetInt(nameof(KnockbackLevel)), 1, MaxKnockbackLevel);
471 550 HitCooldownLevel = Math.Clamp(tag.GetInt(nameof(HitCooldownLevel)), 1, MaxHitCooldownLevel);
551 FatedUnlocked = tag.GetBool(nameof(FatedUnlocked));
552 FatedMaximumStacksLevel = Math.Clamp(tag.GetInt(nameof(FatedMaximumStacksLevel)), 1, MaxFatedStackCapacity);
553 FatedStacksPerHitLevel = Math.Clamp(tag.GetInt(nameof(FatedStacksPerHitLevel)), 1, MaxFatedStacksPerHitLevel);
554 FatedDurationLevel = Math.Clamp(tag.GetInt(nameof(FatedDurationLevel)), 1, MaxFatedDurationLevel);
472 555 ApplyDynamicStats();
473 556 }
474 557
@@ -481,6 +564,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
481 564 writer.Write((byte)AttackSpeedLevel);
482 565 writer.Write((byte)KnockbackLevel);
483 566 writer.Write((byte)HitCooldownLevel);
567 writer.Write(FatedUnlocked);
568 writer.Write((byte)FatedMaximumStacksLevel);
569 writer.Write((byte)FatedStacksPerHitLevel);
570 writer.Write((byte)FatedDurationLevel);
484 571 }
485 572
486 573 public override void NetReceive(BinaryReader reader)
@@ -492,6 +579,10 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
492 579 AttackSpeedLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxAttackSpeedLevel);
493 580 KnockbackLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxKnockbackLevel);
494 581 HitCooldownLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxHitCooldownLevel);
582 FatedUnlocked = reader.ReadBoolean();
583 FatedMaximumStacksLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxFatedStackCapacity);
584 FatedStacksPerHitLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxFatedStacksPerHitLevel);
585 FatedDurationLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxFatedDurationLevel);
495 586 ApplyDynamicStats();
496 587 }
497 588
Modified Items/SkullSickle.cs +1 -1
@@ -15,7 +15,7 @@ public class SkullSickle : NormalSickle
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<DevilSickle>();
16 16 public override int AlternateNextSickleType => ModContent.ItemType<FrostSickle>();
17 17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.Overhead;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.BoneRush;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.BoneBarrage;
19 19 public override Color SickleColor => new(80, 235, 255);
20 20 public override Color SickleSecondaryColor => new(235, 245, 220);
21 21 public override int SickleDustType => DustID.DungeonSpirit;
Modified Items/SoulSickle.cs +0 -1
@@ -25,7 +25,6 @@ public class SoulSickle : NormalSickle
25 25 public override float TextureRotationCorrection => 0.984f;
26 26 public override float BladeTipLength => 124.6f;
27 27 public override int AlternateCooldownFrames => 150;
28 public override int FatedStacksPerHit => 1;
29 28
30 29 public override void SetDefaults()
31 30 {
Modified Items/VoidSickle.cs +1 -2
@@ -15,7 +15,7 @@ public class VoidSickle : NormalSickle
15 15 public override int PrimaryNextSickleType => ModContent.ItemType<DeathSickle>();
16 16 public override int AlternateNextSickleType => ItemID.None;
17 17 public override SickleSwingStyle SwingStyle => SickleSwingStyle.VoidOrbit;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.VoidStep;
18 public override SickleAlternateAttackStyle AlternateAttackStyle => SickleAlternateAttackStyle.VoidRitual;
19 19 public override int ChargedProjectileBonus => 1;
20 20 public override Color SickleColor => new(190, 55, 255);
21 21 public override Color SickleSecondaryColor => new(255, 85, 235);
@@ -27,7 +27,6 @@ public class VoidSickle : NormalSickle
27 27 public override int AlternateCooldownFrames => 120;
28 28 public override int OnHitDebuffType => BuffID.ShadowFlame;
29 29 public override int OnHitDebuffDuration => 60 * 5;
30 public override int FatedStacksPerHit => 2;
31 30
32 31 public override void SetDefaults()
33 32 {
Modified Localization/en-US.hjson +22 -6
@@ -28,17 +28,17 @@ Mods: {
28 28
29 29 SoulSickle: {
30 30 DisplayName: Soul Sickle
31 Tooltip: Soul crescents alternate between circling, hunting and lunging; hits add 1 Fated stack
31 Tooltip: Soul crescents alternate between circling, hunting and lunging; Fated can be unlocked and trained at the altar
32 32 }
33 33
34 34 VoidSickle: {
35 35 DisplayName: Void Sickle
36 Tooltip: Releases void arcs that grow into gravitational rifts; hits inflict Shadowflame and add 2 Fated stacks
36 Tooltip: Releases void arcs that grow into gravitational rifts; hits inflict Shadowflame, and Fated can be trained at the altar
37 37 }
38 38
39 39 DeathSickle: {
40 40 DisplayName: Death Sickle
41 Tooltip: Releases an undying blade that hunts along a death spiral; hits add 3 Fated stacks
41 Tooltip: Releases an undying blade that hunts along a death spiral; inherits and can continue training Fated
42 42 }
43 43
44 44 LegacyDeath: {
@@ -74,6 +74,7 @@ Mods: {
74 74 Tooltip:
75 75 '''
76 76 Condensed from 100 souls
77 Left-click the Soul Jar to absorb it and restore 100 souls
77 78 Used to modify sickles, the robe and wings at the Death Altar
78 79 '''
79 80 }
@@ -118,6 +119,8 @@ Mods: {
118 119
119 120 UI: {
120 121 MeleeOnly: Melee form
122 FatedLocked: "Fated skill tree: locked"
123 FatedStats: "Fated: max {0} stacks | +{1} per hit | {2:0.#}s duration"
121 124 SickleStats:
122 125 '''
123 126 Altar upgrades: Damage Lv.{0} (current-form base damage {1}) | Speed {5}f | Knockback {6}
@@ -130,19 +133,23 @@ Mods: {
130 133
131 134 AlternateHints: {
132 135 HarvestUppercut: Right-click: rush into a rising uppercut that launches knockable enemies
133 BoneRush: Right-click: rush forward with a non-charged bone-wraith thrust
136 BoneBarrage: Right-click: perform a stationary bone-wraith ring slash and release a fan of wisps
134 137 BloodCharge: Hold right-click to charge, then release a circular Blood Covenant execution
135 138 InfernalRush: Right-click: rush forward with a non-charged infernal spin
136 139 FrostCharge: Hold right-click to charge, then release a two-stage frost cross-cut
137 140 SoulCharge: Hold right-click to charge, then release a multi-orbit soul reaping slash
138 VoidStep: Right-click: perform a non-charged Void Step and surrounding rift slash
141 VoidRitual: Right-click: open a stationary void ritual and perform a surrounding rift slash
139 142 }
140 143
141 144 WingStats: Flight {0}f | Speed {1} | Acceleration {2} | Vertical power {3} | Infinite flight: {4}
142 145 RobeStats: Defense {0} | Life +{1} | Life regeneration +{2} | Debuff immunities {3}/{4}
143 146 AltarHint: Right-click a Death Altar to open its modification interface
144 147 SoulCount: Souls: {0}
145 SoulJarHint: Right-click to extract Soul Essence ({0} souls each)
148 SoulJarHint:
149 '''
150 Left-click to absorb 1 Soul Essence (+{0} souls)
151 Right-click to extract 1 Soul Essence (-{0} souls)
152 '''
146 153 AltarTitle: Death Altar · Equipment Modification
147 154 HoldSickle: Hold the equipment you want to modify
148 155 HoldUpgradeableItem: Hold an upgradeable DeathMod sickle, Death Robe or Wings of Death
@@ -166,12 +173,16 @@ Mods: {
166 173 UpgradeValues: {
167 174 Damage: Lv.{0} (current-form base damage {1})
168 175 Frames: "{0}f"
176 Stacks: "Max {0} stacks"
177 StacksPerHit: "+{0} stacks per hit"
178 Seconds: "{0:0.#}s"
169 179 }
170 180
171 181 UnavailableReasons: {
172 182 Maxed: Maximum level
173 183 RequiresProjectile: The current form has no projectile
174 184 RequiresWingMastery: Max all four flight attributes first
185 RequiresFatedUnlock: Unlock Fated first
175 186 }
176 187
177 188 UpgradeNames: {
@@ -184,6 +195,10 @@ Mods: {
184 195 ArmorPenetration: Armor penetration
185 196 HitCooldown: Hit cooldown
186 197 LifeSteal: Life steal
198 FatedUnlock: Unlock Fated
199 FatedMaxStacks: Fated stack cap
200 FatedStacksPerHit: Fated stacks per hit
201 FatedDuration: Fated duration
187 202 WingFlightTime: Flight time
188 203 WingHorizontalSpeed: Horizontal flight speed
189 204 WingAcceleration: Flight acceleration
@@ -221,6 +236,7 @@ Mods: {
221 236
222 237 Messages: {
223 238 EssenceExtracted: Spent 100 souls and extracted 1 Soul Essence.
239 EssenceAbsorbed: Absorbed 1 Soul Essence and gained 100 souls.
224 240 NotEnoughSouls: Not enough souls: extracting Soul Essence requires 100 souls.
225 241 UpgradeSucceeded: Equipment modification complete.
226 242 NotEnoughEssence: Not enough Soul Essence.
Modified Localization/zh-Hans.hjson +22 -6
@@ -28,17 +28,17 @@ Mods: {
28 28
29 29 SoulSickle: {
30 30 DisplayName: 灵魂镰刀
31 Tooltip: 灵魂弧刃会在盘旋、追猎与突进间切换;命中叠加1层命定
31 Tooltip: 灵魂弧刃会在盘旋、追猎与突进间切换;可在祭坛点亮并培养命定
32 32 }
33 33
34 34 VoidSickle: {
35 35 DisplayName: 虚空镰刀
36 Tooltip: 释放成长为引力裂隙的虚空弧;命中施加暗影焰并叠加2层命定
36 Tooltip: 释放成长为引力裂隙的虚空弧;命中施加暗影焰,可在祭坛培养命定
37 37 }
38 38
39 39 DeathSickle: {
40 40 DisplayName: 死神镰刀
41 Tooltip: 释放沿死亡螺旋追猎敌人的不灭刀刃;命中叠加3层命定
41 Tooltip: 释放沿死亡螺旋追猎敌人的不灭刀刃;可继承并继续培养命定
42 42 }
43 43
44 44 LegacyDeath: {
@@ -74,6 +74,7 @@ Mods: {
74 74 Tooltip:
75 75 '''
76 76 由100个灵魂凝聚而成
77 左键灵魂罐可吸收并恢复100灵魂
77 78 用于在死神祭坛改造镰刀、长袍与翅膀
78 79 '''
79 80 }
@@ -118,6 +119,8 @@ Mods: {
118 119
119 120 UI: {
120 121 MeleeOnly: 近战形态
122 FatedLocked: 命定技能树:未点亮
123 FatedStats: 命定:最多 {0} 层 | 每次命中 +{1} 层 | 持续 {2:0.#} 秒
121 124 SickleStats:
122 125 '''
123 126 祭坛改造:攻击 Lv.{0}(当前阶段基础伤害 {1}) | 攻速 {5}帧 | 击退 {6}
@@ -130,19 +133,23 @@ Mods: {
130 133
131 134 AlternateHints: {
132 135 HarvestUppercut: 右键:向准星方向上挑突进,将可击退的敌人挑向空中
133 BoneRush: 右键:无需蓄力,以亡骨镰向前突刺追魂
136 BoneBarrage: 右键:无需蓄力,原地施展亡骨环斩并释放扇形幽光
134 137 BloodCharge: 右键按住蓄力,松开后施展环身血契处决斩
135 138 InfernalRush: 右键:无需蓄力,突进并施展炼狱旋斩
136 139 FrostCharge: 右键按住蓄力,松开后施展两段冰魂交叉斩
137 140 SoulCharge: 右键按住蓄力,松开后施展多圈灵魂回旋斩
138 VoidStep: 右键:无需蓄力,施展虚空踏步与环身裂隙斩
141 VoidRitual: 右键:无需蓄力,原地展开虚空仪式与环身裂隙斩
139 142 }
140 143
141 144 WingStats: 飞行 {0}帧 | 速度 {1} | 加速度 {2} | 垂直能力 {3} | 无限飞行:{4}
142 145 RobeStats: 防御 {0} | 生命 +{1} | 生命回复 +{2} | 减益免疫 {3}/{4}
143 146 AltarHint: 在死神祭坛右键打开改造界面
144 147 SoulCount: 灵魂:{0}
145 SoulJarHint: 右键提取灵魂精华(每个消耗 {0} 灵魂)
148 SoulJarHint:
149 '''
150 左键吸收1个灵魂精华(+{0}灵魂)
151 右键提取1个灵魂精华(-{0}灵魂)
152 '''
146 153 AltarTitle: 死神祭坛 · 装备改造
147 154 HoldSickle: 请将要改造的装备拿在手中
148 155 HoldUpgradeableItem: 请手持可改造的死神镰刀、死神长袍或死亡之翼
@@ -166,12 +173,16 @@ Mods: {
166 173 UpgradeValues: {
167 174 Damage: Lv.{0}(当前阶段基础伤害 {1})
168 175 Frames: "{0}帧"
176 Stacks: "最多 {0} 层"
177 StacksPerHit: "每次命中 +{0} 层"
178 Seconds: "{0:0.#}秒"
169 179 }
170 180
171 181 UnavailableReasons: {
172 182 Maxed: 已达到上限
173 183 RequiresProjectile: 当前形态没有弹幕
174 184 RequiresWingMastery: 需先将四项飞行能力升至满级
185 RequiresFatedUnlock: 需先点亮命定
175 186 }
176 187
177 188 UpgradeNames: {
@@ -184,6 +195,10 @@ Mods: {
184 195 ArmorPenetration: 穿甲改造
185 196 HitCooldown: 伤害间隔
186 197 LifeSteal: 吸血改造
198 FatedUnlock: 点亮命定
199 FatedMaxStacks: 命定层数上限
200 FatedStacksPerHit: 单次命定叠层
201 FatedDuration: 命定持续时间
187 202 WingFlightTime: 飞行时间
188 203 WingHorizontalSpeed: 水平飞行速度
189 204 WingAcceleration: 飞行加速度
@@ -221,6 +236,7 @@ Mods: {
221 236
222 237 Messages: {
223 238 EssenceExtracted: 已消耗100灵魂并提取1个灵魂精华。
239 EssenceAbsorbed: 已吸收1个灵魂精华并获得100灵魂。
224 240 NotEnoughSouls: 灵魂不足:提取灵魂精华需要100灵魂。
225 241 UpgradeSucceeded: 装备改造完成。
226 242 NotEnoughEssence: 灵魂精华不足。
Modified Projectiles/LifeStealWispProjectile.cs +20 -0
Modified Projectiles/SickleSwingProjectile.cs +87 -21
Modified README.md +122 -105
Added README.zh-Hans.md +108 -0
Modified UI/SoulJarUISystem.cs +19 -0
Modified build.txt +2 -2
Modified description.txt +21 -1
Modified docs/PLAY_GUIDE.md +149 -204
Added docs/PLAY_GUIDE.zh-Hans.md +222 -0