Merge branch 'master' of https://github.com/space-wizards/space-station-14 into map-load-refactor

This commit is contained in:
ElectroJr
2025-02-16 16:52:51 +13:00
1624 changed files with 385006 additions and 263837 deletions

View File

@@ -344,6 +344,9 @@ resharper_keep_existing_attribute_arrangement = true
resharper_wrap_chained_binary_patterns = chop_if_long resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long resharper_wrap_chained_method_calls = chop_if_long
resharper_csharp_trailing_comma_in_multiline_lists = true resharper_csharp_trailing_comma_in_multiline_lists = true
resharper_csharp_qualified_using_at_nested_scope = false
resharper_csharp_prefer_qualified_reference = false
resharper_csharp_allow_alias = false
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}] [*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
indent_size = 2 indent_size = 2

5
.envrc
View File

@@ -1,4 +1,5 @@
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then set -e
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM="
fi fi
use flake use flake

45
.github/workflows/publish-testing.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Publish Testing
concurrency:
group: publish-testing
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.6.0
with:
submodules: 'recursive'
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
- name: Get Engine Tag
run: |
cd RobustToolbox
git fetch --depth=1
- name: Install dependencies
run: dotnet restore
- name: Build Packaging
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Publish version
run: Tools/publish_multi_request.py --fork-id wizards-testing
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}

View File

@@ -12,12 +12,12 @@ You want to handle the Build, Clean and Rebuild tasks to prevent missing task er
If you want to learn more about these kinds of things, check out Microsoft's official documentation about MSBuild: If you want to learn more about these kinds of things, check out Microsoft's official documentation about MSBuild:
https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
--> -->
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Python>python3</Python> <Python>python3</Python>
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python> <Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
<ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid> <ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid>
<TargetFrameworkMoniker>.NETFramework, Version=v4.7.2</TargetFrameworkMoniker> <TargetFramework>net4.7.2</TargetFramework>
<RestorePackages>false</RestorePackages> <RestorePackages>false</RestorePackages>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -47,7 +47,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown(); PoolManager.Shutdown();
} }
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog" }; public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
[ParamsSource(nameof(MapsSource))] [ParamsSource(nameof(MapsSource))]
public string Map; public string Map;

View File

@@ -88,6 +88,7 @@ namespace Content.Client.Actions
return; return;
component.Whitelist = state.Whitelist; component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
component.CanTargetSelf = state.CanTargetSelf; component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityTargetActionComponent>(uid, component, state); BaseHandleState<EntityTargetActionComponent>(uid, component, state);
} }
@@ -137,6 +138,7 @@ namespace Content.Client.Actions
component.Priority = state.Priority; component.Priority = state.Priority;
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid); component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
component.RaiseOnUser = state.RaiseOnUser; component.RaiseOnUser = state.RaiseOnUser;
component.RaiseOnAction = state.RaiseOnAction;
component.AutoPopulate = state.AutoPopulate; component.AutoPopulate = state.AutoPopulate;
component.Temporary = state.Temporary; component.Temporary = state.Temporary;
component.ItemIconStyle = state.ItemIconStyle; component.ItemIconStyle = state.ItemIconStyle;

View File

@@ -11,6 +11,8 @@ public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
protected override void Open() protected override void Open()
{ {
base.Open();
_menu = new AtmosAlertsComputerWindow(this, Owner); _menu = new AtmosAlertsComputerWindow(this, Owner);
_menu.OpenCentered(); _menu.OpenCentered();
_menu.OnClose += Close; _menu.OnClose += Close;

View File

@@ -16,6 +16,7 @@ namespace Content.Client.Atmos.EntitySystems
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SpriteSystem _spriteSys = default!; [Dependency] private readonly SpriteSystem _spriteSys = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
private GasTileOverlay _overlay = default!; private GasTileOverlay _overlay = default!;
@@ -25,7 +26,7 @@ namespace Content.Client.Atmos.EntitySystems
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate); SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState); SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys); _overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
_overlayMan.AddOverlay(_overlay); _overlayMan.AddOverlay(_overlay);
} }

View File

@@ -21,6 +21,7 @@ namespace Content.Client.Atmos.Overlays
{ {
private readonly IEntityManager _entManager; private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager; private readonly IMapManager _mapManager;
private readonly SharedTransformSystem _xformSys;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld; public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader; private readonly ShaderInstance _shader;
@@ -46,10 +47,11 @@ namespace Content.Client.Atmos.Overlays
public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys) public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
{ {
_entManager = entManager; _entManager = entManager;
_mapManager = IoCManager.Resolve<IMapManager>(); _mapManager = IoCManager.Resolve<IMapManager>();
_xformSys = xformSys;
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance(); _shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
ZIndex = GasOverlayZIndex; ZIndex = GasOverlayZIndex;
@@ -158,7 +160,8 @@ namespace Content.Client.Atmos.Overlays
_fireFrameCounter, _fireFrameCounter,
_shader, _shader,
overlayQuery, overlayQuery,
xformQuery); xformQuery,
_xformSys);
var mapUid = _mapManager.GetMapEntityId(args.MapId); var mapUid = _mapManager.GetMapEntityId(args.MapId);
@@ -180,7 +183,8 @@ namespace Content.Client.Atmos.Overlays
int[] fireFrameCounter, int[] fireFrameCounter,
ShaderInstance shader, ShaderInstance shader,
EntityQuery<GasTileOverlayComponent> overlayQuery, EntityQuery<GasTileOverlayComponent> overlayQuery,
EntityQuery<TransformComponent> xformQuery) state) => EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSys) state) =>
{ {
if (!state.overlayQuery.TryGetComponent(uid, out var comp) || if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
!state.xformQuery.TryGetComponent(uid, out var gridXform)) !state.xformQuery.TryGetComponent(uid, out var gridXform))
@@ -188,7 +192,7 @@ namespace Content.Client.Atmos.Overlays
return true; return true;
} }
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(); var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
state.drawHandle.SetTransform(worldMatrix); state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i( var localBounds = new Box2i(

View File

@@ -1,6 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io" <DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinSize="280 160" Title="Temperature Control Unit"> MinSize="280 160" Title="{Loc comp-space-heater-ui-title}">
<BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10"> <BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">

View File

@@ -3,13 +3,15 @@ using Content.Shared.Buckle;
using Content.Shared.Buckle.Components; using Content.Shared.Buckle.Components;
using Content.Shared.Rotation; using Content.Shared.Rotation;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.GameStates; using Robust.Client.Graphics;
namespace Content.Client.Buckle; namespace Content.Client.Buckle;
internal sealed class BuckleSystem : SharedBuckleSystem internal sealed class BuckleSystem : SharedBuckleSystem
{ {
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!; [Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -17,6 +19,8 @@ internal sealed class BuckleSystem : SharedBuckleSystem
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange); SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent); SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
SubscribeLocalEvent<BuckleComponent, BuckledEvent>(OnBuckledEvent);
SubscribeLocalEvent<BuckleComponent, UnbuckledEvent>(OnUnbuckledEvent);
} }
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args) private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
@@ -28,13 +32,21 @@ internal sealed class BuckleSystem : SharedBuckleSystem
// This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking // This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking
// sprite rendering for entity layers & direction dependent sorting. // sprite rendering for entity layers & direction dependent sorting.
// Future notes:
// Right now this doesn't handle: other grids, other grids rotating, the camera rotation changing, and many other fun rotation specific things
// The entire thing should be a concern of the engine, or something engine helps to implement properly.
// Give some of the sprite rotations their own drawdepth, maybe as an offset within the rsi, or something like this
// And we won't ever need to set the draw depth manually
if (args.NewRotation == args.OldRotation) if (args.NewRotation == args.OldRotation)
return; return;
if (!TryComp<SpriteComponent>(uid, out var strapSprite)) if (!TryComp<SpriteComponent>(uid, out var strapSprite))
return; return;
var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North; var angle = _xformSystem.GetWorldRotation(uid) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
var isNorth = angle.GetCardinalDir() == Direction.North;
foreach (var buckledEntity in component.BuckledEntities) foreach (var buckledEntity in component.BuckledEntities)
{ {
if (!TryComp<BuckleComponent>(buckledEntity, out var buckle)) if (!TryComp<BuckleComponent>(buckledEntity, out var buckle))
@@ -45,6 +57,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
if (isNorth) if (isNorth)
{ {
// This will only assign if empty, it won't get overwritten by new depth on multiple calls, which do happen easily
buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth; buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth;
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1; buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
} }
@@ -56,6 +69,42 @@ internal sealed class BuckleSystem : SharedBuckleSystem
} }
} }
/// <summary>
/// Lower the draw depth of the buckled entity without needing for the strap entity to rotate/move.
/// Only do so when the entity is facing screen-local north
/// </summary>
private void OnBuckledEvent(Entity<BuckleComponent> ent, ref BuckledEvent args)
{
if (!TryComp<SpriteComponent>(args.Strap, out var strapSprite))
return;
if (!TryComp<SpriteComponent>(ent.Owner, out var buckledSprite))
return;
var angle = _xformSystem.GetWorldRotation(args.Strap) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
if (angle.GetCardinalDir() != Direction.North)
return;
ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth;
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
}
/// <summary>
/// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling.
/// </summary>
private void OnUnbuckledEvent(Entity<BuckleComponent> ent, ref UnbuckledEvent args)
{
if (!TryComp<SpriteComponent>(ent.Owner, out var buckledSprite))
return;
if (!ent.Comp.OriginalDrawDepth.HasValue)
return;
buckledSprite.DrawDepth = ent.Comp.OriginalDrawDepth.Value;
ent.Comp.OriginalDrawDepth = null;
}
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args) private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
{ {
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals)) if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))

View File

@@ -39,6 +39,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
if (message is not CargoBountyConsoleState state) if (message is not CargoBountyConsoleState state)
return; return;
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip); _menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
} }
} }

View File

@@ -0,0 +1,22 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Margin="10 10 10 0"
HorizontalExpand="True">
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<RichTextLabel Name="RewardLabel"/>
<RichTextLabel Name="ManifestLabel"/>
</BoxContainer>
<BoxContainer Orientation="Vertical" MinWidth="120" Margin="0 0 10 0">
<RichTextLabel Name="TimestampLabel" HorizontalAlignment="Right" />
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
<customControls:HSeparator Margin="5 10 5 10"/>
<RichTextLabel Name="NoticeLabel" />
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -0,0 +1,49 @@
using Content.Client.Message;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Cargo.UI;
[GenerateTypedNameReferences]
public sealed partial class BountyHistoryEntry : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public BountyHistoryEntry(CargoBountyHistoryData bounty)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
return;
var items = new List<string>();
foreach (var entry in bountyPrototype.Entries)
{
items.Add(Loc.GetString("bounty-console-manifest-entry",
("amount", entry.Amount),
("item", Loc.GetString(entry.Name))));
}
ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
TimestampLabel.SetMarkup(bounty.Timestamp.ToString(@"hh\:mm\:ss"));
if (bounty.Result == CargoBountyHistoryData.BountyResult.Completed)
{
NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label"));
}
else
{
NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label",
("id", bounty.ActorName ?? "")));
}
}
}

View File

@@ -11,15 +11,28 @@
<PanelContainer.PanelOverride> <PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" /> <gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride> </PanelContainer.PanelOverride>
<ScrollContainer HScrollEnabled="False" <TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True">
HorizontalExpand="True" <ScrollContainer HScrollEnabled="False"
VerticalExpand="True"> HorizontalExpand="True"
<BoxContainer Name="BountyEntriesContainer" VerticalExpand="True">
Orientation="Vertical" <BoxContainer Name="BountyEntriesContainer"
VerticalExpand="True" Orientation="Vertical"
HorizontalExpand="True"> VerticalExpand="True"
</BoxContainer> HorizontalExpand="True" />
</ScrollContainer> </ScrollContainer>
<ScrollContainer HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True">
<Label Name="NoHistoryLabel"
Text="{Loc 'bounty-console-history-empty-label'}"
Visible="False"
Align="Center" />
<BoxContainer Name="BountyHistoryContainer"
Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True" />
</ScrollContainer>
</TabContainer>
</PanelContainer> </PanelContainer>
<!-- Footer --> <!-- Footer -->
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">

View File

@@ -15,9 +15,12 @@ public sealed partial class CargoBountyMenu : FancyWindow
public CargoBountyMenu() public CargoBountyMenu()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label"));
MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label"));
} }
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip) public void UpdateEntries(List<CargoBountyData> bounties, List<CargoBountyHistoryData> history, TimeSpan untilNextSkip)
{ {
BountyEntriesContainer.Children.Clear(); BountyEntriesContainer.Children.Clear();
foreach (var b in bounties) foreach (var b in bounties)
@@ -32,5 +35,21 @@ public sealed partial class CargoBountyMenu : FancyWindow
{ {
MinHeight = 10 MinHeight = 10
}); });
BountyHistoryContainer.Children.Clear();
if (history.Count == 0)
{
NoHistoryLabel.Visible = true;
}
else
{
NoHistoryLabel.Visible = false;
// Show the history in reverse, so last entry is first in the list
for (var i = history.Count - 1; i >= 0; i--)
{
BountyHistoryContainer.AddChild(new BountyHistoryEntry(history[i]));
}
}
} }
} }

View File

@@ -8,6 +8,8 @@ using JetBrains.Annotations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Console; using Robust.Shared.Console;
namespace Content.Client.Changelog namespace Content.Client.Changelog
@@ -15,8 +17,9 @@ namespace Content.Client.Changelog
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ChangelogWindow : FancyWindow public sealed partial class ChangelogWindow : FancyWindow
{ {
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly ChangelogManager _changelog = default!; [Dependency] private readonly ChangelogManager _changelog = default!;
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public ChangelogWindow() public ChangelogWindow()
{ {
@@ -67,8 +70,22 @@ namespace Content.Client.Changelog
Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}")); Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}"));
} }
var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0); // Try to get the current version from the build.json file
VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString())); var version = _cfg.GetCVar(CVars.BuildVersion);
var forkId = _cfg.GetCVar(CVars.BuildForkId);
var versionText = Loc.GetString("changelog-version-unknown");
// Make sure these aren't empty, like in a dev env
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(forkId))
{
versionText = Loc.GetString("changelog-version-tag",
("fork", forkId),
("version", version[..7])); // Only show the first 7 characters
}
// if else statements are ugly, shut up
VersionLabel.Text = versionText;
TabsUpdated(); TabsUpdated();
} }

View File

@@ -1,4 +1,4 @@
<ui:RadialMenu xmlns="https://spacestation14.io" <ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton" BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton" CloseButtonStyleClass="RadialMenuCloseButton"
@@ -7,25 +7,25 @@
MinSize="450 450"> MinSize="450 450">
<!-- Main --> <!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False"> <ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Emotes/vocal.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Emotes/vocal.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
</ui:RadialContainer> </ui:RadialContainer>
<!-- General --> <!-- General -->
<ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Vocal --> <!-- Vocal -->
<ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Hands --> <!-- Hands -->
<ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
</ui:RadialMenu> </ui:RadialMenu>

View File

@@ -50,7 +50,6 @@ public sealed partial class EmotesMenu : RadialMenu
var button = new EmoteMenuButton var button = new EmoteMenuButton
{ {
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f), SetSize = new Vector2(64f, 64f),
ToolTip = Loc.GetString(emote.Name), ToolTip = Loc.GetString(emote.Name),
ProtoId = emote.ID, ProtoId = emote.ID,
@@ -106,7 +105,7 @@ public sealed partial class EmotesMenu : RadialMenu
} }
public sealed class EmoteMenuButton : RadialMenuTextureButton public sealed class EmoteMenuButton : RadialMenuTextureButtonWithSector
{ {
public ProtoId<EmotePrototype> ProtoId { get; set; } public ProtoId<EmotePrototype> ProtoId { get; set; }
} }

View File

@@ -20,7 +20,7 @@ namespace Content.Client.Clickable
"/Textures/Logo", "/Textures/Logo",
}; };
private const float Threshold = 0.25f; private const float Threshold = 0.1f;
private const int ClickRadius = 2; private const int ClickRadius = 2;
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;

View File

@@ -22,7 +22,7 @@ public sealed class CrewManifestSection : BoxContainer
AddChild(new Label() AddChild(new Label()
{ {
StyleClasses = { "LabelBig" }, StyleClasses = { "LabelBig" },
Text = Loc.GetString($"department-{section.ID}") Text = Loc.GetString(section.Name)
}); });
var gridContainer = new GridContainer() var gridContainer = new GridContainer()

View File

@@ -39,6 +39,8 @@ public sealed class CriminalRecordsConsoleBoundUserInterface : BoundUserInterfac
SendMessage(new CriminalRecordChangeStatus(status, null)); SendMessage(new CriminalRecordChangeStatus(status, null));
_window.OnDialogConfirmed += (status, reason) => _window.OnDialogConfirmed += (status, reason) =>
SendMessage(new CriminalRecordChangeStatus(status, reason)); SendMessage(new CriminalRecordChangeStatus(status, reason));
_window.OnStatusFilterPressed += (statusFilter) =>
SendMessage(new CriminalRecordSetStatusFilter(statusFilter));
_window.OnHistoryUpdated += UpdateHistory; _window.OnHistoryUpdated += UpdateHistory;
_window.OnHistoryClosed += () => _historyWindow?.Close(); _window.OnHistoryClosed += () => _historyWindow?.Close();
_window.OnClose += Close; _window.OnClose += Close;

View File

