返回提交历史
Modified
Common/DeathDomainVisualSystem.cs
+47
-28
Modified
Items/DeathNecklace.cs
+2
-2
Modified
Localization/en-US.hjson
+10
-0
Modified
Localization/zh-Hans.hjson
+10
-0
Modified
UI/DeathAltarUISystem.cs
+242
-0
XFEstudio/DeathMod
新增祭坛合成帮助并修正全屏领域
1476909
代码差异
5 个文件
+311
-30
@@ -115,7 +115,7 @@ internal class DeathDomainVisualSystem : ModSystem
115
115
if (!fullScreen)
116
116
DrawDomainRing(batch, pixel, screenCenter, radius, mastery, animation);
117
117
else
118
DrawSovereignSeal(batch, pixel, screenCenter, mastery, animation);
118
DrawFullScreenDomain(batch, pixel, screenCenter, radius, mastery, animation);
119
119
}
120
120
batch.End();
121
121
}
@@ -350,38 +350,57 @@ internal class DeathDomainVisualSystem : ModSystem
350
350
0f);
351
351
}
352
352
353
private static void DrawSovereignSeal(SpriteBatch batch, Texture2D pixel, Vector2 center, int mastery, float animation)
353
private static void DrawFullScreenDomain(
354
SpriteBatch batch,
355
Texture2D pixel,
356
Vector2 center,
357
float playerDomainRadius,
358
int mastery,
359
float animation)
354
360
{
355
float time = Main.GlobalTimeWrappedHourly;
356
361
float reveal = MathHelper.SmoothStep(0f, 1f, animation);
357
float expansion = 1f - (float)Math.Pow(1f - reveal, 3f);
358
float pulse = (0.86f + (float)Math.Sin(time * 3.6f) * 0.1f) * reveal;
359
float farthestX = Math.Max(Math.Abs(center.X), Math.Abs(Main.screenWidth - center.X));
360
float farthestY = Math.Max(Math.Abs(center.Y), Math.Abs(Main.screenHeight - center.Y));
361
// The completed field reaches beyond every screen corner. The extra margin
362
// absorbs the noisy inward edge displacement, keeping the whole viewport
363
// inside the domain even while its boundary ripples.
364
float targetRadius = (float)Math.Sqrt(farthestX * farthestX + farthestY * farthestY)
365
+ 56f
366
+ (float)Math.Sin(time * 1.7f) * 6f;
367
float radius = MathHelper.Lerp(8f, targetRadius, expansion) * (1f + (float)Math.Sin(reveal * MathHelper.Pi) * 0.035f);
368
362
float masteryProgress = GetMasteryProgress(mastery);
369
DrawBlackHoleField(batch, pixel, center, radius, masteryProgress, pulse, time, reveal);
370
for (int layer = 0; layer < 5; layer++)
363
DrawFullScreenBackdrop(batch, masteryProgress, reveal);
364
365
// Full-screen mastery removes the circular crop from the domain scene. The
366
// player's event horizon is still the same max-level field, rather than a
367
// circle enlarged until it reaches the viewport corners.
368
DrawDomainRing(batch, pixel, center, playerDomainRadius, mastery, animation);
369
}
370
371
private static void DrawFullScreenBackdrop(SpriteBatch batch, float mastery, float reveal)
372
{
373
Vector2 worldCenter = Main.screenPosition + new Vector2(Main.screenWidth, Main.screenHeight) * 0.5f;
374
for (int layer = 0; layer < 3; layer++)
371
375
{
372
float layerProgress = layer / 4f;
373
DrawNoiseBoundary(
376
float parallax = layer switch { 0 => 0.07f, 1 => 0.18f, _ => 0.34f };
377
float horizontalViewFraction = layer switch { 0 => 0.68f, 1 => 0.9f, _ => 0.84f };
378
float verticalViewFraction = layer switch { 0 => 0.78f, 1 => 0.9f, _ => 0.86f };
379
float opacity = layer switch
380
{
381
0 => MathHelper.Lerp(0.88f, 1f, mastery) * reveal,
382
1 => MathHelper.Lerp(0.72f, 0.9f, mastery) * reveal,
383
_ => MathHelper.Lerp(0.78f, 0.96f, mastery) * reveal
384
};
385
Texture2D texture = DeathDomainBackdropTextureSystem.GetLayer(layer);
386
float viewWidth = texture.Width * horizontalViewFraction;
387
float viewHeight = texture.Height * verticalViewFraction;
388
float sourceOffsetX = PositiveModulo(worldCenter.X * parallax, texture.Width);
389
float worldHeight = Math.Max(1f, Main.maxTilesY * 16f);
390
float worldDepth = MathHelper.Clamp(worldCenter.Y / worldHeight, 0f, 1f);
391
float sourceOffsetY = worldDepth * Math.Max(0f, texture.Height - viewHeight);
392
393
DrawBackdropBand(
374
394
batch,
375
pixel,
376
center,
377
radius + MathHelper.Lerp(12f, -8f, layerProgress),
378
time * (layer % 2 == 0 ? 1f : -0.8f),
379
21f + layer * 1.37f,
380
MathHelper.Lerp(14f, 5f, layerProgress),
381
MathHelper.Lerp(10f, 1.6f, layerProgress),
382
MathHelper.Lerp(0.13f, 0.9f, layerProgress) * pulse,
383
1f,
384
gaps: layer != 4);
395
texture,
396
Vector2.Zero,
397
Main.screenWidth,
398
Main.screenHeight,
399
sourceOffsetX,
400
sourceOffsetY,
401
viewWidth,
402
viewHeight,
403
opacity);
385
404
}
386
405
}
387
406
@@ -44,7 +44,7 @@ public class DeathNecklace : ModItem, IDeathAltarUpgradeable
44
44
public float DomainRepulsionMaximumSpeed => 1.1f + (RadiusLevel - 1) * 0.12f;
45
45
public int SpawnInterval => Math.Max(34, 100 - (FrequencyLevel - 1) * 7);
46
46
public float NormalSlashLifeRatio => DamageLevel * 0.01f;
47
public float BossSlashLifeRatio => DamageLevel * 0.0001f;
47
public float BossSlashLifeRatio => DamageLevel * 0.001f;
48
48
public int SlashCount => VolleyLevel;
49
49
public bool IsFullScreenDomain => RadiusLevel >= MaxCoreLevel;
50
50
public bool DeathRequiemUnlocked => SovereigntyUnlocked && AllCoreStatsMaxed;
@@ -163,7 +163,7 @@ public class DeathNecklace : ModItem, IDeathAltarUpgradeable
163
163
{
164
164
DeathAltarUpgradeType.NecklaceRadius => $"{(DomainRadius + RadiusGrowthPerLevel) / 16f:0.#} tiles",
165
165
DeathAltarUpgradeType.NecklaceFrequency => $"{Math.Max(34, SpawnInterval - 7) / 60f:0.##}s",
166
DeathAltarUpgradeType.NecklaceDamage => FormatSlashDamage(NormalSlashLifeRatio + 0.01f, BossSlashLifeRatio + 0.0001f),
166
DeathAltarUpgradeType.NecklaceDamage => FormatSlashDamage(NormalSlashLifeRatio + 0.01f, BossSlashLifeRatio + 0.001f),
167
167
DeathAltarUpgradeType.NecklaceVolley => $"×{SlashCount + 1}",
168
168
DeathAltarUpgradeType.NecklaceLifeSteal => $"{LifeStealLevel + 1}%",
169
169
DeathAltarUpgradeType.NecklaceSovereignty => Language.GetTextValue("Mods.DeathMod.UI.FullScreenRequiem"),
@@ -163,6 +163,16 @@ Mods: {
163
163
Right-click to extract 1 Soul Essence (-{0} souls)
164
164
'''
165
165
AltarTitle: Death Altar · Soul Skill Tree
166
RecipeHelpButton: Recipe Help
167
BackToSkillTree: Back to Tree
168
RecipeHelpTitle: "DeathMod Recipes ({0})"
169
NoModRecipes: No DeathMod crafting recipes are currently available
170
RecipeCraftingStation: "Station: {0}"
171
RecipeStationByHand: By hand
172
RecipeStationWorkbench: Work Bench
173
RecipeStationAnvil: Iron or Lead Anvil
174
RecipeStationDeathAltar: Death Altar
175
RecipeStationUnknown: "Unknown station #{0}"
166
176
SelectEquipment: Available equipment
167
177
NoUpgradeableItems: No upgradeable equipment in inventory or equipment slots
168
178
InventoryLocation: Inventory
@@ -163,6 +163,16 @@ Mods: {
163
163
右键提取1个灵魂精华(-{0}灵魂)
164
164
'''
165
165
AltarTitle: 死神祭坛 · 灵魂技能树
166
RecipeHelpButton: 合成帮助
167
BackToSkillTree: 返回技能树
168
RecipeHelpTitle: "死神模组合成表({0})"
169
NoModRecipes: 暂无可用的本模组合成配方
170
RecipeCraftingStation: "制作站:{0}"
171
RecipeStationByHand: 徒手制作
172
RecipeStationWorkbench: 工作台
173
RecipeStationAnvil: 铁砧或铅砧
174
RecipeStationDeathAltar: 死神祭坛
175
RecipeStationUnknown: "未知制作站 #{0}"
166
176
SelectEquipment: 可改造装备
167
177
NoUpgradeableItems: 背包和装备栏中没有可改造装备
168
178
InventoryLocation: 背包
@@ -105,6 +105,12 @@ internal class DeathAltarUIState : UIState
105
105
private UIText? itemText;
106
106
private UIText? resourceText;
107
107
private UIText? pageText;
108
private UITextPanel<string>? recipeHelpButton;
109
private UIPanel? recipeHelpPanel;
110
private UIList? recipeList;
111
private UIText? recipeHelpTitle;
112
private UIText? noRecipesText;
113
private bool recipeHelpVisible;
108
114
private DeathAltarItemReference? selectedItem;
109
115
private int tileX;
110
116
private int tileY;
@@ -142,6 +148,16 @@ internal class DeathAltarUIState : UIState
142
148
};
143
149
panel.Append(close);
144
150
151
recipeHelpButton = new UITextPanel<string>(string.Empty, 0.72f);
152
recipeHelpButton.Left.Set(-156f, 1f);
153
recipeHelpButton.Top.Set(0f, 0f);
154
recipeHelpButton.Width.Set(116f, 0f);
155
recipeHelpButton.Height.Set(30f, 0f);
156
recipeHelpButton.BackgroundColor = new Color(49, 48, 91);
157
recipeHelpButton.BorderColor = new Color(94, 188, 206);
158
recipeHelpButton.OnLeftClick += (_, _) => ToggleRecipeHelp();
159
panel.Append(recipeHelpButton);
160
145
161
selectorTitleText = new UIText(string.Empty, 0.86f, true);
146
162
selectorTitleText.Left.Set(10f, 0f);
147
163
selectorTitleText.Top.Set(49f, 0f);
@@ -195,6 +211,8 @@ internal class DeathAltarUIState : UIState
195
211
next.Height.Set(32f, 0f);
196
212
next.OnLeftClick += (_, _) => ChangePage(1);
197
213
panel.Append(next);
214
215
BuildRecipeHelpPanel();
198
216
}
199
217
200
218
public override void Update(GameTime gameTime)
@@ -225,6 +243,9 @@ internal class DeathAltarUIState : UIState
225
243
itemText?.SetText(selected is null ? Language.GetTextValue("Mods.DeathMod.UI.NoUpgradeableItems") : Language.GetTextValue("Mods.DeathMod.UI.CurrentItem", selected.Name));
226
244
resourceText?.SetText(Language.GetTextValue("Mods.DeathMod.UI.AltarResources", essenceCount, souls));
227
245
pageText?.SetText(Language.GetTextValue("Mods.DeathMod.UI.TreePage", currentPage + 1, pages));
246
recipeHelpButton?.SetText(Language.GetTextValue(recipeHelpVisible
247
? "Mods.DeathMod.UI.BackToSkillTree"
248
: "Mods.DeathMod.UI.RecipeHelpButton"));
228
249
foreach (AltarSkillNode node in nodes)
229
250
node.Refresh(upgradeable, essenceCount, souls);
230
251
}
@@ -238,6 +259,7 @@ internal class DeathAltarUIState : UIState
238
259
displayedItemType = -1;
239
260
itemSignature = 0;
240
261
selectedItem = null;
262
SetRecipeHelpVisible(false, playSound: false);
241
263
}
242
264
243
265
public bool IsPlayerNearAltar()
@@ -381,6 +403,111 @@ internal class DeathAltarUIState : UIState
381
403
panel.RootItemType = itemType;
382
404
displayedItemType = itemType;
383
405
displayedPage = currentPage;
406
BringRecipeHelpToFront();
407
}
408
409
private void BuildRecipeHelpPanel()
410
{
411
recipeHelpPanel = new UIPanel();
412
recipeHelpPanel.Left.Set(TreeLeft - 6f, 0f);
413
recipeHelpPanel.Top.Set(42f, 0f);
414
recipeHelpPanel.Width.Set(-(TreeLeft - 2f), 1f);
415
recipeHelpPanel.Height.Set(584f, 0f);
416
recipeHelpPanel.SetPadding(10f);
417
recipeHelpPanel.BackgroundColor = new Color(18, 14, 35) * 0.99f;
418
recipeHelpPanel.BorderColor = new Color(110, 76, 148);
419
420
recipeHelpTitle = new UIText(string.Empty, 0.9f, true) { HAlign = 0.5f };
421
recipeHelpTitle.Top.Set(2f, 0f);
422
recipeHelpPanel.Append(recipeHelpTitle);
423
424
recipeList = new UIList();
425
recipeList.Top.Set(38f, 0f);
426
recipeList.Width.Set(-20f, 1f);
427
recipeList.Height.Set(-42f, 1f);
428
recipeList.ListPadding = 8f;
429
recipeHelpPanel.Append(recipeList);
430
431
UIScrollbar scrollbar = new();
432
scrollbar.Left.Set(-15f, 1f);
433
scrollbar.Top.Set(38f, 0f);
434
scrollbar.Height.Set(-42f, 1f);
435
recipeHelpPanel.Append(scrollbar);
436
recipeList.SetScrollbar(scrollbar);
437
438
noRecipesText = new UIText(string.Empty, 0.72f) { HAlign = 0.5f };
439
noRecipesText.Top.Set(82f, 0f);
440
}
441
442
private void ToggleRecipeHelp()
443
{
444
SetRecipeHelpVisible(!recipeHelpVisible, playSound: true);
445
}
446
447
private void SetRecipeHelpVisible(bool visible, bool playSound)
448
{
449
recipeHelpVisible = visible;
450
if (panel is null || recipeHelpPanel is null)
451
return;
452
453
if (visible)
454
{
455
RefreshRecipeHelp();
456
BringRecipeHelpToFront();
457
}
458
else if (recipeHelpPanel.Parent is not null)
459
{
460
panel.RemoveChild(recipeHelpPanel);
461
}
462
463
recipeHelpButton?.SetText(Language.GetTextValue(visible
464
? "Mods.DeathMod.UI.BackToSkillTree"
465
: "Mods.DeathMod.UI.RecipeHelpButton"));
466
if (playSound)
467
SoundEngine.PlaySound(SoundID.MenuTick);
468
}
469
470
private void BringRecipeHelpToFront()
471
{
472
if (!recipeHelpVisible || panel is null || recipeHelpPanel is null)
473
return;
474
if (recipeHelpPanel.Parent is not null)
475
panel.RemoveChild(recipeHelpPanel);
476
panel.Append(recipeHelpPanel);
477
}
478
479
private void RefreshRecipeHelp()
480
{
481
if (recipeList is null || recipeHelpPanel is null)
482
return;
483
484
recipeList.Clear();
485
int recipeCount = 0;
486
Mod deathMod = ModContent.GetInstance<DeathMod>();
487
for (int index = 0; index < Main.recipe.Length; index++)
488
{
489
Recipe recipe = Main.recipe[index];
490
if (recipe is null
491
|| recipe.Disabled
492
|| recipe.createItem.IsAir
493
|| recipe.createItem.ModItem?.Mod != deathMod)
494
{
495
continue;
496
}
497
498
recipeList.Add(new AltarRecipeEntry(recipe));
499
recipeCount++;
500
}
501
502
recipeHelpTitle?.SetText(Language.GetTextValue("Mods.DeathMod.UI.RecipeHelpTitle", recipeCount));
503
noRecipesText?.SetText(Language.GetTextValue("Mods.DeathMod.UI.NoModRecipes"));
504
if (noRecipesText is not null)
505
{
506
if (recipeCount == 0 && noRecipesText.Parent is null)
507
recipeHelpPanel.Append(noRecipesText);
508
else if (recipeCount > 0 && noRecipesText.Parent is not null)
509
recipeHelpPanel.RemoveChild(noRecipesText);
510
}
384
511
}
385
512
386
513
private static void GetNodePosition(
@@ -453,6 +580,121 @@ internal class DeathAltarUIState : UIState
453
580
}
454
581
}
455
582
583
internal sealed class AltarRecipeEntry : UIElement
584
{
585
private readonly Item result;
586
private readonly List<Item> ingredients = [];
587
private readonly List<int> requiredTiles = [];
588
589
public AltarRecipeEntry(Recipe recipe)
590
{
591
result = recipe.createItem.Clone();
592
foreach (Item ingredient in recipe.requiredItem)
593
if (!ingredient.IsAir)
594
ingredients.Add(ingredient.Clone());
595
requiredTiles.AddRange(recipe.requiredTile);
596
Width.Set(0f, 1f);
597
Height.Set(104f, 0f);
598
}
599
600
protected override void DrawSelf(SpriteBatch spriteBatch)
601
{
602
Rectangle bounds = GetDimensions().ToRectangle();
603
Texture2D pixel = TextureAssets.MagicPixel.Value;
604
Color background = IsMouseHovering ? new Color(42, 35, 63) : new Color(29, 24, 47);
605
spriteBatch.Draw(pixel, bounds, background * 0.97f);
606
DrawBorder(spriteBatch, pixel, bounds, new Color(91, 69, 123));
607
608
DrawItemIcon(spriteBatch, result, new Vector2(bounds.X + 34f, bounds.Y + 40f), 44f);
609
Utils.DrawBorderString(
610
spriteBatch,
611
FitText(result.Name, 118f, 0.68f),
612
new Vector2(bounds.X + 62f, bounds.Y + 16f),
613
new Color(235, 220, 255),
614
0.68f);
615
if (result.stack > 1)
616
Utils.DrawBorderString(spriteBatch, $"×{result.stack}", new Vector2(bounds.X + 62f, bounds.Y + 43f), Color.White, 0.62f);
617
618
float ingredientsLeft = bounds.X + 188f;
619
float ingredientsWidth = Math.Max(1f, bounds.Right - 8f - ingredientsLeft);
620
float cellWidth = ingredientsWidth / Math.Max(1, ingredients.Count);
621
for (int index = 0; index < ingredients.Count; index++)
622
{
623
Item ingredient = ingredients[index];
624
float centerX = ingredientsLeft + cellWidth * (index + 0.5f);
625
Vector2 iconCenter = new(centerX, bounds.Y + 34f);
626
DrawItemIcon(spriteBatch, ingredient, iconCenter, 34f);
627
string amount = ingredient.stack > 1 ? $" ×{ingredient.stack}" : string.Empty;
628
string ingredientText = FitText(ingredient.Name + amount, Math.Max(40f, cellWidth - 4f), 0.55f);
629
Vector2 textSize = FontAssets.MouseText.Value.MeasureString(ingredientText) * 0.55f;
630
Utils.DrawBorderString(spriteBatch, ingredientText, new Vector2(centerX - textSize.X * 0.5f, bounds.Y + 58f), Color.White, 0.55f);
631
632
Rectangle iconHitbox = new((int)(iconCenter.X - 20f), (int)(iconCenter.Y - 20f), 40, 40);
633
if (iconHitbox.Contains(Main.MouseScreen.ToPoint()))
634
Main.instance.MouseText($"{ingredient.Name}{amount}");
635
}
636
637
string station = GetCraftingStationText();
638
Utils.DrawBorderString(
639
spriteBatch,
640
Language.GetTextValue("Mods.DeathMod.UI.RecipeCraftingStation", station),
641
new Vector2(bounds.X + 12f, bounds.Bottom - 24f),
642
new Color(137, 211, 225),
643
0.58f);
644
}
645
646
private string GetCraftingStationText()
647
{
648
if (requiredTiles.Count == 0)
649
return Language.GetTextValue("Mods.DeathMod.UI.RecipeStationByHand");
650
651
List<string> names = [];
652
foreach (int tileType in requiredTiles)
653
{
654
string name = tileType switch
655
{
656
TileID.WorkBenches => Language.GetTextValue("Mods.DeathMod.UI.RecipeStationWorkbench"),
657
TileID.Anvils => Language.GetTextValue("Mods.DeathMod.UI.RecipeStationAnvil"),
658
_ when tileType == ModContent.TileType<DeathAltarTile>() => Language.GetTextValue("Mods.DeathMod.UI.RecipeStationDeathAltar"),
659
_ => Language.GetTextValue("Mods.DeathMod.UI.RecipeStationUnknown", tileType)
660
};
661
if (!names.Contains(name))
662
names.Add(name);
663
}
664
return string.Join(" / ", names);
665
}
666
667
private static void DrawItemIcon(SpriteBatch spriteBatch, Item item, Vector2 center, float maximumSize)
668
{
669
Main.instance.LoadItem(item.type);
670
Texture2D texture = TextureAssets.Item[item.type].Value;
671
Rectangle frame = Main.itemAnimations[item.type]?.GetFrame(texture) ?? texture.Frame();
672
float scale = Math.Min(1f, maximumSize / Math.Max(frame.Width, frame.Height));
673
spriteBatch.Draw(texture, center, frame, Color.White, 0f, frame.Size() * 0.5f, scale, SpriteEffects.None, 0f);
674
}
675
676
private static string FitText(string text, float maximumWidth, float scale)
677
{
678
if (FontAssets.MouseText.Value.MeasureString(text).X * scale <= maximumWidth)
679
return text;
680
const string ellipsis = "…";
681
while (text.Length > 1
682
&& FontAssets.MouseText.Value.MeasureString(text + ellipsis).X * scale > maximumWidth)
683
{
684
text = text[..^1];
685
}
686
return text + ellipsis;
687
}
688
689
private static void DrawBorder(SpriteBatch batch, Texture2D pixel, Rectangle bounds, Color color)
690
{
691
batch.Draw(pixel, new Rectangle(bounds.X, bounds.Y, bounds.Width, 2), color);
692
batch.Draw(pixel, new Rectangle(bounds.X, bounds.Bottom - 2, bounds.Width, 2), color);
693
batch.Draw(pixel, new Rectangle(bounds.X, bounds.Y, 2, bounds.Height), color);
694
batch.Draw(pixel, new Rectangle(bounds.Right - 2, bounds.Y, 2, bounds.Height), color);
695
}
696
}
697
456
698
internal sealed class AltarItemSelector : UIElement
457
699
{
458
700
private readonly DeathAltarUIState owner;