返回提交历史
Modified
Common/DeathAltarUpgrade.cs
+6
-1
Modified
Common/MyGlobalNPC.cs
+10
-9
Modified
Common/MyGlobalProjectile.cs
+14
-4
Modified
Common/MyPlayer.cs
+43
-1
Modified
DeathMod.cs
+7
-1
Modified
Items/DeathSickle.cs
+0
-1
Modified
Items/NormalSickle.cs
+107
-16
Modified
Items/SkullSickle.cs
+1
-1
Modified
Items/SoulSickle.cs
+0
-1
Modified
Items/VoidSickle.cs
+1
-2
Modified
Localization/en-US.hjson
+22
-6
Modified
Localization/zh-Hans.hjson
+22
-6
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
XFEstudio/DeathMod
feat: add shared Fated skill tree and progression guide
5240732
代码差异
21 个文件
+983
-382
@@ -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)
@@ -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
@@ -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
}
@@ -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)
@@ -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);
@@ -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
{
@@ -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
@@ -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;
@@ -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
{
@@ -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
{
@@ -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.
@@ -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: 灵魂精华不足。