@@ -1,36 +1,140 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'criminal-records-console-window-title'}" Title="{Loc 'criminal-records-console-window-title'}"
MinSize="660 400"> MinSize="695 440">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<!-- Record search bar <BoxContainer Name="AllList"
TODO: make this into a control shared with general records --> Orientation="Vertical"
<BoxContainer Margin="5 5 5 10" HorizontalExpand="true" VerticalAlignment="Center"> VerticalExpand="True"
<OptionButton Name="FilterType" MinWidth="200" Margin="0 0 10 0"/> <!-- Populated in constructor --> HorizontalExpand="True"
<LineEdit Name="FilterText" PlaceHolder="{Loc 'criminal-records-filter-placeholder'}" HorizontalExpand="True"/> Margin="8">
</BoxContainer> <!-- Record search bar -->
<BoxContainer Orientation="Horizontal" VerticalExpand="True"> <BoxContainer Margin="5 5 5 10"
<!-- Record listing --> HorizontalExpand="true"
<BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250"> VerticalAlignment="Center">
<Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/> <OptionButton Name="FilterType"
<Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/> MinWidth="250"
<ScrollContainer VerticalExpand="True"> Margin="0 0 10 0" />
<ItemList Name="RecordListing"/> <!-- Populated when loading state --> <!-- Populated in constructor -->
</ScrollContainer> <LineEdit Name="FilterText"
PlaceHolder="{Loc 'criminal-records-filter-placeholder'}"
HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/> <BoxContainer Orientation="Horizontal"
<!-- Selected record info --> VerticalExpand="True">
<BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False"> <!-- Record listing -->
<Label Name="PersonName" StyleClasses="LabelBig"/> <BoxContainer Orientation="Vertical"
<Label Name="PersonPrints"/> Margin="10 10"
<Label Name="PersonDna"/> MinWidth="250"
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" /> MaxWidth="250">
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5"> <Label Name="RecordListingTitle"
<Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/> Text="{Loc 'criminal-records-console-records-list-title'}"
<OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor --> HorizontalExpand="True"
Align="Center" />
<Label Name="NoRecords"
Text="{Loc 'criminal-records-console-no-records'}"
HorizontalExpand="True"
Align="Center"
FontColorOverride="DarkGray" />
<ScrollContainer VerticalExpand="True">
<ItemList Name="RecordListing" />
<!-- Populated when loading state -->
</ScrollContainer>
</BoxContainer> </BoxContainer>
<RichTextLabel Name="WantedReason" Visible="False"/> <Label Name="RecordUnselected"
<Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/> Text="{Loc 'criminal-records-console-select-record-info'}"
HorizontalExpand="True"
Align="Center"
FontColorOverride="DarkGray" />
<!-- Selected record info -->
<BoxContainer Name="PersonContainer"
Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
Margin="5"
Visible="False">
<Label Name="PersonName"
Margin="0 0 0 5"
StyleClasses="LabelBig" />
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
FontColorOverride="DarkGray" />
<TextureRect Name="PersonJobIcon"
TextureScale="2 2"
Margin="6 0"
VerticalAlignment="Center" />
<Label Name="PersonJob" />
</BoxContainer>
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<Label Text="{Loc 'general-station-record-prints-filter'}"
FontColorOverride="DarkGray" />
<Label Text=":"
Margin="0 0 6 0"
FontColorOverride="DarkGray" />
<Label Name="PersonPrints" />
</BoxContainer>
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<Label Text="{Loc 'general-station-record-dna-filter'}"
FontColorOverride="DarkGray" />
<Label Text=":"
Margin="0 0 6 0"
FontColorOverride="DarkGray" />
<Label Name="PersonDna" />
</BoxContainer>
<PanelContainer StyleClasses="LowDivider"
Margin="0 5 0 5" />
<BoxContainer Orientation="Horizontal"
Margin="0 5 0 5">
<Label Name="StatusLabel"
Text="{Loc 'criminal-records-console-status'}"
FontColorOverride="DarkGray" />
<Label Text=":"
FontColorOverride="DarkGray" />
<Label Name="PersonStatus"
FontColorOverride="DarkGray" />
<AnimatedTextureRect Name="PersonStatusTX"
Margin="8 0" />
<OptionButton Name="StatusOptionButton"
MinWidth="130" />
<!-- Populated in constructor -->
</BoxContainer>
<RichTextLabel Name="WantedReason"
Visible="False"
MaxWidth="425" />
<Button Name="HistoryButton"
Text="{Loc 'criminal-records-console-crime-history'}"
Margin="0 5" />
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<OptionButton
Name="CrewListFilter"
MinWidth="250"
Margin="10 0 10 0" />
</BoxContainer>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal"
Margin="10 2 5 0"
VerticalAlignment="Bottom">
<Label Text="{Loc 'criminal-records-console-flavor-left'}"
StyleClasses="WindowFooterText" />
<Label Text="{Loc 'criminal-records-console-flavor-right'}"
StyleClasses="WindowFooterText"
HorizontalAlignment="Right"
HorizontalExpand="True"
Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark"
Stretch="KeepAspectCentered"
VerticalAlignment="Center"
HorizontalAlignment="Right"
SetSize="19 19" />
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>

View File

@@ -13,6 +13,9 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using System.Numerics;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
namespace Content.Client.CriminalRecords; namespace Content.Client.CriminalRecords;
@@ -24,6 +27,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private readonly IPrototypeManager _proto; private readonly IPrototypeManager _proto;
private readonly IRobustRandom _random; private readonly IRobustRandom _random;
private readonly AccessReaderSystem _accessReader; private readonly AccessReaderSystem _accessReader;
[Dependency] private readonly IEntityManager _entManager = default!;
private readonly SpriteSystem _spriteSystem;
public readonly EntityUid Console; public readonly EntityUid Console;
@@ -33,10 +38,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
public Action<uint?>? OnKeySelected; public Action<uint?>? OnKeySelected;
public Action<StationRecordFilterType, string>? OnFiltersChanged; public Action<StationRecordFilterType, string>? OnFiltersChanged;
public Action<SecurityStatus>? OnStatusSelected; public Action<SecurityStatus>? OnStatusSelected;
public Action<uint>? OnCheckStatus;
public Action<CriminalRecord, bool, bool>? OnHistoryUpdated; public Action<CriminalRecord, bool, bool>? OnHistoryUpdated;
public Action? OnHistoryClosed; public Action? OnHistoryClosed;
public Action<SecurityStatus, string>? OnDialogConfirmed; public Action<SecurityStatus, string>? OnDialogConfirmed;
public Action<SecurityStatus>? OnStatusFilterPressed;
private uint _maxLength; private uint _maxLength;
private bool _access; private bool _access;
private uint? _selectedKey; private uint? _selectedKey;
@@ -46,6 +53,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private StationRecordFilterType _currentFilterType; private StationRecordFilterType _currentFilterType;
private SecurityStatus _currentCrewListFilter;
public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader) public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -55,10 +64,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
_proto = prototypeManager; _proto = prototypeManager;
_random = robustRandom; _random = robustRandom;
_accessReader = accessReader; _accessReader = accessReader;
IoCManager.InjectDependencies(this);
_spriteSystem = _entManager.System<SpriteSystem>();
_maxLength = maxLength; _maxLength = maxLength;
_currentFilterType = StationRecordFilterType.Name; _currentFilterType = StationRecordFilterType.Name;
_currentCrewListFilter = SecurityStatus.None;
OpenCentered(); OpenCentered();
foreach (var item in Enum.GetValues<StationRecordFilterType>()) foreach (var item in Enum.GetValues<StationRecordFilterType>())
@@ -71,6 +84,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
AddStatusSelect(status); AddStatusSelect(status);
} }
//Populate status to filter crew list
foreach (var item in Enum.GetValues<SecurityStatus>())
{
CrewListFilter.AddItem(GetCrewListFilterLocals(item), (int)item);
}
OnClose += () => _reasonDialog?.Close(); OnClose += () => _reasonDialog?.Close();
RecordListing.OnItemSelected += args => RecordListing.OnItemSelected += args =>
@@ -97,6 +116,20 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
} }
}; };
//Select Status to filter crew
CrewListFilter.OnItemSelected += eventArgs =>
{
var type = (SecurityStatus)eventArgs.Id;
if (_currentCrewListFilter != type)
{
_currentCrewListFilter = type;
StatusFilterPressed(type);
}
};
FilterText.OnTextEntered += args => FilterText.OnTextEntered += args =>
{ {
FilterListingOfRecords(args.Text); FilterListingOfRecords(args.Text);
@@ -104,16 +137,21 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
StatusOptionButton.OnItemSelected += args => StatusOptionButton.OnItemSelected += args =>
{ {
SetStatus((SecurityStatus) args.Id); SetStatus((SecurityStatus)args.Id);
}; };
HistoryButton.OnPressed += _ => HistoryButton.OnPressed += _ =>
{ {
if (_selectedRecord is {} record) if (_selectedRecord is { } record)
OnHistoryUpdated?.Invoke(record, _access, true); OnHistoryUpdated?.Invoke(record, _access, true);
}; };
} }
public void StatusFilterPressed(SecurityStatus statusSelected)
{
OnStatusFilterPressed?.Invoke(statusSelected);
}
public void UpdateState(CriminalRecordsConsoleState state) public void UpdateState(CriminalRecordsConsoleState state)
{ {
if (state.Filter != null) if (state.Filter != null)
@@ -129,10 +167,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
} }
} }
if (state.FilterStatus != _currentCrewListFilter)
{
_currentCrewListFilter = state.FilterStatus;
}
_selectedKey = state.SelectedKey; _selectedKey = state.SelectedKey;
FilterType.SelectId((int)_currentFilterType); FilterType.SelectId((int)_currentFilterType);
CrewListFilter.SelectId((int)_currentCrewListFilter);
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0; NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
PopulateRecordListing(state.RecordListing); PopulateRecordListing(state.RecordListing);
@@ -179,7 +221,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
// in parallel to synchronize the items in RecordListing with `entries`. // in parallel to synchronize the items in RecordListing with `entries`.
int i = RecordListing.Count - 1; int i = RecordListing.Count - 1;
int j = entries.Count - 1; int j = entries.Count - 1;
while(i >= 0 && j >= 0) while (i >= 0 && j >= 0)
{ {
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal); var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
if (strcmp == 0) if (strcmp == 0)
@@ -212,23 +254,44 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
// And finally, any remaining items in `entries`, don't exist in RecordListing. Create them. // And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
while (j >= 0) while (j >= 0)
{ {
RecordListing.Insert(0, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key}); RecordListing.Insert(0, new ItemList.Item(RecordListing){ Text = entries[j].Value, Metadata = entries[j].Key });
j--; j--;
} }
} }
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord) private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
{ {
var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown");
var na = Loc.GetString("generic-not-available-shorthand"); var na = Loc.GetString("generic-not-available-shorthand");
PersonName.Text = stationRecord.Name; PersonName.Text = stationRecord.Name;
PersonPrints.Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", stationRecord.Fingerprint ?? na)); PersonJob.Text = stationRecord.JobTitle ?? na;
PersonDna.Text = Loc.GetString("general-station-record-console-record-dna", ("dna", stationRecord.DNA ?? na));
StatusOptionButton.SelectId((int) criminalRecord.Status); // Job icon
if (criminalRecord.Reason is {} reason) if (_proto.TryIndex<JobIconPrototype>(stationRecord.JobIcon, out var proto))
{
PersonJobIcon.Texture = _spriteSystem.Frame0(proto.Icon);
}
PersonPrints.Text = stationRecord.Fingerprint ?? Loc.GetString("generic-not-available-shorthand");
PersonDna.Text = stationRecord.DNA ?? Loc.GetString("generic-not-available-shorthand");
if (criminalRecord.Status != SecurityStatus.None)
{
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/security_icons.rsi"), GetStatusIcon(criminalRecord.Status));
}
PersonStatusTX.SetFromSpriteSpecifier(specifier);
PersonStatusTX.DisplayRect.TextureScale = new Vector2(3f, 3f);
StatusOptionButton.SelectId((int)criminalRecord.Status);
if (criminalRecord.Reason is { } reason)
{ {
var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason")); var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason"));
if (criminalRecord.Status == SecurityStatus.Suspected)
{
message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-suspected-reason"));
}
message.AddText($": {reason}"); message.AddText($": {reason}");
WantedReason.SetMessage(message); WantedReason.SetMessage(message);
WantedReason.Visible = true; WantedReason.Visible = true;
} }
@@ -288,9 +351,37 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
_reasonDialog.OnClose += () => { _reasonDialog = null; }; _reasonDialog.OnClose += () => { _reasonDialog = null; };
} }
private string GetStatusIcon(SecurityStatus status)
{
return status switch
{
SecurityStatus.Paroled => "hud_paroled",
SecurityStatus.Wanted => "hud_wanted",
SecurityStatus.Detained => "hud_incarcerated",
SecurityStatus.Discharged => "hud_discharged",
SecurityStatus.Suspected => "hud_suspected",
_ => "SecurityIconNone"
};
}
private string GetTypeFilterLocals(StationRecordFilterType type) private string GetTypeFilterLocals(StationRecordFilterType type)
{ {
return Loc.GetString($"criminal-records-{type.ToString().ToLower()}-filter"); return Loc.GetString($"criminal-records-{type.ToString().ToLower()}-filter");
} }
private string GetCrewListFilterLocals(SecurityStatus type)
{
string result;
// If "NONE" override to "show all"
if (type == SecurityStatus.None)
{
result = Loc.GetString("criminal-records-console-show-all");
}
else
{
result = Loc.GetString($"criminal-records-status-{type.ToString().ToLower()}");
}
return result;
}
} }

View File

@@ -5,17 +5,24 @@ namespace Content.Client.Dice;
public sealed class DiceSystem : SharedDiceSystem public sealed class DiceSystem : SharedDiceSystem
{ {
protected override void UpdateVisuals(EntityUid uid, DiceComponent? die = null) public override void Initialize()
{ {
if (!Resolve(uid, ref die) || !TryComp(uid, out SpriteComponent? sprite)) base.Initialize();
SubscribeLocalEvent<DiceComponent, AfterAutoHandleStateEvent>(OnDiceAfterHandleState);
}
private void OnDiceAfterHandleState(Entity<DiceComponent> entity, ref AfterAutoHandleStateEvent args)
{
if (!TryComp<SpriteComponent>(entity, out var sprite))
return; return;
// TODO maybe just move each diue to its own RSI? // TODO maybe just move each die to its own RSI?
var state = sprite.LayerGetState(0).Name; var state = sprite.LayerGetState(0).Name;
if (state == null) if (state == null)
return; return;
var prefix = state.Substring(0, state.IndexOf('_')); var prefix = state.Substring(0, state.IndexOf('_'));
sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}"); sprite.LayerSetState(0, $"{prefix}_{entity.Comp.CurrentValue}");
} }
} }

View File

@@ -30,7 +30,9 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
_overlay.RemoveOverlay<DoAfterOverlay>(); _overlay.RemoveOverlay<DoAfterOverlay>();
} }
#pragma warning disable RA0028 // No base call in overriden function
public override void Update(float frameTime) public override void Update(float frameTime)
#pragma warning restore RA0028 // No base call in overriden function
{ {
// Currently this only predicts do afters initiated by the player. // Currently this only predicts do afters initiated by the player.

View File

@@ -1,6 +1,7 @@
using Content.Client.Wires.Visualizers; using Content.Client.Wires.Visualizers;
using Content.Shared.Doors.Components; using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems; using Content.Shared.Doors.Systems;
using Content.Shared.Power;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
@@ -84,7 +85,8 @@ public sealed class AirlockSystem : SharedAirlockSystem
if (!_appearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component)) if (!_appearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
state = DoorState.Closed; state = DoorState.Closed;
if (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.Powered, out var powered, args.Component) && powered) if (_appearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component)
&& powered)
{ {
boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component) boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component)
&& lights && (state == DoorState.Closed || state == DoorState.Welded); && lights && (state == DoorState.Closed || state == DoorState.Welded);

View File

@@ -21,112 +21,131 @@ public sealed class DoorSystem : SharedDoorSystem
protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args) protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args)
{ {
var comp = ent.Comp; var comp = ent.Comp;
comp.OpenSpriteStates = new(2); comp.OpenSpriteStates = new List<(DoorVisualLayers, string)>(2);
comp.ClosedSpriteStates = new(2); comp.ClosedSpriteStates = new List<(DoorVisualLayers, string)>(2);
comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState)); comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState));
comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState)); comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState));
comp.OpeningAnimation = new Animation() comp.OpeningAnimation = new Animation
{ {
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime), Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
AnimationTracks = AnimationTracks =
{ {
new AnimationTrackSpriteFlick() new AnimationTrackSpriteFlick
{ {
LayerKey = DoorVisualLayers.Base, LayerKey = DoorVisualLayers.Base,
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningSpriteState, 0f) } KeyFrames =
} {
new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningSpriteState, 0f),
},
},
}, },
}; };
comp.ClosingAnimation = new Animation() comp.ClosingAnimation = new Animation
{ {
Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime), Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
AnimationTracks = AnimationTracks =
{ {
new AnimationTrackSpriteFlick() new AnimationTrackSpriteFlick
{ {
LayerKey = DoorVisualLayers.Base, LayerKey = DoorVisualLayers.Base,
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingSpriteState, 0f) } KeyFrames =
} {
new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingSpriteState, 0f),
},
},
}, },
}; };
comp.EmaggingAnimation = new Animation () comp.EmaggingAnimation = new Animation
{ {
Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime), Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
AnimationTracks = AnimationTracks =
{ {
new AnimationTrackSpriteFlick() new AnimationTrackSpriteFlick
{ {
LayerKey = DoorVisualLayers.BaseUnlit, LayerKey = DoorVisualLayers.BaseUnlit,
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.EmaggingSpriteState, 0f) } KeyFrames =
} {
new AnimationTrackSpriteFlick.KeyFrame(comp.EmaggingSpriteState, 0f),
},
},
}, },
}; };
} }
private void OnAppearanceChange(EntityUid uid, DoorComponent comp, ref AppearanceChangeEvent args) private void OnAppearanceChange(Entity<DoorComponent> entity, ref AppearanceChangeEvent args)
{ {
if (args.Sprite == null) if (args.Sprite == null)
return; return;
if(!AppearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component)) if (!AppearanceSystem.TryGetData<DoorState>(entity, DoorVisuals.State, out var state, args.Component))
state = DoorState.Closed; state = DoorState.Closed;
if (AppearanceSystem.TryGetData<string>(uid, DoorVisuals.BaseRSI, out var baseRsi, args.Component)) if (AppearanceSystem.TryGetData<string>(entity, DoorVisuals.BaseRSI, out var baseRsi, args.Component))
{ UpdateSpriteLayers(args.Sprite, baseRsi);
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
{
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
}
foreach (var layer in args.Sprite.AllLayers)
{
layer.Rsi = res?.RSI;
}
}
TryComp<AnimationPlayerComponent>(uid, out var animPlayer); if (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
if (_animationSystem.HasRunningAnimation(uid, animPlayer, DoorComponent.AnimationKey)) _animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
_animationSystem.Stop(uid, animPlayer, DoorComponent.AnimationKey); // Halt all running anomations.
args.Sprite.DrawDepth = comp.ClosedDrawDepth; UpdateAppearanceForDoorState(entity, args.Sprite, state);
switch(state) }
private void UpdateAppearanceForDoorState(Entity<DoorComponent> entity, SpriteComponent sprite, DoorState state)
{
sprite.DrawDepth = state is DoorState.Open ? entity.Comp.OpenDrawDepth : entity.Comp.ClosedDrawDepth;
switch (state)
{ {
case DoorState.Open: case DoorState.Open:
args.Sprite.DrawDepth = comp.OpenDrawDepth; foreach (var (layer, layerState) in entity.Comp.OpenSpriteStates)
foreach(var (layer, layerState) in comp.OpenSpriteStates)
{ {
args.Sprite.LayerSetState(layer, layerState); sprite.LayerSetState(layer, layerState);
} }
break;
return;
case DoorState.Closed: case DoorState.Closed:
foreach(var (layer, layerState) in comp.ClosedSpriteStates) foreach (var (layer, layerState) in entity.Comp.ClosedSpriteStates)
{ {
args.Sprite.LayerSetState(layer, layerState); sprite.LayerSetState(layer, layerState);
} }
break;
return;
case DoorState.Opening: case DoorState.Opening:
if (animPlayer != null && comp.OpeningAnimationTime != 0.0) if (entity.Comp.OpeningAnimationTime == 0.0)
_animationSystem.Play((uid, animPlayer), (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey); return;
break;
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Closing: case DoorState.Closing:
if (animPlayer != null && comp.ClosingAnimationTime != 0.0 && comp.CurrentlyCrushing.Count == 0) if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
_animationSystem.Play((uid, animPlayer), (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey); return;
break;
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
return;
case DoorState.Denying: case DoorState.Denying:
if (animPlayer != null) _animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play((uid, animPlayer), (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
break; return;
case DoorState.Welded:
break;
case DoorState.Emagging: case DoorState.Emagging:
if (animPlayer != null) _animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play((uid, animPlayer), (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
break; return;
default:
throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
} }
} }
private void UpdateSpriteLayers(SpriteComponent sprite, string baseRsi)
{
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
{
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
return;
}
sprite.BaseRSI = res.RSI;
}
} }

View File

@@ -43,6 +43,8 @@ namespace Content.Client.GameTicking.Managers
public override void Initialize() public override void Initialize()
{ {
base.Initialize();
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby); SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame); SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus); SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);

View File

@@ -51,7 +51,6 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
var button = new GhostRoleRadioMenuButton() var button = new GhostRoleRadioMenuButton()
{ {
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64, 64), SetSize = new Vector2(64, 64),
ToolTip = Loc.GetString(ghostRoleProto.Name), ToolTip = Loc.GetString(ghostRoleProto.Name),
ProtoId = ghostRoleProto.ID, ProtoId = ghostRoleProto.ID,
@@ -100,7 +99,7 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
} }
} }
public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButton public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButtonWithSector
{ {
public ProtoId<GhostRolePrototype> ProtoId { get; set; } public ProtoId<GhostRolePrototype> ProtoId { get; set; }
} }

