workplace init

This commit is contained in:
Ed
2025-05-22 15:29:48 +03:00
parent 7b7e2d0d22
commit 637e38b4b3
13 changed files with 523 additions and 1 deletions

View File

@@ -0,0 +1,34 @@
using Content.Shared._CP14.Workplace;
using Robust.Client.UserInterface;
namespace Content.Client._CP14.Workplace;
public sealed class CP14WorkplaceBoundUserInterface : BoundUserInterface
{
private CP14WorkplaceWindow? _window;
public CP14WorkplaceBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<CP14WorkplaceWindow>();
_window.OnCraft += entry => SendMessage(new CP14WorkplaceCraftMessage(entry.Recipe));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
switch (state)
{
case CP14WorkplaceState recipesState:
_window?.UpdateState(recipesState);
break;
}
}
}

View File

@@ -0,0 +1,14 @@
<Control xmlns="https://spacestation14.io">
<Button Name="Button">
<GridContainer Columns="2">
<EntityPrototypeView Name="View"
Margin="0,0,4,0"
MinSize="48 48"
MaxSize="48 48"
Scale="2,2"
HorizontalAlignment="Center"
VerticalExpand="True"/>
<Label Name="Name"/>
</GridContainer>
</Button>
</Control>

View File

@@ -0,0 +1,53 @@
using Content.Shared._CP14.Workplace;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Workplace;
[GenerateTypedNameReferences]
public sealed partial class CP14WorkplaceRecipeControl : Control
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public event Action<CP14WorkplaceRecipeEntry, EntityPrototype>? OnSelect;
private readonly EntityPrototype _recipePrototype;
private readonly bool _craftable;
public CP14WorkplaceRecipeControl(CP14WorkplaceRecipeEntry entry)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_recipePrototype = _prototype.Index(entry.Recipe);
_craftable = entry.Craftable;
Button.OnPressed += _ => OnSelect?.Invoke(entry, _recipePrototype);
UpdateColor();
//UpdateName();
UpdateView();
}
private void UpdateColor()
{
if (_craftable)
return;
Button.ModulateSelfOverride = Color.FromHex("#302622");
}
private void UpdateName()
{
//var result = _recipePrototype;
//var counter = _recipePrototype.ResultCount > 1 ? $" x{_recipePrototype.ResultCount}" : "";
//Name.Text = $"{Loc.GetString(result.Name)} {counter}" ;
}
private void UpdateView()
{
View.SetPrototype(_recipePrototype);
}
}

View File

