v245.0.0 (and Storage UI V2) (#1799)

Contains:

- Storage UI v2, required for removing DeferredClose.
- Stock market refactor (mostly some basic changes to stock market,
didn't want to make a whole other PR for it)
- Make guidebook remember where you left off
- Any other PRs are purely for fixing issues related to the above PRs or
the engine update.

🆑
- add: Ported Storage UI v2.
- tweak: The guidebook will now remember where you left off.

---------

Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: 12rabbits <53499656+12rabbits@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
Co-authored-by: gluesniffler <159397573+gluesniffler@users.noreply.github.com>
Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
(cherry picked from commit 3c37ff1c48637d1cdf8bc3c6b1412dad338ea205)
This commit is contained in:
sleepyyapril
2025-02-15 18:21:57 -04:00
committed by Spatison
parent 4b58e3820b
commit 1e423cabf9
34 changed files with 982 additions and 637 deletions

View File

@@ -36,7 +36,7 @@ public sealed partial class StockTradingUi : UIFragment
}
}
private static void SendStockTradingUiMessage(StockTradingUiAction action, int company, float amount, BoundUserInterface userInterface)
private static void SendStockTradingUiMessage(StockTradingUiAction action, int company, int amount, BoundUserInterface userInterface)
{
var newsMessage = new StockTradingUiMessageEvent(action, company, amount);
var message = new CartridgeUiMessage(newsMessage);

View File

@@ -14,8 +14,8 @@ public sealed partial class StockTradingUiFragment : BoxContainer
private readonly Dictionary<int, CompanyEntry> _companyEntries = new();
// Event handlers for the parent UI
public event Action<int, float>? OnBuyButtonPressed;
public event Action<int, float>? OnSellButtonPressed;
public event Action<int, int>? OnBuyButtonPressed;
public event Action<int, int>? OnSellButtonPressed;
// Define colors
public static readonly Color PositiveColor = Color.FromHex("#00ff00"); // Green
@@ -70,8 +70,8 @@ public sealed partial class StockTradingUiFragment : BoxContainer
public CompanyEntry(int companyIndex,
string displayName,
Action<int, float>? onBuyPressed,
Action<int, float>? onSellPressed)
Action<int, int>? onBuyPressed,
Action<int, int>? onSellPressed)
{
Container = new BoxContainer
{
@@ -216,13 +216,13 @@ public sealed partial class StockTradingUiFragment : BoxContainer
// Button click events
_buyButton.OnPressed += _ =>
{
if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0)
if (int.TryParse(_amountEdit.Text, out var amount) && amount > 0)
onBuyPressed?.Invoke(companyIndex, amount);
};
_sellButton.OnPressed += _ =>
{
if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0)
if (int.TryParse(_amountEdit.Text, out var amount) && amount > 0)
onSellPressed?.Invoke(companyIndex, amount);
};
@@ -235,7 +235,7 @@ public sealed partial class StockTradingUiFragment : BoxContainer
};
}
public void Update(StockCompanyStruct company, int ownedStocks)
public void Update(StockCompany company, int ownedStocks)
{
_nameLabel.Text = company.LocalizedDisplayName;
_priceLabel.Text = $"${company.CurrentPrice:F2}";

View File

@@ -2,7 +2,7 @@
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="750 700"
SetSize="850 700"
MinSize="100 200"
Resizable="True"
Title="{Loc 'guidebook-window-title'}">
@@ -21,9 +21,13 @@
Margin="0 5 10 5">
</LineEdit>
</BoxContainer>
<BoxContainer Access="Internal" Name="ReturnContainer" Orientation="Horizontal" HorizontalAlignment="Right" Visible="False">
<Button Name="HomeButton" Text="{Loc 'ui-rules-button-home'}" Margin="0 0 10 0"/>
</BoxContainer>
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
<Control>
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False">
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>

View File

@@ -5,11 +5,13 @@ using Content.Client.UserInterface.ControlExtensions;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
using JetBrains.Annotations;
using Content.Client.UserInterface.Systems.Info;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
namespace Content.Client.Guidebook.Controls;
@@ -21,6 +23,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
private Dictionary<string, GuideEntry> _entries = new();
public ProtoId<GuideEntryPrototype> LastEntry;
public GuidebookWindow()
{
RobustXamlLoader.Load(this);
@@ -37,7 +41,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
{
ShowGuide(entry);
var isRulesEntry = entry.RuleEntry;
ReturnContainer.Visible = isRulesEntry;
HomeButton.OnPressed += _ => ShowGuide(entry);
}
else
ClearSelectedGuide();
}
@@ -66,6 +76,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
}
LastEntry = entry.Id;
}
public void UpdateGuides(
@@ -152,6 +164,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
return null;
}
var rulesProto = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
if (entry.RuleEntry && entry.Id != rulesProto.Id)
return null;
var item = Tree.AddItem(parent);
item.Metadata = entry;
var name = Loc.GetString(entry.Name);

View File

@@ -34,6 +34,8 @@ public class GuideEntry
/// </summary>
[DataField("filterEnabled")] public bool FilterEnabled = default!;
[DataField] public bool RuleEntry;
/// <summary>
/// Priority for sorting top-level guides when shown in a tree / table of contents.
/// If the guide is the child of some other guide, the order simply determined by the order of children in <see cref="Children"/>.

View File

@@ -1,38 +1,80 @@
using Content.Client.Storage.Systems;
using Content.Client.UserInterface.Systems.Storage;
using Content.Client.UserInterface.Systems.Storage.Controls;
using Content.Shared.Storage;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Storage;
[UsedImplicitly]
public sealed class StorageBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Obsolete] public override bool DeferredClose => false;
private readonly StorageSystem _storage;
private StorageWindow? _window;
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
_storage = _entManager.System<StorageSystem>();
}
protected override void Open()
{
base.Open();
if (_entManager.TryGetComponent<StorageComponent>(Owner, out var comp))
_storage.OpenStorageWindow((Owner, comp));
_window = IoCManager.Resolve<IUserInterfaceManager>()
.GetUIController<StorageUIController>()
.CreateStorageWindow(Owner);
if (EntMan.TryGetComponent(Owner, out StorageComponent? storage))
{
_window.UpdateContainer((Owner, storage));
}
_window.OnClose += Close;
_window.FlagDirty();
}
public void Refresh()
{
_window?.FlagDirty();
}
public void Reclaim()
{
if (_window == null)
return;
_window.OnClose -= Close;
_window.Orphan();
_window = null;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
Reclaim();
}
public void Hide()
{
if (_window == null)
return;
_storage.CloseStorageWindow(Owner);
_window.Visible = false;
}
public void Show()
{
if (_window == null)
return;
_window.Visible = true;
}
public void ReOpen()
{
_window?.Orphan();
_window = null;
Open();
}
}

View File

@@ -1,10 +1,11 @@
using System.Linq;
using System.Linq;
using System.Numerics;
using Content.Client.Animations;
using Content.Shared.Hands;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Collections;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Timing;
@@ -13,114 +14,95 @@ namespace Content.Client.Storage.Systems;
public sealed class StorageSystem : SharedStorageSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!;
private readonly List<Entity<StorageComponent>> _openStorages = new();
public int OpenStorageAmount => _openStorages.Count;
public event Action<Entity<StorageComponent>>? StorageUpdated;
public event Action<Entity<StorageComponent>?>? StorageOrderChanged;
private Dictionary<EntityUid, ItemStorageLocation> _oldStoredItems = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
}
public override void UpdateUI(Entity<StorageComponent?> entity)
private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref ComponentHandleState args)
{
if (Resolve(entity.Owner, ref entity.Comp))
StorageUpdated?.Invoke((entity, entity.Comp));
}
if (args.Current is not StorageComponentState state)
return;
public void OpenStorageWindow(Entity<StorageComponent> entity)
{
if (_openStorages.Contains(entity))
component.Grid.Clear();
component.Grid.AddRange(state.Grid);
component.MaxItemSize = state.MaxItemSize;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
_oldStoredItems.Clear();
foreach (var item in component.StoredItems)
{
if (_openStorages.LastOrDefault() == entity)
_oldStoredItems.Add(item.Key, item.Value);
}
component.StoredItems.Clear();
foreach (var (nent, location) in state.StoredItems)
{
var ent = EnsureEntity<StorageComponent>(nent, uid);
component.StoredItems[ent] = location;
}
component.SavedLocations.Clear();
foreach (var loc in state.SavedLocations)
{
component.SavedLocations[loc.Key] = new(loc.Value);
}
var uiDirty = !component.StoredItems.SequenceEqual(_oldStoredItems);
if (uiDirty && UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
storageBui.Refresh();
// Make sure nesting still updated.
var player = _player.LocalEntity;
if (NestedStorage && player != null && ContainerSystem.TryGetContainingContainer((uid, null, null), out var container) &&
UI.TryGetOpenUi<StorageBoundUserInterface>(container.Owner, StorageComponent.StorageUiKey.Key, out var containerBui))
{
CloseStorageWindow((entity, entity.Comp));
containerBui.Hide();
}
else
{
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storageEnt in reverseStorages)
{
if (storageEnt == entity)
break;
CloseStorageBoundUserInterface(storageEnt.Owner);
_openStorages.Remove(entity);
}
storageBui.Show();
}
return;
}
ClearNonParentStorages(entity);
_openStorages.Add(entity);
Entity<StorageComponent>? last = _openStorages.LastOrDefault();
StorageOrderChanged?.Invoke(last);
}
public void CloseStorageWindow(Entity<StorageComponent?> entity)
public override void UpdateUI(Entity<StorageComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
return;
if (!_openStorages.Contains((entity, entity.Comp)))
return;
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storage in reverseStorages)
if (UI.TryGetOpenUi<StorageBoundUserInterface>(entity.Owner, StorageComponent.StorageUiKey.Key, out var sBui))
{
CloseStorageBoundUserInterface(storage.Owner);
_openStorages.Remove(storage);
if (storage.Owner == entity.Owner)
break;
sBui.Refresh();
}
Entity<StorageComponent>? last = null;
if (_openStorages.Any())
last = _openStorages.LastOrDefault();
StorageOrderChanged?.Invoke(last);
}
private void ClearNonParentStorages(EntityUid uid)
protected override void HideStorageWindow(EntityUid uid, EntityUid actor)
{
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storage in reverseStorages)
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
if (storage.Comp.Container.Contains(uid))
break;
CloseStorageBoundUserInterface(storage.Owner);
_openStorages.Remove(storage);
storageBui.Hide();
}
}
private void CloseStorageBoundUserInterface(Entity<UserInterfaceComponent?> entity)
protected override void ShowStorageWindow(EntityUid uid, EntityUid actor)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui)
return;
bui.Close();
}
private void OnShutdown(Entity<StorageComponent> ent, ref ComponentShutdown args)
{
CloseStorageWindow((ent, ent.Comp));
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
storageBui.Show();
}
}
/// <inheritdoc />
@@ -143,7 +125,7 @@ public sealed class StorageSystem : SharedStorageSystem
if (!_timing.IsFirstTimePredicted)
return;
if (finalCoords.InRange(EntityManager, TransformSystem, initialCoords, 0.1f) ||
if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) ||
!Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
{
return;

View File

@@ -4,9 +4,11 @@ using Content.Client.Guidebook;
using Content.Client.Guidebook.Controls;
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Shared.CCVar;
using Content.Shared.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Configuration;
using static Robust.Client.UserInterface.Controls.BaseButton;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
@@ -18,9 +20,11 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
{
[UISystemDependency] private readonly GuidebookSystem _guidebookSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
private GuidebookWindow? _guideWindow;
private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton;
private ProtoId<GuideEntryPrototype>? _lastEntry;
public void OnStateEntered(LobbyState state)
{
@@ -107,6 +111,12 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
{
if (GuidebookButton != null)
GuidebookButton.Pressed = false;
if (_guideWindow != null)
{
_guideWindow.ReturnContainer.Visible = false;
_lastEntry = _guideWindow.LastEntry;
}
}
private void OnWindowOpen()
@@ -162,6 +172,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
}
}
if (selected == null)
{
if (_lastEntry is { } lastEntry && guides.ContainsKey(lastEntry))
{
selected = _lastEntry;
}
else
{
selected = _cfgManager.GetCVar(CCVars.DefaultGuide);
}
}
_guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
// Expand up to depth-2.

View File

@@ -31,13 +31,12 @@ public sealed class HotbarUIController : UIController
ReloadHotbar();
}
public void Setup(HandsContainer handsContainer, StorageContainer storageContainer)
public void Setup(HandsContainer handsContainer)
{
_inventory = UIManager.GetUIController<InventoryUIController>();
_hands = UIManager.GetUIController<HandsUIController>();
_storage = UIManager.GetUIController<StorageUIController>();
_hands.RegisterHandContainer(handsContainer);
_storage.RegisterStorageContainer(storageContainer);
}
public void ReloadHotbar()