View File

@@ -13,6 +13,7 @@ namespace Content.Client.Ghost
[Dependency] private readonly IClientConsoleHost _console = default!; [Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
[Dependency] private readonly ContentEyeSystem _contentEye = default!; [Dependency] private readonly ContentEyeSystem _contentEye = default!;
public int AvailableGhostRoleCount { get; private set; } public int AvailableGhostRoleCount { get; private set; }
@@ -79,8 +80,27 @@ namespace Content.Client.Ghost
if (args.Handled) if (args.Handled)
return; return;
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup"), args.Performer); TryComp<PointLightComponent>(uid, out var light);
_contentEye.RequestToggleLight(uid, component);
if (!component.DrawLight)
{
// normal lighting
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-normal"), args.Performer);
_contentEye.RequestEye(component.DrawFov, true);
}
else if (!light?.Enabled ?? false) // skip this option if we have no PointLightComponent
{
// enable personal light
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-personal-light"), args.Performer);
_pointLightSystem.SetEnabled(uid, true, light);
}
else
{
// fullbright mode
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-fullbright"), args.Performer);
_contentEye.RequestEye(component.DrawFov, false);
_pointLightSystem.SetEnabled(uid, false, light);
}
args.Handled = true; args.Handled = true;
} }

View File

@@ -2,45 +2,38 @@ using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Holopad; using Content.Shared.Holopad;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using System.Linq; using System.Linq;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
namespace Content.Client.Holopad; namespace Content.Client.Holopad;
public sealed class HolopadSystem : SharedHolopadSystem public sealed class HolopadSystem : SharedHolopadSystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<HolopadHologramComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<HolopadHologramComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<HolopadHologramComponent, BeforePostShaderRenderEvent>(OnShaderRender); SubscribeLocalEvent<HolopadHologramComponent, BeforePostShaderRenderEvent>(OnShaderRender);
SubscribeAllEvent<TypingChangedEvent>(OnTypingChanged); SubscribeAllEvent<TypingChangedEvent>(OnTypingChanged);
SubscribeNetworkEvent<PlayerSpriteStateRequest>(OnPlayerSpriteStateRequest);
SubscribeNetworkEvent<PlayerSpriteStateMessage>(OnPlayerSpriteStateMessage);
} }
private void OnComponentInit(EntityUid uid, HolopadHologramComponent component, ComponentInit ev) private void OnComponentStartup(Entity<HolopadHologramComponent> entity, ref ComponentStartup ev)
{ {
if (!TryComp<SpriteComponent>(uid, out var sprite)) UpdateHologramSprite(entity, entity.Comp.LinkedEntity);
return;
UpdateHologramSprite(uid);
} }
private void OnShaderRender(EntityUid uid, HolopadHologramComponent component, BeforePostShaderRenderEvent ev) private void OnShaderRender(Entity<HolopadHologramComponent> entity, ref BeforePostShaderRenderEvent ev)
{ {
if (ev.Sprite.PostShader == null) if (ev.Sprite.PostShader == null)
return; return;
ev.Sprite.PostShader.SetParameter("t", (float)_timing.CurTime.TotalSeconds * component.ScrollRate); UpdateHologramSprite(entity, entity.Comp.LinkedEntity);
} }
private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args) private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args)
@@ -57,100 +50,66 @@ public sealed class HolopadSystem : SharedHolopadSystem
RaiseNetworkEvent(netEv); RaiseNetworkEvent(netEv);
} }
private void OnPlayerSpriteStateRequest(PlayerSpriteStateRequest ev) private void UpdateHologramSprite(EntityUid hologram, EntityUid? target)
{ {
var targetPlayer = GetEntity(ev.TargetPlayer); // Get required components
var player = _playerManager.LocalSession?.AttachedEntity; if (!TryComp<SpriteComponent>(hologram, out var hologramSprite) ||
!TryComp<HolopadHologramComponent>(hologram, out var holopadhologram))
// Ignore the request if received by a player who isn't the target
if (targetPlayer != player)
return;
if (!TryComp<SpriteComponent>(player, out var playerSprite))
return;
var spriteLayerData = new List<PrototypeLayerData>();
if (playerSprite.Visible)
{
// Record the RSI paths, state names and shader paramaters of all visible layers
for (int i = 0; i < playerSprite.AllLayers.Count(); i++)
{
if (!playerSprite.TryGetLayer(i, out var layer))
continue;
if (!layer.Visible ||
string.IsNullOrEmpty(layer.ActualRsi?.Path.ToString()) ||
string.IsNullOrEmpty(layer.State.Name))
continue;
var layerDatum = new PrototypeLayerData();
layerDatum.RsiPath = layer.ActualRsi.Path.ToString();
layerDatum.State = layer.State.Name;
if (layer.CopyToShaderParameters != null)
{
var key = (string)layer.CopyToShaderParameters.LayerKey;
if (playerSprite.LayerMapTryGet(key, out var otherLayerIdx) &&
playerSprite.TryGetLayer(otherLayerIdx, out var otherLayer) &&
otherLayer.Visible)
{
layerDatum.MapKeys = new() { key };
layerDatum.CopyToShaderParameters = new PrototypeCopyToShaderParameters()
{
LayerKey = key,
ParameterTexture = layer.CopyToShaderParameters.ParameterTexture,
ParameterUV = layer.CopyToShaderParameters.ParameterUV
};
}
}
spriteLayerData.Add(layerDatum);
}
}
// Return the recorded data to the server
var evResponse = new PlayerSpriteStateMessage(ev.TargetPlayer, spriteLayerData.ToArray());
RaiseNetworkEvent(evResponse);
}
private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev)
{
UpdateHologramSprite(GetEntity(ev.SpriteEntity), ev.SpriteLayerData);
}
private void UpdateHologramSprite(EntityUid uid, PrototypeLayerData[]? layerData = null)
{
if (!TryComp<SpriteComponent>(uid, out var hologramSprite))
return;
if (!TryComp<HolopadHologramComponent>(uid, out var holopadhologram))
return; return;
// Remove all sprite layers
for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--) for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--)
hologramSprite.RemoveLayer(i); hologramSprite.RemoveLayer(i);
if (layerData == null || layerData.Length == 0) if (TryComp<SpriteComponent>(target, out var targetSprite))
{ {
layerData = new PrototypeLayerData[1]; // Use the target's holographic avatar (if available)
layerData[0] = new PrototypeLayerData() if (TryComp<HolographicAvatarComponent>(target, out var targetAvatar) &&
targetAvatar.LayerData != null)
{ {
RsiPath = holopadhologram.RsiPath, for (int i = 0; i < targetAvatar.LayerData.Length; i++)
State = holopadhologram.RsiState {
}; var layer = targetAvatar.LayerData[i];
hologramSprite.AddLayer(targetAvatar.LayerData[i], i);
}
}
// Otherwise copy the target's current physical appearance
else
{
hologramSprite.CopyFrom(targetSprite);
}
} }
for (int i = 0; i < layerData.Length; i++) // There is no target, display a default sprite instead (if available)
else
{ {
var layer = layerData[i]; if (string.IsNullOrEmpty(holopadhologram.RsiPath) || string.IsNullOrEmpty(holopadhologram.RsiState))
layer.Shader = "unshaded"; return;
hologramSprite.AddLayer(layerData[i], i); var layer = new PrototypeLayerData();
layer.RsiPath = holopadhologram.RsiPath;
layer.State = holopadhologram.RsiState;
hologramSprite.AddLayer(layer);
} }
UpdateHologramShader(uid, hologramSprite, holopadhologram); // Override specific values
hologramSprite.Color = Color.White;
hologramSprite.Offset = holopadhologram.Offset;
hologramSprite.DrawDepth = (int)DrawDepth.Mobs;
hologramSprite.NoRotation = true;
hologramSprite.DirectionOverride = Direction.South;
hologramSprite.EnableDirectionOverride = true;
// Remove shading from all layers (except displacement maps)
for (int i = 0; i < hologramSprite.AllLayers.Count(); i++)
{
if (hologramSprite.TryGetLayer(i, out var layer) && layer.ShaderPrototype != "DisplacedStencilDraw")
hologramSprite.LayerSetShader(i, "unshaded");
}
UpdateHologramShader(hologram, hologramSprite, holopadhologram);
} }
private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram) private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram)

View File

@@ -2,7 +2,6 @@ using Content.Shared.ActionBlocker;
using Content.Shared.Instruments.UI; using Content.Shared.Instruments.UI;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Robust.Client.Audio.Midi; using Robust.Client.Audio.Midi;
using Robust.Client.GameObjects;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -13,7 +12,6 @@ namespace Content.Client.Instruments.UI
public IEntityManager Entities => EntMan; public IEntityManager Entities => EntMan;
[Dependency] public readonly IMidiManager MidiManager = default!; [Dependency] public readonly IMidiManager MidiManager = default!;
[Dependency] public readonly IFileDialogManager FileDialogManager = default!; [Dependency] public readonly IFileDialogManager FileDialogManager = default!;
[Dependency] public readonly IPlayerManager PlayerManager = default!;
[Dependency] public readonly ILocalizationManager Loc = default!; [Dependency] public readonly ILocalizationManager Loc = default!;
public readonly InstrumentSystem Instruments; public readonly InstrumentSystem Instruments;
@@ -41,6 +39,8 @@ namespace Content.Client.Instruments.UI
protected override void Open() protected override void Open()
{ {
base.Open();
_instrumentMenu = this.CreateWindow<InstrumentMenu>(); _instrumentMenu = this.CreateWindow<InstrumentMenu>();
_instrumentMenu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName; _instrumentMenu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;

View File

@@ -256,7 +256,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
dragSprite.DrawDepth = (int) DrawDepth.Overlays; dragSprite.DrawDepth = (int) DrawDepth.Overlays;
if (!dragSprite.NoRotation) if (!dragSprite.NoRotation)
{ {
Transform(_dragShadow.Value).WorldRotation = Transform(_draggedEntity.Value).WorldRotation; _transformSystem.SetWorldRotationNoLerp(_dragShadow.Value, _transformSystem.GetWorldRotation(_draggedEntity.Value));
} }
// drag initiated // drag initiated

View File

@@ -0,0 +1,11 @@
using Content.Shared.ItemRecall;
namespace Content.Client.ItemRecall;
/// <summary>
/// System for handling the ItemRecall ability for wizards.
/// </summary>
public sealed partial class ItemRecallSystem : SharedItemRecallSystem
{
}

View File

@@ -33,8 +33,13 @@ namespace Content.Client.Labels.UI
_focused = false; _focused = false;
LabelLineEdit.Text = _label; LabelLineEdit.Text = _label;
}; };
}
// Give the editor keybard focus, since that's the only protected override void Opened()
{
base.Opened();
// Give the editor keyboard focus, since that's the only
// thing the user will want to be doing with this UI // thing the user will want to be doing with this UI
LabelLineEdit.GrabKeyboardFocus(); LabelLineEdit.GrabKeyboardFocus();
} }

View File

