Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Content.Client/_Goobstation/Factory/ConstructorSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared._Goobstation.Factory;

namespace Content.Client._Goobstation.Factory;

public sealed class ConstructorSystem : SharedConstructorSystem;
5 changes: 5 additions & 0 deletions Content.Client/_Goobstation/Factory/InteractorSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Content.Shared._Goobstation.Factory;

namespace Content.Client._Goobstation.Factory;

public sealed class InteractorSystem : SharedInteractorSystem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Content.Shared._Goobstation.Factory;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;

namespace Content.Client._Goobstation.Factory;

/// <summary>
/// Animations robotic arm's arm layer swinging.
/// Can't be done with engine AnimationPlayer as it can't animate individual layers.
/// </summary>
public sealed class RoboticArmAnimationSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;

public override void FrameUpdate(float frameTime)
{
var query = EntityQueryEnumerator<RoboticArmComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.ItemSlot == null)
continue;

if (comp.NextMove is {} nextMove)
Animate((uid, comp), nextMove);
else
Reset((uid, comp));
}
}

private void Animate(Entity<RoboticArmComponent> ent, TimeSpan nextMove)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;

var started = nextMove - ent.Comp.MoveDelay;
// 0-1 unless something weird happens
var progress = (_timing.CurTime - started) / ent.Comp.MoveDelay;
if (!ent.Comp.HasItem) // returning to the resting position when emptied
progress = 1f - progress;
else if (progress > 1f) // Mono
progress = 2f - progress;
progress = Math.Clamp(progress, 0f, 1f); // Mono
var angle = Angle.FromDegrees(progress * 180f);
sprite.LayerSetRotation(RoboticArmLayers.Arm, angle);
}

private void Reset(Entity<RoboticArmComponent> ent)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;

var angle = ent.Comp.HasItem ? new Angle(Math.PI) : Angle.Zero;
sprite.LayerSetRotation(RoboticArmLayers.Arm, angle);
}
}
186 changes: 186 additions & 0 deletions Content.Client/_Goobstation/Factory/UI/ConstructorBUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
using Content.Client.Construction;
using Content.Client.Construction.UI;
using Content.Shared._Goobstation.Factory;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using System.Linq;

namespace Content.Client._Goobstation.Factory.UI;

public sealed class ConstructorBUI : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly ConstructionSystem _construction;
private readonly EntityWhitelistSystem _whitelist;
private readonly SpriteSystem _sprite;

private ConstructionMenu? _menu;
private string? _id;
private List<ConstructionMenu.ConstructionMenuListData> _recipes = new();
private readonly LocId _favoriteCatName = "construction-category-favorites";
private readonly LocId _forAllCategoryName = "construction-category-all";

public ConstructorBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_construction = EntMan.System<ConstructionSystem>();
_whitelist = EntMan.System<EntityWhitelistSystem>();
_sprite = EntMan.System<SpriteSystem>();

_id = EntMan.GetComponentOrNull<ConstructorComponent>(owner)?.Construction;
}

protected override void Open()
{
base.Open();

// god BLESS whoever made construction ui for having it so decoupled <3
_menu = this.CreateWindow<ConstructionMenu>();
PopulateCategories();
PopulateRecipes(string.Empty, string.Empty);
_menu.PopulateRecipes += (_, args) => PopulateRecipes(args.Item1, args.Item2);
_menu.RecipeSelected += (_, item) =>
{
_menu.ClearRecipeInfo();
if (item != null && item.Prototype != null)
{
_id = item.Prototype.ID;
_menu.SetRecipeInfo(item.Prototype.Name ?? "", item.Prototype.Description ?? "", item?.TargetPrototype,
item!.Prototype.Type != ConstructionType.Item, true); // TODO: favourites

GenerateStepList(item.Prototype);
}
else
{
_id = null;
}
};
_menu.BuildButtonToggled += (_, _) =>
{
SendPredictedMessage(new ConstructorSetProtoMessage(_id));
_menu.Close();
};
}

private void PopulateCategories(string? selected = null)
{
if (_menu is not {} menu)
return;

var categories = new HashSet<string>();

foreach (var prototype in _proto.EnumeratePrototypes<ConstructionPrototype>())
{
var category = prototype.Category;

if (!string.IsNullOrEmpty(category))
categories.Add(category);
}

var categoriesArray = new string[categories.Count + 1];

// hard-coded to show all recipes
var idx = 0;
categoriesArray[idx++] = _forAllCategoryName;

foreach (var cat in categories.OrderBy(Loc.GetString))
{
categoriesArray[idx++] = cat;
}

menu.OptionCategories.Clear();

for (var i = 0; i < categoriesArray.Length; i++)
{
menu.OptionCategories.AddItem(Loc.GetString(categoriesArray[i]), i);

if (!string.IsNullOrEmpty(selected) && selected == categoriesArray[i])
menu.OptionCategories.SelectId(i);
}

menu.Categories = categoriesArray;
}