View File

@@ -1,7 +1,6 @@
<widgets:HotbarGui
xmlns="https://spacestation14.io"
xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
xmlns:storage="clr-namespace:Content.Client.UserInterface.Systems.Storage.Controls"
xmlns:hands="clr-namespace:Content.Client.UserInterface.Systems.Hands.Controls"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
Name="HotbarInterface"
@@ -13,10 +12,8 @@
<BoxContainer Name="StorageContainer"
Access="Public"
HorizontalAlignment="Center"
HorizontalExpand="True"
Margin="10">
<storage:StorageContainer
Name="StoragePanel"
Visible="False"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Name="Hotbar" HorizontalAlignment="Center">
<inventory:ItemSlotButtonContainer

View File

@@ -15,7 +15,7 @@ public sealed partial class HotbarGui : UIWidget
StatusPanelLeft.SetSide(HandUILocation.Left);
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
hotbarController.Setup(HandContainer, StoragePanel);
hotbarController.Setup(HandContainer);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
}

View File

@@ -59,7 +59,7 @@ public sealed class ItemGridPiece : Control, IEntityControl
Location = location;
Visible = true;
MouseFilter = MouseFilterMode.Pass;
MouseFilter = MouseFilterMode.Stop;
TooltipSupplier = SupplyTooltip;
@@ -105,8 +105,11 @@ public sealed class ItemGridPiece : Control, IEntityControl
return;
}
if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity && _storageController.DraggingGhost != this)
if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity &&
_storageController.DraggingGhost != this)
{
return;
}
var adjustedShape = _entityManager.System<ItemSystem>().GetAdjustedItemShape((Entity, itemComponent), Location.Rotation, Vector2i.Zero);
var boundingGrid = adjustedShape.GetBoundingBox();

View File

@@ -3,7 +3,9 @@ using System.Linq;
using System.Numerics;
using Content.Client.Hands.Systems;
using Content.Client.Items.Systems;
using Content.Client.Storage;
using Content.Client.Storage.Systems;
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Item;
using Content.Shared.Storage;
@@ -11,12 +13,14 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Storage.Controls;
public sealed class StorageContainer : BaseWindow
public sealed class StorageWindow : BaseWindow
{
[Dependency] private readonly IEntityManager _entity = default!;
private readonly StorageUIController _storageController;
@@ -27,6 +31,20 @@ public sealed class StorageContainer : BaseWindow
private readonly GridContainer _backgroundGrid;
private readonly GridContainer _sidebar;
private Control _titleContainer;
private Label _titleLabel;
// Needs to be nullable in case a piece is in default spot.
private readonly Dictionary<EntityUid, (ItemStorageLocation? Loc, ItemGridPiece Control)> _pieces = new();
private readonly List<Control> _controlGrid = new();
private ValueList<EntityUid> _contained = new();
private ValueList<EntityUid> _toRemove = new();
private TextureButton? _backButton;
private bool _isDirty;
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPiecePressed;
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
@@ -51,9 +69,10 @@ public sealed class StorageContainer : BaseWindow
private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
private Texture? _sidebarFatTexture;
public StorageContainer()
public StorageWindow()
{
IoCManager.InjectDependencies(this);
Resizable = false;
_storageController = UserInterfaceManager.GetUIController<StorageUIController>();
@@ -63,6 +82,7 @@ public sealed class StorageContainer : BaseWindow
_sidebar = new GridContainer
{
Name = "SideBar",
HSeparationOverride = 0,
VSeparationOverride = 0,
Columns = 1
@@ -70,21 +90,48 @@ public sealed class StorageContainer : BaseWindow
_pieceGrid = new GridContainer
{
Name = "PieceGrid",
HSeparationOverride = 0,
VSeparationOverride = 0
};
_backgroundGrid = new GridContainer
{
Name = "BackgroundGrid",
HSeparationOverride = 0,
VSeparationOverride = 0
};
_titleLabel = new Label()
{
HorizontalExpand = true,
Name = "StorageLabel",
ClipText = true,
Text = "Dummy",
StyleClasses =
{
"FancyWindowTitle",
}
};
_titleContainer = new PanelContainer()
{
StyleClasses =
{
"WindowHeadingBackground"
},
Children =
{
_titleLabel
}
};
var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
_titleContainer,
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
@@ -130,12 +177,22 @@ public sealed class StorageContainer : BaseWindow
if (entity == null)
return;
if (UserInterfaceManager.GetUIController<StorageUIController>().WindowTitle)
{
_titleLabel.Text = Identity.Name(entity.Value, _entity);
_titleContainer.Visible = true;
}
else
{
_titleContainer.Visible = false;
}
BuildGridRepresentation();
}
private void BuildGridRepresentation()
{
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || !comp.Grid.Any())
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || comp.Grid.Count == 0)
return;
var boundingGrid = comp.Grid.GetBoundingBox();
@@ -144,12 +201,13 @@ public sealed class StorageContainer : BaseWindow
#region Sidebar
_sidebar.Children.Clear();
_sidebar.Rows = boundingGrid.Height + 1;
var rows = boundingGrid.Height + 1;
_sidebar.Rows = rows;
var exitButton = new TextureButton
{
TextureNormal = _entity.System<StorageSystem>().OpenStorageAmount == 1
?_exitTexture
: _backTexture,
Name = "ExitButton",
TextureNormal = _exitTexture,
Scale = new Vector2(2, 2),
};
exitButton.OnPressed += _ =>
@@ -165,8 +223,10 @@ public sealed class StorageContainer : BaseWindow
args.Handle();
}
};
var exitContainer = new BoxContainer
{
Name = "ExitContainer",
Children =
{
new TextureRect
@@ -182,28 +242,70 @@ public sealed class StorageContainer : BaseWindow
}
}
};
_sidebar.AddChild(exitContainer);
for (var i = 0; i < boundingGrid.Height - 1; i++)
var offset = 2;
if (_entity.System<StorageSystem>().NestedStorage && rows > 0)
{
_sidebar.AddChild(new TextureRect
_backButton = new TextureButton
{
Texture = _sidebarMiddleTexture,
TextureScale = new Vector2(2, 2),
});
TextureNormal = _backTexture,
Scale = new Vector2(2, 2),
};
_backButton.OnPressed += _ =>
{
var containerSystem = _entity.System<SharedContainerSystem>();
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
_entity.TryGetComponent(container.Owner, out StorageComponent? storage))
{
Close();
if (_entity.System<SharedUserInterfaceSystem>()
.TryGetOpenUi<StorageBoundUserInterface>(container.Owner,
StorageComponent.StorageUiKey.Key,
out var parentBui))
{
parentBui.Show();
}
}
};
var backContainer = new BoxContainer
{
Name = "ExitContainer",
Children =
{
new TextureRect
{
Texture = rows > 2 ? _sidebarMiddleTexture : _sidebarBottomTexture,
TextureScale = new Vector2(2, 2),
Children =
{
_backButton,
}
}
}
};
_sidebar.AddChild(backContainer);
}
if (boundingGrid.Height > 0)
var fillerRows = rows - offset;
for (var i = 0; i < fillerRows; i++)
{
_sidebar.AddChild(new TextureRect
{
Texture = _sidebarBottomTexture,
Texture = i != (fillerRows - 1) ? _sidebarMiddleTexture : _sidebarBottomTexture,
TextureScale = new Vector2(2, 2),
});
}
#endregion
BuildItemPieces();
FlagDirty();
}
public void BuildBackground()
@@ -240,6 +342,34 @@ public sealed class StorageContainer : BaseWindow
}
}
public void Reclaim(ItemStorageLocation location, ItemGridPiece draggingGhost)
{
draggingGhost.OnPiecePressed += OnPiecePressed;
draggingGhost.OnPieceUnpressed += OnPieceUnpressed;
_pieces[draggingGhost.Entity] = (location, draggingGhost);
draggingGhost.Location = location;
var controlIndex = GetGridIndex(draggingGhost);
_controlGrid[controlIndex].AddChild(draggingGhost);
}
private int GetGridIndex(ItemGridPiece piece)
{
return piece.Location.Position.X + piece.Location.Position.Y * _pieceGrid.Columns;
}
public void FlagDirty()
{
_isDirty = true;
}
public void RemoveGrid(ItemGridPiece control)
{
control.Orphan();
_pieces.Remove(control.Entity);
control.OnPiecePressed -= OnPiecePressed;
control.OnPieceUnpressed -= OnPieceUnpressed;
}
public void BuildItemPieces()
{
if (!_entity.EntityExists(StorageEntity))
@@ -248,65 +378,94 @@ public sealed class StorageContainer : BaseWindow
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp))
return;
if (!storageComp.Grid.Any())
if (storageComp.Grid.Count == 0)
return;
var boundingGrid = storageComp.Grid.GetBoundingBox();
var size = _emptyTexture!.Size * 2;
var containedEntities = storageComp.Container.ContainedEntities.Reverse().ToArray();
_contained.Clear();
_contained.AddRange(storageComp.Container.ContainedEntities.Reverse());
//todo. at some point, we may want to only rebuild the pieces that have actually received new data.
_pieceGrid.RemoveAllChildren();
_pieceGrid.Rows = boundingGrid.Height + 1;
_pieceGrid.Columns = boundingGrid.Width + 1;
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
// Build the grid representation
if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
{
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
_pieceGrid.Rows = boundingGrid.Height + 1;
_pieceGrid.Columns = boundingGrid.Width + 1;
_controlGrid.Clear();
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
{
var control = new Control
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
{
MinSize = size
};
var currentPosition = new Vector2i(x, y);
foreach (var (itemEnt, itemPos) in storageComp.StoredItems)
{
if (itemPos.Position != currentPosition)
continue;
if (_entity.TryGetComponent<ItemComponent>(itemEnt, out var itemEntComponent))
var control = new Control
{
ItemGridPiece gridPiece;
MinSize = size
};
if (_storageController.CurrentlyDragging?.Entity is { } dragging
&& dragging == itemEnt)
{
_storageController.CurrentlyDragging.Orphan();
gridPiece = _storageController.CurrentlyDragging;
}
else
{
gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), itemPos, _entity)
{
MinSize = size,
Marked = Array.IndexOf(containedEntities, itemEnt) switch
{
0 => ItemGridPieceMarks.First,
1 => ItemGridPieceMarks.Second,
_ => null,
}
};
gridPiece.OnPiecePressed += OnPiecePressed;
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
}
_controlGrid.Add(control);
_pieceGrid.AddChild(control);
}
}
}
control.AddChild(gridPiece);
}
_toRemove.Clear();
// Remove entities no longer relevant / Update existing ones
foreach (var (ent, data) in _pieces)
{
if (storageComp.StoredItems.TryGetValue(ent, out var updated))
{
if (data.Loc.Equals(updated))
{
DebugTools.Assert(data.Control.Location == updated);
continue;
}
_pieceGrid.AddChild(control);
// Update
data.Control.Location = updated;
var index = GetGridIndex(data.Control);
data.Control.Orphan();
_controlGrid[index].AddChild(data.Control);
_pieces[ent] = (updated, data.Control);
continue;
}
_toRemove.Add(ent);
}
foreach (var ent in _toRemove)
{
_pieces.Remove(ent, out var data);
data.Control.Orphan();
}
// Add new ones
foreach (var (ent, loc) in storageComp.StoredItems)
{
if (_pieces.TryGetValue(ent, out var existing))
{
DebugTools.Assert(existing.Loc == loc);
continue;
}
if (_entity.TryGetComponent<ItemComponent>(ent, out var itemEntComponent))
{
var gridPiece = new ItemGridPiece((ent, itemEntComponent), loc, _entity)
{
MinSize = size,
Marked = _contained.IndexOf(ent) switch
{
0 => ItemGridPieceMarks.First,
1 => ItemGridPieceMarks.Second,
_ => null,
}
};
gridPiece.OnPiecePressed += OnPiecePressed;
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
var controlIndex = loc.Position.X + loc.Position.Y * (boundingGrid.Width + 1);
_controlGrid[controlIndex].AddChild(gridPiece);
_pieces[ent] = (loc, gridPiece);
}
}
}
@@ -318,6 +477,35 @@ public sealed class StorageContainer : BaseWindow
if (!IsOpen)
return;
if (_isDirty)
{
_isDirty = false;
BuildItemPieces();
}
var containerSystem = _entity.System<SharedContainerSystem>();
if (_backButton != null)
{
if (StorageEntity != null && _entity.System<StorageSystem>().NestedStorage)
{
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
_entity.HasComponent<StorageComponent>(container.Owner))
{
_backButton.Visible = true;
}
else
{
_backButton.Visible = false;
}
}
// Hide the button.
else
{
_backButton.Visible = false;
}
}
var itemSystem = _entity.System<ItemSystem>();
var storageSystem = _entity.System<StorageSystem>();
var handsSystem = _entity.System<HandsSystem>();
@@ -327,7 +515,7 @@ public sealed class StorageContainer : BaseWindow
child.ModulateSelfOverride = Color.FromHex("#222222");
}
if (UserInterfaceManager.CurrentlyHovered is StorageContainer con && con != this)
if (UserInterfaceManager.CurrentlyHovered is StorageWindow con && con != this)
return;
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
@@ -376,7 +564,7 @@ public sealed class StorageContainer : BaseWindow
continue;
float spot = 0;
var marked = new List<Control>();
var marked = new ValueList<Control>();
foreach (var location in locations.Value)
{
@@ -503,14 +691,4 @@ public sealed class StorageContainer : BaseWindow
}
}
}
public override void Close()
{
base.Close();
if (StorageEntity == null)
return;
_entity.System<StorageSystem>().CloseStorageWindow(StorageEntity.Value);
}
}