@@ -0,0 +1,72 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'cp14-workbench-ui-title'}"
SetSize="700 500"
MinSize="700 300">
<BoxContainer Orientation="Horizontal">
<!-- Main -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
<!-- Product list (left side UI) -->
<BoxContainer MinWidth="350" HorizontalExpand="True" Orientation="Vertical" Margin="0 0 10 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<!-- Search Bar -->
<LineEdit Name="SearchBar" Margin="4" PlaceHolder="Search" HorizontalExpand="True" />
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
</BoxContainer>
<!-- Crafts container -->
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<BoxContainer Name="CraftsContainer" Orientation="Vertical" HorizontalExpand="True" />
</ScrollContainer>
</BoxContainer>
<!-- Craft view (right side UI) -->
<BoxContainer MinWidth="350" Orientation="Vertical" HorizontalExpand="True"
VerticalExpand="True">
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<!-- Background -->
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#41332f" />
</PanelContainer.PanelOverride>
<!-- Content -->
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<!-- Item info -->
<!-- icon -->
<EntityPrototypeView Name="ItemView"
Scale="2,2"
Margin="8"
MinSize="96 96"
MaxSize="96 96"
HorizontalAlignment="Left" />
<!-- name & description -->
<BoxContainer Margin="5 0 0 0" HorizontalExpand="True" VerticalExpand="True"
Orientation="Vertical">
<RichTextLabel Name="ItemName" Margin="5" />
<RichTextLabel Name="ItemDescription" Margin="5" />
</BoxContainer>
<controls:HLine Color="#404040" Thickness="2" Margin="0 5" />
<!-- Required title -->
<Label Margin="5 0 0 0" Text="{Loc 'cp14-workbench-recipe-list'}" />
<!-- Craft requirements content -->
<!-- Added by code -->
<BoxContainer
Margin="5 0 0 0"
Name="ItemRequirements"
Orientation="Vertical" VerticalExpand="True"
HorizontalExpand="True" />
<controls:HLine Color="#404040" Thickness="5" Margin="0 5" />
<!-- Craft button -->
<Button Name="CraftButton" Text="{Loc 'cp14-workbench-craft'}" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,170 @@
using Content.Shared._CP14.Workplace;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Workplace;
[GenerateTypedNameReferences]
public sealed partial class CP14WorkplaceWindow : DefaultWindow
{
private const int AllCategoryId = -1;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ILogManager _log = default!;
public event Action<CP14WorkplaceRecipeEntry>? OnCraft;
private readonly Dictionary<int, LocId> _categories = new();
private CP14WorkplaceState? _cachedState;
private CP14WorkplaceRecipeEntry? _selectedEntry;
private string _searchFilter = string.Empty;
private ISawmill Sawmill { get; init; }
public CP14WorkplaceWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Sawmill = _log.GetSawmill("cp14_workplace_window");
SearchBar.OnTextChanged += OnSearchChanged;
CraftButton.OnPressed += OnCraftPressed;
OptionCategories.OnItemSelected += OnCategoryItemSelected;
}
public void UpdateRecipesVisibility()
{
if (_cachedState is null)
return;
CraftsContainer.RemoveAllChildren();
var recipes = new List<CP14WorkplaceRecipeEntry>();
foreach (var entry in _cachedState.Recipes)
{
if (!_prototype.TryIndex(entry.Recipe, out var indexedEntry))
{
Sawmill.Error($"No recipe prototype {entry.Recipe} retrieved from cache found");
continue;
}
if (!ProcessSearchFilter(indexedEntry))
continue;
if (!ProcessSearchCategoryFilter(indexedEntry))
continue;
recipes.Add(entry);
}
foreach (var recipe in recipes)
{
var control = new CP14WorkplaceRecipeControl(recipe);
control.OnSelect += RecipeSelect;
CraftsContainer.AddChild(control);
}
if (_selectedEntry is not null && !recipes.Contains(_selectedEntry.Value))
RecipeSelectNull();
}
public void UpdateState(CP14WorkplaceState state)
{
_cachedState = state;
_categories.Clear();
OptionCategories.Clear();
OptionCategories.AddItem(Loc.GetString("cp14-recipe-category-all"), AllCategoryId);
var categories = new List<LocId>();
var count = 0;
foreach (var recipe in state.Recipes)
{
}
}
private void OnSearchChanged(LineEdit.LineEditEventArgs _)
{
_searchFilter = SearchBar.Text.Trim().ToLowerInvariant();
UpdateRecipesVisibility();
}
private bool ProcessSearchFilter(EntityPrototype indexedEntry)
{
if (_searchFilter == string.Empty)
return true;
return indexedEntry.Name.Contains(_searchFilter);
}
private bool ProcessSearchCategoryFilter(EntityPrototype indexedEntry)
{
// If we are searching through all categories, we simply skip the current filter
if (OptionCategories.SelectedId == AllCategoryId)
return true;
if (!_categories.TryGetValue(OptionCategories.SelectedId, out var selectedCategory))
{
Sawmill.Error($"Non-existent {OptionCategories.SelectedId} category id selected. Filter skipped");
return true;
}
if (!indexedEntry.Components.TryGetValue(CP14WorkplaceRecipeComponent.CompName, out var compData) || compData.Component is not CP14WorkplaceRecipeComponent recipeComp)
return false;
if (recipeComp.Category is null)
return false;
return recipeComp.Category == selectedCategory;
}
private void OnCraftPressed(BaseButton.ButtonEventArgs _)
{
if (_selectedEntry is null)
return;
OnCraft?.Invoke(_selectedEntry.Value);
}
private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs obj)
{
OptionCategories.SelectId(obj.Id);
UpdateRecipesVisibility();
}
private void RecipeSelect(CP14WorkplaceRecipeEntry entry, EntityPrototype recipe)
{
_selectedEntry = entry;
ItemView.SetPrototype(recipe);
ItemName.Text = recipe.Name;
ItemDescription.Text = recipe.Description;
ItemRequirements.RemoveAllChildren();
//foreach (var requirement in recipe.Requirements)
//{
// ItemRequirements.AddChild(new CP14WorkbenchRequirementControl(requirement));
//}
CraftButton.Disabled = !entry.Craftable;
}
private void RecipeSelectNull()
{
_selectedEntry = null;
ItemView.SetPrototype(null);
ItemName.Text = string.Empty;
ItemDescription.Text = string.Empty;
ItemRequirements.RemoveAllChildren();
CraftButton.Disabled = true;
}
}

View File