@@ -170,7 +170,7 @@ namespace Content.Client.LateJoin
foreach (var department in departments) foreach (var department in departments)
{ {
var departmentName = Loc.GetString($"department-{department.ID}"); var departmentName = Loc.GetString(department.Name);
_jobCategories[id] = new Dictionary<string, BoxContainer>(); _jobCategories[id] = new Dictionary<string, BoxContainer>();
var stationAvailable = _gameTicker.JobsAvailable[id]; var stationAvailable = _gameTicker.JobsAvailable[id];
var jobsAvailable = new List<JobPrototype>(); var jobsAvailable = new List<JobPrototype>();

View File

@@ -64,7 +64,7 @@ public sealed partial class LatheMenu : DefaultWindow
if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComponent)) if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComponent))
{ {
if (!latheComponent.DynamicRecipes.Any()) if (!latheComponent.DynamicPacks.Any())
{ {
ServerListButton.Visible = false; ServerListButton.Visible = false;
} }

View File

@@ -43,7 +43,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!; [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!; [UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
[UISystemDependency] private readonly GuidebookSystem _guide = default!; [UISystemDependency] private readonly GuidebookSystem _guide = default!;
[UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
private CharacterSetupGui? _characterSetup; private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor; private HumanoidProfileEditor? _profileEditor;

View File

@@ -839,7 +839,7 @@ namespace Content.Client.Lobby.UI
foreach (var department in departments) foreach (var department in departments)
{ {
var departmentName = Loc.GetString($"department-{department.ID}"); var departmentName = Loc.GetString(department.Name);
if (!_jobCategories.TryGetValue(department.ID, out var category)) if (!_jobCategories.TryGetValue(department.ID, out var category))
{ {
@@ -1015,6 +1015,13 @@ namespace Content.Client.Lobby.UI
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection); _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
_loadoutWindow.OpenCenteredLeft(); _loadoutWindow.OpenCenteredLeft();
_loadoutWindow.OnNameChanged += name =>
{
roleLoadout.EntityName = name;
Profile = Profile.WithLoadout(roleLoadout);
SetDirty();
};
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) => _loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
{ {
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager); roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);

View File

@@ -5,17 +5,15 @@
SetSize="800 800" SetSize="800 800"
MinSize="800 128"> MinSize="800 128">
<BoxContainer Orientation="Vertical" VerticalExpand="True"> <BoxContainer Orientation="Vertical" VerticalExpand="True">
<!--
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10"> <BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/> <Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
<PanelContainer HorizontalExpand="True" SetHeight="24"> <PanelContainer HorizontalExpand="True" SetHeight="24">
<PanelContainer.PanelOverride> <PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" /> <graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride> </PanelContainer.PanelOverride>
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/> <LineEdit Name="RoleNameEdit" VerticalExpand="True" HorizontalExpand="True"/>
</PanelContainer> </PanelContainer>
</BoxContainer> </BoxContainer>
-->
<VerticalTabContainer Name="LoadoutGroupsContainer" <VerticalTabContainer Name="LoadoutGroupsContainer"
VerticalExpand="True" VerticalExpand="True"
HorizontalExpand="True"> HorizontalExpand="True">

View File

@@ -1,7 +1,9 @@
using System.Numerics; using System.Numerics;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Dataset;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Random.Helpers;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -13,6 +15,7 @@ namespace Content.Client.Lobby.UI.Loadouts;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class LoadoutWindow : FancyWindow public sealed partial class LoadoutWindow : FancyWindow
{ {
public event Action<string>? OnNameChanged;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed; public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed; public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
@@ -25,6 +28,23 @@ public sealed partial class LoadoutWindow : FancyWindow
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
Profile = profile; Profile = profile;
var protoManager = collection.Resolve<IPrototypeManager>(); var protoManager = collection.Resolve<IPrototypeManager>();
RoleNameEdit.IsValid = text => text.Length <= HumanoidCharacterProfile.MaxLoadoutNameLength;
// Hide if we can't edit the name.
if (!proto.CanCustomizeName)
{
RoleNameBox.Visible = false;
}
else
{
var name = loadout.EntityName;
RoleNameEdit.ToolTip = Loc.GetString(
"loadout-name-edit-tooltip",
("max", HumanoidCharacterProfile.MaxLoadoutNameLength));
RoleNameEdit.Text = name ?? string.Empty;
RoleNameEdit.OnTextChanged += args => OnNameChanged?.Invoke(args.Text);
}
// Hide if no groups // Hide if no groups
if (proto.Groups.Count == 0) if (proto.Groups.Count == 0)

View File

@@ -1,5 +1,8 @@
using Content.Shared.Magic; using Content.Shared.Magic;
using Content.Shared.Magic.Events;
namespace Content.Client.Magic; namespace Content.Client.Magic;
public sealed class MagicSystem : SharedMagicSystem; public sealed class MagicSystem : SharedMagicSystem
{
}

View File

@@ -102,7 +102,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid)) if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
return; return;
StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix)); StartDragging(gridUid, Vector2.Transform(mousePos.Position, _transformSystem.GetInvWorldMatrix(gridUid)));
} }
if (!TryComp(_dragging, out TransformComponent? xform)) if (!TryComp(_dragging, out TransformComponent? xform))
@@ -117,11 +117,11 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
return; return;
} }
var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix); var localToWorld = Vector2.Transform(_localPosition, _transformSystem.GetWorldMatrix(xform));
if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return; if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return;
var requestedGridOrigin = mousePos.Position - xform.WorldRotation.RotateVec(_localPosition); var requestedGridOrigin = mousePos.Position - _transformSystem.GetWorldRotation(xform).RotateVec(_localPosition);
_lastMousePosition = new MapCoordinates(requestedGridOrigin, mousePos.MapId); _lastMousePosition = new MapCoordinates(requestedGridOrigin, mousePos.MapId);
RaiseNetworkEvent(new GridDragRequestPosition() RaiseNetworkEvent(new GridDragRequestPosition()

View File

@@ -20,6 +20,8 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
protected override void Open() protected override void Open()
{ {
base.Open();
_menu = this.CreateWindow<NewsWriterMenu>(); _menu = this.CreateWindow<NewsWriterMenu>();
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed; _menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;

View File

@@ -14,6 +14,8 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
protected override void Open() protected override void Open()
{ {
base.Open();
EntityUid? gridUid = null; EntityUid? gridUid = null;
var stationName = string.Empty; var stationName = string.Empty;

View File

@@ -25,6 +25,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
{ {
[Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SharedTransformSystem _transformSystem;
private readonly SpriteSystem _spriteSystem; private readonly SpriteSystem _spriteSystem;
private NetEntity? _trackedEntity; private NetEntity? _trackedEntity;
@@ -36,10 +37,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_transformSystem = _entManager.System<SharedTransformSystem>();
_spriteSystem = _entManager.System<SpriteSystem>(); _spriteSystem = _entManager.System<SpriteSystem>();
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap; NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
} }
public void Set(string stationName, EntityUid? mapUid) public void Set(string stationName, EntityUid? mapUid)
@@ -290,7 +291,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
{ {
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid, NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
new NavMapBlip new NavMapBlip
(coordinates.Value, (CoordinatesToLocal(coordinates.Value),
_blipTexture, _blipTexture,
(_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray, (_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
sensor.SuitSensorUid == _trackedEntity)); sensor.SuitSensorUid == _trackedEntity));
@@ -356,7 +357,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (NavMap.TrackedEntities.TryGetValue(castSensor.SuitSensorUid, out var data)) if (NavMap.TrackedEntities.TryGetValue(castSensor.SuitSensorUid, out var data))
{ {
data = new NavMapBlip data = new NavMapBlip
(data.Coordinates, (CoordinatesToLocal(data.Coordinates),
data.Texture, data.Texture,
(currTrackedEntity == null || castSensor.SuitSensorUid == currTrackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray, (currTrackedEntity == null || castSensor.SuitSensorUid == currTrackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
castSensor.SuitSensorUid == currTrackedEntity); castSensor.SuitSensorUid == currTrackedEntity);
@@ -421,6 +422,26 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
return false; return false;
} }
/// <summary>
/// Converts the input coordinates to an EntityCoordinates which are in
/// reference to the grid that the map is displaying. This is a stylistic
/// choice; this window deliberately limits the rate that blips update,
/// but if the blip is attached to another grid which is moving, that
/// blip will move smoothly, unlike the others. By converting the
/// coordinates, we are back in control of the blip movement.
/// </summary>
private EntityCoordinates CoordinatesToLocal(EntityCoordinates refCoords)
{
if (NavMap.MapUid != null)
{
return _transformSystem.WithEntityId(refCoords, (EntityUid)NavMap.MapUid);
}
else
{
return refCoords;
}
}
private void ClearOutDatedData() private void ClearOutDatedData()
{ {
SensorsTable.RemoveAllChildren(); SensorsTable.RemoveAllChildren();

View File

@@ -0,0 +1,19 @@
using System.Numerics;
using Content.Client.Movement.Systems;
using Content.Shared.Movement.Components;
namespace Content.Client.Movement.Components;
[RegisterComponent]
public sealed partial class EyeCursorOffsetComponent : SharedEyeCursorOffsetComponent
{
/// <summary>
/// The location the offset will attempt to pan towards; based on the cursor's position in the game window.
/// </summary>
public Vector2 TargetPosition = Vector2.Zero;
/// <summary>
/// The current positional offset being applied. Used to enable gradual panning.
/// </summary>
public Vector2 CurrentPosition = Vector2.Zero;
}

View File

@@ -1,6 +1,7 @@
using System.Numerics; using System.Numerics;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Client.Player; using Robust.Client.Player;
namespace Content.Client.Movement.Systems; namespace Content.Client.Movement.Systems;
@@ -52,4 +53,14 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
{ {
RaisePredictiveEvent(new RequestEyeEvent(drawFov, drawLight)); RaisePredictiveEvent(new RequestEyeEvent(drawFov, drawLight));
} }
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var eyeEntities = AllEntityQuery<ContentEyeComponent, EyeComponent>();
while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent))
{
UpdateEyeOffset((entity, eyeComponent));
}
}
} }

View File

@@ -0,0 +1,91 @@
using System.Numerics;
using Content.Client.Movement.Components;
using Content.Shared.Camera;
using Content.Shared.Inventory;
using Content.Shared.Movement.Systems;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Shared.Map;
using Robust.Client.Player;
namespace Content.Client.Movement.Systems;
public partial class EyeCursorOffsetSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedContentEyeSystem _contentEye = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
// This value is here to make sure the user doesn't have to move their mouse
// all the way out to the edge of the screen to get the full offset.
static private float _edgeOffset = 0.9f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EyeCursorOffsetComponent, GetEyeOffsetEvent>(OnGetEyeOffsetEvent);
}
private void OnGetEyeOffsetEvent(EntityUid uid, EyeCursorOffsetComponent component, ref GetEyeOffsetEvent args)
{
var offset = OffsetAfterMouse(uid, component);
if (offset == null)
return;
args.Offset += offset.Value;
}
public Vector2? OffsetAfterMouse(EntityUid uid, EyeCursorOffsetComponent? component)
{
var localPlayer = _player.LocalPlayer?.ControlledEntity;
var mousePos = _inputManager.MouseScreenPosition;
var screenSize = _clyde.MainWindow.Size;
var minValue = MathF.Min(screenSize.X / 2, screenSize.Y / 2) * _edgeOffset;
var mouseNormalizedPos = new Vector2(-(mousePos.X - screenSize.X / 2) / minValue, (mousePos.Y - screenSize.Y / 2) / minValue); // X needs to be inverted here for some reason, otherwise it ends up flipped.
if (localPlayer == null)
return null;
var playerPos = _transform.GetWorldPosition(localPlayer.Value);
if (component == null)
{
component = EnsureComp<EyeCursorOffsetComponent>(uid);
}
// Doesn't move the offset if the mouse has left the game window!
if (mousePos.Window != WindowId.Invalid)
{
// The offset must account for the in-world rotation.
var eyeRotation = _eyeManager.CurrentEye.Rotation;
var mouseActualRelativePos = Vector2.Transform(mouseNormalizedPos, System.Numerics.Quaternion.CreateFromAxisAngle(-System.Numerics.Vector3.UnitZ, (float)(eyeRotation.Opposite().Theta))); // I don't know, it just works.
// Caps the offset into a circle around the player.
mouseActualRelativePos *= component.MaxOffset;
if (mouseActualRelativePos.Length() > component.MaxOffset)
{
mouseActualRelativePos = mouseActualRelativePos.Normalized() * component.MaxOffset;
}
component.TargetPosition = mouseActualRelativePos;
//Makes the view not jump immediately when moving the cursor fast.
if (component.CurrentPosition != component.TargetPosition)
{
Vector2 vectorOffset = component.TargetPosition - component.CurrentPosition;
if (vectorOffset.Length() > component.OffsetSpeed)
{
vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed;
}
component.CurrentPosition += vectorOffset;
}
}
return component.CurrentPosition;
}
}

View File

@@ -18,6 +18,8 @@ namespace Content.Client.Nuke
protected override void Open() protected override void Open()
{ {
base.Open();
_menu = this.CreateWindow<NukeMenu>(); _menu = this.CreateWindow<NukeMenu>();
_menu.OnKeypadButtonPressed += i => _menu.OnKeypadButtonPressed += i =>

View File

@@ -4,6 +4,7 @@ using Robust.Client.Animations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Animations; using Robust.Shared.Animations;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Client.Orbit; namespace Content.Client.Orbit;
@@ -11,8 +12,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
{ {
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly AnimationPlayerSystem _animations = default!; [Dependency] private readonly AnimationPlayerSystem _animations = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly string _orbitAnimationKey = "orbiting";
private readonly string _orbitStopKey = "orbiting_stop"; private readonly string _orbitStopKey = "orbiting_stop";
public override void Initialize() public override void Initialize()
@@ -21,11 +22,11 @@ public sealed class OrbitVisualsSystem : EntitySystem
SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove); SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<OrbitVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
} }
private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args) private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args)
{ {
_robustRandom.SetSeed((int)_timing.CurTime.TotalMilliseconds);
component.OrbitDistance = component.OrbitDistance =
_robustRandom.NextFloat(0.75f * component.OrbitDistance, 1.25f * component.OrbitDistance); _robustRandom.NextFloat(0.75f * component.OrbitDistance, 1.25f * component.OrbitDistance);
@@ -38,15 +39,10 @@ public sealed class OrbitVisualsSystem : EntitySystem
} }
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid); var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
return;
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey)) if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey))
{ {
_animations.Stop(uid, animationPlayer, _orbitStopKey); _animations.Stop((uid, animationPlayer), _orbitStopKey);
} }
_animations.Play(uid, animationPlayer, GetOrbitAnimation(component), _orbitAnimationKey);
} }
private void OnComponentRemove(EntityUid uid, OrbitVisualsComponent component, ComponentRemove args) private void OnComponentRemove(EntityUid uid, OrbitVisualsComponent component, ComponentRemove args)
@@ -57,14 +53,9 @@ public sealed class OrbitVisualsSystem : EntitySystem
sprite.EnableDirectionOverride = false; sprite.EnableDirectionOverride = false;
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid); var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
{
_animations.Stop(uid, animationPlayer, _orbitAnimationKey);
}
if (!_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey)) if (!_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey))
{ {
_animations.Play(uid, animationPlayer, GetStopAnimation(component, sprite), _orbitStopKey); _animations.Play((uid, animationPlayer), GetStopAnimation(component, sprite), _orbitStopKey);
} }
} }
@@ -74,7 +65,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
foreach (var (orbit, sprite) in EntityManager.EntityQuery<OrbitVisualsComponent, SpriteComponent>()) foreach (var (orbit, sprite) in EntityManager.EntityQuery<OrbitVisualsComponent, SpriteComponent>())
{ {
var angle = new Angle(Math.PI * 2 * orbit.Orbit); var progress = (float)(_timing.CurTime.TotalSeconds / orbit.OrbitLength) % 1;
var angle = new Angle(Math.PI * 2 * progress);
var vec = angle.RotateVec(new Vector2(orbit.OrbitDistance, 0)); var vec = angle.RotateVec(new Vector2(orbit.OrbitDistance, 0));
sprite.Rotation = angle; sprite.Rotation = angle;
@@ -82,38 +74,6 @@ public sealed class OrbitVisualsSystem : EntitySystem
} }
} }
private void OnAnimationCompleted(EntityUid uid, OrbitVisualsComponent component, AnimationCompletedEvent args)
{
if (args.Key == _orbitAnimationKey && TryComp(uid, out AnimationPlayerComponent? animationPlayer))
{
_animations.Play(uid, animationPlayer, GetOrbitAnimation(component), _orbitAnimationKey);
}
}
private Animation GetOrbitAnimation(OrbitVisualsComponent component)
{
var length = component.OrbitLength;
return new Animation()
{
Length = TimeSpan.FromSeconds(length),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(OrbitVisualsComponent),
Property = nameof(OrbitVisualsComponent.Orbit),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(0.0f, 0f),
new AnimationTrackProperty.KeyFrame(1.0f, length),
},
InterpolationMode = AnimationInterpolationMode.Linear
}
}
};
}
private Animation GetStopAnimation(OrbitVisualsComponent component, SpriteComponent sprite) private Animation GetStopAnimation(OrbitVisualsComponent component, SpriteComponent sprite)
{ {
var length = component.OrbitStopLength; var length = component.OrbitStopLength;

View File

@@ -32,6 +32,11 @@ public sealed class TargetOutlineSystem : EntitySystem
/// </summary> /// </summary>
public EntityWhitelist? Whitelist = null; public EntityWhitelist? Whitelist = null;
/// <summary>
/// Blacklist that the target must satisfy.
/// </summary>
public EntityWhitelist? Blacklist = null;
/// <summary> /// <summary>
/// Predicate the target must satisfy. /// Predicate the target must satisfy.
/// </summary> /// </summary>
@@ -93,15 +98,16 @@ public sealed class TargetOutlineSystem : EntitySystem
RemoveHighlights(); RemoveHighlights();
} }
public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, CancellableEntityEventArgs? validationEvent) public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, EntityWhitelist? blacklist, CancellableEntityEventArgs? validationEvent)
{ {
Range = range; Range = range;
CheckObstruction = checkObstructions; CheckObstruction = checkObstructions;
Predicate = predicate; Predicate = predicate;
Whitelist = whitelist; Whitelist = whitelist;
Blacklist = blacklist;
ValidationEvent = validationEvent; ValidationEvent = validationEvent;
_enabled = Predicate != null || Whitelist != null || ValidationEvent != null; _enabled = Predicate != null || Whitelist != null || Blacklist != null || ValidationEvent != null;
} }
public override void Update(float frameTime) public override void Update(float frameTime)

View File

@@ -56,35 +56,35 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
protected virtual void DeactivateInternal() { } protected virtual void DeactivateInternal() { }
private void OnStartup(EntityUid uid, T component, ComponentStartup args) private void OnStartup(Entity<T> ent, ref ComponentStartup args)
{ {
RefreshOverlay(uid); RefreshOverlay();
} }
private void OnRemove(EntityUid uid, T component, ComponentRemove args) private void OnRemove(Entity<T> ent, ref ComponentRemove args)
{ {
RefreshOverlay(uid); RefreshOverlay();
} }
private void OnPlayerAttached(LocalPlayerAttachedEvent args) private void OnPlayerAttached(LocalPlayerAttachedEvent args)
{ {
RefreshOverlay(args.Entity); RefreshOverlay();
} }
private void OnPlayerDetached(LocalPlayerDetachedEvent args) private void OnPlayerDetached(LocalPlayerDetachedEvent args)
{ {
if (_player.LocalSession?.AttachedEntity == null) if (_player.LocalSession?.AttachedEntity is null)
Deactivate(); Deactivate();
} }
private void OnCompEquip(EntityUid uid, T component, GotEquippedEvent args) private void OnCompEquip(Entity<T> ent, ref GotEquippedEvent args)
{ {
RefreshOverlay(args.Equipee); RefreshOverlay();
} }
private void OnCompUnequip(EntityUid uid, T component, GotUnequippedEvent args) private void OnCompUnequip(Entity<T> ent, ref GotUnequippedEvent args)
{ {
RefreshOverlay(args.Equipee); RefreshOverlay();
} }
private void OnRoundRestart(RoundRestartCleanupEvent args) private void OnRoundRestart(RoundRestartCleanupEvent args)
@@ -92,24 +92,24 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
Deactivate(); Deactivate();
} }
protected virtual void OnRefreshEquipmentHud(EntityUid uid, T component, InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args) protected virtual void OnRefreshEquipmentHud(Entity<T> ent, ref InventoryRelayedEvent<RefreshEquipmentHudEvent<T>> args)
{ {
OnRefreshComponentHud(uid, component, args.Args); OnRefreshComponentHud(ent, ref args.Args);
} }
protected virtual void OnRefreshComponentHud(EntityUid uid, T component, RefreshEquipmentHudEvent<T> args) protected virtual void OnRefreshComponentHud(Entity<T> ent, ref RefreshEquipmentHudEvent<T> args)
{ {
args.Active = true; args.Active = true;
args.Components.Add(component); args.Components.Add(ent.Comp);
} }
protected void RefreshOverlay(EntityUid uid) protected void RefreshOverlay()
{ {
if (uid != _player.LocalSession?.AttachedEntity) if (_player.LocalSession?.AttachedEntity is not { } entity)
return; return;
var ev = new RefreshEquipmentHudEvent<T>(TargetSlots); var ev = new RefreshEquipmentHudEvent<T>(TargetSlots);
RaiseLocalEvent(uid, ev); RaiseLocalEvent(entity, ref ev);
if (ev.Active) if (ev.Active)
Update(ev); Update(ev);

View File

@@ -28,7 +28,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args) private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
{ {
RefreshOverlay(ent); RefreshOverlay();
} }
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component) protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)

View File

@@ -47,7 +47,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args) private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
{ {
RefreshOverlay(ent); RefreshOverlay();
} }
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args) private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)

View File

@@ -15,6 +15,16 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
base.Initialize(); base.Initialize();
SubscribeLocalEvent<MindShieldComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent); SubscribeLocalEvent<MindShieldComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
SubscribeLocalEvent<FakeMindShieldComponent, GetStatusIconsEvent>(OnGetStatusIconsEventFake);
}
// TODO: Probably need to get this OFF of client since this can be read by bad actors rather easily
// ...imagine cheating in a game about silly paper dolls
private void OnGetStatusIconsEventFake(EntityUid uid, FakeMindShieldComponent component, ref GetStatusIconsEvent ev)
{
if(!IsActive)
return;
if (component.IsEnabled && _prototype.TryIndex(component.MindShieldStatusIcon, out var fakeStatusIconPrototype))
ev.StatusIcons.Add(fakeStatusIconPrototype);
} }
private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev) private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev)

View File

