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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Content.Shared._DEN.Containers.EntitySystems;
using Content.Shared._DEN.Containers.Events;

namespace Content.Client._DEN.Containers.EntitySystems;

public sealed class ContainerSelectionSystem : SharedContainerSelectionSystem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Content.Client._DEN.Containers.EntitySystems;
using Content.Shared._DEN.Containers.Components;
using Content.Shared._DEN.Containers.Events;
using Content.Shared.EntityTable;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;

namespace Content.Client._DEN.Containers.UI;

[UsedImplicitly]
public sealed class ContainerSelectionBoundUserInterface : BoundUserInterface
{
private ContainerSelectionWindow? _window;

public ContainerSelectionBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}

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

if (!EntMan.TryGetComponent<EntityTableContainerSelectionComponent>(Owner, out var entityTableSelectComp))
return;

_window = this.CreateWindow<ContainerSelectionWindow>();
var controls = ConvertToControls(entityTableSelectComp);
_window.SetControls(controls);
_window.OpenCentered();

}

private IEnumerable<ContainerSelectionControl> ConvertToControls(EntityTableContainerSelectionComponent entityTableSelectComp)
{
var containers = new ContainerSelectionControl[entityTableSelectComp.Selections.Count];
var containerIndex = 0;

// Create a ContainerSelectionControl for each of the possible selections.
foreach (var selection in entityTableSelectComp.Selections)
{
var selectionControl = new ContainerSelectionControl();
selectionControl.SetData(EntMan, selection.SelectionName, selection.Containers);
var curIndex = containerIndex;
selectionControl.ChooseButton.OnPressed += _ => MakeSelection(curIndex);

containers[containerIndex++] = selectionControl;
}

return containers;
}

private void MakeSelection(int index)
{
SendMessage(new ContainerSelectionMessage(index));
_window?.Close();
}
}
27 changes: 27 additions & 0 deletions Content.Client/_DEN/Containers/UI/ContainerSelectionControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<PanelContainer Margin="4">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#2F2F2F" />
</PanelContainer.PanelOverride>

<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="2">
<!-- Set label text in C# -->
<Label Name="HeaderLabel" HorizontalExpand="True" Margin="4" />
<Button Name="ChooseButton" Text="{Loc container-selection-ui-choose-button}" Access="Public"/>
</BoxContainer>

<Collapsible Orientation="Vertical" HorizontalExpand="True" Margin="4">
<CollapsibleHeading Title="{Loc 'container-selection-ui-contents'}" />
<CollapsibleBody HorizontalExpand="True" Margin="6">
<GridContainer Name="EntityGrid" MaxGridWidth="300">
<!-- Populated in C# -->
</GridContainer>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</Control>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Content.Shared.EntityTable;
using Content.Shared.EntityTable.EntitySelectors;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;

namespace Content.Client._DEN.Containers.UI;

[GenerateTypedNameReferences]
public sealed partial class ContainerSelectionControl : Control
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;

public ContainerSelectionControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}

