mirror of
https://gitlab.acidiclight.dev/sociallydistant/sociallydistant.git
synced 2025-01-22 09:31:47 -05:00
Implement skeletal crafting UI
Signed-off-by: Ritchie Frodomar <alkalinethunder@gmail.com>
This commit is contained in:
parent
935007c3f5
commit
0809f86ca9
20 changed files with 700 additions and 8 deletions
|
@ -8,6 +8,7 @@ namespace AcidicGUI.VisualStyles;
|
|||
|
||||
internal sealed class FallbackVisualStyle : IVisualStyle
|
||||
{
|
||||
public int ProgressBarHeight => 6;
|
||||
public int SliderThickness => 12;
|
||||
public Point ToggleSize => new Point(18, 18);
|
||||
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);
|
||||
}
|
||||
|
||||
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(
|
||||
Toggle toggle,
|
||||
GeometryHelper geometry,
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace AcidicGUI.VisualStyles;
|
|||
|
||||
public interface IVisualStyle : IFontFamilyProvider
|
||||
{
|
||||
int ProgressBarHeight { get; }
|
||||
int SliderThickness { get; }
|
||||
Point ToggleSize { get; }
|
||||
Point SwitchSize { get; }
|
||||
|
@ -41,6 +42,8 @@ public interface IVisualStyle : IFontFamilyProvider
|
|||
bool isChecked
|
||||
);
|
||||
|
||||
void DrawProgressBar(Widget widget, GeometryHelper geometry, float fillPercentage);
|
||||
|
||||
void DrawToggleSwitch(
|
||||
Toggle toggle,
|
||||
GeometryHelper geometry,
|
||||
|
|
29
src/AcidicGUI/Widgets/ProgressBar.cs
Normal file
29
src/AcidicGUI/Widgets/ProgressBar.cs
Normal 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);
|
||||
}
|
||||
}
|
128
src/AcidicGUI/Widgets/TablePanel.cs
Normal file
128
src/AcidicGUI/Widgets/TablePanel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,9 +8,22 @@ namespace SociallyDistant.Core.Core
|
|||
|
||||
}
|
||||
|
||||
public enum RecipeCategory
|
||||
{
|
||||
Components,
|
||||
Exploits,
|
||||
Payloads,
|
||||
Attacks,
|
||||
Daemons,
|
||||
ShellExtensions
|
||||
}
|
||||
|
||||
public abstract class CraftingRecipe : IGameContent
|
||||
{
|
||||
public abstract RecipeCategory Category { get; }
|
||||
public abstract string Id { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string Title { get; }
|
||||
|
||||
public abstract IEnumerable<IngredientRequirement> RequiredIngredients { get; }
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace SociallyDistant.Core.Core
|
|||
INarrativeObjectTable<WorldComputerData> Computers { get; }
|
||||
INarrativeObjectTable<WorldInternetServiceProviderData> InternetProviders { get; }
|
||||
INarrativeObjectTable<WorldLocalNetworkData> LocalAreaNetworks { get; }
|
||||
IWorldTable<WorldInventoryItem> Inventory { get; }
|
||||
IWorldTable<WorldNetworkConnection> NetworkConnections { get; }
|
||||
IWorldTable<WorldPortForwardingRule> PortForwardingRules { get; }
|
||||
IWorldTable<WorldCraftedExploitData> CraftedExploits { get; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@
|
|||
SubDocuments = 28,
|
||||
Checkpoints = 29,
|
||||
IngredientLedger = 30,
|
||||
Inventory=31,
|
||||
|
||||
Latest
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ public sealed class CommandAttribute : Attribute
|
|||
|
||||
public string Name => name;
|
||||
|
||||
public bool Cheat { get; set; }
|
||||
|
||||
public CommandAttribute(string name)
|
||||
{
|
||||
this.name = name;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using AcidicGUI.CustomProperties;
|
||||
using AcidicGUI.Layout;
|
||||
using AcidicGUI.ListAdapters;
|
||||
using AcidicGUI.Widgets;
|
||||
|
@ -7,12 +9,13 @@ namespace SociallyDistant.Core.UI.Recycling;
|
|||
|
||||
public sealed class TwoLineListItemWithIcon : Widget
|
||||
{
|
||||
private readonly ListItem root = new();
|
||||
private readonly StackPanel stack = new();
|
||||
private readonly Box icon = new();
|
||||
private readonly StackPanel lines = new();
|
||||
private readonly TextWidget line1 = new();
|
||||
private readonly TextWidget line2 = new();
|
||||
private readonly ListItem root = new();
|
||||
private readonly FlexPanel stack = new();
|
||||
private readonly Box icon = new();
|
||||
private readonly StackPanel lines = new();
|
||||
private readonly TextWidget line1 = new();
|
||||
private readonly TextWidget line2 = new();
|
||||
private readonly Emblem emblem = new();
|
||||
|
||||
public string Line1
|
||||
{
|
||||
|
@ -40,8 +43,32 @@ public sealed class TwoLineListItemWithIcon : Widget
|
|||
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()
|
||||
{
|
||||
emblem.Visibility = Visibility.Collapsed;
|
||||
|
||||
lines.GetCustomProperties<FlexPanelProperties>().Mode = FlexMode.Proportional;
|
||||
|
||||
emblem.VerticalAlignment = VerticalAlignment.Middle;
|
||||
|
||||
line1.WordWrapping = true;
|
||||
line2.WordWrapping = true;
|
||||
line1.UseMarkup = true;
|
||||
|
@ -57,6 +84,7 @@ public sealed class TwoLineListItemWithIcon : Widget
|
|||
stack.ChildWidgets.Add(lines);
|
||||
lines.ChildWidgets.Add(line1);
|
||||
lines.ChildWidgets.Add(line2);
|
||||
stack.ChildWidgets.Add(emblem);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,17 @@ public sealed class ListItemWidgetController<T> : RecyclableWidgetController
|
|||
{
|
||||
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)
|
||||
{
|
||||
Image.Build(listItem.Icon);
|
||||
|
|
|
@ -53,6 +53,7 @@ public class SociallyDistantVisualStyle : IVisualStyle
|
|||
private IFontFamily monospace = null!;
|
||||
private Texture2D? checkboxEmblem;
|
||||
|
||||
public int ProgressBarHeight => 6;
|
||||
public int SliderThickness => 18;
|
||||
public Point ToggleSize => new Point(20, 20);
|
||||
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(
|
||||
Toggle toggle,
|
||||
GeometryHelper geometry,
|
||||
|
|
|
@ -13,6 +13,8 @@ internal sealed class CommandAsset :
|
|||
private readonly CommandAttribute attribute;
|
||||
private readonly Func<IGameContext, ICommandTask> constructor;
|
||||
|
||||
public bool IsCheat => attribute.Cheat;
|
||||
|
||||
public string Name => attribute.Name;
|
||||
|
||||
public CommandAsset(IGameContext context, CommandAttribute attribute, Func<IGameContext, ICommandTask> constructor)
|
||||
|
|
27
src/SociallyDistant/Commands/Cheats/CodingInTheNameOf.cs
Normal file
27
src/SociallyDistant/Commands/Cheats/CodingInTheNameOf.cs
Normal 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;
|
||||
}
|
||||
}
|
27
src/SociallyDistant/Commands/Cheats/KnowledgeIsPower.cs
Normal file
27
src/SociallyDistant/Commands/Cheats/KnowledgeIsPower.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using SociallyDistant.Core.Core;
|
||||
using SociallyDistant.Core.Core.Serialization;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
|
||||
namespace SociallyDistant.Core.WorldData;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace SociallyDistant.Core.WorldData
|
|||
private readonly WorldDataTable<WorldWitnessedObjectData> witnessedObjects;
|
||||
private readonly WorldDataTable<WorldNotificationData> notifications;
|
||||
private readonly NarrativeObjectTable<WorldNewsData> newsArticles;
|
||||
private readonly WorldDataTable<WorldInventoryItem> inventory;
|
||||
|
||||
|
||||
internal IWorldDataObject<ProtectedWorldState> ProtectedWorldData => protectedWorldState;
|
||||
|
@ -44,6 +45,9 @@ namespace SociallyDistant.Core.WorldData
|
|||
|
||||
public ILedger Ledger => ledger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWorldTable<WorldInventoryItem> Inventory => inventory;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWorldDataObject<GlobalWorldData> GlobalWorldState => globalWorldState;
|
||||
|
||||
|
@ -160,7 +164,7 @@ namespace SociallyDistant.Core.WorldData
|
|||
witnessedObjects = new WorldDataTable<WorldWitnessedObjectData>(instanceIdGenerator, eventDispatcher);
|
||||
notifications = new WorldDataTable<WorldNotificationData>(instanceIdGenerator, eventDispatcher);
|
||||
newsArticles = new NarrativeObjectTable<WorldNewsData>(instanceIdGenerator, eventDispatcher);
|
||||
|
||||
inventory = new WorldDataTable<WorldInventoryItem>(instanceIdGenerator, eventDispatcher);
|
||||
}
|
||||
|
||||
public void Serialize(IWorldSerializer serializer)
|
||||
|
@ -196,6 +200,7 @@ namespace SociallyDistant.Core.WorldData
|
|||
witnessedObjects.Serialize(serializer, WorldRevision.MissionFailures);
|
||||
notifications.Serialize(serializer, WorldRevision.Notifications);
|
||||
newsArticles.Serialize(serializer, WorldRevision.Articles);
|
||||
inventory.Serialize(serializer, WorldRevision.Inventory);
|
||||
|
||||
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.
|
||||
// This ensures proper handling of deleting objects that depend on other objects.
|
||||
inventory.Clear();
|
||||
newsArticles.Clear();
|
||||
notifications.Clear();
|
||||
witnessedObjects.Clear();
|
||||
|
|
28
src/SociallyDistant/Recipes/DataScraper.cs
Normal file
28
src/SociallyDistant/Recipes/DataScraper.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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.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;
|
||||
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using AcidicGUI.Widgets;
|
||||
using SociallyDistant.Core.Shell;
|
||||
|
||||
namespace SociallyDistant.UI.Tools.Crafting;
|
||||
|
|
Loading…
Reference in a new issue