@@ -1,50 +1,129 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client" xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.ParticleAccelerator.UI" xmlns:ui="clr-namespace:Content.Client.ParticleAccelerator.UI"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls" Title="{Loc 'particle-accelerator-control-menu-device-version-label'}"
Title="{Loc 'particle-accelerator-control-menu-device-version-label'}" MinSize="320 120">
MinSize="420 320"
SetSize="420 320"> <!-- Main Container -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 10 0 0"> <BoxContainer Orientation="Vertical"
<BoxContainer Orientation="Horizontal" VerticalExpand="True"> VerticalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 5">
<BoxContainer Orientation="Horizontal"> <!-- Sub-Main container -->
<RichTextLabel Name="StatusLabel" HorizontalExpand="True"/> <BoxContainer Orientation="Horizontal"
<RichTextLabel Name="StatusStateLabel"/> VerticalExpand="True"
HorizontalExpand="True">
<!-- Info part -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
Margin="8">
<!-- Info -->
<BoxContainer Orientation="Vertical"
SeparationOverride="4">
<!-- Status -->
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StatusLabel" HorizontalExpand="True"/>
<Control MinWidth="8"/>
<RichTextLabel Name="StatusStateLabel"/>
</BoxContainer>
<!-- Power -->
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="PowerLabel"
HorizontalExpand="True"
VerticalAlignment="Center"/>
<Control MinWidth="8"/>
<Button Name="OffButton"
ToggleMode="False"
Text="{Loc 'particle-accelerator-control-menu-off-button'}"
StyleClasses="OpenRight"/>
<Button Name="OnButton"
ToggleMode="False"
Text="{Loc 'particle-accelerator-control-menu-on-button'}"
StyleClasses="OpenLeft"/>
</BoxContainer>
<!-- Strenght -->
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StrengthLabel"
HorizontalExpand="True"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<Control MinWidth="8"/>
<SpinBox Name="StateSpinBox" Value="0"/>
</BoxContainer>
<!-- Power -->
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
<Control MinWidth="8"/>
<RichTextLabel Name="DrawValueLabel"/>
</BoxContainer>
</BoxContainer> </BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal"> <Control MinHeight="8" VerticalExpand="True"/> <!-- Filler -->
<RichTextLabel Name="PowerLabel" Margin="0 0 20 0" HorizontalExpand="True" VerticalAlignment="Center"/>
<Button Name="OffButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-off-button'}" StyleClasses="OpenRight"/> <!-- Alarm -->
<Button Name="OnButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-on-button'}" StyleClasses="OpenLeft"/> <BoxContainer Name="AlarmControl"
Orientation="Vertical"
VerticalAlignment="Center"
Visible="False">
<controls:StripeBack Margin="-8 0">
<BoxContainer Orientation="Vertical">
<RichTextLabel Name="BigAlarmLabel"
HorizontalAlignment="Center"/>
<RichTextLabel Name="BigAlarmLabelTwo"
HorizontalAlignment="Center"/>
</BoxContainer>
</controls:StripeBack>
<Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}"
HorizontalAlignment="Center"
StyleClasses="LabelSubText"/>
</BoxContainer> </BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="StrengthLabel" Margin="0 0 20 0" HorizontalExpand="True" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<SpinBox Name="StateSpinBox" Value="0"/>
</BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
<RichTextLabel Name="DrawValueLabel"/>
</BoxContainer>
<Control MinHeight="10" VerticalExpand="True"/>
<BoxContainer Name="AlarmControl" Orientation="Vertical" VerticalAlignment="Center" Visible="False">
<RichTextLabel Name="BigAlarmLabel" HorizontalAlignment="Center"/>
<RichTextLabel Name="BigAlarmLabelTwo" HorizontalAlignment="Center"/>
<Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}" HorizontalAlignment="Center" StyleClasses="LabelSubText"/>
</BoxContainer>
<Control MinHeight="10" VerticalExpand="True"/>
</BoxContainer> </BoxContainer>
<customControls:VSeparator Margin="0 0 0 10"/>
<BoxContainer Orientation="Vertical" Margin="20 0 20 0" VerticalAlignment="Center"> <PanelContainer StyleClasses="LowDivider" Margin="0 -8" HorizontalAlignment="Right"/>
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
<!-- PA Visual part -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Center"
Margin="8">
<PanelContainer Name="BackPanel"
HorizontalAlignment="Center">
<PanelContainer.PanelOverride> <PanelContainer.PanelOverride>
<gfx:StyleBoxTexture Modulate="#202023" PatchMarginBottom="10" PatchMarginLeft="10" PatchMarginRight="10" PatchMarginTop="10"/> <gfx:StyleBoxTexture Modulate="#202023"
PatchMarginBottom="8"
PatchMarginLeft="8"
PatchMarginRight="8"
PatchMarginTop="8"/>
</PanelContainer.PanelOverride> </PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" HorizontalAlignment="Center" VerticalExpand="True">
<GridContainer Columns="3" VSeparationOverride="0" HSeparationOverride="0" HorizontalAlignment="Center"> <BoxContainer Orientation="Vertical"
SeparationOverride="6"
VerticalExpand="True"
VerticalAlignment="Stretch"
HorizontalExpand="True"
HorizontalAlignment="Center">
<!-- PA Visualisation -->
<GridContainer Columns="3"
VSeparationOverride="0"
HSeparationOverride="0"
HorizontalAlignment="Center">
<Control/> <Control/>
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/> <ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
<Control/> <Control/>
@@ -58,17 +137,47 @@
<ui:PASegmentControl Name="EmitterForeTexture" BaseState="emitter_fore"/> <ui:PASegmentControl Name="EmitterForeTexture" BaseState="emitter_fore"/>
<ui:PASegmentControl Name="EmitterPortTexture" BaseState="emitter_port"/> <ui:PASegmentControl Name="EmitterPortTexture" BaseState="emitter_port"/>
</GridContainer> </GridContainer>
<Control MinHeight="5"/>
<Button Name="ScanButton" Text="{Loc 'particle-accelerator-control-menu-scan-parts-button'}" HorizontalAlignment="Center"/> <Button Name="ScanButton"
Text="{Loc 'particle-accelerator-control-menu-scan-parts-button'}"
HorizontalAlignment="Center"/>
</BoxContainer> </BoxContainer>
</PanelContainer> </PanelContainer>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
<controls:StripeBack>
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/> <!-- Footer -->
</controls:StripeBack> <BoxContainer Orientation="Vertical"
<BoxContainer Orientation="Horizontal" Margin="12 0 0 0"> VerticalAlignment="Bottom">
<Label Text="{Loc 'particle-accelerator-control-menu-foo-bar-baz'}" StyleClasses="LabelSubText"/>
<controls:StripeBack>
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}"
HorizontalAlignment="Center"
StyleClasses="LabelSubText"
Margin="0 4"/>
</controls:StripeBack>
<BoxContainer Orientation="Horizontal"
Margin="12 0 6 2"
VerticalAlignment="Bottom">
<!-- Footer title -->
<Label Text="{Loc 'particle-accelerator-control-menu-flavor-left'}"
StyleClasses="WindowFooterText" />
<!-- Version -->
<Label Text="{Loc 'particle-accelerator-control-menu-flavor-right'}"
StyleClasses="WindowFooterText"
HorizontalAlignment="Right"
HorizontalExpand="True"
Margin="0 0 4 0" />
<TextureRect StyleClasses="NTLogoDark"
Stretch="KeepAspectCentered"
VerticalAlignment="Center"
HorizontalAlignment="Right"
SetSize="19 19"/>
</BoxContainer>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -10,8 +10,6 @@ namespace Content.Client.Pinpointer.UI;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl> public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl>
{ {
[Dependency] private readonly IEntityManager _entMan = default!;
public readonly EntityCoordinates BeaconPosition; public readonly EntityCoordinates BeaconPosition;
public Action<EntityCoordinates>? OnPressed; public Action<EntityCoordinates>? OnPressed;
public string? Label => BeaconNameLabel.Text; public string? Label => BeaconNameLabel.Text;

View File

@@ -12,6 +12,8 @@ public sealed class PowerMonitoringConsoleBoundUserInterface : BoundUserInterfac
protected override void Open() protected override void Open()
{ {
base.Open();
_menu = this.CreateWindow<PowerMonitoringWindow>(); _menu = this.CreateWindow<PowerMonitoringWindow>();
_menu.SetEntity(Owner); _menu.SetEntity(Owner);
_menu.SendPowerMonitoringConsoleMessageAction += SendPowerMonitoringConsoleMessage; _menu.SendPowerMonitoringConsoleMessageAction += SendPowerMonitoringConsoleMessage;

View File

@@ -4,14 +4,18 @@ using Robust.Client.GameObjects;
namespace Content.Client.Power.Visualizers; namespace Content.Client.Power.Visualizers;
public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComponent> public sealed class CableVisualizerSystem : EntitySystem
{ {
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize();
SubscribeLocalEvent<CableVisualizerComponent, AppearanceChangeEvent>(OnAppearanceChange, after: new[] { typeof(SubFloorHideSystem) }); SubscribeLocalEvent<CableVisualizerComponent, AppearanceChangeEvent>(OnAppearanceChange, after: new[] { typeof(SubFloorHideSystem) });
} }
protected override void OnAppearanceChange(EntityUid uid, CableVisualizerComponent component, ref AppearanceChangeEvent args) private void OnAppearanceChange(EntityUid uid, CableVisualizerComponent component, ref AppearanceChangeEvent args)
{ {
if (args.Sprite == null) if (args.Sprite == null)
return; return;
@@ -23,7 +27,7 @@ public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComp
return; return;
} }
if (!AppearanceSystem.TryGetData<WireVisDirFlags>(uid, WireVisVisuals.ConnectedMask, out var mask, args.Component)) if (!_appearanceSystem.TryGetData<WireVisDirFlags>(uid, WireVisVisuals.ConnectedMask, out var mask, args.Component))
mask = WireVisDirFlags.None; mask = WireVisDirFlags.None;
args.Sprite.LayerSetState(0, $"{component.StatePrefix}{(int) mask}"); args.Sprite.LayerSetState(0, $"{component.StatePrefix}{(int) mask}");

View File

@@ -11,37 +11,37 @@
<!-- The radial menu will try to open so that its center is located where the player's cursor is currently --> <!-- The radial menu will try to open so that its center is located where the player's cursor is currently -->
<!-- Entry layer (shows main categories) --> <!-- Entry layer (shows main categories) -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False"> <ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-walls-and-flooring'}" TargetLayer="WallsAndFlooring" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-walls-and-flooring'}" TargetLayer="WallsAndFlooring" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/walls_and_flooring.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/walls_and_flooring.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-windows-and-grilles'}" TargetLayer="WindowsAndGrilles" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-windows-and-grilles'}" TargetLayer="WindowsAndGrilles" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/windows_and_grilles.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/windows_and_grilles.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-airlocks'}" TargetLayer="Airlocks" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-airlocks'}" TargetLayer="Airlocks" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/airlocks.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/airlocks.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-electrical'}" TargetLayer="Electrical" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-electrical'}" TargetLayer="Electrical" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/multicoil.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/multicoil.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-lighting'}" TargetLayer="Lighting" Visible="False"> <ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-lighting'}" TargetLayer="Lighting" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/lighting.png"/> <TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/lighting.png"/>
</ui:RadialMenuTextureButton> </ui:RadialMenuTextureButtonWithSector>
</ui:RadialContainer> </ui:RadialContainer>
<!-- Walls and flooring --> <!-- Walls and flooring -->
<ui:RadialContainer Name="WallsAndFlooring" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="WallsAndFlooring" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Windows and grilles --> <!-- Windows and grilles -->
<ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Airlocks --> <!-- Airlocks -->
<ui:RadialContainer Name="Airlocks" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="Airlocks" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Computer and machine frames --> <!-- Computer and machine frames -->
<ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Lighting --> <!-- Lighting -->
<ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" Radius="64"/> <ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
</ui:RadialMenu> </ui:RadialMenu>

View File

@@ -74,7 +74,6 @@ public sealed partial class RCDMenu : RadialMenu
var button = new RCDMenuButton() var button = new RCDMenuButton()
{ {
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f), SetSize = new Vector2(64f, 64f),
ToolTip = tooltip, ToolTip = tooltip,
ProtoId = protoId, ProtoId = protoId,
@@ -99,9 +98,7 @@ public sealed partial class RCDMenu : RadialMenu
// is visible in the main radial container (as these all start with Visible = false) // is visible in the main radial container (as these all start with Visible = false)
foreach (var child in main.Children) foreach (var child in main.Children)
{ {
var castChild = child as RadialMenuTextureButton; if (child is not RadialMenuTextureButton castChild)
if (castChild is not RadialMenuTextureButton)
continue; continue;
if (castChild.TargetLayer == proto.Category) if (castChild.TargetLayer == proto.Category)
@@ -169,12 +166,7 @@ public sealed partial class RCDMenu : RadialMenu
} }
} }
public sealed class RCDMenuButton : RadialMenuTextureButton public sealed class RCDMenuButton : RadialMenuTextureButtonWithSector
{ {
public ProtoId<RCDPrototype> ProtoId { get; set; } public ProtoId<RCDPrototype> ProtoId { get; set; }
public RCDMenuButton()
{
}
} }

View File

@@ -9,7 +9,7 @@ public sealed class RadiationSystem : EntitySystem
{ {
[Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!;
public List<RadiationRay>? Rays; public List<DebugRadiationRay>? Rays;
public Dictionary<NetEntity, Dictionary<Vector2i, float>>? ResistanceGrids; public Dictionary<NetEntity, Dictionary<Vector2i, float>>? ResistanceGrids;
public override void Initialize() public override void Initialize()

View File

@@ -23,7 +23,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
if (_enableShuttlePosition) if (_enableShuttlePosition)
{ {
_overlay = new EmergencyShuttleOverlay(EntityManager); _overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
overlayManager.AddOverlay(_overlay); overlayManager.AddOverlay(_overlay);
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage()); RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
} }
@@ -57,23 +57,26 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
/// </summary> /// </summary>
public sealed class EmergencyShuttleOverlay : Overlay public sealed class EmergencyShuttleOverlay : Overlay
{ {
private IEntityManager _entManager; private readonly EntityQuery<TransformComponent> _transformQuery;
private readonly SharedTransformSystem _transformSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityUid? StationUid; public EntityUid? StationUid;
public Box2? Position; public Box2? Position;
public EmergencyShuttleOverlay(IEntityManager entManager) public EmergencyShuttleOverlay(EntityQuery<TransformComponent> transformQuery, SharedTransformSystem transformSystem)
{ {
_entManager = entManager; _transformQuery = transformQuery;
_transformSystem = transformSystem;
} }
protected override void Draw(in OverlayDrawArgs args) protected override void Draw(in OverlayDrawArgs args)
{ {
if (Position == null || !_entManager.TryGetComponent<TransformComponent>(StationUid, out var xform)) return; if (Position == null || !_transformQuery.TryGetComponent(StationUid, out var xform))
return;
args.WorldHandle.SetTransform(xform.WorldMatrix); args.WorldHandle.SetTransform(_transformSystem.GetWorldMatrix(xform));
args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100)); args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100));
args.WorldHandle.SetTransform(Matrix3x2.Identity); args.WorldHandle.SetTransform(Matrix3x2.Identity);
} }

View File

@@ -1,24 +1,14 @@
using System.Text;
using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Systems;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
namespace Content.Client.Shuttles.UI; namespace Content.Client.Shuttles.UI;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class DockObject : PanelContainer public sealed partial class DockObject : PanelContainer
{ {
[PublicAPI]
public event Action? UndockPressed;
[PublicAPI]
public event Action? ViewPressed;
public BoxContainer ContentsContainer => Contents; public BoxContainer ContentsContainer => Contents;
public DockObject() public DockObject()

View File

@@ -163,16 +163,6 @@ public sealed partial class DockingScreen : BoxContainer
} }
dockContainer.AddDock(dock, DockingControl); dockContainer.AddDock(dock, DockingControl);
dockContainer.ViewPressed += () =>
{
OnDockPress(dock);
};
dockContainer.UndockPressed += () =>
{
UndockRequest?.Invoke(dock.Entity);
};
} }
} }

View File

@@ -81,13 +81,19 @@ public sealed partial class NavScreen : BoxContainer
// Get the positive reduced angle. // Get the positive reduced angle.
var displayRot = -worldRot.Reduced(); var displayRot = -worldRot.Reduced();
GridPosition.Text = $"{worldPos.X:0.0}, {worldPos.Y:0.0}"; GridPosition.Text = Loc.GetString("shuttle-console-position-value",
GridOrientation.Text = $"{displayRot.Degrees:0.0}"; ("X", $"{worldPos.X:0.0}"),
("Y", $"{worldPos.Y:0.0}"));
GridOrientation.Text = Loc.GetString("shuttle-console-orientation-value",
("angle", $"{displayRot.Degrees:0.0}"));
var gridVelocity = gridBody.LinearVelocity; var gridVelocity = gridBody.LinearVelocity;
gridVelocity = displayRot.RotateVec(gridVelocity); gridVelocity = displayRot.RotateVec(gridVelocity);
// Get linear velocity relative to the console entity // Get linear velocity relative to the console entity
GridLinearVelocity.Text = $"{gridVelocity.X + 10f * float.Epsilon:0.0}, {gridVelocity.Y + 10f * float.Epsilon:0.0}"; GridLinearVelocity.Text = Loc.GetString("shuttle-console-linear-velocity-value",
GridAngularVelocity.Text = $"{-gridBody.AngularVelocity + 10f * float.Epsilon:0.0}"; ("X", $"{gridVelocity.X + 10f * float.Epsilon:0.0}"),
("Y", $"{gridVelocity.Y + 10f * float.Epsilon:0.0}"));
GridAngularVelocity.Text = Loc.GetString("shuttle-console-angular-velocity-value",
("angularVelocity", $"{-MathHelper.RadiansToDegrees(gridBody.AngularVelocity) + 10f * float.Epsilon:0.0}"));
} }
} }

View File

@@ -1,4 +1,4 @@
<ui:RadialMenu xmlns="https://spacestation14.io" <ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton" BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton" CloseButtonStyleClass="RadialMenuCloseButton"
@@ -7,7 +7,7 @@
MinSize="450 450"> MinSize="450 450">
<!-- Main --> <!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False"> <ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer> </ui:RadialContainer>
</ui:RadialMenu> </ui:RadialMenu>

View File

@@ -54,7 +54,6 @@ public sealed partial class StationAiMenu : RadialMenu
// TODO: This radial boilerplate is quite annoying // TODO: This radial boilerplate is quite annoying
var button = new StationAiMenuButton(action.Event) var button = new StationAiMenuButton(action.Event)
{ {
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f), SetSize = new Vector2(64f, 64f),
ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null, ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null,
}; };
@@ -121,7 +120,7 @@ public sealed partial class StationAiMenu : RadialMenu
} }
} }
public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButtonWithSector
{ {
public BaseStationAiAction Action = action; public BaseStationAiAction Action = action;
} }

View File

@@ -3,12 +3,12 @@ using System.Numerics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Client.Administration.Managers; using Content.Client.Administration.Managers;
using Content.Shared.Database;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
@@ -24,6 +24,7 @@ public sealed class ContentSpriteSystem : EntitySystem
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IResourceManager _resManager = default!; [Dependency] private readonly IResourceManager _resManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!; [Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
private ContentSpriteControl _control = new(); private ContentSpriteControl _control = new();
@@ -42,12 +43,12 @@ public sealed class ContentSpriteSystem : EntitySystem
{ {
base.Shutdown(); base.Shutdown();
foreach (var queued in _control._queuedTextures) foreach (var queued in _control.QueuedTextures)
{ {
queued.Tcs.SetCanceled(); queued.Tcs.SetCanceled();
} }
_control._queuedTextures.Clear(); _control.QueuedTextures.Clear();
_ui.RootControl.RemoveChild(_control); _ui.RootControl.RemoveChild(_control);
} }
@@ -103,7 +104,7 @@ public sealed class ContentSpriteSystem : EntitySystem
var texture = _clyde.CreateRenderTarget(new Vector2i(size.X, size.Y), new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "export"); var texture = _clyde.CreateRenderTarget(new Vector2i(size.X, size.Y), new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "export");
var tcs = new TaskCompletionSource(cancelToken); var tcs = new TaskCompletionSource(cancelToken);
_control._queuedTextures.Enqueue((texture, direction, entity, includeId, tcs)); _control.QueuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
await tcs.Task; await tcs.Task;
} }
@@ -113,13 +114,21 @@ public sealed class ContentSpriteSystem : EntitySystem
if (!_adminManager.IsAdmin()) if (!_adminManager.IsAdmin())
return; return;
var target = ev.Target;
Verb verb = new() Verb verb = new()
{ {
Text = Loc.GetString("export-entity-verb-get-data-text"), Text = Loc.GetString("export-entity-verb-get-data-text"),
Category = VerbCategory.Debug, Category = VerbCategory.Debug,
Act = () => Act = async () =>
{ {
Export(ev.Target); try
{
await Export(target);
}
catch (Exception e)
{
_runtimeLog.LogException(e, $"{nameof(ContentSpriteSystem)}.{nameof(Export)}");
}
}, },
}; };
@@ -141,7 +150,7 @@ public sealed class ContentSpriteSystem : EntitySystem
Direction Direction, Direction Direction,
EntityUid Entity, EntityUid Entity,
bool IncludeId, bool IncludeId,
TaskCompletionSource Tcs)> _queuedTextures = new(); TaskCompletionSource Tcs)> QueuedTextures = new();
private ISawmill _sawmill; private ISawmill _sawmill;
@@ -155,7 +164,7 @@ public sealed class ContentSpriteSystem : EntitySystem
{ {
base.Draw(handle); base.Draw(handle);
while (_queuedTextures.TryDequeue(out var queued)) while (QueuedTextures.TryDequeue(out var queued))
{ {
if (queued.Tcs.Task.IsCanceled) if (queued.Tcs.Task.IsCanceled)
continue; continue;

View File

@@ -4,6 +4,7 @@ using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components; using Content.Shared.StatusIcon.Components;
using Content.Shared.Stealth.Components; using Content.Shared.Stealth.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
@@ -85,6 +86,9 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
if (data.HideOnStealth && TryComp<StealthComponent>(ent, out var stealth) && stealth.Enabled) if (data.HideOnStealth && TryComp<StealthComponent>(ent, out var stealth) && stealth.Enabled)
return false; return false;
if (TryComp<SpriteComponent>(ent, out var sprite) && !sprite.Visible)
return false;
if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer)) if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer))
return false; return false;

View File