View File

@@ -2,6 +2,7 @@ using System.Numerics;
using Content.Client.Examine;
using Content.Client.Hands.Systems;
using Content.Client.Interaction;
using Content.Client.Storage;
using Content.Client.Storage.Systems;
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
using Content.Client.UserInterface.Systems.Storage.Controls;
@@ -9,9 +10,9 @@ using Content.Client.Verbs.UI;
using Content.Shared.CCVar;
using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Storage;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
@@ -23,19 +24,28 @@ namespace Content.Client.UserInterface.Systems.Storage;
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
{
/*
* Things are a bit over the shop but essentially
* - Clicking into storagewindow is handled via storagewindow
* - Clicking out of it is via ItemGridPiece
* - Dragging around is handled here
* - Drawing is handled via ItemGridPiece
* - StorageSystem handles any sim stuff around open windows.
*/
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[UISystemDependency] private readonly StorageSystem _storage = default!;
/// <summary>
/// Cached positions for opening nested storage.
/// </summary>
private readonly Dictionary<EntityUid, Vector2> _reservedStorage = new();
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
private StorageContainer? _container;
private Vector2? _lastContainerPosition;
private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
public ItemGridPiece? DraggingGhost;
public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
public Angle DraggingRotation = Angle.Zero;
public bool StaticStorageUIEnabled;
public bool OpaqueStorageWindow;
@@ -43,6 +53,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
public bool IsDragging => _menuDragHelper.IsDragging;
public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
public bool WindowTitle { get; private set; } = false;
public StorageUIController()
{
_menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
@@ -52,106 +64,93 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
base.Initialize();
UIManager.OnScreenChanged += OnScreenChange;
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
_configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
}
private void OnScreenChange((UIScreen? Old, UIScreen? New) obj)
{
// Handle reconnects with hotbargui.
// Essentially HotbarGui / the screen gets loaded AFTER gamestates at the moment (because clientgameticker manually changes it via event)
// and changing this may be a massive change.
// So instead we'll just manually reload it for now.
if (!StaticStorageUIEnabled ||
obj.New == null ||
!EntityManager.TryGetComponent(_player.LocalEntity, out UserInterfaceUserComponent? userComp))
{
return;
}
// UISystemDependency not injected at this point so do it the old fashion way, I love ordering issues.
var uiSystem = EntityManager.System<SharedUserInterfaceSystem>();
foreach (var bui in uiSystem.GetActorUis((_player.LocalEntity.Value, userComp)))
{
if (!uiSystem.TryGetOpenUi<StorageBoundUserInterface>(bui.Entity, StorageComponent.StorageUiKey.Key, out var storageBui))
continue;
storageBui.ReOpen();
}
}
private void OnStorageWindowTitle(bool obj)
{
WindowTitle = obj;
}
private void OnOpaqueWindowChanged(bool obj)
{
OpaqueStorageWindow = obj;
}
private void OnStaticStorageChanged(bool obj)
{
StaticStorageUIEnabled = obj;
}
public StorageWindow CreateStorageWindow(EntityUid uid)
{
var window = new StorageWindow();
window.MouseFilter = Control.MouseFilterMode.Pass;
window.OnPiecePressed += (args, piece) =>
{
OnPiecePressed(args, window, piece);
};
window.OnPieceUnpressed += (args, piece) =>
{
OnPieceUnpressed(args, window, piece);
};
if (StaticStorageUIEnabled)
{
UIManager.GetActiveUIWidgetOrNull<HotbarGui>()?.StorageContainer.AddChild(window);
}
else
{
window.OpenCenteredLeft();
if (_reservedStorage.Remove(uid, out var pos))
{
LayoutContainer.SetPosition(window, pos);
}
}
return window;
}
public void OnSystemLoaded(StorageSystem system)
{
_input.FirstChanceOnKeyEvent += OnMiddleMouse;
system.StorageUpdated += OnStorageUpdated;
system.StorageOrderChanged += OnStorageOrderChanged;
}
public void OnSystemUnloaded(StorageSystem system)
{
_input.FirstChanceOnKeyEvent -= OnMiddleMouse;
system.StorageUpdated -= OnStorageUpdated;
system.StorageOrderChanged -= OnStorageOrderChanged;
}
private void OnStorageOrderChanged(Entity<StorageComponent>? nullEnt)
{
if (_container == null)
return;
if (IsDragging)
_menuDragHelper.EndDrag();
_container.UpdateContainer(nullEnt);
if (nullEnt is not null)
{
// center it if we knock it off screen somehow.
if (!StaticStorageUIEnabled &&
(_lastContainerPosition == null ||
_lastContainerPosition.Value.X < 0 ||
_lastContainerPosition.Value.Y < 0 ||
_lastContainerPosition.Value.X > _ui.WindowRoot.Width ||
_lastContainerPosition.Value.Y > _ui.WindowRoot.Height))
{
_container.OpenCenteredAt(new Vector2(0.5f, 0.75f));
}
else
{
_container.Open();
var pos = !StaticStorageUIEnabled && _lastContainerPosition != null
? _lastContainerPosition.Value
: Vector2.Zero;
LayoutContainer.SetPosition(_container, pos);
}
if (StaticStorageUIEnabled)
{
// we have to orphan it here because Open() sets the parent.
_container.Orphan();
Hotbar?.StorageContainer.AddChild(_container);
}
_lastContainerPosition = _container.GlobalPosition;
}
else
{
_lastContainerPosition = _container.GlobalPosition;
_container.Close();
}
}
private void OnStaticStorageChanged(bool obj)
{
if (StaticStorageUIEnabled == obj)
return;
StaticStorageUIEnabled = obj;
_lastContainerPosition = null;
if (_container == null)
return;
if (!_container.IsOpen)
return;
_container.Orphan();
if (StaticStorageUIEnabled)
{
Hotbar?.StorageContainer.AddChild(_container);
}
else
{
_ui.WindowRoot.AddChild(_container);
}
if (_entity.TryGetComponent<StorageComponent>(_container.StorageEntity, out var comp))
OnStorageOrderChanged((_container.StorageEntity.Value, comp));
}
private void OnOpaqueWindowChanged(bool obj)
{
if (OpaqueStorageWindow == obj)
return;
OpaqueStorageWindow = obj;
_container?.BuildBackground();
}
/// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
@@ -190,7 +189,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
binding.Mod3 == Keyboard.Key.Control))
return;
if (!IsDragging && _entity.System<HandsSystem>().GetActiveHandEntity() == null)
if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
return;
//clamp it to a cardinal.
@@ -198,43 +197,18 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (DraggingGhost != null)
DraggingGhost.Location.Rotation = DraggingRotation;
if (IsDragging || (_container != null && UIManager.CurrentlyHovered == _container))
if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
keyEvent.Handle();
}
private void OnStorageUpdated(Entity<StorageComponent> uid)
private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{
if (_container?.StorageEntity != uid)
return;
_container.BuildItemPieces();
}
public void RegisterStorageContainer(StorageContainer container)
{
if (_container != null)
{
container.OnPiecePressed -= OnPiecePressed;
container.OnPieceUnpressed -= OnPieceUnpressed;
}
_container = container;
container.OnPiecePressed += OnPiecePressed;
container.OnPieceUnpressed += OnPieceUnpressed;
if (!StaticStorageUIEnabled)
_container.Orphan();
}
private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
{
if (IsDragging || !_container?.IsOpen == true)
if (IsDragging || !window.IsOpen)
return;
if (args.Function == ContentKeyFunctions.MoveStoredItem)
{
DraggingRotation = control.Location.Rotation;
_menuDragHelper.MouseDown(control);
_menuDragHelper.Update(0f);
@@ -242,17 +216,17 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
}
else if (args.Function == ContentKeyFunctions.SaveItemLocation)
{
if (_container?.StorageEntity is not {} storage)
if (window.StorageEntity is not {} storage)
return;
_entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
_entity.GetNetEntity(control.Entity),
_entity.GetNetEntity(storage)));
EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
EntityManager.GetNetEntity(control.Entity),
EntityManager.GetNetEntity(storage)));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.ExamineEntity)
{
_entity.System<ExamineSystem>().DoExamine(control.Entity);
EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
args.Handle();
}
else if (args.Function == EngineKeyFunctions.UseSecondary)
@@ -262,68 +236,102 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
}
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{
_entity.RaisePredictiveEvent(
new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: false));
EntityManager.RaisePredictiveEvent(
new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
_entity.RaisePredictiveEvent(new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: true));
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: true));
args.Handle();
}
window.FlagDirty();
}
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{
if (args.Function != ContentKeyFunctions.MoveStoredItem)
return;
if (_container?.StorageEntity is not { } storageEnt|| !_entity.TryGetComponent<StorageComponent>(storageEnt, out var storageComp))
return;
// Want to get the control under the dragged control.
// This means we can drag the original control around (and not hide the original).
control.MouseFilter = Control.MouseFilterMode.Ignore;
var targetControl = UIManager.MouseGetControl(args.PointerLocation);
var targetStorage = targetControl as StorageWindow;
control.MouseFilter = Control.MouseFilterMode.Pass;
if (DraggingGhost is { } draggingGhost)
var localPlayer = _player.LocalEntity;
window.RemoveGrid(control);
window.FlagDirty();
// If we tried to drag it on top of another grid piece then cancel out.
if (targetControl is ItemGridPiece || window.StorageEntity is not { } sourceStorage || localPlayer == null)
{
window.Reclaim(control.Location, control);
args.Handle();
_menuDragHelper.EndDrag();
return;
}
if (_menuDragHelper.IsDragging && DraggingGhost is { } draggingGhost)
{
var dragEnt = draggingGhost.Entity;
var dragLoc = draggingGhost.Location;
var itemSys = _entity.System<SharedItemSystem>();
var position = _container.GetMouseGridPieceLocation(dragEnt, dragLoc);
var itemBounding = itemSys.GetAdjustedItemShape(dragEnt, dragLoc).GetBoundingBox();
var gridBounding = storageComp.Grid.GetBoundingBox();
// The extended bounding box for if this is out of the window is the grid bounding box dimensions combined
// with the item shape bounding box dimensions. Plus 1 on the left for the sidebar. This makes it so that.
// dropping an item on the floor requires dragging it all the way out of the window.
var left = gridBounding.Left - itemBounding.Width - 1;
var bottom = gridBounding.Bottom - itemBounding.Height;
var top = gridBounding.Top;
var right = gridBounding.Right;
var lenientBounding = new Box2i(left, bottom, right, top);
if (lenientBounding.Contains(position))
// Dragging in the same storage
// The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
if (targetStorage == window)
{
_entity.RaisePredictiveEvent(new StorageSetItemLocationEvent(
_entity.GetNetEntity(draggingGhost.Entity),
_entity.GetNetEntity(storageEnt),
new ItemStorageLocation(DraggingRotation, position)));
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
var newLocation = new ItemStorageLocation(DraggingRotation, position);
EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
EntityManager.GetNetEntity(draggingGhost.Entity),
EntityManager.GetNetEntity(sourceStorage),
newLocation));
window.Reclaim(newLocation, control);
}
else
// Dragging to new storage
else if (targetStorage?.StorageEntity != null && targetStorage != window)
{
_entity.RaisePredictiveEvent(new StorageRemoveItemEvent(
_entity.GetNetEntity(draggingGhost.Entity),
_entity.GetNetEntity(storageEnt)));
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
var newLocation = new ItemStorageLocation(DraggingRotation, position);
// Check it fits and we can move to hand (no free transfers).
if (_storage.ItemFitsInGridLocation(
(dragEnt, null),
(targetStorage.StorageEntity.Value, null),
newLocation))
{
// Can drop and move.
EntityManager.RaisePredictiveEvent(new StorageTransferItemEvent(
EntityManager.GetNetEntity(dragEnt),
EntityManager.GetNetEntity(targetStorage.StorageEntity.Value),
newLocation));
targetStorage.Reclaim(newLocation, control);
DraggingRotation = Angle.Zero;
}
else
{
// Cancel it (rather than dropping).
window.Reclaim(dragLoc, control);
}
}
_menuDragHelper.EndDrag();
_container?.BuildItemPieces();
targetStorage?.FlagDirty();
}
else //if we just clicked, then take it out of the bag.
// If we just clicked, then take it out of the bag.
else
{
_menuDragHelper.EndDrag();
_entity.RaisePredictiveEvent(new StorageInteractWithItemEvent(
_entity.GetNetEntity(control.Entity),
_entity.GetNetEntity(storageEnt)));
EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
EntityManager.GetNetEntity(control.Entity),
EntityManager.GetNetEntity(sourceStorage)));
}
_menuDragHelper.EndDrag();
args.Handle();
}
@@ -332,14 +340,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (_menuDragHelper.Dragged is not { } dragged)
return false;
DraggingGhost!.Orphan();
DraggingRotation = dragged.Location.Rotation;
DraggingGhost = new ItemGridPiece(
(dragged.Entity, _entity.GetComponent<ItemComponent>(dragged.Entity)),
dragged.Location,
_entity);
DraggingGhost.MouseFilter = Control.MouseFilterMode.Ignore;
DraggingGhost.Visible = true;
DraggingGhost.Orphan();
UIManager.PopupRoot.AddChild(DraggingGhost);
SetDraggingRotation();
@@ -350,6 +352,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
if (DraggingGhost == null)
return false;
SetDraggingRotation();
return true;
}
@@ -362,7 +365,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
var offset = ItemGridPiece.GetCenterOffset(
(DraggingGhost.Entity, null),
new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
_entity);
EntityManager);
// I don't know why it divides the position by 2. Hope this helps! -emo
LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
@@ -372,18 +375,13 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
if (DraggingGhost == null)
return;
DraggingGhost.Visible = false;
DraggingGhost = null;
DraggingRotation = Angle.Zero;
}
public override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
_menuDragHelper.Update(args.DeltaSeconds);
if (!StaticStorageUIEnabled && _container?.Parent != null && _lastContainerPosition != null)
_lastContainerPosition = _container.GlobalPosition;
}
}
}

