Implement skeletal crafting UI

Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>
This commit is contained in:
Ritchie Frodomar 2024-08-04 22:59:35 -04:00
parent 935007c3f5
commit 0809f86ca9
20 changed files with 700 additions and 8 deletions

View file

@ -8,6 +8,7 @@ namespace AcidicGUI.VisualStyles;
internal sealed class FallbackVisualStyle : IVisualStyle internal sealed class FallbackVisualStyle : IVisualStyle
{ {
public int ProgressBarHeight => 6;
public int SliderThickness => 12; public int SliderThickness => 12;
public Point ToggleSize => new Point(18, 18); public Point ToggleSize => new Point(18, 18);
public Point SwitchSize => ToggleSize; public Point SwitchSize => ToggleSize;
@ -54,6 +55,14 @@ internal sealed class FallbackVisualStyle : IVisualStyle
geometry.AddQuad(new LayoutRect(scrollBarArea.Left, scrollBarArea.Top + barOffset, scrollBarArea.Width, barHeight), Color.White); geometry.AddQuad(new LayoutRect(scrollBarArea.Left, scrollBarArea.Top + barOffset, scrollBarArea.Width, barHeight), Color.White);
} }
public void DrawProgressBar(Widget widget, GeometryHelper geometry, float fillPercentage)
{
geometry.AddQuad(widget.ContentArea, Color.DimGray);
var fillWidth = (int)MathHelper.Lerp(0, widget.ContentArea.Width, fillPercentage);
geometry.AddQuad(new LayoutRect(widget.ContentArea.Left, widget.ContentArea.Top, fillWidth, widget.ContentArea.Height), Color.Red);
}
public void DrawToggle( public void DrawToggle(
Toggle toggle, Toggle toggle,
GeometryHelper geometry, GeometryHelper geometry,

View file

@ -8,6 +8,7 @@ namespace AcidicGUI.VisualStyles;
public interface IVisualStyle : IFontFamilyProvider public interface IVisualStyle : IFontFamilyProvider
{ {
int ProgressBarHeight { get; }
int SliderThickness { get; } int SliderThickness { get; }
Point ToggleSize { get; } Point ToggleSize { get; }
Point SwitchSize { get; } Point SwitchSize { get; }
@ -41,6 +42,8 @@ public interface IVisualStyle : IFontFamilyProvider
bool isChecked bool isChecked
); );
void DrawProgressBar(Widget widget, GeometryHelper geometry, float fillPercentage);
void DrawToggleSwitch( void DrawToggleSwitch(
Toggle toggle, Toggle toggle,
GeometryHelper geometry, GeometryHelper geometry,

View file

@ -0,0 +1,29 @@
using AcidicGUI.Rendering;
using Microsoft.Xna.Framework;
namespace AcidicGUI.Widgets;
public sealed class ProgressBar : Widget
{
private float fillPercentage = 0;
public float Value
{
get => fillPercentage;
set
{
fillPercentage = MathHelper.Clamp(value, 0, 1);
InvalidateGeometry();
}
}
protected override Point GetContentSize(Point availableSize)
{
return new Point(availableSize.X, GetVisualStyle().ProgressBarHeight);
}
protected override void RebuildGeometry(GeometryHelper geometry)
{
GetVisualStyle().DrawProgressBar(this, geometry, fillPercentage);
}
}

View file

@ -0,0 +1,128 @@
using AcidicGUI.Layout;
using Microsoft.Xna.Framework;
namespace AcidicGUI.Widgets;
public sealed class TablePanel : ContainerWidget
{
private int columnCount = 3;
private int rowSpacing = 0;
private int columnSpacing = 0;
private int[] columnSizesCache = Array.Empty<int>();
public int ColumnCount
{
get => columnCount;
set
{
if (value < 1)
throw new InvalidOperationException("A table must have at least one column.");
columnCount = value;
InvalidateLayout();
}
}
public int ColumnSpacing
{
get => columnSpacing;
set
{
columnSpacing = value;
InvalidateLayout();
}
}
public int RowSpacing
{
get => rowSpacing;
set
{
rowSpacing = value;
InvalidateLayout();
}
}
protected override unsafe Point GetContentSize(Point availableSize)
{
if (Children.Count == 0)
return Point.Zero;
Span<int> columnSizes = stackalloc int[columnCount];
int maxColumnSize = availableSize.X - (columnSpacing * columnCount - 1) / columnCount;
int height = 0;
int rowHeight = 0;
int lastRow = 0;
for (int i = 0; i < Children.Count; i++)
{
var columnIndex = i % columnCount;
var row = i / columnCount;
var child = Children[i];
if (row != lastRow)
{
lastRow = row;
height += rowHeight;
rowHeight = 0;
}
int availableHeight = availableSize.Y - height;
var widgetSize = child.GetCachedContentSize(new Point(maxColumnSize, availableHeight));
columnSizes[columnIndex] = Math.Max(columnSizes[columnIndex], widgetSize.X);
rowHeight = Math.Max(rowHeight, widgetSize.Y);
}
if (rowHeight > 0)
{
height += rowHeight;
rowHeight = 0;
}
var columnsWidth = columnSpacing * (columnCount - 1);
for (var i = 0; i < columnCount; i++)
{
columnsWidth += columnSizes[i];
}
columnSizesCache = columnSizes.ToArray();
return new Point(columnsWidth, height + rowSpacing * lastRow);
}
protected override void ArrangeChildren(IGuiContext context, LayoutRect availableSpace)
{
if (Children.Count == 0)
return;
var rowCount = Math.Max(1, Children.Count / columnCount);
var rowSpace = (availableSpace.Height - rowSpacing * (rowCount - 1)) / rowCount;
int yStart = availableSpace.Top;
int xStart = availableSpace.Left;
int lastRow = 0;
int rowHeight = 0;
for (var i = 0; i < Children.Count; i++)
{
var columnIndex = i % columnCount;
var rowIndex = i / columnCount;
var child = Children[i];
if (rowIndex != lastRow)
{
lastRow = rowIndex;
yStart += rowHeight + rowSpacing;
xStart = availableSpace.Left;
rowHeight = 0;
}
var childSize = child.GetCachedContentSize(new Point(columnSizesCache[columnIndex], rowSpace));
child.UpdateLayout(context, new LayoutRect(xStart, yStart, childSize.X, rowSpace));
rowHeight = Math.Max(rowHeight, childSize.Y);
xStart += columnSizesCache[columnIndex] + columnSpacing;
}
}
}

View file

@ -8,9 +8,22 @@ namespace SociallyDistant.Core.Core
} }
public enum RecipeCategory
{
Components,
Exploits,
Payloads,
Attacks,
Daemons,
ShellExtensions
}
public abstract class CraftingRecipe : IGameContent public abstract class CraftingRecipe : IGameContent
{ {
public abstract RecipeCategory Category { get; }
public abstract string Id { get; } public abstract string Id { get; }
public abstract string Description { get; }
public abstract string Title { get; }
public abstract IEnumerable<IngredientRequirement> RequiredIngredients { get; } public abstract IEnumerable<IngredientRequirement> RequiredIngredients { get; }

View file

@ -16,6 +16,7 @@ namespace SociallyDistant.Core.Core
INarrativeObjectTable<WorldComputerData> Computers { get; } INarrativeObjectTable<WorldComputerData> Computers { get; }
INarrativeObjectTable<WorldInternetServiceProviderData> InternetProviders { get; } INarrativeObjectTable<WorldInternetServiceProviderData> InternetProviders { get; }
INarrativeObjectTable<WorldLocalNetworkData> LocalAreaNetworks { get; } INarrativeObjectTable<WorldLocalNetworkData> LocalAreaNetworks { get; }
IWorldTable<WorldInventoryItem> Inventory { get; }
IWorldTable<WorldNetworkConnection> NetworkConnections { get; } IWorldTable<WorldNetworkConnection> NetworkConnections { get; }
IWorldTable<WorldPortForwardingRule> PortForwardingRules { get; } IWorldTable<WorldPortForwardingRule> PortForwardingRules { get; }
IWorldTable<WorldCraftedExploitData> CraftedExploits { get; } IWorldTable<WorldCraftedExploitData> CraftedExploits { get; }

View file

@ -0,0 +1,43 @@
using SociallyDistant.Core.Core.Serialization;
namespace SociallyDistant.Core.Core.WorldData.Data;
public struct WorldInventoryItem : IWorldData, IDataWithId
{
private ObjectId id;
private ObjectId userId;
private string recipeId;
private int quantity;
public ObjectId InstanceId
{
get => id;
set => id = value;
}
public ObjectId Owner
{
get => userId;
set => userId = value;
}
public string RecipeId
{
get => recipeId;
set => recipeId = value;
}
public int Quantity
{
get => quantity;
set => quantity = value;
}
public void Serialize(IWorldSerializer serializer)
{
SerializationUtility.SerializeAtRevision(ref id, serializer, WorldRevision.Inventory, default);
SerializationUtility.SerializeAtRevision(ref userId, serializer, WorldRevision.Inventory, default);
SerializationUtility.SerializeAtRevision(ref recipeId, serializer, WorldRevision.Inventory, string.Empty);
SerializationUtility.SerializeAtRevision(ref quantity, serializer, WorldRevision.Inventory, default);
}
}

View file

@ -33,6 +33,7 @@
SubDocuments = 28, SubDocuments = 28,
Checkpoints = 29, Checkpoints = 29,
IngredientLedger = 30, IngredientLedger = 30,
Inventory=31,
Latest Latest
} }

View file

@ -7,6 +7,8 @@ public sealed class CommandAttribute : Attribute
public string Name => name; public string Name => name;
public bool Cheat { get; set; }
public CommandAttribute(string name) public CommandAttribute(string name)
{ {
this.name = name; this.name = name;

View file

@ -1,3 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
using AcidicGUI.CustomProperties;
using AcidicGUI.Layout; using AcidicGUI.Layout;
using AcidicGUI.ListAdapters; using AcidicGUI.ListAdapters;
using AcidicGUI.Widgets; using AcidicGUI.Widgets;
@ -7,12 +9,13 @@ namespace SociallyDistant.Core.UI.Recycling;
public sealed class TwoLineListItemWithIcon : Widget public sealed class TwoLineListItemWithIcon : Widget
{ {
private readonly ListItem root = new(); private readonly ListItem root = new();
private readonly StackPanel stack = new(); private readonly FlexPanel stack = new();
private readonly Box icon = new(); private readonly Box icon = new();
private readonly StackPanel lines = new(); private readonly StackPanel lines = new();
private readonly TextWidget line1 = new(); private readonly TextWidget line1 = new();
private readonly TextWidget line2 = new(); private readonly TextWidget line2 = new();
private readonly Emblem emblem = new();
public string Line1 public string Line1
{ {
@ -40,8 +43,32 @@ public sealed class TwoLineListItemWithIcon : Widget
set => root.ClickCallback = value; set => root.ClickCallback = value;
} }
public bool ShowEmblem
{
get => emblem.Visibility == Visibility.Visible;
set
{
if (value)
emblem.Visibility = Visibility.Visible;
else
emblem.Visibility = Visibility.Collapsed;
}
}
public string EmblemText
{
get => emblem.Text;
set => emblem.Text = value;
}
public TwoLineListItemWithIcon() public TwoLineListItemWithIcon()
{ {
emblem.Visibility = Visibility.Collapsed;
lines.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
emblem.VerticalAlignment = VerticalAlignment.Middle;
line1.WordWrapping = true; line1.WordWrapping = true;
line2.WordWrapping = true; line2.WordWrapping = true;
line1.UseMarkup = true; line1.UseMarkup = true;
@ -57,6 +84,7 @@ public sealed class TwoLineListItemWithIcon : Widget
stack.ChildWidgets.Add(lines); stack.ChildWidgets.Add(lines);
lines.ChildWidgets.Add(line1); lines.ChildWidgets.Add(line1);
lines.ChildWidgets.Add(line2); lines.ChildWidgets.Add(line2);
stack.ChildWidgets.Add(emblem);
} }
} }

View file

@ -18,6 +18,17 @@ public sealed class ListItemWidgetController<T> : RecyclableWidgetController
{ {
listItem = GetWidget<TwoLineListItemWithIcon>(); listItem = GetWidget<TwoLineListItemWithIcon>();
if (typeof(T) == typeof(int))
{
listItem.ShowEmblem = true;
listItem.EmblemText = Data!.ToString() ?? "0";
}
else
{
listItem.ShowEmblem = false;
listItem.EmblemText = string.Empty;
}
if (Image != null) if (Image != null)
{ {
Image.Build(listItem.Icon); Image.Build(listItem.Icon);

View file

@ -53,6 +53,7 @@ public class SociallyDistantVisualStyle : IVisualStyle
private IFontFamily monospace = null!; private IFontFamily monospace = null!;
private Texture2D? checkboxEmblem; private Texture2D? checkboxEmblem;
public int ProgressBarHeight => 6;
public int SliderThickness => 18; public int SliderThickness => 18;
public Point ToggleSize => new Point(20, 20); public Point ToggleSize => new Point(20, 20);
public Point SwitchSize => new Point(40, 22); public Point SwitchSize => new Point(40, 22);
@ -485,6 +486,14 @@ public class SociallyDistantVisualStyle : IVisualStyle
} }
} }
public void DrawProgressBar(Widget widget, GeometryHelper geometry, float fillPercentage)
{
geometry.AddRoundedRectangleOutline(widget.ContentArea, 1, 6, accentCyberspace);
var fillWidth = (int)MathHelper.Lerp(0, widget.ContentArea.Width, fillPercentage);
geometry.AddRoundedRectangle(new LayoutRect(widget.ContentArea.Left, widget.ContentArea.Top, fillWidth, widget.ContentArea.Height), 6, accentCyberspace * 0.5f);
}
public void DrawToggleSwitch( public void DrawToggleSwitch(
Toggle toggle, Toggle toggle,
GeometryHelper geometry, GeometryHelper geometry,

View file

@ -13,6 +13,8 @@ internal sealed class CommandAsset :
private readonly CommandAttribute attribute; private readonly CommandAttribute attribute;
private readonly Func<IGameContext, ICommandTask> constructor; private readonly Func<IGameContext, ICommandTask> constructor;
public bool IsCheat => attribute.Cheat;
public string Name => attribute.Name; public string Name => attribute.Name;
public CommandAsset(IGameContext context, CommandAttribute attribute, Func<IGameContext, ICommandTask> constructor) public CommandAsset(IGameContext context, CommandAttribute attribute, Func<IGameContext, ICommandTask> constructor)

View file

@ -0,0 +1,27 @@
using SociallyDistant.Architecture;
using SociallyDistant.Core;
using SociallyDistant.Core.Core;
using SociallyDistant.Core.Modules;
using SociallyDistant.Core.OS.Tasks;
using SociallyDistant.Core.WorldData;
namespace SociallyDistant.Commands.Cheats;
[Command("codinginthenameof", Cheat = true)]
public class CodingInTheNameOf : ScriptableCommand
{
public CodingInTheNameOf(IGameContext gameContext) : base(gameContext)
{
}
protected override Task OnExecute()
{
var ledger = WorldManager.Instance.World.Ledger;
if (ledger is not IngredientLedger ledgerInternal)
return Task.CompletedTask;
ledgerInternal.Deposit(CraftingIngredient.CodeFragments, 50);
Console.WriteLine("Fuck you, I won't code what you tell me!");
return Task.CompletedTask;
}
}

View file

@ -0,0 +1,27 @@
using SociallyDistant.Architecture;
using SociallyDistant.Core;
using SociallyDistant.Core.Core;
using SociallyDistant.Core.Modules;
using SociallyDistant.Core.OS.Tasks;
using SociallyDistant.Core.WorldData;
namespace SociallyDistant.Commands.Cheats;
[Command("knowledgeispower", Cheat = true)]
public class KnowledgeIsPower : ScriptableCommand
{
public KnowledgeIsPower(IGameContext gameContext) : base(gameContext)
{
}
protected override Task OnExecute()
{
var ledger = WorldManager.Instance.World.Ledger;
if (ledger is not IngredientLedger ledgerInternal)
return Task.CompletedTask;
ledgerInternal.Deposit(CraftingIngredient.DataFragments, 50);
Console.WriteLine("Knowledge is power, Noah...");
return Task.CompletedTask;
}
}

View file

@ -1,5 +1,6 @@
using SociallyDistant.Core.Core; using SociallyDistant.Core.Core;
using SociallyDistant.Core.Core.Serialization; using SociallyDistant.Core.Core.Serialization;
using YamlDotNet.Core.Tokens;
namespace SociallyDistant.Core.WorldData; namespace SociallyDistant.Core.WorldData;

View file

@ -35,6 +35,7 @@ namespace SociallyDistant.Core.WorldData
private readonly WorldDataTable<WorldWitnessedObjectData> witnessedObjects; private readonly WorldDataTable<WorldWitnessedObjectData> witnessedObjects;
private readonly WorldDataTable<WorldNotificationData> notifications; private readonly WorldDataTable<WorldNotificationData> notifications;
private readonly NarrativeObjectTable<WorldNewsData> newsArticles; private readonly NarrativeObjectTable<WorldNewsData> newsArticles;
private readonly WorldDataTable<WorldInventoryItem> inventory;
internal IWorldDataObject<ProtectedWorldState> ProtectedWorldData => protectedWorldState; internal IWorldDataObject<ProtectedWorldState> ProtectedWorldData => protectedWorldState;
@ -44,6 +45,9 @@ namespace SociallyDistant.Core.WorldData
public ILedger Ledger => ledger; public ILedger Ledger => ledger;
/// <inheritdoc />
public IWorldTable<WorldInventoryItem> Inventory => inventory;
/// <inheritdoc /> /// <inheritdoc />
public IWorldDataObject<GlobalWorldData> GlobalWorldState => globalWorldState; public IWorldDataObject<GlobalWorldData> GlobalWorldState => globalWorldState;
@ -160,7 +164,7 @@ namespace SociallyDistant.Core.WorldData
witnessedObjects = new WorldDataTable<WorldWitnessedObjectData>(instanceIdGenerator, eventDispatcher); witnessedObjects = new WorldDataTable<WorldWitnessedObjectData>(instanceIdGenerator, eventDispatcher);
notifications = new WorldDataTable<WorldNotificationData>(instanceIdGenerator, eventDispatcher); notifications = new WorldDataTable<WorldNotificationData>(instanceIdGenerator, eventDispatcher);
newsArticles = new NarrativeObjectTable<WorldNewsData>(instanceIdGenerator, eventDispatcher); newsArticles = new NarrativeObjectTable<WorldNewsData>(instanceIdGenerator, eventDispatcher);
inventory = new WorldDataTable<WorldInventoryItem>(instanceIdGenerator, eventDispatcher);
} }
public void Serialize(IWorldSerializer serializer) public void Serialize(IWorldSerializer serializer)
@ -196,6 +200,7 @@ namespace SociallyDistant.Core.WorldData
witnessedObjects.Serialize(serializer, WorldRevision.MissionFailures); witnessedObjects.Serialize(serializer, WorldRevision.MissionFailures);
notifications.Serialize(serializer, WorldRevision.Notifications); notifications.Serialize(serializer, WorldRevision.Notifications);
newsArticles.Serialize(serializer, WorldRevision.Articles); newsArticles.Serialize(serializer, WorldRevision.Articles);
inventory.Serialize(serializer, WorldRevision.Inventory);
eventDispatcher.PauseEvents = false; eventDispatcher.PauseEvents = false;
} }
@ -204,6 +209,7 @@ namespace SociallyDistant.Core.WorldData
{ {
// You must wipe the world in reverse order of how you would create or serialize it. // You must wipe the world in reverse order of how you would create or serialize it.
// This ensures proper handling of deleting objects that depend on other objects. // This ensures proper handling of deleting objects that depend on other objects.
inventory.Clear();
newsArticles.Clear(); newsArticles.Clear();
notifications.Clear(); notifications.Clear();
witnessedObjects.Clear(); witnessedObjects.Clear();

View file

@ -0,0 +1,28 @@
using SociallyDistant.Core.Core;
using SociallyDistant.Core.Modules;
namespace SociallyDistant.Recipes;
public sealed class DataScraper : CraftingRecipe
{
public override RecipeCategory Category => RecipeCategory.ShellExtensions;
public override string Description =>
"""
Scrapes <b>Data Fragments</b> from the remote system, adding them to the Inventory. Has a small chance of locating useful information in home directories but does not download it.
""";
public override string Id => "data_scraper";
public override string Title => "Data Scraper";
public override IEnumerable<IngredientRequirement> RequiredIngredients
{
get
{
yield return new IngredientRequirement(CraftingIngredient.DataFragments, 250);
yield return new IngredientRequirement(CraftingIngredient.CodeFragments, 100);
}
}
protected override void OnCraft(IGameContext game)
{
}
}

View file

@ -1,15 +1,338 @@
using AcidicGUI.CustomProperties;
using AcidicGUI.Layout;
using AcidicGUI.ListAdapters;
using AcidicGUI.TextRendering;
using AcidicGUI.Widgets;
using Microsoft.Xna.Framework;
using SociallyDistant.Core;
using SociallyDistant.Core.Core;
using SociallyDistant.Core.Programs; using SociallyDistant.Core.Programs;
using SociallyDistant.Core.Shell;
using SociallyDistant.Core.UI.Common;
using SociallyDistant.Core.UI.Recycling;
using SociallyDistant.Core.UI.VisualStyles;
using SociallyDistant.UI.FloatingTools.FileManager;
namespace SociallyDistant.UI.Tools.Crafting; namespace SociallyDistant.UI.Tools.Crafting;
public sealed class CraftingProgramController : ProgramController public sealed class CraftingProgramController : ProgramController
{ {
private readonly string ghost = """
______ __ __ _____ __ _ __
/ ____// /_ ____ _____ / /_/ ___/ ____ / /____ (_)/ /_
/ / __ / __ \ / __ \ / ___// __/\__ \ / __ \ / // __ \ / // __/
/ /_/ // / / // /_/ /(__ )/ /_ ___/ // /_/ // // /_/ // // /_
\____//_/ /_/ \____//____/ \__//____// .___//_/ \____//_/ \__/
/_/
""";
private readonly FlexPanel root = new();
private readonly FlexPanel header = new();
private readonly StackPanel headerInfo = new();
private readonly TablePanel resourcesTable = new();
private readonly TextWidget headerTitle = new();
private readonly TextWidget logo = new();
private readonly FlexPanel bodyArea = new();
private readonly RecyclableWidgetList<ScrollView> componentsAndCategories = new();
private readonly FlexPanel inventoryRoot = new();
private readonly FlexPanel specsRoot = new();
private readonly FlexPanel currentItemRoot = new();
private readonly TextWidget storageLabel = new();
private readonly ProgressBar storageProgress = new();
private readonly TextWidget storageValue = new();
private readonly TextWidget skillLabel = new();
private readonly ProgressBar skillProgress = new();
private readonly TextWidget skillValue = new();
private readonly List<CraftingRecipe> recipes = new();
private readonly TextWidget inventoryTitle = new();
private readonly ScrollView inventoryView = new();
private readonly FileGrid inventoryGrid = new();
private readonly TextWidget recipesTitle = new();
private readonly ScrollView recipesView = new();
private readonly FileGrid recipesGrid = new();
private readonly List<InventoryItemModel> inventoryItems = new();
private readonly List<int> filteredRecipeMap = new();
private readonly TextWidget currentItemHeader = new();
private readonly ScrollView itemInfoScroller = new();
private readonly FlexPanel itemBanner = new();
private readonly CompositeIconWidget itemBannerIcon = new();
private readonly TextWidget itemBannerText = new();
private readonly TextWidget itemDescription = new();
private readonly FlexPanel interactionPanel = new();
private readonly TextWidget interactionLabel = new();
private readonly ProgressBar interactionProgress = new();
private readonly TextWidget interactionPercentage = new();
private readonly OverlayWidget interactionOverlay = new();
private readonly TextButton interactionButton = new();
private readonly TextWidget interactionError = new();
private readonly StackPanel interactionsArea = new();
private InventoryItemModel? currentItem;
private RecipeCategory inventoryFilter;
private CraftingRecipe? currentRecipe;
protected CraftingProgramController(ProgramContext context) : base(context) protected CraftingProgramController(ProgramContext context) : base(context)
{ {
context.Window.Content = root;
skillLabel.Text = "Skill:";
storageLabel.Text = "Storage:";
storageValue.Text = "0 / 0 MiB";
skillValue.Text = "0 / 0 XP";
headerTitle.Text = "CRAFTING";
resourcesTable.ColumnCount = 3;
resourcesTable.ColumnSpacing = 3;
resourcesTable.RowSpacing = 3;
root.Padding = 12;
root.Spacing = 12;
bodyArea.Spacing = 12;
logo.Text = ghost;
headerTitle.FontWeight = FontWeight.Bold;
headerInfo.Spacing = 3;
headerInfo.VerticalAlignment = VerticalAlignment.Middle;
logo.VerticalAlignment = VerticalAlignment.Middle;
logo.HorizontalAlignment = HorizontalAlignment.Right;
inventoryTitle.Text = "Inventory";
inventoryTitle.FontWeight = FontWeight.Bold;
recipesTitle.FontWeight = FontWeight.Bold;
itemBannerText.UseMarkup = true;
headerTitle.FontSize = 22;
logo.FontSize = 8;
logo.Font = PresetFontFamily.Monospace;
skillProgress.VerticalAlignment = VerticalAlignment.Middle;
storageProgress.VerticalAlignment = VerticalAlignment.Middle;
currentItemHeader.FontWeight = FontWeight.Bold;
bodyArea.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
logo.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
headerInfo.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
inventoryRoot.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
specsRoot.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
itemInfoScroller.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
inventoryView.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
recipesView.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
itemBannerText.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
interactionProgress.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
interactionPanel.Direction = Direction.Horizontal;
interactionsArea.Padding = new Padding(48, 6);
interactionsArea.Spacing = 3;
interactionPanel.Spacing = 3;
interactionProgress.VerticalAlignment = VerticalAlignment.Middle;
interactionLabel.VerticalAlignment = VerticalAlignment.Middle;
interactionPercentage.VerticalAlignment = VerticalAlignment.Middle;
interactionButton.VerticalAlignment = VerticalAlignment.Middle;
interactionError.VerticalAlignment = VerticalAlignment.Middle;
interactionError.HorizontalAlignment = HorizontalAlignment.Center;
interactionButton.HorizontalAlignment = HorizontalAlignment.Center;
itemBannerIcon.IconSize = 56;
itemBannerIcon.VerticalAlignment = VerticalAlignment.Middle;
itemBannerText.VerticalAlignment = VerticalAlignment.Middle;
itemBanner.Spacing = 6;
itemDescription.UseMarkup = true;
itemDescription.WordWrapping = true;
header.Direction = Direction.Horizontal;
bodyArea.Direction = Direction.Horizontal;
currentItemRoot.MinimumSize = new Point(391, 0);
currentItemRoot.MaximumSize = new Point(391, 0);
itemInfoScroller.Spacing = 12;
itemBanner.Direction = Direction.Horizontal;
itemInfoScroller.Padding = 24;
interactionError.FontWeight = FontWeight.SemiBold;
interactionError.SetCustomProperty(WidgetForegrounds.Common);
interactionError.SetCustomProperty(CommonColor.Red);
interactionPanel.Visibility = Visibility.Hidden;
root.ChildWidgets.Add(header);
root.ChildWidgets.Add(bodyArea);
header.ChildWidgets.Add(headerInfo);
header.ChildWidgets.Add(logo);
headerInfo.ChildWidgets.Add(headerTitle);
headerInfo.ChildWidgets.Add(resourcesTable);
bodyArea.ChildWidgets.Add(componentsAndCategories);
bodyArea.ChildWidgets.Add(inventoryRoot);
bodyArea.ChildWidgets.Add(specsRoot);
bodyArea.ChildWidgets.Add(currentItemRoot);
resourcesTable.ChildWidgets.Add(storageLabel);
resourcesTable.ChildWidgets.Add(storageProgress);
resourcesTable.ChildWidgets.Add(storageValue);
resourcesTable.ChildWidgets.Add(skillLabel);
resourcesTable.ChildWidgets.Add(skillProgress);
resourcesTable.ChildWidgets.Add(skillValue);
inventoryRoot.ChildWidgets.Add(inventoryTitle);
inventoryRoot.ChildWidgets.Add(inventoryView);
inventoryView.ChildWidgets.Add(inventoryGrid);
specsRoot.ChildWidgets.Add(recipesTitle);
specsRoot.ChildWidgets.Add(recipesView);
recipesView.ChildWidgets.Add(recipesGrid);
currentItemRoot.ChildWidgets.Add(currentItemHeader);
currentItemRoot.ChildWidgets.Add(itemInfoScroller);
itemInfoScroller.ChildWidgets.Add(itemBanner);
itemBanner.ChildWidgets.Add(itemBannerIcon);
itemBanner.ChildWidgets.Add(itemBannerText);
itemInfoScroller.ChildWidgets.Add(itemDescription);
currentItemRoot.ChildWidgets.Add(interactionsArea);
interactionsArea.ChildWidgets.Add(interactionPanel);
interactionsArea.ChildWidgets.Add(interactionOverlay);
interactionOverlay.ChildWidgets.Add(interactionError);
interactionOverlay.ChildWidgets.Add(interactionButton);
interactionPanel.ChildWidgets.Add(interactionLabel);
interactionPanel.ChildWidgets.Add(interactionProgress);
interactionPanel.ChildWidgets.Add(interactionPercentage);
} }
protected override void Main() protected override void Main()
{ {
recipes.Clear();
recipes.AddRange(SociallyDistantGame.Instance.ContentManager.GetContentOfType<CraftingRecipe>());
RefreshInterface();
} }
private void RefreshInterface()
{
filteredRecipeMap.Clear();
for (var i = 0; i < recipes.Count; i++)
{
var recipe = recipes[i];
if (recipe.Category != inventoryFilter)
continue;
filteredRecipeMap.Add(i);
}
this.inventoryItems.Clear();
this.inventoryItems.AddRange(WorldManager.Instance.World.Inventory.Where(x => x.Owner == 0).Select(item => new InventoryItemModel { WorldItem = item.InstanceId, Recipe = filteredRecipeMap.Select(i=>recipes[i]).FirstOrDefault(y => y.Id == item.RecipeId)! }).Where(x => x.Recipe != null!).ToArray());
var codeFragmentCount = WorldManager.Instance.World.Ledger.GetItemCount(CraftingIngredient.CodeFragments);
var dataFragmentCount = WorldManager.Instance.World.Ledger.GetItemCount(CraftingIngredient.DataFragments);
RefreshSidebar(codeFragmentCount, dataFragmentCount);
RefreshInventory();
RefreshRecipes();
if (currentItem != null)
{
currentItemRoot.Visibility = Visibility.Visible;
currentItemHeader.Text = "Inventory Item";
SetItemInfo(currentItem.Value.Recipe);
interactionError.Visibility = Visibility.Collapsed;
interactionButton.Visibility = Visibility.Visible;
interactionButton.Text = "Disassemble";
interactionLabel.Text = "Disassembling:";
}
else if (currentRecipe != null)
{
currentItemRoot.Visibility = Visibility.Visible;
currentItemHeader.Text = "Crafting Spec";
SetItemInfo(currentRecipe);
interactionError.Visibility = Visibility.Collapsed;
interactionButton.Visibility = Visibility.Visible;
interactionButton.Text = "Craft";
interactionLabel.Text = "Crafting:";
}
else
{
currentItemRoot.Visibility = Visibility.Hidden;
}
}
private void SetItemInfo(CraftingRecipe data)
{
itemBannerIcon.Icon = MaterialIcons.Star;
itemBannerText.Text = $"<b><size=32>{data.Title.ToUpper()}</size></b>{Environment.NewLine}{data.Category}";
itemDescription.Text = data.Description;
}
private void RefreshRecipes()
{
recipesTitle.Text = inventoryFilter.ToString();
var models = new List<FileIconModel>();
for (int i = 0; i < filteredRecipeMap.Count; i++)
{
var recipe = recipes[filteredRecipeMap[i]];
models.Add(new FileIconModel
{
Title = recipe.Title,
Id = i,
OpenHandler = SelectRecipe
});
}
recipesGrid.SetFiles(models);
}
private void RefreshInventory()
{
inventoryGrid.SetFiles(inventoryItems.Select(x => new FileIconModel
{
Title = x.Recipe.Title,
Id = x.WorldItem.Id,
Selected = currentItem?.WorldItem == x.WorldItem,
OpenHandler = SelectItem
}));
}
private void SelectRecipe(int id)
{
currentItem = null;
currentRecipe = recipes[filteredRecipeMap[id]];
RefreshInterface();
}
private void SelectItem(int id)
{
currentRecipe = null;
currentItem = inventoryItems.First(x => x.WorldItem == id);
RefreshInterface();
}
private void RefreshSidebar(int codeFragments, int dataFragments)
{
var builder = new WidgetBuilder();
builder.Begin();
builder.AddSection("Crafting Components", out SectionWidget components);
builder.AddWidget(new ListItemWidget<int> { Title = "Data Fragments", Data = dataFragments }, components);
builder.AddWidget(new ListItemWidget<int> { Title = "Code Fragments", Data = codeFragments }, components);
builder.AddSection("Craftables", out SectionWidget craftables);
foreach (RecipeCategory category in Enum.GetValues<RecipeCategory>())
{
builder.AddWidget(new ListItemWidget<RecipeCategory>() { Title = category.ToString(), Data = category, Callback = SetFilter, Selected = category == inventoryFilter }, craftables);
}
componentsAndCategories.SetWidgets(builder.Build());
}
private void SetFilter(RecipeCategory category)
{
inventoryFilter = category;
RefreshInterface();
}
}
public struct InventoryItemModel
{
public CraftingRecipe Recipe;
public ObjectId WorldItem;
} }

View file

@ -1,3 +1,4 @@
using AcidicGUI.Widgets;
using SociallyDistant.Core.Shell; using SociallyDistant.Core.Shell;
namespace SociallyDistant.UI.Tools.Crafting; namespace SociallyDistant.UI.Tools.Crafting;