@@ -1,39 +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 Content.Shared.Storage;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Storage; namespace Content.Client.Storage;
[UsedImplicitly] [UsedImplicitly]
public sealed class StorageBoundUserInterface : BoundUserInterface public sealed class StorageBoundUserInterface : BoundUserInterface
{ {
[Dependency] private readonly IEntityManager _entManager = default!; private StorageWindow? _window;
private readonly StorageSystem _storage;
[Obsolete] public override bool DeferredClose => false;
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{ {
IoCManager.InjectDependencies(this);
_storage = _entManager.System<StorageSystem>();
} }
protected override void Open() protected override void Open()
{ {
base.Open(); base.Open();
if (_entManager.TryGetComponent<StorageComponent>(Owner, out var comp)) _window = IoCManager.Resolve<IUserInterfaceManager>()
_storage.OpenStorageWindow((Owner, comp)); .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) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);
if (!disposing)
Reclaim();
}
public void Hide()
{
if (_window == null)
return; 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

@@ -4,7 +4,8 @@ using Content.Client.Animations;
using Content.Shared.Hands; using Content.Shared.Hands;
using Content.Shared.Storage; using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems; using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Collections; using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -13,114 +14,91 @@ namespace Content.Client.Storage.Systems;
public sealed class StorageSystem : SharedStorageSystem public sealed class StorageSystem : SharedStorageSystem
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!; [Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!;
private readonly List<Entity<StorageComponent>> _openStorages = new(); private Dictionary<EntityUid, ItemStorageLocation> _oldStoredItems = new();
public int OpenStorageAmount => _openStorages.Count;
public event Action<Entity<StorageComponent>>? StorageUpdated;
public event Action<Entity<StorageComponent>?>? StorageOrderChanged;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown); SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation); SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities); SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
} }
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;
_oldStoredItems.Clear();
foreach (var item in component.StoredItems)
{
_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))
{
containerBui.Hide();
}
}
}
public override void UpdateUI(Entity<StorageComponent?> entity) public override void UpdateUI(Entity<StorageComponent?> entity)
{ {
if (Resolve(entity.Owner, ref entity.Comp)) if (UI.TryGetOpenUi<StorageBoundUserInterface>(entity.Owner, StorageComponent.StorageUiKey.Key, out var sBui))
StorageUpdated?.Invoke((entity, entity.Comp));
}
public void OpenStorageWindow(Entity<StorageComponent> entity)
{
if (_openStorages.Contains(entity))
{ {
if (_openStorages.LastOrDefault() == entity) sBui.Refresh();
{
CloseStorageWindow((entity, entity.Comp));
}
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);
}
}
return;
}
ClearNonParentStorages(entity);
_openStorages.Add(entity);
Entity<StorageComponent>? last = _openStorages.LastOrDefault();
StorageOrderChanged?.Invoke(last);
}
public void CloseStorageWindow(Entity<StorageComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
if (!_openStorages.Contains((entity, entity.Comp)))
return;
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storage in reverseStorages)
{
CloseStorageBoundUserInterface(storage.Owner);
_openStorages.Remove(storage);
if (storage.Owner == entity.Owner)
break;
}
Entity<StorageComponent>? last = null;
if (_openStorages.Any())
last = _openStorages.LastOrDefault();
StorageOrderChanged?.Invoke(last);
}
private void ClearNonParentStorages(EntityUid uid)
{
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storage in reverseStorages)
{
if (storage.Comp.Container.Contains(uid))
break;
CloseStorageBoundUserInterface(storage.Owner);
_openStorages.Remove(storage);
} }
} }
private void CloseStorageBoundUserInterface(Entity<UserInterfaceComponent?> entity) protected override void HideStorageWindow(EntityUid uid, EntityUid actor)
{ {
if (!Resolve(entity, ref entity.Comp, false)) if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
return; {
storageBui.Hide();
if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui) }
return;
bui.Close();
} }
private void OnShutdown(Entity<StorageComponent> ent, ref ComponentShutdown args) protected override void ShowStorageWindow(EntityUid uid, EntityUid actor)
{ {
CloseStorageWindow((ent, ent.Comp)); if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
storageBui.Show();
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -142,7 +120,7 @@ public sealed class StorageSystem : SharedStorageSystem
{ {
if (!_timing.IsFirstTimePredicted) if (!_timing.IsFirstTimePredicted)
return; return;
if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) || if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) ||
!Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId)) !Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
{ {

View File

@@ -27,6 +27,8 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
protected override void Open() protected override void Open()
{ {
base.Open();
_menu = this.CreateWindow<StoreMenu>(); _menu = this.CreateWindow<StoreMenu>();
if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store)) if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store))
_menu.Title = Loc.GetString(store.Name); _menu.Title = Loc.GetString(store.Name);

View File

@@ -21,6 +21,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
protected override void Open() protected override void Open()
{ {
base.Open();
_window = new(); _window = new();
if (_type == SurveillanceCameraSetupUiKey.Router) if (_type == SurveillanceCameraSetupUiKey.Router)

View File

@@ -87,9 +87,6 @@ public sealed partial class DialogWindow : FancyWindow
Prompts.AddChild(box); Prompts.AddChild(box);
} }
// Grab keyboard focus for the first dialog entry
_promptLines[0].Item2.GrabKeyboardFocus();
OkButton.OnPressed += _ => Confirm(); OkButton.OnPressed += _ => Confirm();
CancelButton.OnPressed += _ => CancelButton.OnPressed += _ =>
@@ -110,6 +107,14 @@ public sealed partial class DialogWindow : FancyWindow
OpenCentered(); OpenCentered();
} }
protected override void Opened()
{
base.Opened();
// Grab keyboard focus for the first dialog entry
_promptLines[0].Item2.GrabKeyboardFocus();
}
private void Confirm() private void Confirm()
{ {
var results = new Dictionary<string, string>(); var results = new Dictionary<string, string>();

View File

@@ -7,7 +7,6 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Graphics;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -96,7 +95,6 @@ public sealed partial class FancyTree : Control
private void LoadIcons() private void LoadIcons()
{ {
IconColor = TryGetStyleProperty(StylePropertyIconColor, out Color color) ? color : Color.White; IconColor = TryGetStyleProperty(StylePropertyIconColor, out Color color) ? color : Color.White;
string? path;
if (!TryGetStyleProperty(StylePropertyIconExpanded, out IconExpanded)) if (!TryGetStyleProperty(StylePropertyIconExpanded, out IconExpanded))
IconExpanded = _resCache.GetTexture(DefaultIconExpanded); IconExpanded = _resCache.GetTexture(DefaultIconExpanded);

View File

@@ -1,4 +1,3 @@
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
@@ -8,6 +7,11 @@ namespace Content.Client.UserInterface.Controls;
[Virtual] [Virtual]
public class RadialContainer : LayoutContainer public class RadialContainer : LayoutContainer
{ {
/// <summary>
/// Increment of radius per child element to be rendered.
/// </summary>
private const float RadiusIncrement = 5f;
/// <summary> /// <summary>
/// Specifies the anglular range, in radians, in which child elements will be placed. /// Specifies the anglular range, in radians, in which child elements will be placed.
/// The first value denotes the angle at which the first element is to be placed, and /// The first value denotes the angle at which the first element is to be placed, and
@@ -49,10 +53,30 @@ public class RadialContainer : LayoutContainer
public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise; public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise;
/// <summary> /// <summary>
/// Determines how far from the radial container's center that its child elements will be placed /// Radial menu radius determines how far from the radial container's center its child elements will be placed.
/// To correctly display dynamic amount of elements control actually resizes depending on amount of child buttons,
/// but uses this property as base value for final radius calculation.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float Radius { get; set; } = 100f; public float InitialRadius { get; set; } = 100f;
/// <summary>
/// Radial menu radius determines how far from the radial container's center its child elements will be placed.
/// This is dynamically calculated (based on child button count) radius, result of <see cref="InitialRadius"/> and
/// <see cref="RadiusIncrement"/> multiplied by currently visible child button count.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float CalculatedRadius { get; private set; }
/// <summary>
/// Determines radial menu button sectors inner radius, is a multiplier of <see cref="InitialRadius"/>.
/// </summary>
public float InnerRadiusMultiplier { get; set; } = 0.5f;
/// <summary>
/// Determines radial menu button sectors outer radius, is a multiplier of <see cref="InitialRadius"/>.
/// </summary>
public float OuterRadiusMultiplier { get; set; } = 1.5f;
/// <summary> /// <summary>
/// Sets whether the container should reserve a space on the layout for child which are not currently visible /// Sets whether the container should reserve a space on the layout for child which are not currently visible
@@ -67,37 +91,74 @@ public class RadialContainer : LayoutContainer
{ {
} }
protected override void Draw(DrawingHandleScreen handle) /// <inheritdoc />
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{ {
var children = ReserveSpaceForHiddenChildren
const float baseRadius = 100f; ? Children
const float radiusIncrement = 5f; : Children.Where(x => x.Visible);
var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible);
var childCount = children.Count(); var childCount = children.Count();
// Add padding from the center at higher child counts so they don't overlap. // Add padding from the center at higher child counts so they don't overlap.
Radius = baseRadius + (childCount * radiusIncrement); CalculatedRadius = InitialRadius + (childCount * RadiusIncrement);
var isAntiClockwise = RadialAlignment == RAlignment.AntiClockwise;
// Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements // Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements
var arc = AngularRange.Y - AngularRange.X; var arc = AngularRange.Y - AngularRange.X;
arc = (arc < 0) ? MathF.Tau + arc : arc; arc = arc < 0
arc = (RadialAlignment == RAlignment.AntiClockwise) ? MathF.Tau - arc : arc; ? MathF.Tau + arc
: arc;
arc = isAntiClockwise
? MathF.Tau - arc
: arc;
// Account for both circular arrangements and arc-based arrangements // Account for both circular arrangements and arc-based arrangements
var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f) ? 0 : 1; var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f)
? 0
: 1;
// Determine the separation between child elements // Determine the separation between child elements
var sepAngle = arc / (childCount - childMod); var sepAngle = arc / (childCount - childMod);
sepAngle *= (RadialAlignment == RAlignment.AntiClockwise) ? -1f : 1f; sepAngle *= isAntiClockwise
? -1f
: 1f;
var controlCenter = finalSize * 0.5f;
// Adjust the positions of all the child elements // Adjust the positions of all the child elements
foreach (var (i, child) in children.Select((x, i) => (i, x))) var query = children.Select((x, index) => (index, x));
foreach (var (childIndex, child) in query)
{ {
var position = new Vector2(Radius * MathF.Sin(AngularRange.X + sepAngle * i) + Width / 2f - child.Width / 2f, -Radius * MathF.Cos(AngularRange.X + sepAngle * i) + Height / 2f - child.Height / 2f); const float angleOffset = MathF.PI * 0.5f;
var targetAngleOfChild = AngularRange.X + sepAngle * (childIndex + 0.5f) + angleOffset;
// flooring values for snapping float values to physical grid -
// it prevents gaps and overlapping between different button segments
var position = new Vector2(
MathF.Floor(CalculatedRadius * MathF.Cos(targetAngleOfChild)),
MathF.Floor(-CalculatedRadius * MathF.Sin(targetAngleOfChild))
) + controlCenter - child.DesiredSize * 0.5f + Position;
SetPosition(child, position); SetPosition(child, position);
// radial menu buttons with sector need to also know in which sector and around which point
// they should be rendered, how much space sector should should take etc.
if (child is IRadialMenuItemWithSector tb)
{
tb.AngleSectorFrom = sepAngle * childIndex;
tb.AngleSectorTo = sepAngle * (childIndex + 1);
tb.AngleOffset = angleOffset;
tb.InnerRadius = CalculatedRadius * InnerRadiusMultiplier;
tb.OuterRadius = CalculatedRadius * OuterRadiusMultiplier;
tb.ParentCenter = controlCenter;
}
} }
return base.ArrangeOverride(finalSize);
} }
/// <summary> /// <summary>
@@ -109,4 +170,5 @@ public class RadialContainer : LayoutContainer
Clockwise, Clockwise,
AntiClockwise, AntiClockwise,
} }
} }

View File

@@ -3,6 +3,9 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Shared.Input;
using Robust.Client.Graphics;
using Robust.Shared.Input;
namespace Content.Client.UserInterface.Controls; namespace Content.Client.UserInterface.Controls;
@@ -12,11 +15,16 @@ public class RadialMenu : BaseWindow
/// <summary> /// <summary>
/// Contextual button used to traverse through previous layers of the radial menu /// Contextual button used to traverse through previous layers of the radial menu
/// </summary> /// </summary>
public TextureButton? ContextualButton { get; set; } public RadialMenuContextualCentralTextureButton ContextualButton { get; }
/// <summary>
/// Button that represents outer area of menu (closes menu on outside clicks).
/// </summary>
public RadialMenuOuterAreaButton MenuOuterAreaButton { get; }
/// <summary> /// <summary>
/// Set a style class to be applied to the contextual button when it is set to move the user back through previous layers of the radial menu /// Set a style class to be applied to the contextual button when it is set to move the user back through previous layers of the radial menu
/// </summary> /// </summary>
public string? BackButtonStyleClass public string? BackButtonStyleClass
{ {
get get
@@ -52,7 +60,7 @@ public class RadialMenu : BaseWindow
} }
} }
private List<Control> _path = new(); private readonly List<Control> _path = new();
private string? _backButtonStyleClass; private string? _backButtonStyleClass;
private string? _closeButtonStyleClass; private string? _closeButtonStyleClass;
@@ -60,8 +68,8 @@ public class RadialMenu : BaseWindow
/// A free floating menu which enables the quick display of one or more radial containers /// A free floating menu which enables the quick display of one or more radial containers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Only one radial container is visible at a time (each container forming a separate 'layer' within /// Only one radial container is visible at a time (each container forming a separate 'layer' within
/// the menu), along with a contextual button at the menu center, which will either return the user /// the menu), along with a contextual button at the menu center, which will either return the user
/// to the previous layer or close the menu if there are no previous layers left to traverse. /// to the previous layer or close the menu if there are no previous layers left to traverse.
/// To create a functional radial menu, simply parent one or more named radial containers to it, /// To create a functional radial menu, simply parent one or more named radial containers to it,
/// and populate the radial containers with RadialMenuButtons. Setting the TargetLayer field of these /// and populate the radial containers with RadialMenuButtons. Setting the TargetLayer field of these
@@ -78,23 +86,56 @@ public class RadialMenu : BaseWindow
} }
// Auto generate a contextual button for moving back through visited layers // Auto generate a contextual button for moving back through visited layers
ContextualButton = new TextureButton() ContextualButton = new RadialMenuContextualCentralTextureButton
{ {
HorizontalAlignment = HAlignment.Center, HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center, VerticalAlignment = VAlignment.Center,
SetSize = new Vector2(64f, 64f), SetSize = new Vector2(64f, 64f),
}; };
MenuOuterAreaButton = new RadialMenuOuterAreaButton();
ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer(); ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer();
MenuOuterAreaButton.OnButtonUp += _ => Close();
AddChild(ContextualButton); AddChild(ContextualButton);
AddChild(MenuOuterAreaButton);
// Hide any further add children, unless its promoted to the active layer // Hide any further add children, unless its promoted to the active layer
OnChildAdded += child => child.Visible = (GetCurrentActiveLayer() == child); OnChildAdded += child =>
{
child.Visible = GetCurrentActiveLayer() == child;
SetupContextualButtonData(child);
};
}
private void SetupContextualButtonData(Control child)
{
if (child is RadialContainer { Visible: true } container)
{
var parentCenter = MinSize * 0.5f;
ContextualButton.ParentCenter = parentCenter;
MenuOuterAreaButton.ParentCenter = parentCenter;
ContextualButton.InnerRadius = container.CalculatedRadius * container.InnerRadiusMultiplier;
MenuOuterAreaButton.OuterRadius = container.CalculatedRadius * container.OuterRadiusMultiplier;
}
}
/// <inheritdoc />
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var result = base.ArrangeOverride(finalSize);
var currentLayer = GetCurrentActiveLayer();
if (currentLayer != null)
{
SetupContextualButtonData(currentLayer);
}
return result;
} }
private Control? GetCurrentActiveLayer() private Control? GetCurrentActiveLayer()
{ {
var children = Children.Where(x => x != ContextualButton); var children = Children.Where(x => x != ContextualButton && x != MenuOuterAreaButton);
if (!children.Any()) if (!children.Any())
return null; return null;
@@ -116,7 +157,7 @@ public class RadialMenu : BaseWindow
foreach (var child in Children) foreach (var child in Children)
{ {
if (child == ContextualButton) if (child == ContextualButton || child == MenuOuterAreaButton)
continue; continue;
// Hide layers which are not of interest // Hide layers which are not of interest
@@ -129,6 +170,7 @@ public class RadialMenu : BaseWindow
else else
{ {
child.Visible = true; child.Visible = true;
SetupContextualButtonData(child);
result = true; result = true;
} }
} }
@@ -158,7 +200,7 @@ public class RadialMenu : BaseWindow
// Hide all children except the contextual button // Hide all children except the contextual button
foreach (var child in Children) foreach (var child in Children)
{ {
if (child != ContextualButton) if (child != ContextualButton && child != MenuOuterAreaButton)
child.Visible = false; child.Visible = false;
} }
@@ -172,49 +214,86 @@ public class RadialMenu : BaseWindow
} }
} }
/// <summary>
/// Base class for radial menu buttons. Excludes all actions except clicks and alt-clicks
/// from interactions.
/// </summary>
[Virtual] [Virtual]
public class RadialMenuButton : Button public class RadialMenuTextureButtonBase : TextureButton
{ {
/// <summary> /// <inheritdoc />
/// Upon clicking this button the radial menu will transition to the named layer protected RadialMenuTextureButtonBase()
/// </summary>
public string? TargetLayer { get; set; }
/// <summary>
/// A simple button that can move the user to a different layer within a radial menu
/// </summary>
public RadialMenuButton()
{ {
OnButtonUp += OnClicked; EnableAllKeybinds = true;
} }
private void OnClicked(ButtonEventArgs args) /// <inheritdoc />
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{ {
if (TargetLayer == null || TargetLayer == string.Empty) if (args.Function == EngineKeyFunctions.UIClick
return; || args.Function == ContentKeyFunctions.AltActivateItemInWorld)
base.KeyBindUp(args);
var parent = FindParentMultiLayerContainer(this);
if (parent == null)
return;
parent.TryToMoveToNewLayer(TargetLayer);
} }
}
private RadialMenu? FindParentMultiLayerContainer(Control control) /// <summary>
/// Special button for closing radial menu or going back between radial menu levels.
/// Is looking like just <see cref="TextureButton "/> but considers whole space around
/// itself (til radial menu buttons) as itself in case of clicking. But this 'effect'
/// works only if control have parent, and ActiveContainer property is set.
/// Also considers all space outside of radial menu buttons as itself for clicking.
/// </summary>
public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTextureButtonBase
{
public float InnerRadius { get; set; }
public Vector2? ParentCenter { get; set; }
/// <inheritdoc />
protected override bool HasPoint(Vector2 point)
{ {
foreach (var ancestor in control.GetSelfAndLogicalAncestors()) if (ParentCenter == null)
{ {
if (ancestor is RadialMenu) return base.HasPoint(point);
return ancestor as RadialMenu;
} }
return null; var distSquared = (point + Position - ParentCenter.Value).LengthSquared();
var innerRadiusSquared = InnerRadius * InnerRadius;
// comparing to squared values is faster then making sqrt
return distSquared < innerRadiusSquared;
}
}
/// <summary>
/// Menu button for outer area of radial menu (covers everything 'outside').
/// </summary>
public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase
{
public float OuterRadius { get; set; }
public Vector2? ParentCenter { get; set; }
/// <inheritdoc />
protected override bool HasPoint(Vector2 point)
{
if (ParentCenter == null)
{
return base.HasPoint(point);
}
var distSquared = (point + Position - ParentCenter.Value).LengthSquared();
var outerRadiusSquared = OuterRadius * OuterRadius;
// comparing to squared values is faster, then making sqrt
return distSquared > outerRadiusSquared;
} }
} }
[Virtual] [Virtual]
public class RadialMenuTextureButton : TextureButton public class RadialMenuTextureButton : RadialMenuTextureButtonBase
{ {
/// <summary> /// <summary>
/// Upon clicking this button the radial menu will be moved to the named layer /// Upon clicking this button the radial menu will be moved to the named layer
@@ -226,6 +305,7 @@ public class RadialMenuTextureButton : TextureButton
/// </summary> /// </summary>
public RadialMenuTextureButton() public RadialMenuTextureButton()
{ {
EnableAllKeybinds = true;
OnButtonUp += OnClicked; OnButtonUp += OnClicked;
} }
@@ -246,10 +326,329 @@ public class RadialMenuTextureButton : TextureButton
{ {
foreach (var ancestor in control.GetSelfAndLogicalAncestors()) foreach (var ancestor in control.GetSelfAndLogicalAncestors())
{ {
if (ancestor is RadialMenu) if (ancestor is RadialMenu menu)
return ancestor as RadialMenu; return menu;
} }
return null; return null;
} }
} }
public interface IRadialMenuItemWithSector
{
/// <summary>
/// Angle in radian where button sector should start.
/// </summary>
public float AngleSectorFrom { set; }
/// <summary>
/// Angle in radian where button sector should end.
/// </summary>
public float AngleSectorTo { set; }
/// <summary>
/// Outer radius for drawing segment and pointer detection.
/// </summary>
public float OuterRadius { set; }
/// <summary>
/// Outer radius for drawing segment and pointer detection.
/// </summary>
public float InnerRadius { set; }
/// <summary>
/// Offset in radian by which menu button should be rotated.
/// </summary>
public float AngleOffset { set; }
/// <summary>
/// Coordinates of center in parent component - button container.
/// </summary>
public Vector2 ParentCenter { set; }
}
[Virtual]
public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadialMenuItemWithSector
{
private Vector2[]? _sectorPointsForDrawing;
private float _angleSectorFrom;
private float _angleSectorTo;
private float _outerRadius;
private float _innerRadius;
private float _angleOffset;
private bool _isWholeCircle;
private Vector2? _parentCenter;
private Color _backgroundColorSrgb = Color.ToSrgb(new Color(70, 73, 102, 128));
private Color _hoverBackgroundColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128));
private Color _borderColorSrgb = Color.ToSrgb(new Color(173, 216, 230, 70));
private Color _hoverBorderColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128));
/// <summary>
/// Marker, that control should render border of segment. Is false by default.
/// </summary>
/// <remarks>
/// By default color of border is same as color of background. Use <see cref="BorderColor"/>
/// and <see cref="HoverBorderColor"/> to change it.
/// </remarks>
public bool DrawBorder { get; set; } = false;
/// <summary>
/// Marker, that control should render background of all sector. Is true by default.
/// </summary>
public bool DrawBackground { get; set; } = true;
/// <summary>
/// Marker, that control should render separator lines.
/// Separator lines are used to visually separate sector of radial menu items.
/// Is true by default
/// </summary>
public bool DrawSeparators { get; set; } = true;
/// <summary>
/// Color of background in non-hovered state. Accepts RGB color, works with sRGB for DrawPrimitive internally.
/// </summary>
public Color BackgroundColor
{
get => Color.FromSrgb(_backgroundColorSrgb);
set => _backgroundColorSrgb = Color.ToSrgb(value);
}
/// <summary>
/// Color of background in hovered state. Accepts RGB color, works with sRGB for DrawPrimitive internally.
/// </summary>
public Color HoverBackgroundColor
{
get => Color.FromSrgb(_hoverBackgroundColorSrgb);
set => _hoverBackgroundColorSrgb = Color.ToSrgb(value);
}
/// <summary>
/// Color of button border. Accepts RGB color, works with sRGB for DrawPrimitive internally.
/// </summary>
public Color BorderColor
{
get => Color.FromSrgb(_borderColorSrgb);
set => _borderColorSrgb = Color.ToSrgb(value);
}
/// <summary>
/// Color of button border when button is hovered. Accepts RGB color, works with sRGB for DrawPrimitive internally.
/// </summary>
public Color HoverBorderColor
{
get => Color.FromSrgb(_hoverBorderColorSrgb);
set => _hoverBorderColorSrgb = Color.ToSrgb(value);
}
/// <summary>
/// Color of separator lines.
/// Separator lines are used to visually separate sector of radial menu items.
/// </summary>
public Color SeparatorColor { get; set; } = new Color(128, 128, 128, 128);
/// <inheritdoc />
float IRadialMenuItemWithSector.AngleSectorFrom
{
set
{
_angleSectorFrom = value;
_isWholeCircle = IsWholeCircle(value, _angleSectorTo);
}
}
/// <inheritdoc />
float IRadialMenuItemWithSector.AngleSectorTo
{
set
{
_angleSectorTo = value;
_isWholeCircle = IsWholeCircle(_angleSectorFrom, value);
}
}
/// <inheritdoc />
float IRadialMenuItemWithSector.OuterRadius { set => _outerRadius = value; }
/// <inheritdoc />
float IRadialMenuItemWithSector.InnerRadius { set => _innerRadius = value; }
/// <inheritdoc />
public float AngleOffset { set => _angleOffset = value; }
/// <inheritdoc />
Vector2 IRadialMenuItemWithSector.ParentCenter { set => _parentCenter = value; }
/// <summary>
/// A simple texture button that can move the user to a different layer within a radial menu
/// </summary>
public RadialMenuTextureButtonWithSector()
{
}
/// <inheritdoc />
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (_parentCenter == null)
{
return;
}
// draw sector where space that button occupies actually is
var containerCenter = (_parentCenter.Value - Position) * UIScale;
var angleFrom = _angleSectorFrom + _angleOffset;
var angleTo = _angleSectorTo + _angleOffset;
if (DrawBackground)
{
var segmentColor = DrawMode == DrawModeEnum.Hover
? _hoverBackgroundColorSrgb
: _backgroundColorSrgb;
DrawAnnulusSector(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, segmentColor);
}
if (DrawBorder)
{
var borderColor = DrawMode == DrawModeEnum.Hover
? _hoverBorderColorSrgb
: _borderColorSrgb;
DrawAnnulusSector(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, borderColor, false);
}
if (!_isWholeCircle && DrawSeparators)
{
DrawSeparatorLines(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, SeparatorColor);
}
}
/// <inheritdoc />
protected override bool HasPoint(Vector2 point)
{
if (_parentCenter == null)
{
return base.HasPoint(point);
}
var outerRadiusSquared = _outerRadius * _outerRadius;
var innerRadiusSquared = _innerRadius * _innerRadius;
var distSquared = (point + Position - _parentCenter.Value).LengthSquared();
var isInRadius = distSquared < outerRadiusSquared && distSquared > innerRadiusSquared;
if (!isInRadius)
{
return false;
}
// difference from the center of the parent to the `point`
var pointFromParent = point + Position - _parentCenter.Value;
// Flip Y to get from ui coordinates to natural coordinates
var angle = MathF.Atan2(-pointFromParent.Y, pointFromParent.X) - _angleOffset;
if (angle < 0)
{
// atan2 range is -pi->pi, while angle sectors are
// 0->2pi, so remap the result into that range
angle = MathF.PI * 2 + angle;
}
var isInAngle = angle >= _angleSectorFrom && angle < _angleSectorTo;
return isInAngle;
}
/// <summary>
/// Draw segment between two concentrated circles from and to certain angles.
/// </summary>
/// <param name="drawingHandleScreen">Drawing handle, to which rendering should be delegated.</param>
/// <param name="center">Point where circle center should be.</param>
/// <param name="radiusInner">Radius of internal circle.</param>
/// <param name="radiusOuter">Radius of external circle.</param>
/// <param name="angleSectorFrom">Angle in radian, from which sector should start.</param>
/// <param name="angleSectorTo">Angle in radian, from which sector should start.</param>
/// <param name="color">Color for drawing.</param>
/// <param name="filled">Should figure be filled, or have only border.</param>
private void DrawAnnulusSector(
DrawingHandleScreen drawingHandleScreen,
Vector2 center,
float radiusInner,
float radiusOuter,
float angleSectorFrom,
float angleSectorTo,
Color color,
bool filled = true
)
{
const float minimalSegmentSize = MathF.Tau / 128f;
var requestedSegmentSize = angleSectorTo - angleSectorFrom;
var segmentCount = (int)(requestedSegmentSize / minimalSegmentSize) + 1;
var anglePerSegment = requestedSegmentSize / (segmentCount - 1);
var bufferSize = segmentCount * 2;
if (_sectorPointsForDrawing == null || _sectorPointsForDrawing.Length != bufferSize)
{
_sectorPointsForDrawing ??= new Vector2[bufferSize];
}
for (var i = 0; i < segmentCount; i++)
{
var angle = angleSectorFrom + anglePerSegment * i;
// Flip Y to get from ui coordinates to natural coordinates
var unitPos = new Vector2(MathF.Cos(angle), -MathF.Sin(angle));
var outerPoint = center + unitPos * radiusOuter;
var innerPoint = center + unitPos * radiusInner;
if (filled)
{
// to make filled sector we need to create strip from triangles
_sectorPointsForDrawing[i * 2] = outerPoint;
_sectorPointsForDrawing[i * 2 + 1] = innerPoint;
}
else
{
// to make border of sector we need points ordered as sequences on radius
_sectorPointsForDrawing[i] = outerPoint;
_sectorPointsForDrawing[bufferSize - 1 - i] = innerPoint;
}
}
var type = filled
? DrawPrimitiveTopology.TriangleStrip
: DrawPrimitiveTopology.LineStrip;
drawingHandleScreen.DrawPrimitives(type, _sectorPointsForDrawing, color);
}
private static void DrawSeparatorLines(
DrawingHandleScreen drawingHandleScreen,
Vector2 center,
float radiusInner,
float radiusOuter,
float angleSectorFrom,
float angleSectorTo,
Color color
)
{
var fromPoint = new Angle(-angleSectorFrom).RotateVec(Vector2.UnitX);
drawingHandleScreen.DrawLine(
center + fromPoint * radiusOuter,
center + fromPoint * radiusInner,
color
);
var toPoint = new Angle(-angleSectorTo).RotateVec(Vector2.UnitX);
drawingHandleScreen.DrawLine(
center + toPoint * radiusOuter,
center + toPoint * radiusInner,
color
);
}
private static bool IsWholeCircle(float angleSectorFrom, float angleSectorTo)
{
return new Angle(angleSectorFrom).EqualsApprox(new Angle(angleSectorTo));
}
}

