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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

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

XFEstudio/DeathMod

新增祭坛合成帮助并修正全屏领域

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

代码差异

5 个文件 +311 -30
Modified Common/DeathDomainVisualSystem.cs +47 -28
@@ -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
Modified Items/DeathNecklace.cs +2 -2
@@ -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"),
Modified Localization/en-US.hjson +10 -0
@@ -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
Modified Localization/zh-Hans.hjson +10 -0
@@ -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: 背包
Modified UI/DeathAltarUISystem.cs +242 -0
@@ -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;