@@ -20,7 +20,7 @@ using Robust.Shared.Random;
namespace Content.Server._CP14.Workbench;
public sealed partial class CP14WorkbenchSystem : CP14SharedWorkbenchSystem
public sealed partial class CP14WorkbenchSystem : Shared._CP14.Workplace.CP14SharedWorkbenchSystem
{
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;

View File

@@ -0,0 +1,65 @@
using Content.Shared._CP14.Workplace;
using Content.Shared.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Workplace;
public sealed partial class CP14WorkplaceSystem : CP14SharedWorkbenchSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypeReload);
SubscribeLocalEvent<CP14WorkplaceComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CP14WorkplaceComponent, BeforeActivatableUIOpenEvent>(OnBeforeUIOpen);
}
private void OnPrototypeReload(PrototypesReloadedEventArgs ev)
{
if (!ev.WasModified<EntityPrototype>())
return;
var query = EntityQueryEnumerator<CP14WorkplaceComponent>();
while (query.MoveNext(out var uid, out var workplace))
{
CacheWorkplaceRecipes((uid, workplace));
}
}
private void OnMapInit(Entity<CP14WorkplaceComponent> ent, ref MapInitEvent args)
{
CacheWorkplaceRecipes(ent);
}
private void OnBeforeUIOpen(Entity<CP14WorkplaceComponent> ent, ref BeforeActivatableUIOpenEvent args)
{
UpdateUIState(ent, args.User);
}
private void UpdateUIState(Entity<CP14WorkplaceComponent> entity, EntityUid user)
{
}
private void CacheWorkplaceRecipes(Entity<CP14WorkplaceComponent> entity)
{
entity.Comp.CachedRecipes.Clear();
var allEnts = _proto.EnumeratePrototypes<EntityPrototype>();
foreach (var recipe in allEnts)
{
if (!recipe.Components.TryGetComponent(CP14WorkplaceRecipeComponent.CompName, out var compData) || compData is not CP14WorkplaceRecipeComponent recipeComp)
continue;
if (!entity.Comp.Tags.Contains(recipeComp.Tag))
continue;
entity.Comp.CachedRecipes.Add(recipe);
}
}
}

View File

@@ -0,0 +1,5 @@
namespace Content.Shared._CP14.Workplace;
public abstract partial class CP14SharedWorkbenchSystem : EntitySystem
{
}

View File

@@ -0,0 +1,24 @@
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Workplace;
/// <summary>
///
/// </summary>
[RegisterComponent]
public sealed partial class CP14WorkplaceComponent : Component
{
/// <summary>
/// Recipes matching these tags will be available for use on this workplace
/// </summary>
[DataField]
public HashSet<ProtoId<TagPrototype>> Tags = new();
/// <summary>
/// The cached list of recipes available for crafting at this workstation.
/// Cached when initializing a workstation or reloading prototypes
/// </summary>
[DataField]
public HashSet<EntProtoId> CachedRecipes = new();
}

View File

@@ -0,0 +1,25 @@
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Workplace;
/// <summary>
///
/// </summary>
[RegisterComponent]
public sealed partial class CP14WorkplaceRecipeComponent : Component
{
public const string CompName = "CP14WorkplaceRecipe";
/// <summary>
/// Recipes matching these tags will be available for use on this workplace
/// </summary>
[DataField(required: true)]
public ProtoId<TagPrototype> Tag;
/// <summary>
/// Recipes will be organized by category if LocId matches
/// </summary>
[DataField]
public LocId? Category;
}

View File

@@ -0,0 +1,28 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Workplace;
[Serializable, NetSerializable]
public enum CP14WorkplaceUiKey
{
Key,
}
[Serializable, NetSerializable]
public sealed class CP14WorkplaceCraftMessage(EntProtoId recipe) : BoundUserInterfaceMessage
{
public readonly EntProtoId Recipe = recipe;
}
[Serializable, NetSerializable]
public sealed class CP14WorkplaceState(List<CP14WorkplaceRecipeEntry> recipes) : BoundUserInterfaceState
{
public readonly List<CP14WorkplaceRecipeEntry> Recipes = recipes;
}
[Serializable, NetSerializable]
public readonly record struct CP14WorkplaceRecipeEntry(
EntProtoId Recipe,
bool Craftable
);

View File

@@ -0,0 +1,26 @@
- type: entity
parent:
- BaseStructure
id: CP14BaseWorkplace
categories: [ ForkFiltered ]
abstract: true
components:
- type: Sprite
snapCardinals: true
sprite: _CP14/Structures/Furniture/workbench.rsi
state: filler
- type: Icon
sprite: _CP14/Structures/Furniture/workbench.rsi
state: filler
- type: ActivatableUI
key: enum.CP14WorkplaceUiKey.Key
requiresComplex: true
singleUser: true
- type: Climbable
- type: Clickable
- type: InteractionOutline
- type: PlaceableSurface
- type: UserInterface
interfaces:
enum.CP14WorkplaceUiKey.Key:
type: CP14WorkplaceBoundUserInterface

View File

@@ -0,0 +1,6 @@
- type: entity
id: CP14BaseWorkplaceRecipe
abstract: true
components:
- type: CP14WorkplaceRecipe
tag: CP14RecipeMeltingFurnace