View File

@@ -952,7 +952,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
var range = entityAction.CheckCanAccess ? action.Range : -1; var range = entityAction.CheckCanAccess ? action.Range : -1;
_interactionOutline?.SetEnabled(false); _interactionOutline?.SetEnabled(false);
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, null); _targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
} }
/// <summary> /// <summary>

View File

@@ -56,19 +56,20 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
_window = UIManager.CreateWindow<CharacterWindow>(); _window = UIManager.CreateWindow<CharacterWindow>();
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
_window.OnClose += DeactivateButton;
_window.OnOpen += ActivateButton;
CommandBinds.Builder CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCharacterMenu, .Bind(ContentKeyFunctions.OpenCharacterMenu,
InputCmdHandler.FromDelegate(_ => ToggleWindow())) InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<CharacterUIController>(); .Register<CharacterUIController>();
} }
public void OnStateExited(GameplayState state) public void OnStateExited(GameplayState state)
{ {
if (_window != null) if (_window != null)
{ {
_window.Dispose(); _window.Close();
_window = null; _window = null;
} }
@@ -105,18 +106,27 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
} }
CharacterButton.OnPressed += CharacterButtonPressed; CharacterButton.OnPressed += CharacterButtonPressed;
}
if (_window == null) private void DeactivateButton()
{
if (CharacterButton == null)
{ {
return; return;
} }
_window.OnClose += DeactivateButton; CharacterButton.Pressed = false;
_window.OnOpen += ActivateButton;
} }
private void DeactivateButton() => CharacterButton!.Pressed = false; private void ActivateButton()
private void ActivateButton() => CharacterButton!.Pressed = true; {
if (CharacterButton == null)
{
return;
}
CharacterButton.Pressed = true;
}
private void CharacterUpdated(CharacterData data) private void CharacterUpdated(CharacterData data)
{ {
@@ -150,7 +160,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
var objectiveLabel = new RichTextLabel var objectiveLabel = new RichTextLabel
{ {
StyleClasses = {StyleNano.StyleClassTooltipActionTitle} StyleClasses = { StyleNano.StyleClassTooltipActionTitle }
}; };
objectiveLabel.SetMessage(objectiveText); objectiveLabel.SetMessage(objectiveText);
@@ -245,10 +255,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
if (_window == null) if (_window == null)
return; return;
if (CharacterButton != null) CharacterButton?.SetClickPressed(!_window.IsOpen);
{
CharacterButton.SetClickPressed(!_window.IsOpen);
}
if (_window.IsOpen) if (_window.IsOpen)
{ {

View File

@@ -3,6 +3,7 @@ using Content.Shared.FixedPoint;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Traits.Assorted;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
@@ -94,7 +95,11 @@ public sealed class DamageOverlayUiController : UIController
{ {
case MobState.Alive: case MobState.Alive:
{ {
if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage)) if (EntityManager.HasComponent<PainNumbnessComponent>(entity))
{
_overlay.BruteLevel = 0;
}
else if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
{ {
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float(); _overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,9 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Client.Hands.Systems; using Content.Client.Hands.Systems;
using Content.Client.Items.Systems; using Content.Client.Items.Systems;
using Content.Client.Storage;
using Content.Client.Storage.Systems; using Content.Client.Storage.Systems;
using Content.Shared.IdentityManagement;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Storage; using Content.Shared.Storage;
@@ -11,12 +13,14 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Storage.Controls; namespace Content.Client.UserInterface.Systems.Storage.Controls;
public sealed class StorageContainer : BaseWindow public sealed class StorageWindow : BaseWindow
{ {
[Dependency] private readonly IEntityManager _entity = default!; [Dependency] private readonly IEntityManager _entity = default!;
private readonly StorageUIController _storageController; private readonly StorageUIController _storageController;
@@ -27,6 +31,20 @@ public sealed class StorageContainer : BaseWindow
private readonly GridContainer _backgroundGrid; private readonly GridContainer _backgroundGrid;
private readonly GridContainer _sidebar; 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>? OnPiecePressed;
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed; public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
@@ -51,9 +69,10 @@ public sealed class StorageContainer : BaseWindow
private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat"; private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
private Texture? _sidebarFatTexture; private Texture? _sidebarFatTexture;
public StorageContainer() public StorageWindow()
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
Resizable = false;
_storageController = UserInterfaceManager.GetUIController<StorageUIController>(); _storageController = UserInterfaceManager.GetUIController<StorageUIController>();
@@ -63,6 +82,7 @@ public sealed class StorageContainer : BaseWindow
_sidebar = new GridContainer _sidebar = new GridContainer
{ {
Name = "SideBar",
HSeparationOverride = 0, HSeparationOverride = 0,
VSeparationOverride = 0, VSeparationOverride = 0,
Columns = 1 Columns = 1
@@ -70,21 +90,48 @@ public sealed class StorageContainer : BaseWindow
_pieceGrid = new GridContainer _pieceGrid = new GridContainer
{ {
Name = "PieceGrid",
HSeparationOverride = 0, HSeparationOverride = 0,
VSeparationOverride = 0 VSeparationOverride = 0
}; };
_backgroundGrid = new GridContainer _backgroundGrid = new GridContainer
{ {
Name = "BackgroundGrid",
HSeparationOverride = 0, HSeparationOverride = 0,
VSeparationOverride = 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 var container = new BoxContainer
{ {
Orientation = BoxContainer.LayoutOrientation.Vertical, Orientation = BoxContainer.LayoutOrientation.Vertical,
Children = Children =
{ {
_titleContainer,
new BoxContainer new BoxContainer
{ {
Orientation = BoxContainer.LayoutOrientation.Horizontal, Orientation = BoxContainer.LayoutOrientation.Horizontal,
@@ -130,12 +177,22 @@ public sealed class StorageContainer : BaseWindow
if (entity == null) if (entity == null)
return; return;
if (UserInterfaceManager.GetUIController<StorageUIController>().WindowTitle)
{
_titleLabel.Text = Identity.Name(entity.Value, _entity);
_titleContainer.Visible = true;
}
else
{
_titleContainer.Visible = false;
}
BuildGridRepresentation(); BuildGridRepresentation();
} }
private void 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; return;
var boundingGrid = comp.Grid.GetBoundingBox(); var boundingGrid = comp.Grid.GetBoundingBox();
@@ -144,12 +201,13 @@ public sealed class StorageContainer : BaseWindow
#region Sidebar #region Sidebar
_sidebar.Children.Clear(); _sidebar.Children.Clear();
_sidebar.Rows = boundingGrid.Height + 1; var rows = boundingGrid.Height + 1;
_sidebar.Rows = rows;
var exitButton = new TextureButton var exitButton = new TextureButton
{ {
TextureNormal = _entity.System<StorageSystem>().OpenStorageAmount == 1 Name = "ExitButton",
?_exitTexture TextureNormal = _exitTexture,
: _backTexture,
Scale = new Vector2(2, 2), Scale = new Vector2(2, 2),
}; };
exitButton.OnPressed += _ => exitButton.OnPressed += _ =>
@@ -165,8 +223,10 @@ public sealed class StorageContainer : BaseWindow
args.Handle(); args.Handle();
} }
}; };
var exitContainer = new BoxContainer var exitContainer = new BoxContainer
{ {
Name = "ExitContainer",
Children = Children =
{ {
new TextureRect new TextureRect
@@ -182,28 +242,70 @@ public sealed class StorageContainer : BaseWindow
} }
} }
}; };
_sidebar.AddChild(exitContainer); _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, TextureNormal = _backTexture,
TextureScale = new Vector2(2, 2), 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 _sidebar.AddChild(new TextureRect
{ {
Texture = _sidebarBottomTexture, Texture = i != (fillerRows - 1) ? _sidebarMiddleTexture : _sidebarBottomTexture,
TextureScale = new Vector2(2, 2), TextureScale = new Vector2(2, 2),
}); });
} }
#endregion #endregion
BuildItemPieces(); FlagDirty();
} }
public void BuildBackground() public void BuildBackground()
@@ -240,70 +342,127 @@ 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() public void BuildItemPieces()
{ {
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp)) if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp))
return; return;
if (!storageComp.Grid.Any()) if (storageComp.Grid.Count == 0)
return; return;
var boundingGrid = storageComp.Grid.GetBoundingBox(); var boundingGrid = storageComp.Grid.GetBoundingBox();
var size = _emptyTexture!.Size * 2; 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. // Build the grid representation
if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
_pieceGrid.RemoveAllChildren();
_pieceGrid.Rows = boundingGrid.Height + 1;
_pieceGrid.Columns = boundingGrid.Width + 1;
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
{ {
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 control = new Control
};
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))
{ {
ItemGridPiece gridPiece; MinSize = size
};
if (_storageController.CurrentlyDragging?.Entity is { } dragging _controlGrid.Add(control);
&& dragging == itemEnt) _pieceGrid.AddChild(control);
{ }
_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;
}
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);
} }
} }
} }
@@ -315,6 +474,35 @@ public sealed class StorageContainer : BaseWindow
if (!IsOpen) if (!IsOpen)
return; 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 itemSystem = _entity.System<ItemSystem>();
var storageSystem = _entity.System<StorageSystem>(); var storageSystem = _entity.System<StorageSystem>();
var handsSystem = _entity.System<HandsSystem>(); var handsSystem = _entity.System<HandsSystem>();
@@ -324,7 +512,7 @@ public sealed class StorageContainer : BaseWindow
child.ModulateSelfOverride = Color.FromHex("#222222"); child.ModulateSelfOverride = Color.FromHex("#222222");
} }
if (UserInterfaceManager.CurrentlyHovered is StorageContainer con && con != this) if (UserInterfaceManager.CurrentlyHovered is StorageWindow con && con != this)
return; return;
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent)) if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
@@ -373,7 +561,7 @@ public sealed class StorageContainer : BaseWindow
continue; continue;
float spot = 0; float spot = 0;
var marked = new List<Control>(); var marked = new ValueList<Control>();
foreach (var location in locations.Value) foreach (var location in locations.Value)
{ {
@@ -500,14 +688,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.Examine;
using Content.Client.Hands.Systems; using Content.Client.Hands.Systems;
using Content.Client.Interaction; using Content.Client.Interaction;
using Content.Client.Storage;
using Content.Client.Storage.Systems; using Content.Client.Storage.Systems;
using Content.Client.UserInterface.Systems.Hotbar.Widgets; using Content.Client.UserInterface.Systems.Hotbar.Widgets;
using Content.Client.UserInterface.Systems.Storage.Controls; using Content.Client.UserInterface.Systems.Storage.Controls;
@@ -9,9 +10,9 @@ using Content.Client.Verbs.UI;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Storage; using Content.Shared.Storage;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -23,19 +24,23 @@ namespace Content.Client.UserInterface.Systems.Storage;
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem> 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 IConfigurationManager _configuration = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IInputManager _input = 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!;
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper; private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
private StorageContainer? _container;
private Vector2? _lastContainerPosition; public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
public ItemGridPiece? DraggingGhost;
public Angle DraggingRotation = Angle.Zero; public Angle DraggingRotation = Angle.Zero;
public bool StaticStorageUIEnabled; public bool StaticStorageUIEnabled;
public bool OpaqueStorageWindow; public bool OpaqueStorageWindow;
@@ -43,6 +48,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
public bool IsDragging => _menuDragHelper.IsDragging; public bool IsDragging => _menuDragHelper.IsDragging;
public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged; public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
public bool WindowTitle { get; private set; } = false;
public StorageUIController() public StorageUIController()
{ {
_menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag); _menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
@@ -52,106 +59,88 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{ {
base.Initialize(); base.Initialize();
UIManager.OnScreenChanged += OnScreenChange;
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true); _configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, 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();
}
return window;
} }
public void OnSystemLoaded(StorageSystem system) public void OnSystemLoaded(StorageSystem system)
{ {
_input.FirstChanceOnKeyEvent += OnMiddleMouse; _input.FirstChanceOnKeyEvent += OnMiddleMouse;
system.StorageUpdated += OnStorageUpdated;
system.StorageOrderChanged += OnStorageOrderChanged;
} }
public void OnSystemUnloaded(StorageSystem system) public void OnSystemUnloaded(StorageSystem system)
{ {
_input.FirstChanceOnKeyEvent -= OnMiddleMouse; _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? /// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
@@ -190,7 +179,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
binding.Mod3 == Keyboard.Key.Control)) binding.Mod3 == Keyboard.Key.Control))
return; return;
if (!IsDragging && _entity.System<HandsSystem>().GetActiveHandEntity() == null) if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
return; return;
//clamp it to a cardinal. //clamp it to a cardinal.
@@ -198,43 +187,18 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (DraggingGhost != null) if (DraggingGhost != null)
DraggingGhost.Location.Rotation = DraggingRotation; DraggingGhost.Location.Rotation = DraggingRotation;
if (IsDragging || (_container != null && UIManager.CurrentlyHovered == _container)) if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
keyEvent.Handle(); keyEvent.Handle();
} }
private void OnStorageUpdated(Entity<StorageComponent> uid) private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{ {
if (_container?.StorageEntity != uid) if (IsDragging || !window.IsOpen)
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)
return; return;
if (args.Function == ContentKeyFunctions.MoveStoredItem) if (args.Function == ContentKeyFunctions.MoveStoredItem)
{ {
DraggingRotation = control.Location.Rotation; DraggingRotation = control.Location.Rotation;
_menuDragHelper.MouseDown(control); _menuDragHelper.MouseDown(control);
_menuDragHelper.Update(0f); _menuDragHelper.Update(0f);
@@ -242,17 +206,17 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
} }
else if (args.Function == ContentKeyFunctions.SaveItemLocation) else if (args.Function == ContentKeyFunctions.SaveItemLocation)
{ {
if (_container?.StorageEntity is not {} storage) if (window.StorageEntity is not {} storage)
return; return;
_entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent( EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
_entity.GetNetEntity(control.Entity), EntityManager.GetNetEntity(control.Entity),
_entity.GetNetEntity(storage))); EntityManager.GetNetEntity(storage)));
args.Handle(); args.Handle();
} }
else if (args.Function == ContentKeyFunctions.ExamineEntity) else if (args.Function == ContentKeyFunctions.ExamineEntity)
{ {
_entity.System<ExamineSystem>().DoExamine(control.Entity); EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
args.Handle(); args.Handle();
} }
else if (args.Function == EngineKeyFunctions.UseSecondary) else if (args.Function == EngineKeyFunctions.UseSecondary)
@@ -262,62 +226,102 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
} }
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld) else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{ {
_entity.RaisePredictiveEvent( EntityManager.RaisePredictiveEvent(
new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: false)); new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
args.Handle(); args.Handle();
} }
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld) 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(); 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) if (args.Function != ContentKeyFunctions.MoveStoredItem)
return; return;
if (_container?.StorageEntity is not { } storageEnt|| !_entity.TryGetComponent<StorageComponent>(storageEnt, out var storageComp)) // Want to get the control under the dragged control.
return; // 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 dragEnt = draggingGhost.Entity;
var dragLoc = draggingGhost.Location; var dragLoc = draggingGhost.Location;
var itemSys = _entity.System<SharedItemSystem>();
var position = _container.GetMouseGridPieceLocation(dragEnt, dragLoc); // Dragging in the same storage
var itemBounding = itemSys.GetAdjustedItemShape(dragEnt, dragLoc).GetBoundingBox(); // The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
var gridBounding = storageComp.Grid.GetBoundingBox(); if (targetStorage == window)
// 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))
{ {
_entity.RaisePredictiveEvent(new StorageSetItemLocationEvent( var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
_entity.GetNetEntity(draggingGhost.Entity), var newLocation = new ItemStorageLocation(DraggingRotation, position);
_entity.GetNetEntity(storageEnt),
new ItemStorageLocation(DraggingRotation, position))); EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
EntityManager.GetNetEntity(draggingGhost.Entity),
EntityManager.GetNetEntity(sourceStorage),
newLocation));
window.Reclaim(newLocation, control);
}
// Dragging to new storage
else if (targetStorage?.StorageEntity != null && targetStorage != window)
{
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(); targetStorage?.FlagDirty();
_container?.BuildItemPieces();
} }
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(); EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
_entity.RaisePredictiveEvent(new StorageInteractWithItemEvent( EntityManager.GetNetEntity(control.Entity),
_entity.GetNetEntity(control.Entity), EntityManager.GetNetEntity(sourceStorage)));
_entity.GetNetEntity(storageEnt)));
} }
_menuDragHelper.EndDrag();
args.Handle(); args.Handle();
} }
@@ -326,14 +330,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (_menuDragHelper.Dragged is not { } dragged) if (_menuDragHelper.Dragged is not { } dragged)
return false; return false;
DraggingGhost!.Orphan();
DraggingRotation = dragged.Location.Rotation; 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); UIManager.PopupRoot.AddChild(DraggingGhost);
SetDraggingRotation(); SetDraggingRotation();
@@ -344,6 +342,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{ {
if (DraggingGhost == null) if (DraggingGhost == null)
return false; return false;
SetDraggingRotation(); SetDraggingRotation();
return true; return true;
} }
@@ -356,7 +355,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
var offset = ItemGridPiece.GetCenterOffset( var offset = ItemGridPiece.GetCenterOffset(
(DraggingGhost.Entity, null), (DraggingGhost.Entity, null),
new ItemStorageLocation(DraggingRotation, Vector2i.Zero), new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
_entity); EntityManager);
// I don't know why it divides the position by 2. Hope this helps! -emo // I don't know why it divides the position by 2. Hope this helps! -emo
LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset ); LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
@@ -366,18 +365,13 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{ {
if (DraggingGhost == null) if (DraggingGhost == null)
return; return;
DraggingGhost.Visible = false;
DraggingGhost = null;
DraggingRotation = Angle.Zero; DraggingRotation = Angle.Zero;
} }
public override void FrameUpdate(FrameEventArgs args) public override void FrameUpdate(FrameEventArgs args)
{ {
base.FrameUpdate(args); base.FrameUpdate(args);
_menuDragHelper.Update(args.DeltaSeconds); _menuDragHelper.Update(args.DeltaSeconds);
if (!StaticStorageUIEnabled && _container?.Parent != null && _lastContainerPosition != null)
_lastContainerPosition = _container.GlobalPosition;
} }
} }