View File

@@ -144,7 +144,7 @@ namespace Content.Client.Viewport
_inputManager.ViewportKeyEvent(this, args);
}
protected override void Draw(DrawingHandleScreen handle)
protected override void Draw(IRenderHandle handle)
{
EnsureViewportCreated();
@@ -170,7 +170,7 @@ namespace Content.Client.Viewport
var drawBox = GetDrawBox();
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
}

View File

@@ -16,7 +16,7 @@ public sealed partial class StationStockMarketComponent : Component
/// The list of companies you can invest in
/// </summary>
[DataField]
public List<StockCompanyStruct> Companies = [];
public List<StockCompany> Companies = [];
/// <summary>
/// The list of shares owned by the station
@@ -53,19 +53,12 @@ public sealed partial class StationStockMarketComponent : Component
[DataField]
public List<MarketChange> MarketChanges =
[
new() { Chance = 0.86f, Range = new Vector2(-0.05f, 0.05f) }, // Minor
new() { Chance = 0.10f, Range = new Vector2(-0.3f, 0.2f) }, // Moderate
new() { Chance = 0.03f, Range = new Vector2(-0.5f, 1.5f) }, // Major
new() { Chance = 0.01f, Range = new Vector2(-0.9f, 4.0f) }, // Catastrophic
new(0.86f, new Vector2(-0.05f, 0.05f)), // Minor
new(0.10f, new Vector2(-0.3f, 0.2f)), // Moderate
new(0.03f, new Vector2(-0.5f, 1.5f)), // Major
new(0.01f, new Vector2(-0.9f, 4.0f)), // Catastrophic
];
}
[DataDefinition]
public sealed partial class MarketChange
{
[DataField(required: true)]
public float Chance;
[DataField(required: true)]
public Vector2 Range;
}
[DataRecord]
public record struct MarketChange(float Chance, Vector2 Range);

