返回提交历史
Modified
Common/DeathAltarUpgrade.cs
+6
-2
Modified
Common/SickleUpgradeService.cs
+12
-1
Modified
DeathMod.cs
+5
-1
Modified
Items/NormalSickle.cs
+115
-8
Modified
Localization/en-US.hjson
+4
-0
Modified
Localization/zh-Hans.hjson
+4
-0
Modified
UI/DeathAltarUISystem.cs
+27
-4
XFEstudio/DeathMod
新增镰刀形态退阶与精华返还
a1e683f
代码差异
7 个文件
+173
-16
@@ -8,6 +8,7 @@ public enum DeathAltarUpgradeType : byte
8
8
{
9
9
Advancement,
10
10
AlternateAdvancement,
11
Regression,
11
12
Damage,
12
13
ProjectileCount,
13
14
ProjectileDuration,
@@ -65,7 +66,8 @@ public enum DeathAltarUpgradeType : byte
65
66
public enum DeathAltarCurrency : byte
66
67
{
67
68
Souls,
68
SoulEssence
69
SoulEssence,
70
SoulEssenceRefund
69
71
}
70
72
71
73
public enum DeathAltarUnavailableReason : byte
@@ -75,7 +77,8 @@ public enum DeathAltarUnavailableReason : byte
75
77
RequiresWingMastery,
76
78
RequiresFatedUnlock,
77
79
RequiresNecklaceMastery,
78
RequiresAllSickleUpgrades
80
RequiresAllSickleUpgrades,
81
RequiresHigherSickleForm
79
82
}
80
83
81
84
public enum DeathAltarItemStorage : byte
@@ -113,6 +116,7 @@ public readonly record struct DeathAltarPrice(int Amount, DeathAltarCurrency Cur
113
116
public static DeathAltarPrice Unavailable => new(-1, DeathAltarCurrency.SoulEssence);
114
117
public static DeathAltarPrice Souls(int amount) => new(amount, DeathAltarCurrency.Souls);
115
118
public static DeathAltarPrice Essence(int amount) => new(amount, DeathAltarCurrency.SoulEssence);
119
public static DeathAltarPrice RefundEssence(int amount) => new(amount, DeathAltarCurrency.SoulEssenceRefund);
116
120
}
117
121
118
122
public interface IDeathAltarUpgradeable
@@ -85,12 +85,23 @@ internal static class DeathAltarUpgradeService
85
85
: [];
86
86
if (price.Currency == DeathAltarCurrency.Souls && !soulPlayer.TrySpendSouls(price.Amount))
87
87
return false;
88
if (price.Currency == DeathAltarCurrency.SoulEssenceRefund)
89
{
90
player.QuickSpawnItem(
91
player.GetSource_Misc("DeathMod:SickleRegression"),
92
essenceType,
93
price.Amount);
94
}
88
95
89
96
SyncUpgradeableItem(player, itemReference);
90
97
foreach (int slot in changedEssenceSlots)
91
98
SyncInventorySlot(player, slot);
92
99
93
DeathMod.SendOperationResult(player, DeathMod.OperationResult.UpgradeSucceeded);
100
DeathMod.SendOperationResult(
101
player,
102
price.Currency == DeathAltarCurrency.SoulEssenceRefund
103
? DeathMod.OperationResult.SickleRegressed
104
: DeathMod.OperationResult.UpgradeSucceeded);
94
105
return true;
95
106
}
96
107
@@ -42,6 +42,7 @@ public class DeathMod : Mod
42
42
EssenceAbsorbed,
43
43
NotEnoughSouls,
44
44
UpgradeSucceeded,
45
SickleRegressed,
45
46
NotEnoughEssence,
46
47
NotEnoughUpgradeSouls,
47
48
HoldSickle,
@@ -594,7 +595,10 @@ public class DeathMod : Mod
594
595
private static void ShowOperationResult(OperationResult result)
595
596
{
596
597
string key = $"Mods.DeathMod.Messages.{result}";
597
Color color = result is OperationResult.EssenceExtracted or OperationResult.EssenceAbsorbed or OperationResult.UpgradeSucceeded
598
Color color = result is OperationResult.EssenceExtracted
599
or OperationResult.EssenceAbsorbed
600
or OperationResult.UpgradeSucceeded
601
or OperationResult.SickleRegressed
598
602
? new Color(90, 235, 255)
599
603
: new Color(255, 105, 130);
600
604
Main.NewText(Language.GetTextValue(key), color);
@@ -69,7 +69,8 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
69
69
DeathAltarUpgradeType.FatedUnlock,
70
70
DeathAltarUpgradeType.FatedMaxStacks,
71
71
DeathAltarUpgradeType.FatedStacksPerHit,
72
DeathAltarUpgradeType.FatedDuration
72
DeathAltarUpgradeType.FatedDuration,
73
DeathAltarUpgradeType.Regression
73
74
];
74
75
75
76
private static readonly DeathAltarUpgradeType[] BranchingUpgradeTypes =
@@ -87,7 +88,8 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
87
88
DeathAltarUpgradeType.FatedUnlock,
88
89
DeathAltarUpgradeType.FatedMaxStacks,
89
90
DeathAltarUpgradeType.FatedStacksPerHit,
90
DeathAltarUpgradeType.FatedDuration
91
DeathAltarUpgradeType.FatedDuration,
92
DeathAltarUpgradeType.Regression
91
93
];
92
94
93
95
public int DamageLevel { get; internal set; } = 1;
@@ -102,6 +104,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
102
104
public int FatedMaximumStacksLevel { get; internal set; } = 1;
103
105
public int FatedStacksPerHitLevel { get; internal set; } = 1;
104
106
public int FatedDurationLevel { get; internal set; } = 1;
107
private int previousTierOneType;
108
private int previousTierTwoType;
109
private int previousTierThreeType;
105
110
106
111
public virtual int SickleTier => 0;
107
112
public virtual int BaseSickleDamage => 1;
@@ -341,6 +346,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
341
346
DeathAltarUpgradeType.AlternateAdvancement => AlternateNextSickleType == ItemID.None
342
347
? DeathAltarPrice.Unavailable
343
348
: DeathAltarPrice.Essence(GetAdvancementEssenceCost()),
349
DeathAltarUpgradeType.Regression => SickleTier <= 0
350
? DeathAltarPrice.Unavailable
351
: DeathAltarPrice.RefundEssence(GetRegressionEssenceRefund()),
344
352
DeathAltarUpgradeType.Damage => DamageLevel >= MaxDamageLevel
345
353
? DeathAltarPrice.Unavailable
346
354
: DamageLevel < 20
@@ -387,16 +395,19 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
387
395
388
396
public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType upgradeType)
389
397
{
390
return upgradeType is DeathAltarUpgradeType.Advancement or DeathAltarUpgradeType.AlternateAdvancement
391
? Advance(item, upgradeType)
392
: ApplyUpgrade(upgradeType);
398
return upgradeType switch
399
{
400
DeathAltarUpgradeType.Advancement or DeathAltarUpgradeType.AlternateAdvancement => Advance(item, upgradeType),
401
DeathAltarUpgradeType.Regression => Regress(item),
402
_ => ApplyUpgrade(upgradeType)
403
};
393
404
}
394
405
395
406
public string GetCurrentUpgradeValue(DeathAltarUpgradeType upgradeType)
396
407
{
397
408
return upgradeType switch
398
409
{
399
DeathAltarUpgradeType.Advancement or DeathAltarUpgradeType.AlternateAdvancement => Item.Name,
410
DeathAltarUpgradeType.Advancement or DeathAltarUpgradeType.AlternateAdvancement or DeathAltarUpgradeType.Regression => Item.Name,
400
411
DeathAltarUpgradeType.Damage => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Damage", DamageLevel, ScaledBaseDamage),
401
412
DeathAltarUpgradeType.AttackSpeed => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Frames", AttackUseTime),
402
413
DeathAltarUpgradeType.Knockback => TotalBaseKnockback.ToString("0.0"),
@@ -419,6 +430,7 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
419
430
{
420
431
DeathAltarUpgradeType.Advancement => Lang.GetItemNameValue(PrimaryNextSickleType),
421
432
DeathAltarUpgradeType.AlternateAdvancement => Lang.GetItemNameValue(AlternateNextSickleType),
433
DeathAltarUpgradeType.Regression => Lang.GetItemNameValue(GetPreviousSickleType()),
422
434
DeathAltarUpgradeType.Damage => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Damage", DamageLevel + 1, GetScaledDamage(DamageLevel + 1)),
423
435
DeathAltarUpgradeType.AttackSpeed => Language.GetTextValue("Mods.DeathMod.UI.UpgradeValues.Frames", AttackUseTime - 1),
424
436
DeathAltarUpgradeType.Knockback => (TotalBaseKnockback + 0.5f).ToString("0.0"),
@@ -444,6 +456,8 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
444
456
{
445
457
return DeathAltarUnavailableReason.RequiresAllSickleUpgrades;
446
458
}
459
if (upgradeType == DeathAltarUpgradeType.Regression && SickleTier <= 0)
460
return DeathAltarUnavailableReason.RequiresHigherSickleForm;
447
461
if ((upgradeType is DeathAltarUpgradeType.ProjectileCount or DeathAltarUpgradeType.ProjectileDuration or DeathAltarUpgradeType.HitCooldown) && !SupportsProjectileCount)
448
462
return DeathAltarUnavailableReason.RequiresProjectile;
449
463
if ((upgradeType is DeathAltarUpgradeType.FatedMaxStacks or DeathAltarUpgradeType.FatedStacksPerHit or DeathAltarUpgradeType.FatedDuration) && !FatedUnlocked)
@@ -545,7 +559,22 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
545
559
int nextType = advancementType == DeathAltarUpgradeType.AlternateAdvancement
546
560
? AlternateNextSickleType
547
561
: PrimaryNextSickleType;
548
if (nextType == ItemID.None)
562
return TransformSickle(item, nextType, recordCurrentForm: true);
563
}
564
565
private bool Regress(Item item)
566
{
567
if (SickleTier <= 0)
568
return false;
569
return TransformSickle(item, GetPreviousSickleType(), recordCurrentForm: false);
570
}
571
572
private bool TransformSickle(Item item, int targetType, bool recordCurrentForm)
573
{
574
if (targetType <= ItemID.None || ItemLoader.GetItem(targetType) is not NormalSickle targetTemplate)
575
return false;
576
int expectedTier = SickleTier + (recordCurrentForm ? 1 : -1);
577
if (targetTemplate.SickleTier != expectedTier)
549
578
return false;
550
579
551
580
int damageLevel = DamageLevel;
@@ -560,10 +589,29 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
560
589
int fatedMaximumStacksLevel = FatedMaximumStacksLevel;
561
590
int fatedStacksPerHitLevel = FatedStacksPerHitLevel;
562
591
int fatedDurationLevel = FatedDurationLevel;
592
int tierOneHistory = previousTierOneType;
593
int tierTwoHistory = previousTierTwoType;
594
int tierThreeHistory = previousTierThreeType;
595
int currentType = item.type;
596
if (recordCurrentForm)
597
{
598
switch (SickleTier)
599
{
600
case 1:
601
tierOneHistory = currentType;
602
break;
603
case 2:
604
tierTwoHistory = currentType;
605
break;
606
case 3:
607
tierThreeHistory = currentType;
608
break;
609
}
610
}
563
611
int prefix = item.prefix;
564
612
bool favorited = item.favorited;
565
613
566
item.SetDefaults(nextType);
614
item.SetDefaults(targetType);
567
615
if (prefix > 0)
568
616
item.Prefix(prefix);
569
617
@@ -582,11 +630,45 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
582
630
upgraded.FatedMaximumStacksLevel = fatedMaximumStacksLevel;
583
631
upgraded.FatedStacksPerHitLevel = fatedStacksPerHitLevel;
584
632
upgraded.FatedDurationLevel = fatedDurationLevel;
633
upgraded.previousTierOneType = tierOneHistory;
634
upgraded.previousTierTwoType = tierTwoHistory;
635
upgraded.previousTierThreeType = tierThreeHistory;
585
636
upgraded.ApplyDynamicStats();
586
637
item.favorited = favorited;
587
638
return true;
588
639
}
589
640
641
private int GetPreviousSickleType()
642
{
643
int storedType = SickleTier switch
644
{
645
2 => previousTierOneType,
646
3 => previousTierTwoType,
647
4 => previousTierThreeType,
648
_ => ItemID.None
649
};
650
if (storedType > ItemID.None
651
&& ItemLoader.GetItem(storedType) is NormalSickle storedSickle
652
&& storedSickle.SickleTier == SickleTier - 1)
653
{
654
return storedType;
655
}
656
657
// Items created before regression history existed use a deterministic
658
// canonical predecessor. Newly advanced items always return along the exact
659
// branch the player originally chose.
660
return this switch
661
{
662
SkullSickle or BloodSickle => ModContent.ItemType<NormalSickle>(),
663
FrostSickle => ModContent.ItemType<BloodSickle>(),
664
DevilSickle => ModContent.ItemType<SkullSickle>(),
665
SoulSickle => ModContent.ItemType<DevilSickle>(),
666
VoidSickle => ModContent.ItemType<FrostSickle>(),
667
DeathSickle => ModContent.ItemType<SoulSickle>(),
668
_ => ItemID.None
669
};
670
}
671
590
672
public override void SaveData(TagCompound tag)
591
673
{
592
674
tag[nameof(DamageLevel)] = DamageLevel;
@@ -601,6 +683,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
601
683
tag[nameof(FatedMaximumStacksLevel)] = FatedMaximumStacksLevel;
602
684
tag[nameof(FatedStacksPerHitLevel)] = FatedStacksPerHitLevel;
603
685
tag[nameof(FatedDurationLevel)] = FatedDurationLevel;
686
tag[nameof(previousTierOneType)] = previousTierOneType;
687
tag[nameof(previousTierTwoType)] = previousTierTwoType;
688
tag[nameof(previousTierThreeType)] = previousTierThreeType;
604
689
}
605
690
606
691
public override void LoadData(TagCompound tag)
@@ -617,6 +702,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
617
702
FatedMaximumStacksLevel = Math.Clamp(tag.GetInt(nameof(FatedMaximumStacksLevel)), 1, MaxFatedStackLevel);
618
703
FatedStacksPerHitLevel = Math.Clamp(tag.GetInt(nameof(FatedStacksPerHitLevel)), 1, MaxFatedStacksPerHitLevel);
619
704
FatedDurationLevel = Math.Clamp(tag.GetInt(nameof(FatedDurationLevel)), 1, MaxFatedDurationLevel);
705
previousTierOneType = tag.GetInt(nameof(previousTierOneType));
706
previousTierTwoType = tag.GetInt(nameof(previousTierTwoType));
707
previousTierThreeType = tag.GetInt(nameof(previousTierThreeType));
620
708
ApplyDynamicStats();
621
709
}
622
710
@@ -634,6 +722,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
634
722
writer.Write((byte)FatedMaximumStacksLevel);
635
723
writer.Write((byte)FatedStacksPerHitLevel);
636
724
writer.Write((byte)FatedDurationLevel);
725
writer.Write(previousTierOneType);
726
writer.Write(previousTierTwoType);
727
writer.Write(previousTierThreeType);
637
728
}
638
729
639
730
public override void NetReceive(BinaryReader reader)
@@ -650,6 +741,9 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
650
741
FatedMaximumStacksLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxFatedStackLevel);
651
742
FatedStacksPerHitLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxFatedStacksPerHitLevel);
652
743
FatedDurationLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxFatedDurationLevel);
744
previousTierOneType = reader.ReadInt32();
745
previousTierTwoType = reader.ReadInt32();
746
previousTierThreeType = reader.ReadInt32();
653
747
ApplyDynamicStats();
654
748
}
655
749
@@ -711,6 +805,19 @@ public class NormalSickle : ModItem, IDeathAltarUpgradeable
711
805
};
712
806
}
713
807
808
private int GetRegressionEssenceRefund()
809
{
810
// Refund exactly the cost paid to enter the current form.
811
return SickleTier switch
812
{
813
1 => 1,
814
2 => 16,
815
3 => 32,
816
4 => 99,
817
_ => 0
818
};
819
}
820
714
821
private void SpawnLegacyProjectiles(IEntitySource source, Vector2 position, Vector2 velocity, int type, int damage, float knockback, int owner)
715
822
{
716
823
if (!SupportsProjectileCount || type <= ProjectileID.None)
@@ -185,6 +185,7 @@ Mods: {
185
185
UpgradeCosts: {
186
186
Souls: "{0} character souls"
187
187
SoulEssence: "{0} Soul Essence"
188
SoulEssenceRefund: "Refunds {0} Soul Essence"
188
189
}
189
190
190
191
UpgradeValues: {
@@ -202,11 +203,13 @@ Mods: {
202
203
RequiresFatedUnlock: Unlock Fated first
203
204
RequiresNecklaceMastery: Max all five domain attributes first
204
205
RequiresAllSickleUpgrades: Max every other upgrade on this sickle before entering form 5
206
RequiresHigherSickleForm: The initial form cannot regress any further
205
207
}
206
208
207
209
UpgradeNames: {
208
210
Advancement: Primary-form advancement
209
211
AlternateAdvancement: Branch-form advancement
212
Regression: Regress to previous form
210
213
Damage: Damage modification
211
214
AttackSpeed: Attack speed
212
215
Knockback: Knockback
@@ -269,6 +272,7 @@ Mods: {
269
272
EssenceAbsorbed: Absorbed 1 Soul Essence and gained 100 souls.
270
273
NotEnoughSouls: Not enough souls: extracting Soul Essence requires 100 souls.
271
274
UpgradeSucceeded: Equipment modification complete.
275
SickleRegressed: The sickle returned to its previous form and its advancement Soul Essence was refunded.
272
276
NotEnoughEssence: Not enough Soul Essence.
273
277
NotEnoughUpgradeSouls: Not enough character souls for this low-tier infusion.
274
278
HoldSickle: The selected altar item is no longer in that slot; select it again.
@@ -185,6 +185,7 @@ Mods: {
185
185
UpgradeCosts: {
186
186
Souls: "{0} 人物灵魂"
187
187
SoulEssence: "{0} 灵魂精华"
188
SoulEssenceRefund: "返还 {0} 灵魂精华"
188
189
}
189
190
190
191
UpgradeValues: {
@@ -202,11 +203,13 @@ Mods: {
202
203
RequiresFatedUnlock: 需先点亮命定
203
204
RequiresNecklaceMastery: 需先将五项领域能力升至满级
204
205
RequiresAllSickleUpgrades: 进阶第5形态前,需先将当前镰刀的其它升级全部升满
206
RequiresHigherSickleForm: 初始形态无法继续退阶
205
207
}
206
208
207
209
UpgradeNames: {
208
210
Advancement: 主形态进阶
209
211
AlternateAdvancement: 分支形态进阶
212
Regression: 退回上一形态
210
213
Damage: 攻击改造
211
214
AttackSpeed: 攻速改造
212
215
Knockback: 击退改造
@@ -269,6 +272,7 @@ Mods: {
269
272
EssenceAbsorbed: 已吸收1个灵魂精华并获得100灵魂。
270
273
NotEnoughSouls: 灵魂不足:提取灵魂精华需要100灵魂。
271
274
UpgradeSucceeded: 装备改造完成。
275
SickleRegressed: 镰刀已退回上一形态,进阶消耗的灵魂精华已返还。
272
276
NotEnoughEssence: 灵魂精华不足。
273
277
NotEnoughUpgradeSouls: 人物灵魂不足,无法完成这次低阶注魂。
274
278
HoldSickle: 祭坛所选物品已不在原槽位,请重新选择。
@@ -362,7 +362,7 @@ internal class DeathAltarUIState : UIState
362
362
for (int index = 0; index < count; index++)
363
363
{
364
364
AltarSkillNode node = new(types[first + index], this, index);
365
GetNodePosition(index, fatedIndex, convergenceRootIndex, out int column, out int row);
365
GetNodePosition(types[first + index], index, fatedIndex, convergenceRootIndex, out int column, out int row);
366
366
float left = convergenceRootIndex == 4 && index < convergenceRootIndex
367
367
? TreeLeft + 18f + index * 137f
368
368
: TreeLeft + column * 109f;
@@ -383,8 +383,21 @@ internal class DeathAltarUIState : UIState
383
383
displayedPage = currentPage;
384
384
}
385
385
386
private static void GetNodePosition(int index, int fatedIndex, int convergenceRootIndex, out int column, out int row)
386
private static void GetNodePosition(
387
DeathAltarUpgradeType type,
388
int index,
389
int fatedIndex,
390
int convergenceRootIndex,
391
out int column,
392
out int row)
387
393
{
394
if (type == DeathAltarUpgradeType.Regression)
395
{
396
column = 4;
397
row = 2;
398
return;
399
}
400
388
401
if (convergenceRootIndex >= 0)
389
402
{
390
403
column = index == convergenceRootIndex ? 2 : index;
@@ -430,7 +443,12 @@ internal class DeathAltarUIState : UIState
430
443
431
444
internal static string FormatPrice(DeathAltarPrice price)
432
445
{
433
string key = price.Currency == DeathAltarCurrency.Souls ? "Souls" : "SoulEssence";
446
string key = price.Currency switch
447
{
448
DeathAltarCurrency.Souls => "Souls",
449
DeathAltarCurrency.SoulEssenceRefund => "SoulEssenceRefund",
450
_ => "SoulEssence"
451
};
434
452
return Language.GetTextValue($"Mods.DeathMod.UI.UpgradeCosts.{key}", price.Amount);
435
453
}
436
454
}
@@ -519,7 +537,12 @@ internal sealed class AltarSkillNode : UIPanel
519
537
DeathAltarPrice price = upgradeable?.GetUpgradePrice(UpgradeType) ?? DeathAltarPrice.Unavailable;
520
538
maxed = !price.IsAvailable && upgradeable?.GetUnavailableReason(UpgradeType) == DeathAltarUnavailableReason.Maxed;
521
539
canRequest = price.IsAvailable;
522
bool affordable = price.Currency == DeathAltarCurrency.Souls ? souls >= price.Amount : essenceCount >= price.Amount;
540
bool affordable = price.Currency switch
541
{
542
DeathAltarCurrency.Souls => souls >= price.Amount,
543
DeathAltarCurrency.SoulEssence => essenceCount >= price.Amount,
544
_ => true
545
};
523
546
Lit = maxed || canRequest && affordable;
524
547
string detail;
525
548
if (upgradeable is null)