/// <summary>
/// Set the display data used by this control.
/// </summary>
/// <param name="entMan">Entity manager (used for retrieving the list of spqwns from EntityTableSelectors)</param>
/// <param name="selectionName">The localization string for the selection category.</param>
/// <param name="tables">The map of containers to EntityTableSelectors for this selection, used to display the contained entities.</param>
public void SetData(IEntityManager entMan, LocId selectionName, Dictionary<string, EntityTableSelector> tables)
{
HeaderLabel.Text = Loc.GetString(selectionName);

foreach (var entContainer in tables)
{
var ctx = new EntityTableContext();
foreach (var (proto, _) in entContainer.Value.ListSpawns(entMan, _prototypeManager, ctx))
{
var protoView = new ContainerSelectionEntityView();
protoView.SetEntityPrototype(proto);

EntityGrid.AddChild(protoView);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Margin="4">
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#4f4f4f" />
</PanelContainer.PanelOverride>
<EntityPrototypeView Name="EntityView" SetSize="32 32" Stretch="Fill" Scale="2 2" />
</PanelContainer>
</Control>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;

namespace Content.Client._DEN.Containers.UI;

[GenerateTypedNameReferences]
public sealed partial class ContainerSelectionEntityView : Control
{
public ContainerSelectionEntityView()
{
RobustXamlLoader.Load(this);
}

/// <summary>
/// Sets the displayed prototype.
/// </summary>
/// <param name="proto">The prototype to display.</param>
public void SetEntityPrototype(EntProtoId proto)
{
EntityView.SetPrototype(proto);
}
}
12 changes: 12 additions & 0 deletions Content.Client/_DEN/Containers/UI/ContainerSelectionWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'container-selection-ui-title'}"
MinWidth="350"
MinHeight="500">
<BoxContainer Orientation="Vertical">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="False" VScrollEnabled="True">
<BoxContainer Name="Options" HorizontalExpand="True" Orientation="Vertical">
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</controls:FancyWindow>
26 changes: 26 additions & 0 deletions Content.Client/_DEN/Containers/UI/ContainerSelectionWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace Content.Client._DEN.Containers.UI;

[GenerateTypedNameReferences]
public sealed partial class ContainerSelectionWindow : FancyWindow
{
public ContainerSelectionWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}

public void SetControls(IEnumerable<ContainerSelectionControl> controls)
{
// Remove any old controls to prevent duplicates.
Options.RemoveAllChildren();
foreach (var control in controls)
{
Options.AddChild(control);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Linq;
using System.Numerics;
using Content.Shared._DEN.Containers.Components;
using Content.Shared._DEN.Containers.EntitySystems;
using Content.Shared._DEN.Containers.Events;
using Content.Shared.ActionBlocker;
using Content.Shared.Destructible;
using Content.Shared.EntityTable;
using Content.Shared.EntityTable.EntitySelectors;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Random;

namespace Content.Server._DEN.Containers.EntitySystems;

public sealed partial class ContainerSelectionSystem : SharedContainerSelectionSystem
{
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly EntityTableSystem _entityTable = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<EntityTableContainerSelectionComponent, ContainerSelectionMessage>(OnContainerSelectionMessage);
SubscribeLocalEvent<EntityTableContainerSelectionComponent, DestructionEventArgs>(OnDestruction,
before: [typeof(SharedStorageSystem)]);
}

private void OnContainerSelectionMessage(Entity<EntityTableContainerSelectionComponent> ent, ref ContainerSelectionMessage message)
{
// Can the user even reach the container anymore?
if (!_blockerSystem.CanInteract(message.Actor, ent))
return;

// Don't allow invalid selections.
if (ent.Comp.Selections.Count < message.SelectionIndex)
return;

var selection = ent.Comp.Selections[message.SelectionIndex];
OnSelectionMade(ent, selection);
}

private void OnDestruction(Entity<EntityTableContainerSelectionComponent> ent,
ref DestructionEventArgs args)
{
// If no selection has been made but our container is destroyed, populate the contents with one of the choices
// at random so that the container still has everything someone breaking it open would expect it to have.
// There's no reasonable way to provide a selection UI and option on destruction, since it's an instant event
// that might not even have a player nearby, so random is the best they get.
OnSelectionMade(ent, _random.Pick(ent.Comp.Selections));
}

private void OnSelectionMade(Entity<EntityTableContainerSelectionComponent> ent,
ContainerSelectionEntry selection)
{
if (TerminatingOrDeleted(ent) || !Exists(ent))
return;

// This selection component has already delivered its goods, bail.
if (ent.Comp.SelectionMade)
return;

if (!TryComp(ent, out ContainerManagerComponent? containerComp))
return;

var xform = Transform(ent);
var coords = new EntityCoordinates(ent, Vector2.Zero);

foreach (var (containerId, table) in selection.Containers)
{
SpawnTableInTarget(ent, containerComp, xform, containerId, table, coords);
}

// Close the UI, mark the selection as made, and let all the clients know so they stop updating the UI.
_uiSystem.CloseUi(ent.Owner, ContainerSelectionUiKey.Key);
ent.Comp.SelectionMade = true;
Dirty(ent, ent.Comp);
}

private void SpawnTableInTarget(EntityUid target,
ContainerManagerComponent containerComp,
TransformComponent xform,
string containerId,
EntityTableSelector table,
EntityCoordinates coords)
{
// Does our target container actually exist?
if (!_containers.TryGetContainer(target, containerId, out var container, containerComp))
{
Log.Error(
$"Entity {ToPrettyString(target)} with a {nameof(EntityTableContainerSelectionComponent)} is missing a container ({containerId}).");
return;
}

// Get the contents we're filling with.
var spawns = _entityTable.GetSpawns(table);
foreach (var proto in spawns)
{
// Spawn the entity, try inserting it into the container, if we can't, log an error so someone knows
// their entity prototype is overfull and drop it on the ground instead.
var spawn = Spawn(proto, coords);
if (!_containers.Insert(spawn, container, containerXform: xform))
{
var alreadyContained = container.ContainedEntities.Count > 0
? string.Join("\n", container.ContainedEntities.Select(e => $"\t - {ToPrettyString(e)}"))
: "< empty >";
Log.Error(
$"Entity {ToPrettyString(target)} with a {nameof(EntityTableContainerSelectionComponent)} failed to insert an entity: {ToPrettyString(spawn)}.\nCurrent contents:\n{alreadyContained}");
_transform.AttachToGridOrMap(spawn);
break;
}
}
}
}
7 changes: 6 additions & 1 deletion Content.Shared/EntityTable/EntitySelectors/EntSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ protected override IEnumerable<EntProtoId> GetSpawnsImplementation(System.Random

protected override IEnumerable<(EntProtoId spawn, double)> ListSpawnsImplementation(IEntityManager entMan, IPrototypeManager proto, EntityTableContext ctx)
{
yield return (Id, 1f);
// DEN: Return an accurate count.
var num = Amount.Maximum();
for (var i = 0; i < num; i++)
{
yield return (Id, 1f);
}
}

protected override IEnumerable<(EntProtoId spawn, double)> AverageSpawnsImplementation(IEntityManager entMan, IPrototypeManager proto, EntityTableContext ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ public override float Average()

return Trials * Chance;
}

// DEN: Maximum allows for accurate ListSpawns
public override float Maximum()
{
return Trials;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ public override float Average()
{
return Value;
}

// DEN: Used for accurate ListSpawns
public override float Maximum()
{
return Value;
}
}
7 changes: 7 additions & 0 deletions Content.Shared/EntityTable/ValueSelector/NumberSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ public abstract partial class NumberSelector
/// </summary>
/// <returns>The average amount of occurrences</returns>
public abstract float Average();

// DEN: Used for accurate ListSpawns results
/// <summary>
/// Maximum number of occurrences
/// </summary>
/// <returns>The maximum number of occurrences</returns>
public abstract float Maximum();
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ public override float Average()
{
return (Range.X + Range.Y) / 2f;
}

// DEN: For accurate ListSpawns results.
public override float Maximum()
{
return Range.Y;
}
}
Loading
Loading