View File

@@ -9,9 +9,7 @@ using Content.Shared.Access.Systems;
using Content.Shared.CartridgeLoader;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.Database;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@@ -22,15 +20,14 @@ namespace Content.Server.DeltaV.Cargo.Systems;
/// </summary>
public sealed class StockMarketSystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _accessSystem = default!;
[Dependency] private readonly AccessReaderSystem _access = default!;
[Dependency] private readonly CargoSystem _cargo = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly IdCardSystem _idCard = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _sawmill = default!;
private const float MaxPrice = 262144; // 1/64 of max safe integer
@@ -64,38 +61,27 @@ public sealed class StockMarketSystem : EntitySystem
if (args is not StockTradingUiMessageEvent message)
return;
var user = args.Actor;
var companyIndex = message.CompanyIndex;
var amount = (int)message.Amount;
var station = ent.Comp.Station;
var amount = message.Amount;
var loader = GetEntity(args.LoaderUid);
var xform = Transform(loader);
// Ensure station and stock market components are valid
if (station == null || !TryComp<StationStockMarketComponent>(station, out var stockMarket))
if (ent.Comp.Station is not {} station || !TryComp<StationStockMarketComponent>(station, out var stockMarket))
return;
// Validate company index
if (companyIndex < 0 || companyIndex >= stockMarket.Companies.Count)
return;
if (!TryComp<AccessReaderComponent>(ent.Owner, out var access))
if (!TryComp<AccessReaderComponent>(ent, out var access))
return;
// Attempt to retrieve ID card from loader
IdCardComponent? idCard = null;
if (_idCardSystem.TryGetIdCard(loader, out var pdaId))
idCard = pdaId;
// Play deny sound and exit if access is not allowed
if (idCard == null || !_accessSystem.IsAllowed(pdaId.Owner, ent.Owner, access))
// Attempt to retrieve ID card from loader,
// play deny sound and exit if access is not allowed
if (!_idCard.TryGetIdCard(loader, out var idCard) || !_access.IsAllowed(idCard, ent.Owner, access))
{
_audio.PlayEntity(
stockMarket.DenySound,
Filter.Empty().AddInRange(_transform.GetMapCoordinates(loader, xform), 0.05f),
loader,
true,
AudioParams.Default.WithMaxDistance(0.05f)
);
_audio.PlayEntity(stockMarket.DenySound, loader, user);
return;
}
@@ -110,15 +96,15 @@ public sealed class StockMarketSystem : EntitySystem
case StockTradingUiAction.Buy:
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"{ToPrettyString(loader)} attempting to buy {amount} stocks of {company.LocalizedDisplayName}");
success = TryBuyStocks(station.Value, stockMarket, companyIndex, amount);
$"{ToPrettyString(user):user} attempting to buy {amount} stocks of {company.LocalizedDisplayName}");
success = TryChangeStocks(station, stockMarket, companyIndex, amount, user);
break;
case StockTradingUiAction.Sell:
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"{ToPrettyString(loader)} attempting to sell {amount} stocks of {company.LocalizedDisplayName}");
success = TrySellStocks(station.Value, stockMarket, companyIndex, amount);
$"{ToPrettyString(user):user} attempting to sell {amount} stocks of {company.LocalizedDisplayName}");
success = TryChangeStocks(station, stockMarket, companyIndex, -amount, user);
break;
default:
@@ -126,23 +112,29 @@ public sealed class StockMarketSystem : EntitySystem
}
// Play confirmation sound if the transaction was successful
_audio.PlayEntity(success ? stockMarket.ConfirmSound : stockMarket.DenySound, loader, args.Actor);
_audio.PlayEntity(success ? stockMarket.ConfirmSound : stockMarket.DenySound, loader, user);
}
finally
{
// Raise the event to update the UI regardless of outcome
var ev = new StockMarketUpdatedEvent(station.Value);
RaiseLocalEvent(ev);
UpdateStockMarket(station);
}
}
private bool TryBuyStocks(
private void UpdateStockMarket(EntityUid station)
{
var ev = new StockMarketUpdatedEvent(station);
RaiseLocalEvent(ref ev);
}
private bool TryChangeStocks(
EntityUid station,
StationStockMarketComponent stockMarket,
int companyIndex,
int amount)
int amount,
EntityUid user)
{
if (amount <= 0 || companyIndex < 0 || companyIndex >= stockMarket.Companies.Count)
if (amount == 0 || companyIndex < 0 || companyIndex >= stockMarket.Companies.Count)
return false;
// Check if the station has a bank account
@@ -152,58 +144,37 @@ public sealed class StockMarketSystem : EntitySystem
var company = stockMarket.Companies[companyIndex];
var totalValue = (int)Math.Round(company.CurrentPrice * amount);
// See if we can afford it
if (bank.Balance < totalValue)
return false;
if (!stockMarket.StockOwnership.TryGetValue(companyIndex, out var currentOwned))
currentOwned = 0;
// Update the bank account
_cargo.UpdateBankAccount(station, bank, -totalValue);
stockMarket.StockOwnership[companyIndex] = currentOwned + amount;
if (amount > 0)
{
// Buying: see if we can afford it
if (bank.Balance < totalValue)
return false;
}
else
{
// Selling: see if we have enough stocks to sell
var selling = -amount;
if (currentOwned < selling)
return false;
}
// Log the transaction
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"[StockMarket] Bought {amount} stocks of {company.LocalizedDisplayName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})");
return true;
}
private bool TrySellStocks(
EntityUid station,
StationStockMarketComponent stockMarket,
int companyIndex,
int amount)
{
if (amount <= 0 || companyIndex < 0 || companyIndex >= stockMarket.Companies.Count)
return false;
// Check if the station has a bank account
if (!TryComp<StationBankAccountComponent>(station, out var bank))
return false;
if (!stockMarket.StockOwnership.TryGetValue(companyIndex, out var currentOwned) || currentOwned < amount)
return false;
var company = stockMarket.Companies[companyIndex];
var totalValue = (int)Math.Round(company.CurrentPrice * amount);
// Update stock ownership
var newAmount = currentOwned - amount;
var newAmount = currentOwned + amount;
if (newAmount > 0)
stockMarket.StockOwnership[companyIndex] = newAmount;
else
stockMarket.StockOwnership.Remove(companyIndex);
// Update the bank account
_cargo.UpdateBankAccount(station, bank, totalValue);
// Update the bank account (take away for buying and give for selling)
_cargo.UpdateBankAccount(station, bank, -totalValue);
// Log the transaction
var verb = amount > 0 ? "bought" : "sold";
_adminLogger.Add(LogType.Action,
LogImpact.Medium,
$"[StockMarket] Sold {amount} stocks of {company.LocalizedDisplayName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})");
$"[StockMarket] {ToPrettyString(user):user} {verb} {Math.Abs(amount)} stocks of {company.LocalizedDisplayName} at {company.CurrentPrice:F2} credits each (Total: {totalValue})");
return true;
}
@@ -216,7 +187,7 @@ public sealed class StockMarketSystem : EntitySystem
var changeType = DetermineMarketChange(stockMarket.MarketChanges);
var multiplier = CalculatePriceMultiplier(changeType);
UpdatePriceHistory(company);
UpdatePriceHistory(ref company);
// Update price with multiplier
var oldPrice = company.CurrentPrice;
@@ -234,8 +205,7 @@ public sealed class StockMarketSystem : EntitySystem
var percentChange = (company.CurrentPrice - oldPrice) / oldPrice * 100;
// Raise the event
var ev = new StockMarketUpdatedEvent(station);
RaiseLocalEvent(ev);
UpdateStockMarket(station);
// Log it
_adminLogger.Add(LogType.Action,
@@ -264,13 +234,12 @@ public sealed class StockMarketSystem : EntitySystem
return false;
var company = stockMarket.Companies[companyIndex];
UpdatePriceHistory(company);
UpdatePriceHistory(ref company);
company.CurrentPrice = MathF.Max(newPrice, company.BasePrice * 0.1f);
stockMarket.Companies[companyIndex] = company;
var ev = new StockMarketUpdatedEvent(station);
RaiseLocalEvent(ev);
UpdateStockMarket(station);
return true;
}
@@ -284,7 +253,7 @@ public sealed class StockMarketSystem : EntitySystem
string displayName)
{
// Create a new company struct with the specified parameters
var company = new StockCompanyStruct
var company = new StockCompany
{
LocalizedDisplayName = displayName, // Assume there's no Loc for it
BasePrice = basePrice,
@@ -292,36 +261,33 @@ public sealed class StockMarketSystem : EntitySystem
PriceHistory = [],
};
UpdatePriceHistory(ref company);
stockMarket.Companies.Add(company);
UpdatePriceHistory(company);
var ev = new StockMarketUpdatedEvent(station);
RaiseLocalEvent(ev);
UpdateStockMarket(station);
return true;
}
/// <summary>
/// Attempts to add a new company to the station using the StockCompanyStruct
/// Attempts to add a new company to the station using the StockCompany
/// </summary>
/// <returns>False if the company already exists, true otherwise</returns>
public bool TryAddCompany(EntityUid station,
StationStockMarketComponent stockMarket,
StockCompanyStruct company)
public bool TryAddCompany(Entity<StationStockMarketComponent> station,
StockCompany company)
{
// Add the new company to the dictionary
stockMarket.Companies.Add(company);
// Make sure it has a price history
UpdatePriceHistory(company);
UpdatePriceHistory(ref company);
var ev = new StockMarketUpdatedEvent(station);
RaiseLocalEvent(ev);
// Add the new company to the dictionary
station.Comp.Companies.Add(company);
UpdateStockMarket(station);
return true;
}
private static void UpdatePriceHistory(StockCompanyStruct company)
private static void UpdatePriceHistory(ref StockCompany company)
{
// Create if null
company.PriceHistory ??= [];
@@ -370,7 +336,9 @@ public sealed class StockMarketSystem : EntitySystem
return Math.Clamp(result, change.Range.X, change.Range.Y);
}
}
public sealed class StockMarketUpdatedEvent(EntityUid station) : EntityEventArgs
{
public EntityUid Station = station;
}
/// <summary>
/// Broadcast whenever a stock market is updated.
/// </summary>
[ByRefEvent]
public record struct StockMarketUpdatedEvent(EntityUid Station);

View File

