Merge branch 'master' of https://github.com/space-wizards/space-station-14 into map-load-refactor
This commit is contained in:
@@ -344,6 +344,9 @@ resharper_keep_existing_attribute_arrangement = true
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
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}]
|
||||
indent_size = 2
|
||||
|
||||
5
.envrc
5
.envrc
@@ -1,4 +1,5 @@
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
||||
set -e
|
||||
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
|
||||
use flake
|
||||
|
||||
45
.github/workflows/publish-testing.yml
vendored
Normal file
45
.github/workflows/publish-testing.yml
vendored
Normal 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 }}
|
||||
@@ -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:
|
||||
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>
|
||||
<Python>python3</Python>
|
||||
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
|
||||
<ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid>
|
||||
<TargetFrameworkMoniker>.NETFramework, Version=v4.7.2</TargetFrameworkMoniker>
|
||||
<TargetFramework>net4.7.2</TargetFramework>
|
||||
<RestorePackages>false</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -47,7 +47,7 @@ public class MapLoadBenchmark
|
||||
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))]
|
||||
public string Map;
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Content.Client.Actions
|
||||
return;
|
||||
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.Blacklist = state.Blacklist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
@@ -137,6 +138,7 @@ namespace Content.Client.Actions
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
|
||||
component.RaiseOnUser = state.RaiseOnUser;
|
||||
component.RaiseOnAction = state.RaiseOnAction;
|
||||
component.AutoPopulate = state.AutoPopulate;
|
||||
component.Temporary = state.Temporary;
|
||||
component.ItemIconStyle = state.ItemIconStyle;
|
||||
|
||||
@@ -11,6 +11,8 @@ public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new AtmosAlertsComputerWindow(this, Owner);
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSys = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
|
||||
private GasTileOverlay _overlay = default!;
|
||||
|
||||
@@ -25,7 +26,7 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys);
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
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 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;
|
||||
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||
_xformSys = xformSys;
|
||||
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
@@ -158,7 +160,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
_fireFrameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery);
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
|
||||
@@ -180,7 +183,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
int[] fireFrameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery) state) =>
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
@@ -188,7 +192,7 @@ namespace Content.Client.Atmos.Overlays
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
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">
|
||||
|
||||
|
||||
@@ -3,13 +3,15 @@ using Content.Shared.Buckle;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Rotation;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Buckle;
|
||||
|
||||
internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
{
|
||||
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -17,6 +19,8 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
|
||||
SubscribeLocalEvent<BuckleComponent, BuckledEvent>(OnBuckledEvent);
|
||||
SubscribeLocalEvent<BuckleComponent, UnbuckledEvent>(OnUnbuckledEvent);
|
||||
}
|
||||
|
||||
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
|
||||
// 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)
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var strapSprite))
|
||||
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)
|
||||
{
|
||||
if (!TryComp<BuckleComponent>(buckledEntity, out var buckle))
|
||||
@@ -45,6 +57,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
||||
|
||||
@@ -39,6 +39,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
||||
if (message is not CargoBountyConsoleState state)
|
||||
return;
|
||||
|
||||
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
|
||||
_menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
|
||||
}
|
||||
}
|
||||
|
||||
22
Content.Client/Cargo/UI/BountyHistoryEntry.xaml
Normal file
22
Content.Client/Cargo/UI/BountyHistoryEntry.xaml
Normal 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>
|
||||
49
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
Normal file
49
Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
Normal 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 ?? "")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,28 @@
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True">
|
||||
<ScrollContainer HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer Name="BountyEntriesContainer"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
</BoxContainer>
|
||||
HorizontalExpand="True" />
|
||||
</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>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
||||
@@ -15,9 +15,12 @@ public sealed partial class CargoBountyMenu : FancyWindow
|
||||
public CargoBountyMenu()
|
||||
{
|
||||
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();
|
||||
foreach (var b in bounties)
|
||||
@@ -32,5 +35,21 @@ public sealed partial class CargoBountyMenu : FancyWindow
|
||||
{
|
||||
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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Changelog
|
||||
@@ -15,8 +17,9 @@ namespace Content.Client.Changelog
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ChangelogWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly ChangelogManager _changelog = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public ChangelogWindow()
|
||||
{
|
||||
@@ -67,8 +70,22 @@ namespace Content.Client.Changelog
|
||||
Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}"));
|
||||
}
|
||||
|
||||
var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0);
|
||||
VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString()));
|
||||
// Try to get the current version from the build.json file
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
BackButtonStyleClass="RadialMenuBackButton"
|
||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
@@ -7,25 +7,25 @@
|
||||
MinSize="450 450">
|
||||
|
||||
<!-- Main -->
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
</ui:RadialContainer>
|
||||
|
||||
<!-- General -->
|
||||
<ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
<ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
|
||||
|
||||
<!-- Vocal -->
|
||||
<ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
<ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
|
||||
|
||||
<!-- Hands -->
|
||||
<ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
<ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
|
||||
|
||||
</ui:RadialMenu>
|
||||
|
||||
@@ -50,7 +50,6 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
|
||||
var button = new EmoteMenuButton
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = Loc.GetString(emote.Name),
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Content.Client.Clickable
|
||||
"/Textures/Logo",
|
||||
};
|
||||
|
||||
private const float Threshold = 0.25f;
|
||||
private const float Threshold = 0.1f;
|
||||
private const int ClickRadius = 2;
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class CrewManifestSection : BoxContainer
|
||||
AddChild(new Label()
|
||||
{
|
||||
StyleClasses = { "LabelBig" },
|
||||
Text = Loc.GetString($"department-{section.ID}")
|
||||
Text = Loc.GetString(section.Name)
|
||||
});
|
||||
|
||||
var gridContainer = new GridContainer()
|
||||
|
||||
@@ -39,6 +39,8 @@ public sealed class CriminalRecordsConsoleBoundUserInterface : BoundUserInterfac
|
||||
SendMessage(new CriminalRecordChangeStatus(status, null));
|
||||
_window.OnDialogConfirmed += (status, reason) =>
|
||||
SendMessage(new CriminalRecordChangeStatus(status, reason));
|
||||
_window.OnStatusFilterPressed += (statusFilter) =>
|
||||
SendMessage(new CriminalRecordSetStatusFilter(statusFilter));
|
||||
_window.OnHistoryUpdated += UpdateHistory;
|
||||
_window.OnHistoryClosed += () => _historyWindow?.Close();
|
||||
_window.OnClose += Close;
|
||||
|
||||
@@ -1,36 +1,140 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'criminal-records-console-window-title'}"
|
||||
MinSize="660 400">
|
||||
MinSize="695 440">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<!-- Record search bar
|
||||
TODO: make this into a control shared with general records -->
|
||||
<BoxContainer Margin="5 5 5 10" HorizontalExpand="true" VerticalAlignment="Center">
|
||||
<OptionButton Name="FilterType" MinWidth="200" Margin="0 0 10 0"/> <!-- Populated in constructor -->
|
||||
<LineEdit Name="FilterText" PlaceHolder="{Loc 'criminal-records-filter-placeholder'}" HorizontalExpand="True"/>
|
||||
<BoxContainer Name="AllList"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="8">
|
||||
<!-- Record search bar -->
|
||||
<BoxContainer Margin="5 5 5 10"
|
||||
HorizontalExpand="true"
|
||||
VerticalAlignment="Center">
|
||||
<OptionButton Name="FilterType"
|
||||
MinWidth="250"
|
||||
Margin="0 0 10 0" />
|
||||
<!-- Populated in constructor -->
|
||||
<LineEdit Name="FilterText"
|
||||
PlaceHolder="{Loc 'criminal-records-filter-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
VerticalExpand="True">
|
||||
<!-- Record listing -->
|
||||
<BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250">
|
||||
<Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/>
|
||||
<Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
Margin="10 10"
|
||||
MinWidth="250"
|
||||
MaxWidth="250">
|
||||
<Label Name="RecordListingTitle"
|
||||
Text="{Loc 'criminal-records-console-records-list-title'}"
|
||||
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 -->
|
||||
<ItemList Name="RecordListing" />
|
||||
<!-- Populated when loading state -->
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
|
||||
<Label Name="RecordUnselected"
|
||||
Text="{Loc 'criminal-records-console-select-record-info'}"
|
||||
HorizontalExpand="True"
|
||||
Align="Center"
|
||||
FontColorOverride="DarkGray" />
|
||||
<!-- Selected record info -->
|
||||
<BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False">
|
||||
<Label Name="PersonName" StyleClasses="LabelBig"/>
|
||||
<Label Name="PersonPrints"/>
|
||||
<Label Name="PersonDna"/>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5">
|
||||
<Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/>
|
||||
<OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor -->
|
||||
<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>
|
||||
<RichTextLabel Name="WantedReason" Visible="False"/>
|
||||
<Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/>
|
||||
<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>
|
||||
|
||||
@@ -13,6 +13,9 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.CriminalRecords;
|
||||
|
||||
@@ -24,6 +27,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
private readonly IPrototypeManager _proto;
|
||||
private readonly IRobustRandom _random;
|
||||
private readonly AccessReaderSystem _accessReader;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public readonly EntityUid Console;
|
||||
|
||||
@@ -33,10 +38,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
public Action<uint?>? OnKeySelected;
|
||||
public Action<StationRecordFilterType, string>? OnFiltersChanged;
|
||||
public Action<SecurityStatus>? OnStatusSelected;
|
||||
public Action<uint>? OnCheckStatus;
|
||||
public Action<CriminalRecord, bool, bool>? OnHistoryUpdated;
|
||||
public Action? OnHistoryClosed;
|
||||
public Action<SecurityStatus, string>? OnDialogConfirmed;
|
||||
|
||||
public Action<SecurityStatus>? OnStatusFilterPressed;
|
||||
private uint _maxLength;
|
||||
private bool _access;
|
||||
private uint? _selectedKey;
|
||||
@@ -46,6 +53,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
private StationRecordFilterType _currentFilterType;
|
||||
|
||||
private SecurityStatus _currentCrewListFilter;
|
||||
|
||||
public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -55,10 +64,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
_proto = prototypeManager;
|
||||
_random = robustRandom;
|
||||
_accessReader = accessReader;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
_maxLength = maxLength;
|
||||
_currentFilterType = StationRecordFilterType.Name;
|
||||
|
||||
_currentCrewListFilter = SecurityStatus.None;
|
||||
|
||||
OpenCentered();
|
||||
|
||||
foreach (var item in Enum.GetValues<StationRecordFilterType>())
|
||||
@@ -71,6 +84,12 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
AddStatusSelect(status);
|
||||
}
|
||||
|
||||
//Populate status to filter crew list
|
||||
foreach (var item in Enum.GetValues<SecurityStatus>())
|
||||
{
|
||||
CrewListFilter.AddItem(GetCrewListFilterLocals(item), (int)item);
|
||||
}
|
||||
|
||||
OnClose += () => _reasonDialog?.Close();
|
||||
|
||||
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 =>
|
||||
{
|
||||
FilterListingOfRecords(args.Text);
|
||||
@@ -104,16 +137,21 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
StatusOptionButton.OnItemSelected += args =>
|
||||
{
|
||||
SetStatus((SecurityStatus) args.Id);
|
||||
SetStatus((SecurityStatus)args.Id);
|
||||
};
|
||||
|
||||
HistoryButton.OnPressed += _ =>
|
||||
{
|
||||
if (_selectedRecord is {} record)
|
||||
if (_selectedRecord is { } record)
|
||||
OnHistoryUpdated?.Invoke(record, _access, true);
|
||||
};
|
||||
}
|
||||
|
||||
public void StatusFilterPressed(SecurityStatus statusSelected)
|
||||
{
|
||||
OnStatusFilterPressed?.Invoke(statusSelected);
|
||||
}
|
||||
|
||||
public void UpdateState(CriminalRecordsConsoleState state)
|
||||
{
|
||||
if (state.Filter != null)
|
||||
@@ -129,10 +167,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
}
|
||||
}
|
||||
|
||||
if (state.FilterStatus != _currentCrewListFilter)
|
||||
{
|
||||
_currentCrewListFilter = state.FilterStatus;
|
||||
}
|
||||
|
||||
_selectedKey = state.SelectedKey;
|
||||
|
||||
FilterType.SelectId((int)_currentFilterType);
|
||||
|
||||
CrewListFilter.SelectId((int)_currentCrewListFilter);
|
||||
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
|
||||
PopulateRecordListing(state.RecordListing);
|
||||
|
||||
@@ -179,7 +221,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
// in parallel to synchronize the items in RecordListing with `entries`.
|
||||
int i = RecordListing.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);
|
||||
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.
|
||||
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--;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
PersonName.Text = stationRecord.Name;
|
||||
PersonPrints.Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", stationRecord.Fingerprint ?? na));
|
||||
PersonDna.Text = Loc.GetString("general-station-record-console-record-dna", ("dna", stationRecord.DNA ?? na));
|
||||
PersonJob.Text = stationRecord.JobTitle ?? na;
|
||||
|
||||
StatusOptionButton.SelectId((int) criminalRecord.Status);
|
||||
if (criminalRecord.Reason is {} reason)
|
||||
// Job icon
|
||||
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"));
|
||||
|
||||
if (criminalRecord.Status == SecurityStatus.Suspected)
|
||||
{
|
||||
message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-suspected-reason"));
|
||||
}
|
||||
message.AddText($": {reason}");
|
||||
|
||||
WantedReason.SetMessage(message);
|
||||
WantedReason.Visible = true;
|
||||
}
|
||||
@@ -288,9 +351,37 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
_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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,24 @@ namespace Content.Client.Dice;
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var prefix = state.Substring(0, state.IndexOf('_'));
|
||||
sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}");
|
||||
sprite.LayerSetState(0, $"{prefix}_{entity.Comp.CurrentValue}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
|
||||
_overlay.RemoveOverlay<DoAfterOverlay>();
|
||||
}
|
||||
|
||||
#pragma warning disable RA0028 // No base call in overriden function
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Wires.Visualizers;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.Animations;
|
||||
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))
|
||||
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)
|
||||
&& lights && (state == DoorState.Closed || state == DoorState.Welded);
|
||||
|
||||
@@ -21,112 +21,131 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
protected override void OnComponentInit(Entity<DoorComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
var comp = ent.Comp;
|
||||
comp.OpenSpriteStates = new(2);
|
||||
comp.ClosedSpriteStates = new(2);
|
||||
comp.OpenSpriteStates = new List<(DoorVisualLayers, string)>(2);
|
||||
comp.ClosedSpriteStates = new List<(DoorVisualLayers, string)>(2);
|
||||
|
||||
comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState));
|
||||
comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState));
|
||||
|
||||
comp.OpeningAnimation = new Animation()
|
||||
comp.OpeningAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
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),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
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),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
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)
|
||||
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;
|
||||
|
||||
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 (_animationSystem.HasRunningAnimation(entity, DoorComponent.AnimationKey))
|
||||
_animationSystem.Stop(entity.Owner, DoorComponent.AnimationKey);
|
||||
|
||||
UpdateAppearanceForDoorState(entity, args.Sprite, 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:
|
||||
foreach (var (layer, layerState) in entity.Comp.OpenSpriteStates)
|
||||
{
|
||||
sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
|
||||
return;
|
||||
case DoorState.Closed:
|
||||
foreach (var (layer, layerState) in entity.Comp.ClosedSpriteStates)
|
||||
{
|
||||
sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
|
||||
return;
|
||||
case DoorState.Opening:
|
||||
if (entity.Comp.OpeningAnimationTime == 0.0)
|
||||
return;
|
||||
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.OpeningAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Closing:
|
||||
if (entity.Comp.ClosingAnimationTime == 0.0 || entity.Comp.CurrentlyCrushing.Count != 0)
|
||||
return;
|
||||
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.ClosingAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Denying:
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.DenyingAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
case DoorState.Emagging:
|
||||
_animationSystem.Play(entity, (Animation)entity.Comp.EmaggingAnimation, DoorComponent.AnimationKey);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
foreach (var layer in args.Sprite.AllLayers)
|
||||
{
|
||||
layer.Rsi = res?.RSI;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
TryComp<AnimationPlayerComponent>(uid, out var animPlayer);
|
||||
if (_animationSystem.HasRunningAnimation(uid, animPlayer, DoorComponent.AnimationKey))
|
||||
_animationSystem.Stop(uid, animPlayer, DoorComponent.AnimationKey); // Halt all running anomations.
|
||||
|
||||
args.Sprite.DrawDepth = comp.ClosedDrawDepth;
|
||||
switch(state)
|
||||
{
|
||||
case DoorState.Open:
|
||||
args.Sprite.DrawDepth = comp.OpenDrawDepth;
|
||||
foreach(var (layer, layerState) in comp.OpenSpriteStates)
|
||||
{
|
||||
args.Sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
break;
|
||||
case DoorState.Closed:
|
||||
foreach(var (layer, layerState) in comp.ClosedSpriteStates)
|
||||
{
|
||||
args.Sprite.LayerSetState(layer, layerState);
|
||||
}
|
||||
break;
|
||||
case DoorState.Opening:
|
||||
if (animPlayer != null && comp.OpeningAnimationTime != 0.0)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
case DoorState.Closing:
|
||||
if (animPlayer != null && comp.ClosingAnimationTime != 0.0 && comp.CurrentlyCrushing.Count == 0)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
case DoorState.Denying:
|
||||
if (animPlayer != null)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
case DoorState.Welded:
|
||||
break;
|
||||
case DoorState.Emagging:
|
||||
if (animPlayer != null)
|
||||
_animationSystem.Play((uid, animPlayer), (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
|
||||
}
|
||||
sprite.BaseRSI = res.RSI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace Content.Client.GameTicking.Managers
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
|
||||
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
|
||||
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);
|
||||
|
||||
@@ -51,7 +51,6 @@ public sealed partial class GhostRoleRadioMenu : RadialMenu
|
||||
|
||||
var button = new GhostRoleRadioMenuButton()
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64, 64),
|
||||
ToolTip = Loc.GetString(ghostRoleProto.Name),
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Client.Ghost
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
|
||||
[Dependency] private readonly ContentEyeSystem _contentEye = default!;
|
||||
|
||||
public int AvailableGhostRoleCount { get; private set; }
|
||||
@@ -79,8 +80,27 @@ namespace Content.Client.Ghost
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup"), args.Performer);
|
||||
_contentEye.RequestToggleLight(uid, component);
|
||||
TryComp<PointLightComponent>(uid, out var light);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,45 +2,38 @@ using Content.Shared.Chat.TypingIndicator;
|
||||
using Content.Shared.Holopad;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Holopad;
|
||||
|
||||
public sealed class HolopadSystem : SharedHolopadSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HolopadHologramComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<HolopadHologramComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<HolopadHologramComponent, BeforePostShaderRenderEvent>(OnShaderRender);
|
||||
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))
|
||||
return;
|
||||
|
||||
UpdateHologramSprite(uid);
|
||||
UpdateHologramSprite(entity, entity.Comp.LinkedEntity);
|
||||
}
|
||||
|
||||
private void OnShaderRender(EntityUid uid, HolopadHologramComponent component, BeforePostShaderRenderEvent ev)
|
||||
private void OnShaderRender(Entity<HolopadHologramComponent> entity, ref BeforePostShaderRenderEvent ev)
|
||||
{
|
||||
if (ev.Sprite.PostShader == null)
|
||||
return;
|
||||
|
||||
ev.Sprite.PostShader.SetParameter("t", (float)_timing.CurTime.TotalSeconds * component.ScrollRate);
|
||||
UpdateHologramSprite(entity, entity.Comp.LinkedEntity);
|
||||
}
|
||||
|
||||
private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args)
|
||||
@@ -57,100 +50,66 @@ public sealed class HolopadSystem : SharedHolopadSystem
|
||||
RaiseNetworkEvent(netEv);
|
||||
}
|
||||
|
||||
private void OnPlayerSpriteStateRequest(PlayerSpriteStateRequest ev)
|
||||
private void UpdateHologramSprite(EntityUid hologram, EntityUid? target)
|
||||
{
|
||||
var targetPlayer = GetEntity(ev.TargetPlayer);
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
|
||||
// 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))
|
||||
// Get required components
|
||||
if (!TryComp<SpriteComponent>(hologram, out var hologramSprite) ||
|
||||
!TryComp<HolopadHologramComponent>(hologram, out var holopadhologram))
|
||||
return;
|
||||
|
||||
// Remove all sprite layers
|
||||
for (int i = hologramSprite.AllLayers.Count() - 1; i >= 0; i--)
|
||||
hologramSprite.RemoveLayer(i);
|
||||
|
||||
if (layerData == null || layerData.Length == 0)
|
||||
if (TryComp<SpriteComponent>(target, out var targetSprite))
|
||||
{
|
||||
layerData = new PrototypeLayerData[1];
|
||||
layerData[0] = new PrototypeLayerData()
|
||||
// Use the target's holographic avatar (if available)
|
||||
if (TryComp<HolographicAvatarComponent>(target, out var targetAvatar) &&
|
||||
targetAvatar.LayerData != null)
|
||||
{
|
||||
RsiPath = holopadhologram.RsiPath,
|
||||
State = holopadhologram.RsiState
|
||||
};
|
||||
for (int i = 0; i < targetAvatar.LayerData.Length; i++)
|
||||
{
|
||||
var layer = targetAvatar.LayerData[i];
|
||||
hologramSprite.AddLayer(targetAvatar.LayerData[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < layerData.Length; i++)
|
||||
// Otherwise copy the target's current physical appearance
|
||||
else
|
||||
{
|
||||
var layer = layerData[i];
|
||||
layer.Shader = "unshaded";
|
||||
|
||||
hologramSprite.AddLayer(layerData[i], i);
|
||||
hologramSprite.CopyFrom(targetSprite);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateHologramShader(uid, hologramSprite, holopadhologram);
|
||||
// There is no target, display a default sprite instead (if available)
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(holopadhologram.RsiPath) || string.IsNullOrEmpty(holopadhologram.RsiState))
|
||||
return;
|
||||
|
||||
var layer = new PrototypeLayerData();
|
||||
layer.RsiPath = holopadhologram.RsiPath;
|
||||
layer.State = holopadhologram.RsiState;
|
||||
|
||||
hologramSprite.AddLayer(layer);
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
@@ -13,7 +12,6 @@ namespace Content.Client.Instruments.UI
|
||||
public IEntityManager Entities => EntMan;
|
||||
[Dependency] public readonly IMidiManager MidiManager = default!;
|
||||
[Dependency] public readonly IFileDialogManager FileDialogManager = default!;
|
||||
[Dependency] public readonly IPlayerManager PlayerManager = default!;
|
||||
[Dependency] public readonly ILocalizationManager Loc = default!;
|
||||
|
||||
public readonly InstrumentSystem Instruments;
|
||||
@@ -41,6 +39,8 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_instrumentMenu = this.CreateWindow<InstrumentMenu>();
|
||||
_instrumentMenu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
|
||||
dragSprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
if (!dragSprite.NoRotation)
|
||||
{
|
||||
Transform(_dragShadow.Value).WorldRotation = Transform(_draggedEntity.Value).WorldRotation;
|
||||
_transformSystem.SetWorldRotationNoLerp(_dragShadow.Value, _transformSystem.GetWorldRotation(_draggedEntity.Value));
|
||||
}
|
||||
|
||||
// drag initiated
|
||||
|
||||
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal file
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -33,8 +33,13 @@ namespace Content.Client.Labels.UI
|
||||
_focused = false;
|
||||
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
|
||||
LabelLineEdit.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Content.Client.LateJoin
|
||||
|
||||
foreach (var department in departments)
|
||||
{
|
||||
var departmentName = Loc.GetString($"department-{department.ID}");
|
||||
var departmentName = Loc.GetString(department.Name);
|
||||
_jobCategories[id] = new Dictionary<string, BoxContainer>();
|
||||
var stationAvailable = _gameTicker.JobsAvailable[id];
|
||||
var jobsAvailable = new List<JobPrototype>();
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
|
||||
if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComponent))
|
||||
{
|
||||
if (!latheComponent.DynamicRecipes.Any())
|
||||
if (!latheComponent.DynamicPacks.Any())
|
||||
{
|
||||
ServerListButton.Visible = false;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
|
||||
[UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
|
||||
|
||||
private CharacterSetupGui? _characterSetup;
|
||||
private HumanoidProfileEditor? _profileEditor;
|
||||
|
||||
@@ -839,7 +839,7 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -1015,6 +1015,13 @@ namespace Content.Client.Lobby.UI
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
_loadoutWindow.OpenCenteredLeft();
|
||||
|
||||
_loadoutWindow.OnNameChanged += name =>
|
||||
{
|
||||
roleLoadout.EntityName = name;
|
||||
Profile = Profile.WithLoadout(roleLoadout);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
|
||||
{
|
||||
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
SetSize="800 800"
|
||||
MinSize="800 128">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<!--
|
||||
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
|
||||
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
|
||||
<PanelContainer HorizontalExpand="True" SetHeight="24">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/>
|
||||
<LineEdit Name="RoleNameEdit" VerticalExpand="True" HorizontalExpand="True"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
-->
|
||||
<VerticalTabContainer Name="LoadoutGroupsContainer"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
@@ -13,6 +15,7 @@ namespace Content.Client.Lobby.UI.Loadouts;
|
||||
[GenerateTypedNameReferences]
|
||||
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>>? OnLoadoutUnpressed;
|
||||
|
||||
@@ -25,6 +28,23 @@ public sealed partial class LoadoutWindow : FancyWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
Profile = profile;
|
||||
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
|
||||
if (proto.Groups.Count == 0)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic;
|
||||
using Content.Shared.Magic.Events;
|
||||
|
||||
namespace Content.Client.Magic;
|
||||
|
||||
public sealed class MagicSystem : SharedMagicSystem;
|
||||
public sealed class MagicSystem : SharedMagicSystem
|
||||
{
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
|
||||
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))
|
||||
@@ -117,11 +117,11 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix);
|
||||
var localToWorld = Vector2.Transform(_localPosition, _transformSystem.GetWorldMatrix(xform));
|
||||
|
||||
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);
|
||||
|
||||
RaiseNetworkEvent(new GridDragRequestPosition()
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<NewsWriterMenu>();
|
||||
|
||||
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
EntityUid? gridUid = null;
|
||||
var stationName = string.Empty;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private NetEntity? _trackedEntity;
|
||||
@@ -36,10 +37,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_transformSystem = _entManager.System<SharedTransformSystem>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
||||
|
||||
}
|
||||
|
||||
public void Set(string stationName, EntityUid? mapUid)
|
||||
@@ -290,7 +291,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
{
|
||||
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
|
||||
new NavMapBlip
|
||||
(coordinates.Value,
|
||||
(CoordinatesToLocal(coordinates.Value),
|
||||
_blipTexture,
|
||||
(_trackedEntity == null || sensor.SuitSensorUid == _trackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
|
||||
sensor.SuitSensorUid == _trackedEntity));
|
||||
@@ -356,7 +357,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
if (NavMap.TrackedEntities.TryGetValue(castSensor.SuitSensorUid, out var data))
|
||||
{
|
||||
data = new NavMapBlip
|
||||
(data.Coordinates,
|
||||
(CoordinatesToLocal(data.Coordinates),
|
||||
data.Texture,
|
||||
(currTrackedEntity == null || castSensor.SuitSensorUid == currTrackedEntity) ? Color.LimeGreen : Color.LimeGreen * Color.DimGray,
|
||||
castSensor.SuitSensorUid == currTrackedEntity);
|
||||
@@ -421,6 +422,26 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
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()
|
||||
{
|
||||
SensorsTable.RemoveAllChildren();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
|
||||
namespace Content.Client.Movement.Systems;
|
||||
@@ -52,4 +53,14 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs
Normal file
91
Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ namespace Content.Client.Nuke
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<NukeMenu>();
|
||||
|
||||
_menu.OnKeypadButtonPressed += i =>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Orbit;
|
||||
|
||||
@@ -11,8 +12,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animations = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly string _orbitAnimationKey = "orbiting";
|
||||
private readonly string _orbitStopKey = "orbiting_stop";
|
||||
|
||||
public override void Initialize()
|
||||
@@ -21,11 +22,11 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<OrbitVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args)
|
||||
{
|
||||
_robustRandom.SetSeed((int)_timing.CurTime.TotalMilliseconds);
|
||||
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);
|
||||
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
|
||||
return;
|
||||
|
||||
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)
|
||||
@@ -57,14 +53,9 @@ public sealed class OrbitVisualsSystem : EntitySystem
|
||||
sprite.EnableDirectionOverride = false;
|
||||
|
||||
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
|
||||
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
|
||||
{
|
||||
_animations.Stop(uid, animationPlayer, _orbitAnimationKey);
|
||||
}
|
||||
|
||||
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>())
|
||||
{
|
||||
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));
|
||||
|
||||
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)
|
||||
{
|
||||
var length = component.OrbitStopLength;
|
||||
|
||||
@@ -32,6 +32,11 @@ public sealed class TargetOutlineSystem : EntitySystem
|
||||
/// </summary>
|
||||
public EntityWhitelist? Whitelist = null;
|
||||
|
||||
/// <summary>
|
||||
/// Blacklist that the target must satisfy.
|
||||
/// </summary>
|
||||
public EntityWhitelist? Blacklist = null;
|
||||
|
||||
/// <summary>
|
||||
/// Predicate the target must satisfy.
|
||||
/// </summary>
|
||||
@@ -93,15 +98,16 @@ public sealed class TargetOutlineSystem : EntitySystem
|
||||
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;
|
||||
CheckObstruction = checkObstructions;
|
||||
Predicate = predicate;
|
||||
Whitelist = whitelist;
|
||||
Blacklist = blacklist;
|
||||
ValidationEvent = validationEvent;
|
||||
|
||||
_enabled = Predicate != null || Whitelist != null || ValidationEvent != null;
|
||||
_enabled = Predicate != null || Whitelist != null || Blacklist != null || ValidationEvent != null;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -56,35 +56,35 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
|
||||
|
||||
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)
|
||||
{
|
||||
RefreshOverlay(args.Entity);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(LocalPlayerDetachedEvent args)
|
||||
{
|
||||
if (_player.LocalSession?.AttachedEntity == null)
|
||||
if (_player.LocalSession?.AttachedEntity is null)
|
||||
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)
|
||||
@@ -92,24 +92,24 @@ public abstract class EquipmentHudSystem<T> : EntitySystem where T : IComponent
|
||||
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.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;
|
||||
|
||||
var ev = new RefreshEquipmentHudEvent<T>(TargetSlots);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
|
||||
if (ev.Active)
|
||||
Update(ev);
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
|
||||
|
||||
private void OnHandleState(Entity<ShowHealthBarsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
RefreshOverlay(ent);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
|
||||
|
||||
private void OnHandleState(Entity<ShowHealthIconsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
RefreshOverlay(ent);
|
||||
RefreshOverlay();
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)
|
||||
|
||||
@@ -15,6 +15,16 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
|
||||
base.Initialize();
|
||||
|
||||
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)
|
||||
|
||||
@@ -2,49 +2,128 @@
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
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'}"
|
||||
MinSize="420 320"
|
||||
SetSize="420 320">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 10 0 0">
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 5">
|
||||
MinSize="320 120">
|
||||
|
||||
<!-- Main Container -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
|
||||
<!-- Sub-Main container -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
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>
|
||||
<Control MinHeight="5"/>
|
||||
|
||||
<!-- Power -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<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"/>
|
||||
<Button Name="OnButton" ToggleMode="False" Text="{Loc 'particle-accelerator-control-menu-on-button'}" StyleClasses="OpenLeft"/>
|
||||
<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>
|
||||
<Control MinHeight="5"/>
|
||||
|
||||
<!-- Strenght -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="StrengthLabel" Margin="0 0 20 0" HorizontalExpand="True" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<RichTextLabel Name="StrengthLabel"
|
||||
HorizontalExpand="True"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Control MinWidth="8"/>
|
||||
|
||||
<SpinBox Name="StateSpinBox" Value="0"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="5"/>
|
||||
|
||||
<!-- Power -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="DrawLabel" HorizontalExpand="True"/>
|
||||
<Control MinWidth="8"/>
|
||||
<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"/>
|
||||
|
||||
<Control MinHeight="8" VerticalExpand="True"/> <!-- Filler -->
|
||||
|
||||
<!-- Alarm -->
|
||||
<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>
|
||||
<customControls:VSeparator Margin="0 0 0 10"/>
|
||||
<BoxContainer Orientation="Vertical" Margin="20 0 20 0" VerticalAlignment="Center">
|
||||
<PanelContainer Name="BackPanel" HorizontalAlignment="Center">
|
||||
</controls:StripeBack>
|
||||
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-service-manual-reference'}"
|
||||
HorizontalAlignment="Center"
|
||||
StyleClasses="LabelSubText"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 -8" HorizontalAlignment="Right"/>
|
||||
|
||||
<!-- PA Visual part -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8">
|
||||
|
||||
<PanelContainer Name="BackPanel"
|
||||
HorizontalAlignment="Center">
|
||||
|
||||
<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>
|
||||
<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/>
|
||||
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
|
||||
<Control/>
|
||||
@@ -58,17 +137,47 @@
|
||||
<ui:PASegmentControl Name="EmitterForeTexture" BaseState="emitter_fore"/>
|
||||
<ui:PASegmentControl Name="EmitterPortTexture" BaseState="emitter_port"/>
|
||||
</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>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalAlignment="Bottom">
|
||||
|
||||
<controls:StripeBack>
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-check-containment-field-warning'}" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="4 4 0 4"/>
|
||||
<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 0 0">
|
||||
<Label Text="{Loc 'particle-accelerator-control-menu-foo-bar-baz'}" StyleClasses="LabelSubText"/>
|
||||
|
||||
<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>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace Content.Client.Pinpointer.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public readonly EntityCoordinates BeaconPosition;
|
||||
public Action<EntityCoordinates>? OnPressed;
|
||||
public string? Label => BeaconNameLabel.Text;
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed class PowerMonitoringConsoleBoundUserInterface : BoundUserInterfac
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<PowerMonitoringWindow>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.SendPowerMonitoringConsoleMessageAction += SendPowerMonitoringConsoleMessage;
|
||||
|
||||
@@ -4,14 +4,18 @@ using Robust.Client.GameObjects;
|
||||
|
||||
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()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
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)
|
||||
return;
|
||||
@@ -23,7 +27,7 @@ public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComp
|
||||
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;
|
||||
|
||||
args.Sprite.LayerSetState(0, $"{component.StatePrefix}{(int) mask}");
|
||||
|
||||
@@ -11,37 +11,37 @@
|
||||
<!-- 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) -->
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-walls-and-flooring'}" TargetLayer="WallsAndFlooring" Visible="False">
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-windows-and-grilles'}" TargetLayer="WindowsAndGrilles" Visible="False">
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-airlocks'}" TargetLayer="Airlocks" Visible="False">
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-electrical'}" TargetLayer="Electrical" Visible="False">
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-lighting'}" TargetLayer="Lighting" Visible="False">
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
<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"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
</ui:RadialMenuTextureButtonWithSector>
|
||||
</ui:RadialContainer>
|
||||
|
||||
<!-- 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 -->
|
||||
<ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
<ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
|
||||
|
||||
<!-- 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 -->
|
||||
<ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
<ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
|
||||
|
||||
<!-- Lighting -->
|
||||
<ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
<ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
|
||||
|
||||
</ui:RadialMenu>
|
||||
|
||||
@@ -74,7 +74,6 @@ public sealed partial class RCDMenu : RadialMenu
|
||||
|
||||
var button = new RCDMenuButton()
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = tooltip,
|
||||
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)
|
||||
foreach (var child in main.Children)
|
||||
{
|
||||
var castChild = child as RadialMenuTextureButton;
|
||||
|
||||
if (castChild is not RadialMenuTextureButton)
|
||||
if (child is not RadialMenuTextureButton castChild)
|
||||
continue;
|
||||
|
||||
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 RCDMenuButton()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public sealed class RadiationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
public List<RadiationRay>? Rays;
|
||||
public List<DebugRadiationRay>? Rays;
|
||||
public Dictionary<NetEntity, Dictionary<Vector2i, float>>? ResistanceGrids;
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
|
||||
if (_enableShuttlePosition)
|
||||
{
|
||||
_overlay = new EmergencyShuttleOverlay(EntityManager);
|
||||
_overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
|
||||
}
|
||||
@@ -57,23 +57,26 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
/// </summary>
|
||||
public sealed class EmergencyShuttleOverlay : Overlay
|
||||
{
|
||||
private IEntityManager _entManager;
|
||||
private readonly EntityQuery<TransformComponent> _transformQuery;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityUid? StationUid;
|
||||
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)
|
||||
{
|
||||
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.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
using System.Text;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Shuttles.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DockObject : PanelContainer
|
||||
{
|
||||
[PublicAPI]
|
||||
public event Action? UndockPressed;
|
||||
|
||||
[PublicAPI]
|
||||
public event Action? ViewPressed;
|
||||
|
||||
public BoxContainer ContentsContainer => Contents;
|
||||
|
||||
public DockObject()
|
||||
|
||||
@@ -163,16 +163,6 @@ public sealed partial class DockingScreen : BoxContainer
|
||||
}
|
||||
|
||||
dockContainer.AddDock(dock, DockingControl);
|
||||
|
||||
dockContainer.ViewPressed += () =>
|
||||
{
|
||||
OnDockPress(dock);
|
||||
};
|
||||
|
||||
dockContainer.UndockPressed += () =>
|
||||
{
|
||||
UndockRequest?.Invoke(dock.Entity);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,13 +81,19 @@ public sealed partial class NavScreen : BoxContainer
|
||||
// Get the positive reduced angle.
|
||||
var displayRot = -worldRot.Reduced();
|
||||
|
||||
GridPosition.Text = $"{worldPos.X:0.0}, {worldPos.Y:0.0}";
|
||||
GridOrientation.Text = $"{displayRot.Degrees:0.0}";
|
||||
GridPosition.Text = Loc.GetString("shuttle-console-position-value",
|
||||
("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;
|
||||
gridVelocity = displayRot.RotateVec(gridVelocity);
|
||||
// Get linear velocity relative to the console entity
|
||||
GridLinearVelocity.Text = $"{gridVelocity.X + 10f * float.Epsilon:0.0}, {gridVelocity.Y + 10f * float.Epsilon:0.0}";
|
||||
GridAngularVelocity.Text = $"{-gridBody.AngularVelocity + 10f * float.Epsilon:0.0}";
|
||||
GridLinearVelocity.Text = Loc.GetString("shuttle-console-linear-velocity-value",
|
||||
("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}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
BackButtonStyleClass="RadialMenuBackButton"
|
||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
@@ -7,7 +7,7 @@
|
||||
MinSize="450 450">
|
||||
|
||||
<!-- 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:RadialMenu>
|
||||
|
||||
@@ -54,7 +54,6 @@ public sealed partial class StationAiMenu : RadialMenu
|
||||
// TODO: This radial boilerplate is quite annoying
|
||||
var button = new StationAiMenuButton(action.Event)
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -24,6 +24,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
|
||||
private ContentSpriteControl _control = new();
|
||||
|
||||
@@ -42,12 +43,12 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
foreach (var queued in _control._queuedTextures)
|
||||
foreach (var queued in _control.QueuedTextures)
|
||||
{
|
||||
queued.Tcs.SetCanceled();
|
||||
}
|
||||
|
||||
_control._queuedTextures.Clear();
|
||||
_control.QueuedTextures.Clear();
|
||||
|
||||
_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 tcs = new TaskCompletionSource(cancelToken);
|
||||
|
||||
_control._queuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
|
||||
_control.QueuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
|
||||
|
||||
await tcs.Task;
|
||||
}
|
||||
@@ -113,13 +114,21 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
if (!_adminManager.IsAdmin())
|
||||
return;
|
||||
|
||||
var target = ev.Target;
|
||||
Verb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("export-entity-verb-get-data-text"),
|
||||
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,
|
||||
EntityUid Entity,
|
||||
bool IncludeId,
|
||||
TaskCompletionSource Tcs)> _queuedTextures = new();
|
||||
TaskCompletionSource Tcs)> QueuedTextures = new();
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
@@ -155,7 +164,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
while (_queuedTextures.TryDequeue(out var queued))
|
||||
while (QueuedTextures.TryDequeue(out var queued))
|
||||
{
|
||||
if (queued.Tcs.Task.IsCanceled)
|
||||
continue;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -85,6 +86,9 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
|
||||
if (data.HideOnStealth && TryComp<StealthComponent>(ent, out var stealth) && stealth.Enabled)
|
||||
return false;
|
||||
|
||||
if (TryComp<SpriteComponent>(ent, out var sprite) && !sprite.Visible)
|
||||
return false;
|
||||
|
||||
if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -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 JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Storage;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
private readonly StorageSystem _storage;
|
||||
|
||||
[Obsolete] public override bool DeferredClose => false;
|
||||
private StorageWindow? _window;
|
||||
|
||||
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_storage = _entManager.System<StorageSystem>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if (_entManager.TryGetComponent<StorageComponent>(Owner, out var comp))
|
||||
_storage.OpenStorageWindow((Owner, comp));
|
||||
_window = IoCManager.Resolve<IUserInterfaceManager>()
|
||||
.GetUIController<StorageUIController>()
|
||||
.CreateStorageWindow(Owner);
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out StorageComponent? storage))
|
||||
{
|
||||
_window.UpdateContainer((Owner, storage));
|
||||
}
|
||||
|
||||
_window.OnClose += Close;
|
||||
_window.FlagDirty();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
_window?.FlagDirty();
|
||||
}
|
||||
|
||||
public void Reclaim()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.OnClose -= Close;
|
||||
_window.Orphan();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
|
||||
Reclaim();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_storage.CloseStorageWindow(Owner);
|
||||
_window.Visible = false;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.Visible = true;
|
||||
}
|
||||
|
||||
public void ReOpen()
|
||||
{
|
||||
_window?.Orphan();
|
||||
_window = null;
|
||||
Open();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ using Content.Client.Animations;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -13,114 +14,91 @@ namespace Content.Client.Storage.Systems;
|
||||
public sealed class StorageSystem : SharedStorageSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!;
|
||||
|
||||
private readonly List<Entity<StorageComponent>> _openStorages = new();
|
||||
public int OpenStorageAmount => _openStorages.Count;
|
||||
|
||||
public event Action<Entity<StorageComponent>>? StorageUpdated;
|
||||
public event Action<Entity<StorageComponent>?>? StorageOrderChanged;
|
||||
private Dictionary<EntityUid, ItemStorageLocation> _oldStoredItems = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
|
||||
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
|
||||
SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (Resolve(entity.Owner, ref entity.Comp))
|
||||
StorageUpdated?.Invoke((entity, entity.Comp));
|
||||
}
|
||||
|
||||
public void OpenStorageWindow(Entity<StorageComponent> entity)
|
||||
if (UI.TryGetOpenUi<StorageBoundUserInterface>(entity.Owner, StorageComponent.StorageUiKey.Key, out var sBui))
|
||||
{
|
||||
if (_openStorages.Contains(entity))
|
||||
{
|
||||
if (_openStorages.LastOrDefault() == entity)
|
||||
{
|
||||
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);
|
||||
sBui.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseStorageBoundUserInterface(Entity<UserInterfaceComponent?> entity)
|
||||
protected override void HideStorageWindow(EntityUid uid, EntityUid actor)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui)
|
||||
return;
|
||||
|
||||
bui.Close();
|
||||
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
|
||||
{
|
||||
storageBui.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
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 />
|
||||
|
||||
@@ -27,6 +27,8 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<StoreMenu>();
|
||||
if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store))
|
||||
_menu.Title = Loc.GetString(store.Name);
|
||||
|
||||
@@ -21,6 +21,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new();
|
||||
|
||||
if (_type == SurveillanceCameraSetupUiKey.Router)
|
||||
|
||||
@@ -87,9 +87,6 @@ public sealed partial class DialogWindow : FancyWindow
|
||||
Prompts.AddChild(box);
|
||||
}
|
||||
|
||||
// Grab keyboard focus for the first dialog entry
|
||||
_promptLines[0].Item2.GrabKeyboardFocus();
|
||||
|
||||
OkButton.OnPressed += _ => Confirm();
|
||||
|
||||
CancelButton.OnPressed += _ =>
|
||||
@@ -110,6 +107,14 @@ public sealed partial class DialogWindow : FancyWindow
|
||||
OpenCentered();
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
// Grab keyboard focus for the first dialog entry
|
||||
_promptLines[0].Item2.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
var results = new Dictionary<string, string>();
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -96,7 +95,6 @@ public sealed partial class FancyTree : Control
|
||||
private void LoadIcons()
|
||||
{
|
||||
IconColor = TryGetStyleProperty(StylePropertyIconColor, out Color color) ? color : Color.White;
|
||||
string? path;
|
||||
|
||||
if (!TryGetStyleProperty(StylePropertyIconExpanded, out IconExpanded))
|
||||
IconExpanded = _resCache.GetTexture(DefaultIconExpanded);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
@@ -8,6 +7,11 @@ namespace Content.Client.UserInterface.Controls;
|
||||
[Virtual]
|
||||
public class RadialContainer : LayoutContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Increment of radius per child element to be rendered.
|
||||
/// </summary>
|
||||
private const float RadiusIncrement = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
@@ -49,10 +53,30 @@ public class RadialContainer : LayoutContainer
|
||||
public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise;
|
||||
|
||||
/// <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>
|
||||
[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>
|
||||
/// Sets whether the container should reserve a space on the layout for child which are not currently visible
|
||||
@@ -68,38 +92,75 @@ public class RadialContainer : LayoutContainer
|
||||
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
/// <inheritdoc />
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var children = ReserveSpaceForHiddenChildren
|
||||
? Children
|
||||
: Children.Where(x => x.Visible);
|
||||
|
||||
const float baseRadius = 100f;
|
||||
const float radiusIncrement = 5f;
|
||||
|
||||
var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible);
|
||||
var childCount = children.Count();
|
||||
|
||||
// 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
|
||||
var arc = AngularRange.Y - AngularRange.X;
|
||||
arc = (arc < 0) ? MathF.Tau + arc : arc;
|
||||
arc = (RadialAlignment == RAlignment.AntiClockwise) ? MathF.Tau - arc : arc;
|
||||
arc = arc < 0
|
||||
? MathF.Tau + arc
|
||||
: arc;
|
||||
arc = isAntiClockwise
|
||||
? MathF.Tau - arc
|
||||
: arc;
|
||||
|
||||
// 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
|
||||
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
|
||||
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);
|
||||
|
||||
// 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>
|
||||
/// Specifies the different radial alignment modes
|
||||
/// </summary>
|
||||
@@ -109,4 +170,5 @@ public class RadialContainer : LayoutContainer
|
||||
Clockwise,
|
||||
AntiClockwise,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
@@ -12,7 +15,12 @@ public class RadialMenu : BaseWindow
|
||||
/// <summary>
|
||||
/// Contextual button used to traverse through previous layers of the radial menu
|
||||
/// </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>
|
||||
/// 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
|
||||
@@ -52,7 +60,7 @@ public class RadialMenu : BaseWindow
|
||||
}
|
||||
}
|
||||
|
||||
private List<Control> _path = new();
|
||||
private readonly List<Control> _path = new();
|
||||
private string? _backButtonStyleClass;
|
||||
private string? _closeButtonStyleClass;
|
||||
|
||||
@@ -78,23 +86,56 @@ public class RadialMenu : BaseWindow
|
||||
}
|
||||
|
||||
// Auto generate a contextual button for moving back through visited layers
|
||||
ContextualButton = new TextureButton()
|
||||
ContextualButton = new RadialMenuContextualCentralTextureButton
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
};
|
||||
MenuOuterAreaButton = new RadialMenuOuterAreaButton();
|
||||
|
||||
ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer();
|
||||
MenuOuterAreaButton.OnButtonUp += _ => Close();
|
||||
AddChild(ContextualButton);
|
||||
AddChild(MenuOuterAreaButton);
|
||||
|
||||
// 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()
|
||||
{
|
||||
var children = Children.Where(x => x != ContextualButton);
|
||||
var children = Children.Where(x => x != ContextualButton && x != MenuOuterAreaButton);
|
||||
|
||||
if (!children.Any())
|
||||
return null;
|
||||
@@ -116,7 +157,7 @@ public class RadialMenu : BaseWindow
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child == ContextualButton)
|
||||
if (child == ContextualButton || child == MenuOuterAreaButton)
|
||||
continue;
|
||||
|
||||
// Hide layers which are not of interest
|
||||
@@ -129,6 +170,7 @@ public class RadialMenu : BaseWindow
|
||||
else
|
||||
{
|
||||
child.Visible = true;
|
||||
SetupContextualButtonData(child);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +200,7 @@ public class RadialMenu : BaseWindow
|
||||
// Hide all children except the contextual button
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child != ContextualButton)
|
||||
if (child != ContextualButton && child != MenuOuterAreaButton)
|
||||
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]
|
||||
public class RadialMenuButton : Button
|
||||
public class RadialMenuTextureButtonBase : TextureButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon clicking this button the radial menu will transition to the named layer
|
||||
/// </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()
|
||||
/// <inheritdoc />
|
||||
protected RadialMenuTextureButtonBase()
|
||||
{
|
||||
OnButtonUp += OnClicked;
|
||||
EnableAllKeybinds = true;
|
||||
}
|
||||
|
||||
private void OnClicked(ButtonEventArgs args)
|
||||
/// <inheritdoc />
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if (TargetLayer == null || TargetLayer == string.Empty)
|
||||
return;
|
||||
if (args.Function == EngineKeyFunctions.UIClick
|
||||
|| args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||
base.KeyBindUp(args);
|
||||
}
|
||||
}
|
||||
|
||||
var parent = FindParentMultiLayerContainer(this);
|
||||
/// <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; }
|
||||
|
||||
if (parent == null)
|
||||
return;
|
||||
public Vector2? ParentCenter { get; set; }
|
||||
|
||||
parent.TryToMoveToNewLayer(TargetLayer);
|
||||
/// <inheritdoc />
|
||||
protected override bool HasPoint(Vector2 point)
|
||||
{
|
||||
if (ParentCenter == null)
|
||||
{
|
||||
return base.HasPoint(point);
|
||||
}
|
||||
|
||||
private RadialMenu? FindParentMultiLayerContainer(Control control)
|
||||
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)
|
||||
{
|
||||
foreach (var ancestor in control.GetSelfAndLogicalAncestors())
|
||||
if (ParentCenter == null)
|
||||
{
|
||||
if (ancestor is RadialMenu)
|
||||
return ancestor as RadialMenu;
|
||||
return base.HasPoint(point);
|
||||
}
|
||||
|
||||
return null;
|
||||
var distSquared = (point + Position - ParentCenter.Value).LengthSquared();
|
||||
|
||||
var outerRadiusSquared = OuterRadius * OuterRadius;
|
||||
|
||||
// comparing to squared values is faster, then making sqrt
|
||||
return distSquared > outerRadiusSquared;
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class RadialMenuTextureButton : TextureButton
|
||||
public class RadialMenuTextureButton : RadialMenuTextureButtonBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon clicking this button the radial menu will be moved to the named layer
|
||||
@@ -226,6 +305,7 @@ public class RadialMenuTextureButton : TextureButton
|
||||
/// </summary>
|
||||
public RadialMenuTextureButton()
|
||||
{
|
||||
EnableAllKeybinds = true;
|
||||
OnButtonUp += OnClicked;
|
||||
}
|
||||
|
||||
@@ -246,10 +326,329 @@ public class RadialMenuTextureButton : TextureButton
|
||||
{
|
||||
foreach (var ancestor in control.GetSelfAndLogicalAncestors())
|
||||
{
|
||||
if (ancestor is RadialMenu)
|
||||
return ancestor as RadialMenu;
|
||||
if (ancestor is RadialMenu menu)
|
||||
return menu;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -952,7 +952,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
var range = entityAction.CheckCanAccess ? action.Range : -1;
|
||||
|
||||
_interactionOutline?.SetEnabled(false);
|
||||
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, null);
|
||||
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -56,7 +56,8 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
_window = UIManager.CreateWindow<CharacterWindow>();
|
||||
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
||||
|
||||
|
||||
_window.OnClose += DeactivateButton;
|
||||
_window.OnOpen += ActivateButton;
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenCharacterMenu,
|
||||
@@ -68,7 +69,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_window.Dispose();
|
||||
_window.Close();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
@@ -105,18 +106,27 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
}
|
||||
|
||||
CharacterButton.OnPressed += CharacterButtonPressed;
|
||||
}
|
||||
|
||||
if (_window == null)
|
||||
private void DeactivateButton()
|
||||
{
|
||||
if (CharacterButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window.OnClose += DeactivateButton;
|
||||
_window.OnOpen += ActivateButton;
|
||||
CharacterButton.Pressed = false;
|
||||
}
|
||||
|
||||
private void DeactivateButton() => CharacterButton!.Pressed = false;
|
||||
private void ActivateButton() => CharacterButton!.Pressed = true;
|
||||
private void ActivateButton()
|
||||
{
|
||||
if (CharacterButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterButton.Pressed = true;
|
||||
}
|
||||
|
||||
private void CharacterUpdated(CharacterData data)
|
||||
{
|
||||
@@ -150,7 +160,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
|
||||
var objectiveLabel = new RichTextLabel
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassTooltipActionTitle}
|
||||
StyleClasses = { StyleNano.StyleClassTooltipActionTitle }
|
||||
};
|
||||
objectiveLabel.SetMessage(objectiveText);
|
||||
|
||||
@@ -245,10 +255,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
if (CharacterButton != null)
|
||||
{
|
||||
CharacterButton.SetClickPressed(!_window.IsOpen);
|
||||
}
|
||||
CharacterButton?.SetClickPressed(!_window.IsOpen);
|
||||
|
||||
if (_window.IsOpen)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Traits.Assorted;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
@@ -94,7 +95,11 @@ public sealed class DamageOverlayUiController : UIController
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -31,13 +31,12 @@ public sealed class HotbarUIController : UIController
|
||||
ReloadHotbar();
|
||||
}
|
||||
|
||||
public void Setup(HandsContainer handsContainer, StorageContainer storageContainer)
|
||||
public void Setup(HandsContainer handsContainer)
|
||||
{
|
||||
_inventory = UIManager.GetUIController<InventoryUIController>();
|
||||
_hands = UIManager.GetUIController<HandsUIController>();
|
||||
_storage = UIManager.GetUIController<StorageUIController>();
|
||||
_hands.RegisterHandContainer(handsContainer);
|
||||
_storage.RegisterStorageContainer(storageContainer);
|
||||
}
|
||||
|
||||
public void ReloadHotbar()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<widgets:HotbarGui
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
|
||||
xmlns:storage="clr-namespace:Content.Client.UserInterface.Systems.Storage.Controls"
|
||||
xmlns:hands="clr-namespace:Content.Client.UserInterface.Systems.Hands.Controls"
|
||||
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
|
||||
Name="HotbarInterface"
|
||||
@@ -13,10 +12,8 @@
|
||||
<BoxContainer Name="StorageContainer"
|
||||
Access="Public"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalExpand="True"
|
||||
Margin="10">
|
||||
<storage:StorageContainer
|
||||
Name="StoragePanel"
|
||||
Visible="False"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Name="Hotbar" HorizontalAlignment="Center">
|
||||
<inventory:ItemSlotButtonContainer
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed partial class HotbarGui : UIWidget
|
||||
StatusPanelLeft.SetSide(HandUILocation.Left);
|
||||
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
|
||||
|
||||
hotbarController.Setup(HandContainer, StoragePanel);
|
||||
hotbarController.Setup(HandContainer);
|
||||
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class ItemGridPiece : Control, IEntityControl
|
||||
Location = location;
|
||||
|
||||
Visible = true;
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
|
||||
@@ -105,8 +105,11 @@ public sealed class ItemGridPiece : Control, IEntityControl
|
||||
return;
|
||||
}
|
||||
|
||||
if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity && _storageController.DraggingGhost != this)
|
||||
if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity &&
|
||||
_storageController.DraggingGhost != this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var adjustedShape = _entityManager.System<ItemSystem>().GetAdjustedItemShape((Entity, itemComponent), Location.Rotation, Vector2i.Zero);
|
||||
var boundingGrid = adjustedShape.GetBoundingBox();
|
||||
|
||||
@@ -3,7 +3,9 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Client.Storage;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Storage;
|
||||
@@ -11,12 +13,14 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
|
||||
public sealed class StorageContainer : BaseWindow
|
||||
public sealed class StorageWindow : BaseWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
private readonly StorageUIController _storageController;
|
||||
@@ -27,6 +31,20 @@ public sealed class StorageContainer : BaseWindow
|
||||
private readonly GridContainer _backgroundGrid;
|
||||
private readonly GridContainer _sidebar;
|
||||
|
||||
private Control _titleContainer;
|
||||
private Label _titleLabel;
|
||||
|
||||
// Needs to be nullable in case a piece is in default spot.
|
||||
private readonly Dictionary<EntityUid, (ItemStorageLocation? Loc, ItemGridPiece Control)> _pieces = new();
|
||||
private readonly List<Control> _controlGrid = new();
|
||||
|
||||
private ValueList<EntityUid> _contained = new();
|
||||
private ValueList<EntityUid> _toRemove = new();
|
||||
|
||||
private TextureButton? _backButton;
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPiecePressed;
|
||||
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
|
||||
|
||||
@@ -51,9 +69,10 @@ public sealed class StorageContainer : BaseWindow
|
||||
private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
|
||||
private Texture? _sidebarFatTexture;
|
||||
|
||||
public StorageContainer()
|
||||
public StorageWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
Resizable = false;
|
||||
|
||||
_storageController = UserInterfaceManager.GetUIController<StorageUIController>();
|
||||
|
||||
@@ -63,6 +82,7 @@ public sealed class StorageContainer : BaseWindow
|
||||
|
||||
_sidebar = new GridContainer
|
||||
{
|
||||
Name = "SideBar",
|
||||
HSeparationOverride = 0,
|
||||
VSeparationOverride = 0,
|
||||
Columns = 1
|
||||
@@ -70,21 +90,48 @@ public sealed class StorageContainer : BaseWindow
|
||||
|
||||
_pieceGrid = new GridContainer
|
||||
{
|
||||
Name = "PieceGrid",
|
||||
HSeparationOverride = 0,
|
||||
VSeparationOverride = 0
|
||||
};
|
||||
|
||||
_backgroundGrid = new GridContainer
|
||||
{
|
||||
Name = "BackgroundGrid",
|
||||
HSeparationOverride = 0,
|
||||
VSeparationOverride = 0
|
||||
};
|
||||
|
||||
_titleLabel = new Label()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Name = "StorageLabel",
|
||||
ClipText = true,
|
||||
Text = "Dummy",
|
||||
StyleClasses =
|
||||
{
|
||||
"FancyWindowTitle",
|
||||
}
|
||||
};
|
||||
|
||||
_titleContainer = new PanelContainer()
|
||||
{
|
||||
StyleClasses =
|
||||
{
|
||||
"WindowHeadingBackground"
|
||||
},
|
||||
Children =
|
||||
{
|
||||
_titleLabel
|
||||
}
|
||||
};
|
||||
|
||||
var container = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
_titleContainer,
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
@@ -130,12 +177,22 @@ public sealed class StorageContainer : BaseWindow
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
if (UserInterfaceManager.GetUIController<StorageUIController>().WindowTitle)
|
||||
{
|
||||
_titleLabel.Text = Identity.Name(entity.Value, _entity);
|
||||
_titleContainer.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_titleContainer.Visible = false;
|
||||
}
|
||||
|
||||
BuildGridRepresentation();
|
||||
}
|
||||
|
||||
private void BuildGridRepresentation()
|
||||
{
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || !comp.Grid.Any())
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || comp.Grid.Count == 0)
|
||||
return;
|
||||
|
||||
var boundingGrid = comp.Grid.GetBoundingBox();
|
||||
@@ -144,12 +201,13 @@ public sealed class StorageContainer : BaseWindow
|
||||
|
||||
#region Sidebar
|
||||
_sidebar.Children.Clear();
|
||||
_sidebar.Rows = boundingGrid.Height + 1;
|
||||
var rows = boundingGrid.Height + 1;
|
||||
_sidebar.Rows = rows;
|
||||
|
||||
var exitButton = new TextureButton
|
||||
{
|
||||
TextureNormal = _entity.System<StorageSystem>().OpenStorageAmount == 1
|
||||
?_exitTexture
|
||||
: _backTexture,
|
||||
Name = "ExitButton",
|
||||
TextureNormal = _exitTexture,
|
||||
Scale = new Vector2(2, 2),
|
||||
};
|
||||
exitButton.OnPressed += _ =>
|
||||
@@ -165,8 +223,10 @@ public sealed class StorageContainer : BaseWindow
|
||||
args.Handle();
|
||||
}
|
||||
};
|
||||
|
||||
var exitContainer = new BoxContainer
|
||||
{
|
||||
Name = "ExitContainer",
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
@@ -182,28 +242,70 @@ public sealed class StorageContainer : BaseWindow
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_sidebar.AddChild(exitContainer);
|
||||
for (var i = 0; i < boundingGrid.Height - 1; i++)
|
||||
var offset = 2;
|
||||
|
||||
if (_entity.System<StorageSystem>().NestedStorage && rows > 0)
|
||||
{
|
||||
_sidebar.AddChild(new TextureRect
|
||||
_backButton = new TextureButton
|
||||
{
|
||||
Texture = _sidebarMiddleTexture,
|
||||
TextureNormal = _backTexture,
|
||||
Scale = new Vector2(2, 2),
|
||||
};
|
||||
_backButton.OnPressed += _ =>
|
||||
{
|
||||
var containerSystem = _entity.System<SharedContainerSystem>();
|
||||
|
||||
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
|
||||
_entity.TryGetComponent(container.Owner, out StorageComponent? storage))
|
||||
{
|
||||
Close();
|
||||
|
||||
if (_entity.System<SharedUserInterfaceSystem>()
|
||||
.TryGetOpenUi<StorageBoundUserInterface>(container.Owner,
|
||||
StorageComponent.StorageUiKey.Key,
|
||||
out var parentBui))
|
||||
{
|
||||
parentBui.Show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var backContainer = new BoxContainer
|
||||
{
|
||||
Name = "ExitContainer",
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = rows > 2 ? _sidebarMiddleTexture : _sidebarBottomTexture,
|
||||
TextureScale = new Vector2(2, 2),
|
||||
});
|
||||
Children =
|
||||
{
|
||||
_backButton,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_sidebar.AddChild(backContainer);
|
||||
}
|
||||
|
||||
if (boundingGrid.Height > 0)
|
||||
var fillerRows = rows - offset;
|
||||
|
||||
for (var i = 0; i < fillerRows; i++)
|
||||
{
|
||||
_sidebar.AddChild(new TextureRect
|
||||
{
|
||||
Texture = _sidebarBottomTexture,
|
||||
Texture = i != (fillerRows - 1) ? _sidebarMiddleTexture : _sidebarBottomTexture,
|
||||
TextureScale = new Vector2(2, 2),
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
BuildItemPieces();
|
||||
FlagDirty();
|
||||
}
|
||||
|
||||
public void BuildBackground()
|
||||
@@ -240,23 +342,54 @@ public sealed class StorageContainer : BaseWindow
|
||||
}
|
||||
}
|
||||
|
||||
public void Reclaim(ItemStorageLocation location, ItemGridPiece draggingGhost)
|
||||
{
|
||||
draggingGhost.OnPiecePressed += OnPiecePressed;
|
||||
draggingGhost.OnPieceUnpressed += OnPieceUnpressed;
|
||||
_pieces[draggingGhost.Entity] = (location, draggingGhost);
|
||||
draggingGhost.Location = location;
|
||||
var controlIndex = GetGridIndex(draggingGhost);
|
||||
_controlGrid[controlIndex].AddChild(draggingGhost);
|
||||
}
|
||||
|
||||
private int GetGridIndex(ItemGridPiece piece)
|
||||
{
|
||||
return piece.Location.Position.X + piece.Location.Position.Y * _pieceGrid.Columns;
|
||||
}
|
||||
|
||||
public void FlagDirty()
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public void RemoveGrid(ItemGridPiece control)
|
||||
{
|
||||
control.Orphan();
|
||||
_pieces.Remove(control.Entity);
|
||||
control.OnPiecePressed -= OnPiecePressed;
|
||||
control.OnPieceUnpressed -= OnPieceUnpressed;
|
||||
}
|
||||
|
||||
public void BuildItemPieces()
|
||||
{
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp))
|
||||
return;
|
||||
|
||||
if (!storageComp.Grid.Any())
|
||||
if (storageComp.Grid.Count == 0)
|
||||
return;
|
||||
|
||||
var boundingGrid = storageComp.Grid.GetBoundingBox();
|
||||
var size = _emptyTexture!.Size * 2;
|
||||
var containedEntities = storageComp.Container.ContainedEntities.Reverse().ToArray();
|
||||
_contained.Clear();
|
||||
_contained.AddRange(storageComp.Container.ContainedEntities.Reverse());
|
||||
|
||||
//todo. at some point, we may want to only rebuild the pieces that have actually received new data.
|
||||
|
||||
_pieceGrid.RemoveAllChildren();
|
||||
// Build the grid representation
|
||||
if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
|
||||
{
|
||||
_pieceGrid.Rows = boundingGrid.Height + 1;
|
||||
_pieceGrid.Columns = boundingGrid.Width + 1;
|
||||
_controlGrid.Clear();
|
||||
|
||||
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
|
||||
{
|
||||
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
|
||||
@@ -266,29 +399,58 @@ public sealed class StorageContainer : BaseWindow
|
||||
MinSize = size
|
||||
};
|
||||
|
||||
var currentPosition = new Vector2i(x, y);
|
||||
|
||||
foreach (var (itemEnt, itemPos) in storageComp.StoredItems)
|
||||
{
|
||||
if (itemPos.Position != currentPosition)
|
||||
continue;
|
||||
|
||||
if (_entity.TryGetComponent<ItemComponent>(itemEnt, out var itemEntComponent))
|
||||
{
|
||||
ItemGridPiece gridPiece;
|
||||
|
||||
if (_storageController.CurrentlyDragging?.Entity is { } dragging
|
||||
&& dragging == itemEnt)
|
||||
{
|
||||
_storageController.CurrentlyDragging.Orphan();
|
||||
gridPiece = _storageController.CurrentlyDragging;
|
||||
_controlGrid.Add(control);
|
||||
_pieceGrid.AddChild(control);
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
|
||||
_toRemove.Clear();
|
||||
|
||||
// Remove entities no longer relevant / Update existing ones
|
||||
foreach (var (ent, data) in _pieces)
|
||||
{
|
||||
gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), itemPos, _entity)
|
||||
if (storageComp.StoredItems.TryGetValue(ent, out var updated))
|
||||
{
|
||||
if (data.Loc.Equals(updated))
|
||||
{
|
||||
DebugTools.Assert(data.Control.Location == updated);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 = Array.IndexOf(containedEntities, itemEnt) switch
|
||||
Marked = _contained.IndexOf(ent) switch
|
||||
{
|
||||
0 => ItemGridPieceMarks.First,
|
||||
1 => ItemGridPieceMarks.Second,
|
||||
@@ -297,13 +459,10 @@ public sealed class StorageContainer : BaseWindow
|
||||
};
|
||||
gridPiece.OnPiecePressed += OnPiecePressed;
|
||||
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
|
||||
}
|
||||
var controlIndex = loc.Position.X + loc.Position.Y * (boundingGrid.Width + 1);
|
||||
|
||||
control.AddChild(gridPiece);
|
||||
}
|
||||
}
|
||||
|
||||
_pieceGrid.AddChild(control);
|
||||
_controlGrid[controlIndex].AddChild(gridPiece);
|
||||
_pieces[ent] = (loc, gridPiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,6 +474,35 @@ public sealed class StorageContainer : BaseWindow
|
||||
if (!IsOpen)
|
||||
return;
|
||||
|
||||
if (_isDirty)
|
||||
{
|
||||
_isDirty = false;
|
||||
BuildItemPieces();
|
||||
}
|
||||
|
||||
var containerSystem = _entity.System<SharedContainerSystem>();
|
||||
|
||||
if (_backButton != null)
|
||||
{
|
||||
if (StorageEntity != null && _entity.System<StorageSystem>().NestedStorage)
|
||||
{
|
||||
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
|
||||
_entity.HasComponent<StorageComponent>(container.Owner))
|
||||
{
|
||||
_backButton.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_backButton.Visible = false;
|
||||
}
|
||||
}
|
||||
// Hide the button.
|
||||
else
|
||||
{
|
||||
_backButton.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
var itemSystem = _entity.System<ItemSystem>();
|
||||
var storageSystem = _entity.System<StorageSystem>();
|
||||
var handsSystem = _entity.System<HandsSystem>();
|
||||
@@ -324,7 +512,7 @@ public sealed class StorageContainer : BaseWindow
|
||||
child.ModulateSelfOverride = Color.FromHex("#222222");
|
||||
}
|
||||
|
||||
if (UserInterfaceManager.CurrentlyHovered is StorageContainer con && con != this)
|
||||
if (UserInterfaceManager.CurrentlyHovered is StorageWindow con && con != this)
|
||||
return;
|
||||
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
|
||||
@@ -373,7 +561,7 @@ public sealed class StorageContainer : BaseWindow
|
||||
continue;
|
||||
|
||||
float spot = 0;
|
||||
var marked = new List<Control>();
|
||||
var marked = new ValueList<Control>();
|
||||
|
||||
foreach (var location in locations.Value)
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Numerics;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Client.Interaction;
|
||||
using Content.Client.Storage;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
@@ -9,9 +10,9 @@ using Content.Client.Verbs.UI;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -23,19 +24,23 @@ namespace Content.Client.UserInterface.Systems.Storage;
|
||||
|
||||
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
|
||||
{
|
||||
/*
|
||||
* Things are a bit over the shop but essentially
|
||||
* - Clicking into storagewindow is handled via storagewindow
|
||||
* - Clicking out of it is via ItemGridPiece
|
||||
* - Dragging around is handled here
|
||||
* - Drawing is handled via ItemGridPiece
|
||||
* - StorageSystem handles any sim stuff around open windows.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[UISystemDependency] private readonly StorageSystem _storage = default!;
|
||||
|
||||
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
|
||||
private StorageContainer? _container;
|
||||
|
||||
private Vector2? _lastContainerPosition;
|
||||
|
||||
private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
|
||||
|
||||
public ItemGridPiece? DraggingGhost;
|
||||
public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
|
||||
public Angle DraggingRotation = Angle.Zero;
|
||||
public bool StaticStorageUIEnabled;
|
||||
public bool OpaqueStorageWindow;
|
||||
@@ -43,6 +48,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
public bool IsDragging => _menuDragHelper.IsDragging;
|
||||
public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
|
||||
|
||||
public bool WindowTitle { get; private set; } = false;
|
||||
|
||||
public StorageUIController()
|
||||
{
|
||||
_menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
|
||||
@@ -52,106 +59,88 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UIManager.OnScreenChanged += OnScreenChange;
|
||||
|
||||
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
|
||||
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
|
||||
_configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
|
||||
}
|
||||
|
||||
private void OnScreenChange((UIScreen? Old, UIScreen? New) obj)
|
||||
{
|
||||
// Handle reconnects with hotbargui.
|
||||
|
||||
// Essentially HotbarGui / the screen gets loaded AFTER gamestates at the moment (because clientgameticker manually changes it via event)
|
||||
// and changing this may be a massive change.
|
||||
// So instead we'll just manually reload it for now.
|
||||
if (!StaticStorageUIEnabled ||
|
||||
obj.New == null ||
|
||||
!EntityManager.TryGetComponent(_player.LocalEntity, out UserInterfaceUserComponent? userComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// UISystemDependency not injected at this point so do it the old fashion way, I love ordering issues.
|
||||
var uiSystem = EntityManager.System<SharedUserInterfaceSystem>();
|
||||
|
||||
foreach (var bui in uiSystem.GetActorUis((_player.LocalEntity.Value, userComp)))
|
||||
{
|
||||
if (!uiSystem.TryGetOpenUi<StorageBoundUserInterface>(bui.Entity, StorageComponent.StorageUiKey.Key, out var storageBui))
|
||||
continue;
|
||||
|
||||
storageBui.ReOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStorageWindowTitle(bool obj)
|
||||
{
|
||||
WindowTitle = obj;
|
||||
}
|
||||
|
||||
private void OnOpaqueWindowChanged(bool obj)
|
||||
{
|
||||
OpaqueStorageWindow = obj;
|
||||
}
|
||||
|
||||
private void OnStaticStorageChanged(bool obj)
|
||||
{
|
||||
StaticStorageUIEnabled = obj;
|
||||
}
|
||||
|
||||
public StorageWindow CreateStorageWindow(EntityUid uid)
|
||||
{
|
||||
var window = new StorageWindow();
|
||||
window.MouseFilter = Control.MouseFilterMode.Pass;
|
||||
|
||||
window.OnPiecePressed += (args, piece) =>
|
||||
{
|
||||
OnPiecePressed(args, window, piece);
|
||||
};
|
||||
window.OnPieceUnpressed += (args, piece) =>
|
||||
{
|
||||
OnPieceUnpressed(args, window, piece);
|
||||
};
|
||||
|
||||
if (StaticStorageUIEnabled)
|
||||
{
|
||||
UIManager.GetActiveUIWidgetOrNull<HotbarGui>()?.StorageContainer.AddChild(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public void OnSystemLoaded(StorageSystem system)
|
||||
{
|
||||
_input.FirstChanceOnKeyEvent += OnMiddleMouse;
|
||||
system.StorageUpdated += OnStorageUpdated;
|
||||
system.StorageOrderChanged += OnStorageOrderChanged;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(StorageSystem system)
|
||||
{
|
||||
_input.FirstChanceOnKeyEvent -= OnMiddleMouse;
|
||||
system.StorageUpdated -= OnStorageUpdated;
|
||||
system.StorageOrderChanged -= OnStorageOrderChanged;
|
||||
}
|
||||
|
||||
private void OnStorageOrderChanged(Entity<StorageComponent>? nullEnt)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
if (IsDragging)
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
_container.UpdateContainer(nullEnt);
|
||||
|
||||
if (nullEnt is not null)
|
||||
{
|
||||
// center it if we knock it off screen somehow.
|
||||
if (!StaticStorageUIEnabled &&
|
||||
(_lastContainerPosition == null ||
|
||||
_lastContainerPosition.Value.X < 0 ||
|
||||
_lastContainerPosition.Value.Y < 0 ||
|
||||
_lastContainerPosition.Value.X > _ui.WindowRoot.Width ||
|
||||
_lastContainerPosition.Value.Y > _ui.WindowRoot.Height))
|
||||
{
|
||||
_container.OpenCenteredAt(new Vector2(0.5f, 0.75f));
|
||||
}
|
||||
else
|
||||
{
|
||||
_container.Open();
|
||||
|
||||
var pos = !StaticStorageUIEnabled && _lastContainerPosition != null
|
||||
? _lastContainerPosition.Value
|
||||
: Vector2.Zero;
|
||||
|
||||
LayoutContainer.SetPosition(_container, pos);
|
||||
}
|
||||
|
||||
if (StaticStorageUIEnabled)
|
||||
{
|
||||
// we have to orphan it here because Open() sets the parent.
|
||||
_container.Orphan();
|
||||
Hotbar?.StorageContainer.AddChild(_container);
|
||||
}
|
||||
_lastContainerPosition = _container.GlobalPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastContainerPosition = _container.GlobalPosition;
|
||||
_container.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStaticStorageChanged(bool obj)
|
||||
{
|
||||
if (StaticStorageUIEnabled == obj)
|
||||
return;
|
||||
|
||||
StaticStorageUIEnabled = obj;
|
||||
_lastContainerPosition = null;
|
||||
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
if (!_container.IsOpen)
|
||||
return;
|
||||
|
||||
_container.Orphan();
|
||||
if (StaticStorageUIEnabled)
|
||||
{
|
||||
Hotbar?.StorageContainer.AddChild(_container);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ui.WindowRoot.AddChild(_container);
|
||||
}
|
||||
|
||||
if (_entity.TryGetComponent<StorageComponent>(_container.StorageEntity, out var comp))
|
||||
OnStorageOrderChanged((_container.StorageEntity.Value, comp));
|
||||
}
|
||||
|
||||
private void OnOpaqueWindowChanged(bool obj)
|
||||
{
|
||||
if (OpaqueStorageWindow == obj)
|
||||
return;
|
||||
OpaqueStorageWindow = obj;
|
||||
_container?.BuildBackground();
|
||||
}
|
||||
|
||||
/// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
|
||||
@@ -190,7 +179,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
binding.Mod3 == Keyboard.Key.Control))
|
||||
return;
|
||||
|
||||
if (!IsDragging && _entity.System<HandsSystem>().GetActiveHandEntity() == null)
|
||||
if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
|
||||
return;
|
||||
|
||||
//clamp it to a cardinal.
|
||||
@@ -198,43 +187,18 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
if (DraggingGhost != null)
|
||||
DraggingGhost.Location.Rotation = DraggingRotation;
|
||||
|
||||
if (IsDragging || (_container != null && UIManager.CurrentlyHovered == _container))
|
||||
if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
|
||||
keyEvent.Handle();
|
||||
}
|
||||
|
||||
private void OnStorageUpdated(Entity<StorageComponent> uid)
|
||||
private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
|
||||
{
|
||||
if (_container?.StorageEntity != uid)
|
||||
return;
|
||||
|
||||
_container.BuildItemPieces();
|
||||
}
|
||||
|
||||
public void RegisterStorageContainer(StorageContainer container)
|
||||
{
|
||||
if (_container != null)
|
||||
{
|
||||
container.OnPiecePressed -= OnPiecePressed;
|
||||
container.OnPieceUnpressed -= OnPieceUnpressed;
|
||||
}
|
||||
|
||||
_container = container;
|
||||
container.OnPiecePressed += OnPiecePressed;
|
||||
container.OnPieceUnpressed += OnPieceUnpressed;
|
||||
|
||||
if (!StaticStorageUIEnabled)
|
||||
_container.Orphan();
|
||||
}
|
||||
|
||||
private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
|
||||
{
|
||||
if (IsDragging || !_container?.IsOpen == true)
|
||||
if (IsDragging || !window.IsOpen)
|
||||
return;
|
||||
|
||||
if (args.Function == ContentKeyFunctions.MoveStoredItem)
|
||||
{
|
||||
DraggingRotation = control.Location.Rotation;
|
||||
|
||||
_menuDragHelper.MouseDown(control);
|
||||
_menuDragHelper.Update(0f);
|
||||
|
||||
@@ -242,17 +206,17 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.SaveItemLocation)
|
||||
{
|
||||
if (_container?.StorageEntity is not {} storage)
|
||||
if (window.StorageEntity is not {} storage)
|
||||
return;
|
||||
|
||||
_entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
|
||||
_entity.GetNetEntity(control.Entity),
|
||||
_entity.GetNetEntity(storage)));
|
||||
EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
|
||||
EntityManager.GetNetEntity(control.Entity),
|
||||
EntityManager.GetNetEntity(storage)));
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
_entity.System<ExamineSystem>().DoExamine(control.Entity);
|
||||
EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
||||
@@ -262,62 +226,102 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
||||
{
|
||||
_entity.RaisePredictiveEvent(
|
||||
new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: false));
|
||||
EntityManager.RaisePredictiveEvent(
|
||||
new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||
{
|
||||
_entity.RaisePredictiveEvent(new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: true));
|
||||
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: true));
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
window.FlagDirty();
|
||||
}
|
||||
|
||||
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
|
||||
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
|
||||
{
|
||||
if (args.Function != ContentKeyFunctions.MoveStoredItem)
|
||||
return;
|
||||
|
||||
if (_container?.StorageEntity is not { } storageEnt|| !_entity.TryGetComponent<StorageComponent>(storageEnt, out var storageComp))
|
||||
return;
|
||||
// Want to get the control under the dragged control.
|
||||
// This means we can drag the original control around (and not hide the original).
|
||||
control.MouseFilter = Control.MouseFilterMode.Ignore;
|
||||
var targetControl = UIManager.MouseGetControl(args.PointerLocation);
|
||||
var targetStorage = targetControl as StorageWindow;
|
||||
control.MouseFilter = Control.MouseFilterMode.Pass;
|
||||
|
||||
if (DraggingGhost is { } draggingGhost)
|
||||
var localPlayer = _player.LocalEntity;
|
||||
window.RemoveGrid(control);
|
||||
window.FlagDirty();
|
||||
|
||||
// If we tried to drag it on top of another grid piece then cancel out.
|
||||
if (targetControl is ItemGridPiece || window.StorageEntity is not { } sourceStorage || localPlayer == null)
|
||||
{
|
||||
window.Reclaim(control.Location, control);
|
||||
args.Handle();
|
||||
_menuDragHelper.EndDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_menuDragHelper.IsDragging && DraggingGhost is { } draggingGhost)
|
||||
{
|
||||
var dragEnt = draggingGhost.Entity;
|
||||
var dragLoc = draggingGhost.Location;
|
||||
var itemSys = _entity.System<SharedItemSystem>();
|
||||
|
||||
var position = _container.GetMouseGridPieceLocation(dragEnt, dragLoc);
|
||||
var itemBounding = itemSys.GetAdjustedItemShape(dragEnt, dragLoc).GetBoundingBox();
|
||||
var gridBounding = storageComp.Grid.GetBoundingBox();
|
||||
|
||||
// The extended bounding box for if this is out of the window is the grid bounding box dimensions combined
|
||||
// with the item shape bounding box dimensions. Plus 1 on the left for the sidebar. This makes it so that.
|
||||
// dropping an item on the floor requires dragging it all the way out of the window.
|
||||
var left = gridBounding.Left - itemBounding.Width - 1;
|
||||
var bottom = gridBounding.Bottom - itemBounding.Height;
|
||||
var top = gridBounding.Top;
|
||||
var right = gridBounding.Right;
|
||||
var lenientBounding = new Box2i(left, bottom, right, top);
|
||||
|
||||
if (lenientBounding.Contains(position))
|
||||
// Dragging in the same storage
|
||||
// The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
|
||||
if (targetStorage == window)
|
||||
{
|
||||
_entity.RaisePredictiveEvent(new StorageSetItemLocationEvent(
|
||||
_entity.GetNetEntity(draggingGhost.Entity),
|
||||
_entity.GetNetEntity(storageEnt),
|
||||
new ItemStorageLocation(DraggingRotation, position)));
|
||||
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
|
||||
var newLocation = new ItemStorageLocation(DraggingRotation, position);
|
||||
|
||||
EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
|
||||
EntityManager.GetNetEntity(draggingGhost.Entity),
|
||||
EntityManager.GetNetEntity(sourceStorage),
|
||||
newLocation));
|
||||
|
||||
window.Reclaim(newLocation, control);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
targetStorage?.FlagDirty();
|
||||
}
|
||||
// If we just clicked, then take it out of the bag.
|
||||
else
|
||||
{
|
||||
EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
|
||||
EntityManager.GetNetEntity(control.Entity),
|
||||
EntityManager.GetNetEntity(sourceStorage)));
|
||||
}
|
||||
|
||||
_menuDragHelper.EndDrag();
|
||||
_container?.BuildItemPieces();
|
||||
}
|
||||
else //if we just clicked, then take it out of the bag.
|
||||
{
|
||||
_menuDragHelper.EndDrag();
|
||||
_entity.RaisePredictiveEvent(new StorageInteractWithItemEvent(
|
||||
_entity.GetNetEntity(control.Entity),
|
||||
_entity.GetNetEntity(storageEnt)));
|
||||
}
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
@@ -326,14 +330,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
if (_menuDragHelper.Dragged is not { } dragged)
|
||||
return false;
|
||||
|
||||
DraggingGhost!.Orphan();
|
||||
DraggingRotation = dragged.Location.Rotation;
|
||||
DraggingGhost = new ItemGridPiece(
|
||||
(dragged.Entity, _entity.GetComponent<ItemComponent>(dragged.Entity)),
|
||||
dragged.Location,
|
||||
_entity);
|
||||
DraggingGhost.MouseFilter = Control.MouseFilterMode.Ignore;
|
||||
DraggingGhost.Visible = true;
|
||||
DraggingGhost.Orphan();
|
||||
|
||||
UIManager.PopupRoot.AddChild(DraggingGhost);
|
||||
SetDraggingRotation();
|
||||
@@ -344,6 +342,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
{
|
||||
if (DraggingGhost == null)
|
||||
return false;
|
||||
|
||||
SetDraggingRotation();
|
||||
return true;
|
||||
}
|
||||
@@ -356,7 +355,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
var offset = ItemGridPiece.GetCenterOffset(
|
||||
(DraggingGhost.Entity, null),
|
||||
new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
|
||||
_entity);
|
||||
EntityManager);
|
||||
|
||||
// I don't know why it divides the position by 2. Hope this helps! -emo
|
||||
LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
|
||||
@@ -366,18 +365,13 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
{
|
||||
if (DraggingGhost == null)
|
||||
return;
|
||||
DraggingGhost.Visible = false;
|
||||
DraggingGhost = null;
|
||||
|
||||
DraggingRotation = Angle.Zero;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
_menuDragHelper.Update(args.DeltaSeconds);
|
||||
|
||||
if (!StaticStorageUIEnabled && _container?.Parent != null && _lastContainerPosition != null)
|
||||
_lastContainerPosition = _container.GlobalPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Viewport;
|
||||
@@ -15,6 +17,7 @@ public sealed class ViewportUIController : UIController
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = 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 const int ViewportHeight = 15;
|
||||
private MainViewport? Viewport => UIManager.ActiveScreen?.GetWidget<MainViewport>();
|
||||
@@ -93,8 +96,11 @@ public sealed class ViewportUIController : UIController
|
||||
_entMan.TryGetComponent(ent, out EyeComponent? eye);
|
||||
|
||||
if (eye?.Eye == _eyeManager.CurrentEye
|
||||
&& _entMan.GetComponent<TransformComponent>(ent.Value).WorldPosition == default)
|
||||
return; // nothing to worry about, the player is just in null space... actually that is probably a problem?
|
||||
&& _entMan.GetComponent<TransformComponent>(ent.Value).MapID == MapId.Nullspace)
|
||||
{
|
||||
// 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
|
||||
// does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings:
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Content.Client.Viewport
|
||||
_inputManager.ViewportKeyEvent(this, args);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
protected override void Draw(IRenderHandle handle)
|
||||
{
|
||||
EnsureViewportCreated();
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Content.Client.Viewport
|
||||
var drawBox = GetDrawBox();
|
||||
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
|
||||
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
|
||||
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using Content.Client.Ghost;
|
||||
using Content.Shared.Voting;
|
||||
|
||||
namespace Content.Client.Voting;
|
||||
|
||||
public sealed class VotingSystem : EntitySystem
|
||||
{
|
||||
|
||||
public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions
|
||||
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -138,6 +138,7 @@ public sealed partial class MeleeWeaponSystem
|
||||
const float length = 0.15f;
|
||||
var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
|
||||
var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance));
|
||||
sprite.Rotation += spriteRotation;
|
||||
|
||||
return new Animation()
|
||||
{
|
||||
|
||||
@@ -170,7 +170,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
var targetCoordinates = xform.Coordinates;
|
||||
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)
|
||||
|
||||
49
Content.Client/Wieldable/WieldableSystem.cs
Normal file
49
Content.Client/Wieldable/WieldableSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Whitelist;
|
||||
@@ -104,51 +105,34 @@ public sealed class CargoTest
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var protoManager = server.ProtoMan;
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
|
||||
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>()
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.Where(p => !p.Components.ContainsKey("MapGrid")) // Grids are not for sale.
|
||||
.Select(p => p.ID)
|
||||
.Where(p => p.Components.ContainsKey("StaticPrice"))
|
||||
.ToList();
|
||||
|
||||
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)
|
||||
&& stackpricecomp.Price > 0)
|
||||
if (proto.TryGetComponent<StackPriceComponent>(out var stackPriceComp, compFact) && stackPriceComp.Price > 0)
|
||||
{
|
||||
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
|
||||
{
|
||||
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
|
||||
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),
|
||||
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();
|
||||
|
||||
41
Content.IntegrationTests/Tests/ContrabandTest.cs
Normal file
41
Content.IntegrationTests/Tests/ContrabandTest.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ namespace Content.IntegrationTests.Tests
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.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)
|
||||
.ToList();
|
||||
|
||||
@@ -101,6 +102,7 @@ namespace Content.IntegrationTests.Tests
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.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)
|
||||
.ToList();
|
||||
foreach (var protoId in protoIds)
|
||||
@@ -341,6 +343,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"DebugExceptionInitialize",
|
||||
"DebugExceptionStartup",
|
||||
"GridFill",
|
||||
"RoomFill",
|
||||
"Map", // We aren't testing a map entity in this test
|
||||
"MapGrid",
|
||||
"Broadphase",
|
||||
|
||||
@@ -26,6 +26,7 @@ public sealed class LatheTest
|
||||
var compFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var materialStorageSystem = server.System<SharedMaterialStorageSystem>();
|
||||
var whitelistSystem = server.System<EntityWhitelistSystem>();
|
||||
var latheSystem = server.System<SharedLatheSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -74,14 +75,14 @@ public sealed class LatheTest
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the recipes assigned to this lathe
|
||||
var recipes = new List<ProtoId<LatheRecipePrototype>>();
|
||||
recipes.AddRange(latheComp.StaticRecipes);
|
||||
recipes.AddRange(latheComp.DynamicRecipes);
|
||||
// Collect all possible recipes assigned to this lathe
|
||||
var recipes = new HashSet<ProtoId<LatheRecipePrototype>>();
|
||||
latheSystem.AddRecipesFromPacks(recipes, latheComp.StaticPacks);
|
||||
latheSystem.AddRecipesFromPacks(recipes, latheComp.DynamicPacks);
|
||||
if (latheProto.TryGetComponent<EmagLatheRecipesComponent>(out var emagRecipesComp, compFactory))
|
||||
{
|
||||
recipes.AddRange(emagRecipesComp.EmagStaticRecipes);
|
||||
recipes.AddRange(emagRecipesComp.EmagDynamicRecipes);
|
||||
latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagStaticPacks);
|
||||
latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagDynamicPacks);
|
||||
}
|
||||
|
||||
// Check each recipe assigned to this lathe
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
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(StringArrayValid), 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(EntProtoIdTArrayValid), 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(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(StringArrayInvalid), protos), Has.Count.EqualTo(2));
|
||||
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(EntProtoIdTArrayInvalid), protos), Has.Count.EqualTo(2));
|
||||
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(ProtoIdListInvalid), protos), Has.Count.EqualTo(2));
|
||||
@@ -88,24 +93,48 @@ public sealed class StaticFieldValidationTest
|
||||
public static EntProtoId Tag = "StaticFieldTestEnt";
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTValid
|
||||
{
|
||||
public static EntProtoId<TransformComponent> Tag = "StaticFieldTestEnt";
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdInvalid
|
||||
{
|
||||
public static EntProtoId Tag = string.Empty;
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTInvalid
|
||||
{
|
||||
public static EntProtoId<TransformComponent> Tag = string.Empty;
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdArrayValid
|
||||
{
|
||||
public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdTArrayValid
|
||||
{
|
||||
public static EntProtoId<TransformComponent>[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class EntProtoIdArrayInvalid
|
||||
{
|
||||
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)]
|
||||
private sealed class ProtoIdTestValid
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user