// copypasted and optimised from ConstructionMenuPresenter
private void PopulateRecipes(string search, string category)
{
if (PlayerManager.LocalEntity is not { } user
|| _menu is not { } menu)
return;

search = search.Trim().ToLowerInvariant();
var searching = !string.IsNullOrEmpty(search);
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;

_recipes.Clear();
foreach (var recipe in _proto.EnumeratePrototypes<ConstructionPrototype>())
{
if (recipe.Hide)
continue;

if (_whitelist.IsWhitelistFail(recipe.EntityWhitelist, user))
continue;

if (searching
&& recipe.Name != null
&& !recipe.Name.ToLowerInvariant().Contains(search))
continue;

if (!isEmptyCategory)
{
// TODO: when favourites get sent from server do this
// currently its specific to the G menu
//if (!_favoritedRecipes.Contains(recipe))
if (category == _favoriteCatName)
continue;
else if (recipe.Category != category)
continue;
}

if (!_construction!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
continue;

if (!_proto.TryIndex(targetProtoId, out EntityPrototype? proto))
continue;

_recipes.Add(new(recipe, proto));
}

_recipes.Sort((a, b) => string.Compare(a.Prototype.Name, b.Prototype.Name, StringComparison.InvariantCulture));

var recipesList = menu.Recipes;
recipesList.PopulateList(_recipes);

menu.RecipesGridScrollContainer.Visible = false;
menu.Recipes.Visible = true;
}

private void GenerateStepList(ConstructionPrototype proto)
{
if (_construction.GetGuide(proto) is not { } guide
|| _menu is not { } menu)
return;

var list = menu.RecipeStepList;
foreach (var entry in guide.Entries)
{
var text = entry.Arguments != null
? Loc.GetString(entry.Localization, entry.Arguments)
: Loc.GetString(entry.Localization);

if (entry.EntryNumber is { } number)
text = Loc.GetString("construction-presenter-step-wrapper",
("step-number", number), ("text", text));

// The padding needs to be applied regardless of text length... (See PadLeft documentation)
text = text.PadLeft(text.Length + entry.Padding);

var icon = entry.Icon != null ? _sprite.Frame0(entry.Icon) : Texture.Transparent;
list.AddItem(text, icon, false);
}
}
}
22 changes: 22 additions & 0 deletions Content.Client/_Goobstation/Factory/UI/LabelFilterBUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Content.Shared._Goobstation.Factory.Filters;
using Robust.Client.UserInterface;

namespace Content.Client._Goobstation.Factory.UI;

public sealed class LabelFilterBUI : BoundUserInterface
{
private LabelFilterWindow? _window;

public LabelFilterBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}

protected override void Open()
{
base.Open();

_window = this.CreateWindow<LabelFilterWindow>();
_window.SetEntity(Owner);
_window.OnSetLabel += label => SendPredictedMessage(new LabelFilterSetLabelMessage(label));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="using:Content.Client.UserInterface.Controls"
Title="{Loc 'label-filter-window-title'}"
MinSize="300 100">
<LineEdit Name="LabelEdit" PlaceHolder="{Loc 'label-filter-placeholder'}"/>
</controls:FancyWindow>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Content.Client.UserInterface.Controls;
using Content.Shared._Goobstation.Factory.Filters;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;

namespace Content.Client._Goobstation.Factory.UI;

[GenerateTypedNameReferences]
public sealed partial class LabelFilterWindow : FancyWindow
{
[Dependency] private readonly EntityManager _entMan = default!;

public event Action<string>? OnSetLabel;

public LabelFilterWindow()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);

LabelEdit.OnTextChanged += _ => OnSetLabel?.Invoke(LabelEdit.Text);
}

public void SetEntity(EntityUid uid)
{
if (!_entMan.TryGetComponent<LabelFilterComponent>(uid, out var comp))
return;

var max = comp.MaxLength;
LabelEdit.IsValid = label => label.Length < max;
LabelEdit.Text = comp.Label;
}
}
23 changes: 23 additions & 0 deletions Content.Client/_Goobstation/Factory/UI/NameFilterBUI.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Content.Shared._Goobstation.Factory.Filters;
using Robust.Client.UserInterface;

namespace Content.Client._Goobstation.Factory.UI;

public sealed class NameFilterBUI : BoundUserInterface
{
private NameFilterWindow? _window;

public NameFilterBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}

protected override void Open()
{
base.Open();

_window = this.CreateWindow<NameFilterWindow>();
_window.SetEntity(Owner);
_window.OnSetName += name => SendPredictedMessage(new NameFilterSetNameMessage(name));
_window.OnSetMode += mode => SendPredictedMessage(new NameFilterSetModeMessage(mode));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="using:Content.Client.UserInterface.Controls"
Title="{Loc 'name-filter-window-title'}"
MinSize="350 100">
<BoxContainer Orientation="Horizontal">
<OptionButton Name="ModeButton" MaxHeight="50"/>
<LineEdit Name="NameEdit" HorizontalExpand="True"/>
</BoxContainer>
</controls:FancyWindow>
45 changes: 45 additions & 0 deletions Content.Client/_Goobstation/Factory/UI/NameFilterWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Content.Client.UserInterface.Controls;
using Content.Shared._Goobstation.Factory.Filters;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;

namespace Content.Client._Goobstation.Factory.UI;

[GenerateTypedNameReferences]
public sealed partial class NameFilterWindow : FancyWindow
{
[Dependency] private readonly EntityManager _entMan = default!;

public event Action<string>? OnSetName;
public event Action<NameFilterMode>? OnSetMode;

public NameFilterWindow()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);

foreach (var mode in Enum.GetValues<NameFilterMode>())
{
ModeButton.AddItem(Loc.GetString($"name-filter-mode-{mode}"), (int) mode);
}

ModeButton.OnItemSelected += args =>
{
ModeButton.SelectId(args.Id);
OnSetMode?.Invoke((NameFilterMode) args.Id);
};

NameEdit.OnTextChanged += _ => OnSetName?.Invoke(NameEdit.Text);
}

public void SetEntity(EntityUid uid)
{
if (!_entMan.TryGetComponent<NameFilterComponent>(uid, out var comp))
return;

ModeButton.SelectId((int) comp.Mode);
var max = comp.MaxLength;
NameEdit.IsValid = name => name.Length < max;
NameEdit.Text = comp.Name;
}
}
Loading
Loading