@@ -27,7 +27,7 @@ public sealed class StockTradingCartridgeSystem : EntitySystem
private void OnBalanceUpdated(Entity<StockTradingCartridgeComponent> ent, ref BankBalanceUpdatedEvent args)
{
UpdateAllCartridges(args.Station);
UpdateAllCartridges(args.Station);
}
private void OnUiReady(Entity<StockTradingCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
@@ -35,7 +35,7 @@ public sealed class StockTradingCartridgeSystem : EntitySystem
UpdateUI(ent, args.Loader);
}
private void OnStockMarketUpdated(StockMarketUpdatedEvent args)
private void OnStockMarketUpdated(ref StockMarketUpdatedEvent args)
{
UpdateAllCartridges(args.Station);
}
@@ -81,17 +81,9 @@ public sealed class StockTradingCartridgeSystem : EntitySystem
!TryComp<StationBankAccountComponent>(ent.Comp.Station, out var bankAccount))
return;
// Convert company data to UI state format
var entries = stockMarket.Companies.Select(company => new StockCompanyStruct(
displayName: company.LocalizedDisplayName,
currentPrice: company.CurrentPrice,
basePrice: company.BasePrice,
priceHistory: company.PriceHistory))
.ToList();
// Send the UI state with balance and owned stocks
var state = new StockTradingUiState(
entries: entries,
entries: stockMarket.Companies,
ownedStocks: stockMarket.StockOwnership,
balance: bankAccount.Balance
);

View File

@@ -29,13 +29,13 @@ public sealed partial class OrganComponent : Component, ISurgeryToolComponent //
/// without referencing the prototype or hardcoding.
/// </summary>
[DataField, AlwaysPushInheritance]
public string SlotId = "";
[DataField]
public string SlotId = string.Empty;
[DataField, AlwaysPushInheritance]
[DataField]
public string ToolName { get; set; } = "An organ";
[DataField, AlwaysPushInheritance]
[DataField]
public float Speed { get; set; } = 1f;
/// <summary>

View File

@@ -37,16 +37,16 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent
[DataField, AutoNetworkedField]
public FixedPoint2 VitalDamage = 100;
[DataField, AlwaysPushInheritance]
[DataField]
public string ToolName { get; set; } = "A body part";
[DataField, AlwaysPushInheritance]
public string SlotId = "";
[DataField]
public string SlotId = string.Empty;
[DataField, AutoNetworkedField]
public bool? Used { get; set; } = null;
[DataField, AlwaysPushInheritance]
[DataField]
public float Speed { get; set; } = 1f;
/// <summary>
@@ -125,7 +125,7 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent
/// <summary>
/// Shitmed Change: The ID of the base layer for this body part.
/// </summary>
[DataField, AutoNetworkedField, AlwaysPushInheritance]
[DataField, AutoNetworkedField]
public string? BaseLayerId;
/// <summary>
@@ -146,7 +146,7 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent
};
[DataField, AutoNetworkedField, AlwaysPushInheritance]
[DataField, AutoNetworkedField]
public BodyPartType PartType = BodyPartType.Other;
@@ -158,7 +158,7 @@ public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent
[DataField("vital"), AutoNetworkedField]
public bool IsVital;
[DataField, AutoNetworkedField, AlwaysPushInheritance]
[DataField, AutoNetworkedField]
public BodyPartSymmetry Symmetry = BodyPartSymmetry.None;
/// <summary>

View File