View File

@@ -3,8 +3,10 @@ using Content.Client.UserInterface.Systems.Gameplay;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Systems.Viewport; namespace Content.Client.UserInterface.Systems.Viewport;
@@ -15,6 +17,7 @@ public sealed class ViewportUIController : UIController
[Dependency] private readonly IPlayerManager _playerMan = default!; [Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[UISystemDependency] private readonly SharedTransformSystem? _transformSystem = default!;
public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15); public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15);
public const int ViewportHeight = 15; public const int ViewportHeight = 15;
private MainViewport? Viewport => UIManager.ActiveScreen?.GetWidget<MainViewport>(); private MainViewport? Viewport => UIManager.ActiveScreen?.GetWidget<MainViewport>();
@@ -93,8 +96,11 @@ public sealed class ViewportUIController : UIController
_entMan.TryGetComponent(ent, out EyeComponent? eye); _entMan.TryGetComponent(ent, out EyeComponent? eye);
if (eye?.Eye == _eyeManager.CurrentEye if (eye?.Eye == _eyeManager.CurrentEye
&& _entMan.GetComponent<TransformComponent>(ent.Value).WorldPosition == default) && _entMan.GetComponent<TransformComponent>(ent.Value).MapID == MapId.Nullspace)
return; // nothing to worry about, the player is just in null space... actually that is probably a problem? {
// nothing to worry about, the player is just in null space... actually that is probably a problem?
return;
}
// Currently, this shouldn't happen. This likely happened because the main eye was set to null. When this // Currently, this shouldn't happen. This likely happened because the main eye was set to null. When this
// does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings: // does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings:

View File

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

View File

@@ -1,15 +1,11 @@
using Content.Client.Ghost;
using Content.Shared.Voting; using Content.Shared.Voting;
namespace Content.Client.Voting; namespace Content.Client.Voting;
public sealed class VotingSystem : EntitySystem public sealed class VotingSystem : EntitySystem
{ {
public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions
[Dependency] private readonly GhostSystem _ghostSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();

View File

@@ -138,6 +138,7 @@ public sealed partial class MeleeWeaponSystem
const float length = 0.15f; const float length = 0.15f;
var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f)); var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance)); var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance));
sprite.Rotation += spriteRotation;
return new Animation() return new Animation()
{ {

View File

@@ -170,7 +170,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
var targetCoordinates = xform.Coordinates; var targetCoordinates = xform.Coordinates;
var targetLocalAngle = xform.LocalRotation; var targetLocalAngle = xform.LocalRotation;
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range, overlapCheck: false);
} }
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform) protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)

View File

@@ -0,0 +1,49 @@
using System.Numerics;
using Content.Client.Movement.Components;
using Content.Client.Movement.Systems;
using Content.Shared.Camera;
using Content.Shared.Hands;
using Content.Shared.Movement.Components;
using Content.Shared.Wieldable;
using Content.Shared.Wieldable.Components;
using Robust.Client.Timing;
namespace Content.Client.Wieldable;
public sealed class WieldableSystem : SharedWieldableSystem
{
[Dependency] private readonly EyeCursorOffsetSystem _eyeOffset = default!;
[Dependency] private readonly IClientGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CursorOffsetRequiresWieldComponent, ItemUnwieldedEvent>(OnEyeOffsetUnwielded);
SubscribeLocalEvent<CursorOffsetRequiresWieldComponent, HeldRelayedEvent<GetEyeOffsetRelayedEvent>>(OnGetEyeOffset);
}
public void OnEyeOffsetUnwielded(Entity<CursorOffsetRequiresWieldComponent> entity, ref ItemUnwieldedEvent args)
{
if (!TryComp(entity.Owner, out EyeCursorOffsetComponent? cursorOffsetComp))
return;
if (_gameTiming.IsFirstTimePredicted)
cursorOffsetComp.CurrentPosition = Vector2.Zero;
}
public void OnGetEyeOffset(Entity<CursorOffsetRequiresWieldComponent> entity, ref HeldRelayedEvent<GetEyeOffsetRelayedEvent> args)
{
if (!TryComp(entity.Owner, out WieldableComponent? wieldableComp))
return;
if (!wieldableComp.Wielded)
return;
var offset = _eyeOffset.OffsetAfterMouse(entity.Owner, null);
if (offset == null)
return;
args.Args.Offset += offset.Value;
}
}

View File

@@ -7,6 +7,7 @@ using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Cargo.Prototypes; using Content.Shared.Cargo.Prototypes;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Prototypes;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
@@ -104,51 +105,34 @@ public sealed class CargoTest
await using var pair = await PoolManager.GetServerClient(); await using var pair = await PoolManager.GetServerClient();
var server = pair.Server; var server = pair.Server;
var testMap = await pair.CreateTestMap(); var protoManager = server.ProtoMan;
var compFact = server.ResolveDependency<IComponentFactory>();
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var mapId = testMap.MapId;
var grid = mapManager.CreateGridEntity(mapId);
var coord = new EntityCoordinates(grid.Owner, 0, 0);
var protoIds = protoManager.EnumeratePrototypes<EntityPrototype>() var protoIds = protoManager.EnumeratePrototypes<EntityPrototype>()
.Where(p => !p.Abstract) .Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p)) .Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // Grids are not for sale. .Where(p => p.Components.ContainsKey("StaticPrice"))
.Select(p => p.ID)
.ToList(); .ToList();
foreach (var proto in protoIds) foreach (var proto in protoIds)
{ {
var ent = entManager.SpawnEntity(proto, coord); // Sanity check
Assert.That(proto.TryGetComponent<StaticPriceComponent>(out var staticPriceComp, compFact), Is.True);
if (entManager.TryGetComponent<StackPriceComponent>(ent, out var stackpricecomp) if (proto.TryGetComponent<StackPriceComponent>(out var stackPriceComp, compFact) && stackPriceComp.Price > 0)
&& stackpricecomp.Price > 0)
{ {
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp)) Assert.That(staticPriceComp.Price, Is.EqualTo(0),
{ $"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
$"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
}
} }
if (entManager.HasComponent<StackComponent>(ent)) if (proto.HasComponent<StackComponent>(compFact))
{ {
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp)) Assert.That(staticPriceComp.Price, Is.EqualTo(0),
{ $"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
$"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
}
} }
entManager.DeleteEntity(ent);
} }
mapManager.DeleteMap(mapId);
}); });
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();

View File

@@ -0,0 +1,41 @@
using Content.Shared.Contraband;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests;
[TestFixture]
public sealed class ContrabandTest
{
[Test]
public async Task EntityShowDepartmentsAndJobs()
{
await using var pair = await PoolManager.GetServerClient();
var client = pair.Client;
var protoMan = client.ResolveDependency<IPrototypeManager>();
var componentFactory = client.ResolveDependency<IComponentFactory>();
await client.WaitAssertion(() =>
{
foreach (var proto in protoMan.EnumeratePrototypes<EntityPrototype>())
{
if (proto.Abstract || pair.IsTestPrototype(proto))
continue;
if (!proto.TryGetComponent<ContrabandComponent>(out var contraband, componentFactory))
continue;
Assert.That(protoMan.TryIndex(contraband.Severity, out var severity, false),
@$"{proto.ID} has a ContrabandComponent with a unknown severity.");
if (!severity.ShowDepartmentsAndJobs)
continue;
Assert.That(contraband.AllowedDepartments.Count + contraband.AllowedJobs.Count, Is.Not.EqualTo(0),
@$"{proto.ID} has a ContrabandComponent with ShowDepartmentsAndJobs but no allowed departments or jobs.");
}
});
await pair.CleanReturnAsync();
}
}

View File

@@ -39,6 +39,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract) .Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p)) .Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise. .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Select(p => p.ID) .Select(p => p.ID)
.ToList(); .ToList();
@@ -101,6 +102,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract) .Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p)) .Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise. .Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Select(p => p.ID) .Select(p => p.ID)
.ToList(); .ToList();
foreach (var protoId in protoIds) foreach (var protoId in protoIds)
@@ -341,6 +343,7 @@ namespace Content.IntegrationTests.Tests
"DebugExceptionInitialize", "DebugExceptionInitialize",
"DebugExceptionStartup", "DebugExceptionStartup",
"GridFill", "GridFill",
"RoomFill",
"Map", // We aren't testing a map entity in this test "Map", // We aren't testing a map entity in this test
"MapGrid", "MapGrid",
"Broadphase", "Broadphase",

View File

@@ -26,6 +26,7 @@ public sealed class LatheTest
var compFactory = server.ResolveDependency<IComponentFactory>(); var compFactory = server.ResolveDependency<IComponentFactory>();
var materialStorageSystem = server.System<SharedMaterialStorageSystem>(); var materialStorageSystem = server.System<SharedMaterialStorageSystem>();
var whitelistSystem = server.System<EntityWhitelistSystem>(); var whitelistSystem = server.System<EntityWhitelistSystem>();
var latheSystem = server.System<SharedLatheSystem>();
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
@@ -74,14 +75,14 @@ public sealed class LatheTest
} }
} }
// Collect all the recipes assigned to this lathe // Collect all possible recipes assigned to this lathe
var recipes = new List<ProtoId<LatheRecipePrototype>>(); var recipes = new HashSet<ProtoId<LatheRecipePrototype>>();
recipes.AddRange(latheComp.StaticRecipes); latheSystem.AddRecipesFromPacks(recipes, latheComp.StaticPacks);
recipes.AddRange(latheComp.DynamicRecipes); latheSystem.AddRecipesFromPacks(recipes, latheComp.DynamicPacks);
if (latheProto.TryGetComponent<EmagLatheRecipesComponent>(out var emagRecipesComp, compFactory)) if (latheProto.TryGetComponent<EmagLatheRecipesComponent>(out var emagRecipesComp, compFactory))
{ {
recipes.AddRange(emagRecipesComp.EmagStaticRecipes); latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagStaticPacks);
recipes.AddRange(emagRecipesComp.EmagDynamicRecipes); latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagDynamicPacks);
} }
// Check each recipe assigned to this lathe // Check each recipe assigned to this lathe

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Reflection; using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -29,7 +30,9 @@ public sealed class StaticFieldValidationTest
Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos), Is.Empty);
@@ -39,7 +42,9 @@ public sealed class StaticFieldValidationTest
Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos), Has.Count.EqualTo(1)); Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos), Has.Count.EqualTo(1)); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos), Has.Count.EqualTo(1)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2));
@@ -88,24 +93,48 @@ public sealed class StaticFieldValidationTest
public static EntProtoId Tag = "StaticFieldTestEnt"; public static EntProtoId Tag = "StaticFieldTestEnt";
} }
[Reflect(false)]
private sealed class EntProtoIdTValid
{
public static EntProtoId<TransformComponent> Tag = "StaticFieldTestEnt";
}
[Reflect(false)] [Reflect(false)]
private sealed class EntProtoIdInvalid private sealed class EntProtoIdInvalid
{ {
public static EntProtoId Tag = string.Empty; public static EntProtoId Tag = string.Empty;
} }
[Reflect(false)]
private sealed class EntProtoIdTInvalid
{
public static EntProtoId<TransformComponent> Tag = string.Empty;
}
[Reflect(false)] [Reflect(false)]
private sealed class EntProtoIdArrayValid private sealed class EntProtoIdArrayValid
{ {
public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"]; public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
} }
[Reflect(false)]
private sealed class EntProtoIdTArrayValid
{
public static EntProtoId<TransformComponent>[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
}
[Reflect(false)] [Reflect(false)]
private sealed class EntProtoIdArrayInvalid private sealed class EntProtoIdArrayInvalid
{ {
public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty]; public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty];
} }
[Reflect(false)]
private sealed class EntProtoIdTArrayInvalid
{
public static EntProtoId<TransformComponent>[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty];
}
[Reflect(false)] [Reflect(false)]
private sealed class ProtoIdTestValid private sealed class ProtoIdTestValid
{ {

Some files were not shown because too many files have changed in this diff Show More