@@ -57,4 +57,23 @@ public sealed partial class CCVars
/// </summary>
public static readonly CVarDef<bool> OpaqueStorageWindow =
CVarDef.Create("control.opaque_storage_background", false, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// Whether or not the storage window has a title of the entity name.
/// </summary>
public static readonly CVarDef<bool> StorageWindowTitle =
CVarDef.Create("control.storage_window_title", false, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// How many storage windows are allowed to be open at once.
/// Recommended that you utilise this in conjunction with <see cref="StaticStorageUI"/>
/// </summary>
public static readonly CVarDef<int> StorageLimit =
CVarDef.Create("control.storage_limit", 1, CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Whether or not storage can be opened recursively.
/// </summary>
public static readonly CVarDef<bool> NestedStorage =
CVarDef.Create("control.nested_storage", true, CVar.REPLICATED | CVar.SERVER);
}

View File

@@ -3,12 +3,12 @@ using Robust.Shared.Serialization;
namespace Content.Shared.CartridgeLoader.Cartridges;
[Serializable, NetSerializable]
public sealed class StockTradingUiMessageEvent(StockTradingUiAction action, int companyIndex, float amount)
public sealed class StockTradingUiMessageEvent(StockTradingUiAction action, int companyIndex, int amount)
: CartridgeMessageEvent
{
public readonly StockTradingUiAction Action = action;
public readonly int CompanyIndex = companyIndex;
public readonly float Amount = amount;
public readonly int Amount = amount;
}
[Serializable, NetSerializable]

View File

@@ -4,19 +4,19 @@ namespace Content.Shared.CartridgeLoader.Cartridges;
[Serializable, NetSerializable]
public sealed class StockTradingUiState(
List<StockCompanyStruct> entries,
List<StockCompany> entries,
Dictionary<int, int> ownedStocks,
float balance)
: BoundUserInterfaceState
{
public readonly List<StockCompanyStruct> Entries = entries;
public readonly List<StockCompany> Entries = entries;
public readonly Dictionary<int, int> OwnedStocks = ownedStocks;
public readonly float Balance = balance;
}
// No structure, zero fucks given
[DataDefinition, Serializable]
public partial struct StockCompanyStruct
public partial struct StockCompany
{
/// <summary>
/// The displayed name of the company shown in the UI.
@@ -55,7 +55,7 @@ public partial struct StockCompanyStruct
[DataField]
public List<float>? PriceHistory;
public StockCompanyStruct(string displayName, float currentPrice, float basePrice, List<float>? priceHistory)
public StockCompany(string displayName, float currentPrice, float basePrice, List<float>? priceHistory)
{
DisplayName = displayName;
_displayName = null;

View File

@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Destructible;
@@ -26,6 +27,7 @@ using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Input.Binding;
@@ -34,36 +36,46 @@ using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Storage.EntitySystems;
public abstract class SharedStorageSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] protected readonly IRobustRandom Random = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
[Dependency] protected readonly ActionBlockerSystem ActionBlocker = default!;
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] protected readonly SharedEntityStorageSystem EntityStorage = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] protected readonly SharedItemSystem ItemSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
[Dependency] private readonly SharedStackSystem _stack = default!;
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
[Dependency] protected readonly UseDelaySystem UseDelay = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
private EntityQuery<ItemComponent> _itemQuery;
private EntityQuery<StackComponent> _stackQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<UserInterfaceUserComponent> _userQuery;
/// <summary>
/// Whether we're allowed to go up-down storage via UI.
/// </summary>
public bool NestedStorage = true;
[ValidatePrototypeId<ItemSizePrototype>]
public const string DefaultStorageMaxItemSize = "Normal";
@@ -75,10 +87,15 @@ public abstract class SharedStorageSystem : EntitySystem
private ItemSizePrototype _defaultStorageMaxItemSize = default!;
/// <summary>
/// Flag for whether we're checking for nested storage interactions.
/// </summary>
private bool _nestedCheck;
public bool CheckingCanInsert;
private List<EntityUid> _entList = new();
private HashSet<EntityUid> _entSet = new();
private readonly List<EntityUid> _entList = new();
private readonly HashSet<EntityUid> _entSet = new();
private readonly List<ItemSizePrototype> _sortedSizes = new();
private FrozenDictionary<string, ItemSizePrototype> _nextSmallest = FrozenDictionary<string, ItemSizePrototype>.Empty;
@@ -86,6 +103,11 @@ public abstract class SharedStorageSystem : EntitySystem
private const string QuickInsertUseDelayID = "quickInsert";
private const string OpenUiUseDelayID = "storage";
/// <summary>
/// How many storage windows are allowed to be open at once.
/// </summary>
private int _openStorageLimit = -1;
protected readonly List<string> CantFillReasons = [];
/// <inheritdoc />
@@ -96,8 +118,11 @@ public abstract class SharedStorageSystem : EntitySystem
_itemQuery = GetEntityQuery<ItemComponent>();
_stackQuery = GetEntityQuery<StackComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_userQuery = GetEntityQuery<UserInterfaceUserComponent>();
_prototype.PrototypesReloaded += OnPrototypesReloaded;
Subs.CVar(_cfg, CCVars.StorageLimit, OnStorageLimitChanged, true);
Subs.BuiEvents<StorageComponent>(StorageComponent.StorageUiKey.Key, subs =>
{
subs.Event<BoundUIClosedEvent>(OnBoundUIClosed);
@@ -107,7 +132,6 @@ public abstract class SharedStorageSystem : EntitySystem
SubscribeLocalEvent<StorageComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<StorageComponent, GetVerbsEvent<ActivationVerb>>(AddUiVerb);
SubscribeLocalEvent<StorageComponent, ComponentGetState>(OnStorageGetState);
SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
SubscribeLocalEvent<StorageComponent, ComponentInit>(OnComponentInit, before: new[] { typeof(SharedContainerSystem) });
SubscribeLocalEvent<StorageComponent, GetVerbsEvent<UtilityVerb>>(AddTransferVerbs);
SubscribeLocalEvent<StorageComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ItemSlotsSystem) });
@@ -115,6 +139,7 @@ public abstract class SharedStorageSystem : EntitySystem
SubscribeLocalEvent<StorageComponent, OpenStorageImplantEvent>(OnImplantActivate);
SubscribeLocalEvent<StorageComponent, AfterInteractEvent>(AfterInteract);
SubscribeLocalEvent<StorageComponent, DestructionEventArgs>(OnDestroy);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundUIAttempt);
SubscribeLocalEvent<StorageComponent, BoundUIOpenedEvent>(OnBoundUIOpen);
SubscribeLocalEvent<StorageComponent, LockToggledEvent>(OnLockToggled);
SubscribeLocalEvent<MetaDataComponent, StackCountChangedEvent>(OnStackCountChanged);
@@ -125,10 +150,11 @@ public abstract class SharedStorageSystem : EntitySystem
SubscribeLocalEvent<StorageComponent, AreaPickupDoAfterEvent>(OnDoAfter);
SubscribeAllEvent<OpenNestedStorageEvent>(OnStorageNested);
SubscribeAllEvent<StorageTransferItemEvent>(OnStorageTransfer);
SubscribeAllEvent<StorageInteractWithItemEvent>(OnInteractWithItem);
SubscribeAllEvent<StorageSetItemLocationEvent>(OnSetItemLocation);
SubscribeAllEvent<StorageInsertItemIntoLocationEvent>(OnInsertItemIntoLocation);
SubscribeAllEvent<StorageRemoveItemEvent>(OnRemoveItem);
SubscribeAllEvent<StorageSaveItemLocationEvent>(OnSaveItemLocation);
SubscribeLocalEvent<StorageComponent, GotReclaimedEvent>(OnReclaimed);
@@ -138,12 +164,24 @@ public abstract class SharedStorageSystem : EntitySystem
.Bind(ContentKeyFunctions.OpenBelt, InputCmdHandler.FromDelegate(HandleOpenBelt, handle: false))
.Register<SharedStorageSystem>();
Subs.CVar(_cfg, CCVars.NestedStorage, OnNestedStorageCvar, true);
UpdatePrototypeCache();
}
private void OnNestedStorageCvar(bool obj)
{
NestedStorage = obj;
}
private void OnStorageLimitChanged(int obj)
{
_openStorageLimit = obj;
}
private void OnRemove(Entity<StorageComponent> entity, ref ComponentRemove args)
{
_ui.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key);
UI.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key);
}
private void OnMapInit(Entity<StorageComponent> entity, ref MapInitEvent args)
@@ -172,28 +210,6 @@ public abstract class SharedStorageSystem : EntitySystem
};
}
private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref ComponentHandleState args)
{
if (args.Current is not StorageComponentState state)
return;
component.Grid.Clear();
component.Grid.AddRange(state.Grid);
component.MaxItemSize = state.MaxItemSize;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
component.StoredItems.Clear();
foreach (var (nent, location) in state.StoredItems)
{
var ent = EnsureEntity<StorageComponent>(nent, uid);
component.StoredItems[ent] = location;
}
component.SavedLocations = state.SavedLocations;
}
public override void Shutdown()
{
_prototype.PrototypesReloaded -= OnPrototypesReloaded;
@@ -228,7 +244,7 @@ public abstract class SharedStorageSystem : EntitySystem
private void OnComponentInit(EntityUid uid, StorageComponent storageComp, ComponentInit args)
{
storageComp.Container = _containerSystem.EnsureContainer<Container>(uid, StorageComponent.ContainerId);
storageComp.Container = ContainerSystem.EnsureContainer<Container>(uid, StorageComponent.ContainerId);
UpdateAppearance((uid, storageComp, null));
}
@@ -247,7 +263,7 @@ public abstract class SharedStorageSystem : EntitySystem
// close ui
foreach (var entity in storageComp.Container.ContainedEntities)
{
_ui.CloseUis(entity, actor);
UI.CloseUis(entity, actor);
}
}
@@ -256,7 +272,7 @@ public abstract class SharedStorageSystem : EntitySystem
CloseNestedInterfaces(uid, args.Actor, storageComp);
// If UI is closed for everyone
if (!_ui.IsUiOpen(uid, args.UiKey))
if (!UI.IsUiOpen(uid, args.UiKey))
{
UpdateAppearance((uid, storageComp, null));
Audio.PlayPredicted(storageComp.StorageCloseSound, uid, args.Actor);
@@ -269,7 +285,7 @@ public abstract class SharedStorageSystem : EntitySystem
return;
// Does this player currently have the storage UI open?
var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User);
var uiOpen = UI.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User);
ActivationVerb verb = new()
{
@@ -277,7 +293,7 @@ public abstract class SharedStorageSystem : EntitySystem
{
if (uiOpen)
{
_ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
}
else
{
@@ -315,16 +331,16 @@ public abstract class SharedStorageSystem : EntitySystem
if (!CanInteract(entity, (uid, storageComp), silent: silent))
return;
if (!UI.TryOpenUi(uid, StorageComponent.StorageUiKey.Key, entity))
return;
if (!silent)
{
if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key))
Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity);
Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity);
if (useDelay != null)
UseDelay.TryResetDelay((uid, useDelay), id: OpenUiUseDelayID);
}
_ui.OpenUi(uid, StorageComponent.StorageUiKey.Key, entity);
}
public virtual void UpdateUI(Entity<StorageComponent?> entity) {}
@@ -382,18 +398,43 @@ public abstract class SharedStorageSystem : EntitySystem
return;
// Toggle
if (_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User))
if (UI.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User))
{
_ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
}
else
{
OpenStorageUI(uid, args.User, storageComp, false);
// Handle recursively opening nested storages.
if (ContainerSystem.TryGetContainingContainer((args.Target, null, null), out var container) &&
UI.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, args.User))
{
_nestedCheck = true;
HideStorageWindow(container.Owner, args.User);
OpenStorageUI(uid, args.User, storageComp, silent: true);
_nestedCheck = false;
}
else
{
// If you need something more sophisticated for multi-UI you'll need to code some smarter
// interactions.
if (_openStorageLimit == 1)
UI.CloseUserUis<StorageComponent.StorageUiKey>(args.User);
OpenStorageUI(uid, args.User, storageComp, silent: false);
}
}
args.Handled = true;
}
protected virtual void HideStorageWindow(EntityUid uid, EntityUid actor)
{
}
protected virtual void ShowStorageWindow(EntityUid uid, EntityUid actor)
{
}
/// <summary>
/// Specifically for storage implants.
/// </summary>
@@ -402,7 +443,13 @@ public abstract class SharedStorageSystem : EntitySystem
if (args.Handled)
return;
OpenStorageUI(uid, args.Performer, storageComp, false);
var uiOpen = UI.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.Performer);
if (uiOpen)
UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.Performer);
else
OpenStorageUI(uid, args.Performer, storageComp, false);
args.Handled = true;
}
@@ -466,7 +513,7 @@ public abstract class SharedStorageSystem : EntitySystem
if (args.Target is not { Valid: true } target)
return;
if (_containerSystem.IsEntityInContainer(target)
if (ContainerSystem.IsEntityInContainer(target)
|| target == args.User
|| !_itemQuery.HasComponent(target))
{
@@ -517,7 +564,7 @@ public abstract class SharedStorageSystem : EntitySystem
var entity = GetEntity(args.Entities[i]);
// Check again, situation may have changed for some entities, but we'll still pick up any that are valid
if (_containerSystem.IsEntityInContainer(entity)
if (ContainerSystem.IsEntityInContainer(entity)
|| entity == args.Args.User
|| !_itemQuery.HasComponent(entity))
{
@@ -562,7 +609,7 @@ public abstract class SharedStorageSystem : EntitySystem
private void OnReclaimed(EntityUid uid, StorageComponent storageComp, GotReclaimedEvent args)
{
_containerSystem.EmptyContainer(storageComp.Container, destination: args.ReclaimerCoordinates);
ContainerSystem.EmptyContainer(storageComp.Container, destination: args.ReclaimerCoordinates);
}
private void OnDestroy(EntityUid uid, StorageComponent storageComp, DestructionEventArgs args)
@@ -570,7 +617,7 @@ public abstract class SharedStorageSystem : EntitySystem
var coordinates = TransformSystem.GetMoverCoordinates(uid);
// Being destroyed so need to recalculate.
_containerSystem.EmptyContainer(storageComp.Container, destination: coordinates);
ContainerSystem.EmptyContainer(storageComp.Container, destination: coordinates);
}
/// <summary>
@@ -622,17 +669,52 @@ public abstract class SharedStorageSystem : EntitySystem
TrySetItemStorageLocation(item!, storage!, msg.Location);
}
private void OnRemoveItem(StorageRemoveItemEvent msg, EntitySessionEventArgs args)
private void OnStorageNested(OpenNestedStorageEvent msg, EntitySessionEventArgs args)
{
if (!ValidateInput(args, msg.StorageEnt, msg.ItemEnt, out var player, out var storage, out var item))
if (!NestedStorage)
return;
if (!TryGetEntity(msg.InteractedItemUid, out var itemEnt))
return;
_nestedCheck = true;
var result = ValidateInput(args,
msg.StorageUid,
msg.InteractedItemUid,
out var player,
out var storage,
out var item);
if (!result)
{
_nestedCheck = false;
return;
}
HideStorageWindow(storage.Owner, player.Owner);
OpenStorageUI(item.Owner, player.Owner, silent: true);
_nestedCheck = false;
}
private void OnStorageTransfer(StorageTransferItemEvent msg, EntitySessionEventArgs args)
{
if (!TryGetEntity(msg.ItemEnt, out var itemEnt))
return;
var localPlayer = args.SenderSession.AttachedEntity;
if (!TryComp(localPlayer, out HandsComponent? handsComp) || !_sharedHandsSystem.TryPickup(localPlayer.Value, itemEnt.Value, handsComp: handsComp, animate: false))
return;
if (!ValidateInput(args, msg.StorageEnt, msg.ItemEnt, out var player, out var storage, out var item, held: true))
return;
_adminLog.Add(
LogType.Storage,
LogImpact.Low,
$"{ToPrettyString(player):player} is removing {ToPrettyString(item):item} from {ToPrettyString(storage):storage}");
TransformSystem.DropNextTo(item.Owner, player.Owner);
Audio.PlayPredicted(storage.Comp.StorageRemoveSound, storage, player, _audioParams);
$"{ToPrettyString(player):player} is inserting {ToPrettyString(item):item} into {ToPrettyString(storage):storage}");
InsertAt(storage!, item!, msg.Location, out _, player, stackAutomatically: false);
}
private void OnInsertItemIntoLocation(StorageInsertItemIntoLocationEvent msg, EntitySessionEventArgs args)
@@ -655,9 +737,46 @@ public abstract class SharedStorageSystem : EntitySystem
SaveItemLocation(storage!, item.Owner);
}
private void OnBoundUIOpen(EntityUid uid, StorageComponent storageComp, BoundUIOpenedEvent args)
private void OnBoundUIOpen(Entity<StorageComponent> ent, ref BoundUIOpenedEvent args)
{
UpdateAppearance((uid, storageComp, null));
UpdateAppearance((ent.Owner, ent.Comp, null));
}
private void OnBoundUIAttempt(BoundUserInterfaceMessageAttempt args)
{
if (args.UiKey is not StorageComponent.StorageUiKey.Key ||
_openStorageLimit == -1 ||
_nestedCheck ||
args.Message is not OpenBoundInterfaceMessage)
return;
var uid = args.Target;
var actor = args.Actor;
var count = 0;
if (_userQuery.TryComp(actor, out var userComp))
{
foreach (var (ui, keys) in userComp.OpenInterfaces)
{
if (ui == uid)
continue;
foreach (var key in keys)
{
if (key is not StorageComponent.StorageUiKey)
continue;
count++;
if (count >= _openStorageLimit)
{
args.Cancel();
}
break;
}
}
}
}
private void OnEntInserted(Entity<StorageComponent> entity, ref EntInsertedIntoContainerMessage args)
@@ -673,7 +792,7 @@ public abstract class SharedStorageSystem : EntitySystem
{
if (!TryGetAvailableGridSpace((entity.Owner, entity.Comp), (args.Entity, null), out var location))
{
_containerSystem.Remove(args.Entity, args.Container, force: true);
ContainerSystem.Remove(args.Entity, args.Container, force: true);
return;
}
@@ -735,7 +854,7 @@ public abstract class SharedStorageSystem : EntitySystem
var capacity = storage.Grid.GetArea();
var used = GetCumulativeItemAreas((uid, storage));
var isOpen = _ui.IsUiOpen(entity.Owner, StorageComponent.StorageUiKey.Key);
var isOpen = UI.IsUiOpen(entity.Owner, StorageComponent.StorageUiKey.Key);
_appearance.SetData(uid, StorageVisuals.StorageUsed, used, appearance);
_appearance.SetData(uid, StorageVisuals.Capacity, capacity, appearance);
@@ -845,7 +964,7 @@ public abstract class SharedStorageSystem : EntitySystem
}
CheckingCanInsert = true;
if (!_containerSystem.CanInsert(insertEnt, storageComp.Container))
if (!ContainerSystem.CanInsert(insertEnt, storageComp.Container))
{
CheckingCanInsert = false;
reason = null;
@@ -946,7 +1065,7 @@ public abstract class SharedStorageSystem : EntitySystem
if (!stackAutomatically || !_stackQuery.TryGetComponent(insertEnt, out var insertStack))
{
if (!_containerSystem.Insert(insertEnt, storageComp.Container))
if (!ContainerSystem.Insert(insertEnt, storageComp.Container))
return false;
if (playSound)
@@ -972,7 +1091,7 @@ public abstract class SharedStorageSystem : EntitySystem
// Still stackable remaining
if (insertStack.Count > 0
&& !_containerSystem.Insert(insertEnt, storageComp.Container)
&& !ContainerSystem.Insert(insertEnt, storageComp.Container)
&& toInsertCount == insertStack.Count)
{
// Failed to insert anything.
@@ -1050,6 +1169,7 @@ public abstract class SharedStorageSystem : EntitySystem
return false;
storageEnt.Comp.StoredItems[itemEnt] = location;
UpdateUI(storageEnt);
Dirty(storageEnt, storageEnt.Comp);
return true;
}
@@ -1182,6 +1302,7 @@ public abstract class SharedStorageSystem : EntitySystem
}
Dirty(ent, ent.Comp);
UpdateUI((ent.Owner, ent.Comp));
}
/// <summary>
@@ -1353,16 +1474,16 @@ public abstract class SharedStorageSystem : EntitySystem
return;
// Gets everyone looking at the UI
foreach (var actor in _ui.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList())
foreach (var actor in UI.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList())
{
if (!CanInteract(actor, (uid, component)))
_ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor);
UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor);
}
}
private void OnStackCountChanged(EntityUid uid, MetaDataComponent component, StackCountChangedEvent args)
{
if (_containerSystem.TryGetContainingContainer((uid, null, component), out var container) &&
if (ContainerSystem.TryGetContainingContainer((uid, null, component), out var container) &&
container.ID == StorageComponent.ContainerId)
{
UpdateAppearance(container.Owner);
@@ -1394,13 +1515,13 @@ public abstract class SharedStorageSystem : EntitySystem
if (!ActionBlocker.CanInteract(playerEnt, storageEnt))
return;
if (!_ui.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt))
if (!UI.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt))
{
OpenStorageUI(storageEnt.Value, playerEnt, silent: false);
}
else
{
_ui.CloseUi(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt);
UI.CloseUi(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt);
}
}
@@ -1455,7 +1576,7 @@ public abstract class SharedStorageSystem : EntitySystem
// TODO STORAGE use BUI events
// This would automatically validate that the UI is open & that the user can interact.
// However, we still need to manually validate that items being used are in the users hands or in the storage.
if (!_ui.IsUiOpen(storageUid.Value, StorageComponent.StorageUiKey.Key, playerUid))
if (!UI.IsUiOpen(storageUid.Value, StorageComponent.StorageUiKey.Key, playerUid))
return false;
if (!ActionBlocker.CanInteract(playerUid, storageUid))

View File

@@ -138,6 +138,19 @@ namespace Content.Shared.Storage
}
}
[Serializable, NetSerializable]
public sealed class OpenNestedStorageEvent : EntityEventArgs
{
public readonly NetEntity InteractedItemUid;
public readonly NetEntity StorageUid;
public OpenNestedStorageEvent(NetEntity interactedItemUid, NetEntity storageUid)
{
InteractedItemUid = interactedItemUid;
StorageUid = storageUid;
}
}
[Serializable, NetSerializable]
public sealed class StorageInteractWithItemEvent : EntityEventArgs
{
@@ -170,16 +183,22 @@ namespace Content.Shared.Storage
}
[Serializable, NetSerializable]
public sealed class StorageRemoveItemEvent : EntityEventArgs
public sealed class StorageTransferItemEvent : EntityEventArgs
{
public readonly NetEntity ItemEnt;
/// <summary>
/// Target storage to receive the transfer.
/// </summary>
public readonly NetEntity StorageEnt;
public StorageRemoveItemEvent(NetEntity itemEnt, NetEntity storageEnt)
public readonly ItemStorageLocation Location;
public StorageTransferItemEvent(NetEntity itemEnt, NetEntity storageEnt, ItemStorageLocation location)
{
ItemEnt = itemEnt;
StorageEnt = storageEnt;
Location = location;
}
}

View File

@@ -18,7 +18,7 @@ public sealed partial class SurgeryAddMarkingStepComponent : Component
/// against the entity to validate that the marking is not already present.
/// </summary>
[DataField]
public String MatchString = "";
public string MatchString = string.Empty;
/// <summary>
/// What type of organ is required for this surgery?

View File

@@ -18,12 +18,11 @@ public sealed partial class SurgeryRemoveMarkingStepComponent : Component
/// against the entity to validate that the marking is present.
/// </summary>
[DataField]
public String MatchString = "";
public string MatchString = string.Empty;
/// <summary>
/// Will this step spawn an item as a result of removing the markings? If so, which?
/// </summary>
[DataField]
public EntProtoId? ItemSpawn = default!;
}

View File

@@ -1752,56 +1752,4 @@
- type: MaterialStorage
whitelist:
tags:
- PrizeTicket
- type: entity
id: MedicalBiofabricator
parent: [BaseLathe, BaseMaterialSiloUtilizer]
name: medical biofabricator
description: Produces organs and other organic matter that can be surgically grafted onto patients with biomass.
components:
- type: Sprite
sprite: _Shitmed/Structures/Machines/limbgrower.rsi
snapCardinals: true
layers:
- state: limbgrower_idleoff
map: ["enum.LatheVisualLayers.IsRunning"]
# - state: limbgrower_idleoff
# shader: unshaded
# map: ["enum.PowerDeviceVisualLayers.Powered"]
# - state: inserting
# map: ["enum.MaterialStorageVisualLayers.Inserting"]
# - state: panel
# map: ["enum.WiresVisualLayers.MaintenancePanel"]
- type: Machine
board: MedicalBiofabMachineBoard
- type: MaterialStorage
whitelist:
tags:
- Sheet
- RawMaterial
- Ingot
- Wooden
- ClothMade
- Gauze
- Metal
- type: Lathe
idleState: limbgrower_idleoff
runningState: limbgrower_idleon
staticRecipes:
- SynthLiver
- SynthHeart
- SynthLungs
- SynthEyes
- SynthLeftLeg
- SynthRightLeg
- SynthLeftFoot
- SynthRightFoot
- SynthLeftArm
- SynthRightArm
- SynthLeftHand
- SynthRightHand
- type: EmagLatheRecipes
emagStaticRecipes:
- PizzaLeftArm
- PizzaRightArm
- PrizeTicket

View File

@@ -6,7 +6,7 @@
- type: guideEntry
id: SpaceLaw
name: guide-entry-rules-space-law
priority: 60
priority: 1
text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SpaceLaw.xml"
children:
- SpaceLawControlledSubstances
@@ -36,4 +36,4 @@
id: SpaceLawCrimeList
name: guide-entry-rules-sl-crime-list
priority: 50
text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml"
text: "/ServerInfo/Guidebook/ServerRules/SpaceLaw/SLCrimeList.xml"

View File

@@ -6,6 +6,7 @@
- Forensics
- Defusal
- CriminalRecords
- SpaceLaw
- type: guideEntry
id: Forensics

View File

@@ -3,7 +3,6 @@
name: guide-entry-ss14
text: "/ServerInfo/Guidebook/SpaceStation14.xml"
children:
- SpaceLaw
- Controls
- Jobs
- Survival

View File

@@ -19,8 +19,6 @@
- Folder
components:
- AnomalyScanner
- AnomalyLocatorUnpowered
- AnomalyLocatorWideUnpowered
- NodeScanner
- Flash

View File

@@ -0,0 +1,46 @@
- type: entity
parent: BaseLathe
id: MedicalBiofabricator
name: medical biofabricator
description: Produces organs and other organic matter that can be surgically grafted onto patients with biomass.
components:
- type: Sprite
sprite: _Shitmed/Structures/Machines/limbgrower.rsi
snapCardinals: true
layers:
- state: limbgrower_idleoff
map: ["enum.LatheVisualLayers.IsRunning"]
# - state: limbgrower_idleoff
# shader: unshaded
# map: ["enum.PowerDeviceVisualLayers.Powered"]
# - state: inserting
# map: ["enum.MaterialStorageVisualLayers.Inserting"]
# - state: panel
# map: ["enum.WiresVisualLayers.MaintenancePanel"]
- type: Machine
board: MedicalBiofabMachineBoard
- type: MaterialStorage
whitelist:
tags:
- Sheet
- RawMaterial
- type: Lathe
idleState: limbgrower_idleoff
runningState: limbgrower_idleon
staticRecipes:
- SynthLiver
- SynthHeart
- SynthLungs
- SynthEyes
- SynthLeftLeg
- SynthRightLeg
- SynthLeftFoot
- SynthRightFoot
- SynthLeftArm
- SynthRightArm
- SynthLeftHand
- SynthRightHand
- type: EmagLatheRecipes
emagStaticRecipes:
- PizzaLeftArm
- PizzaRightArm