Merge remote-tracking branch 'upstream/stable' into ed-30-04-2025-upstream-sync
# Conflicts: # Content.Client/Parallax/ParallaxControl.cs # Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs # Content.IntegrationTests/Tests/PostMapInitTest.cs # Content.Server/Chat/Managers/ChatManager.cs # Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs # Content.Server/Labels/Label/LabelSystem.cs # Content.Shared/Actions/SharedActionsSystem.cs # Content.Shared/Fluids/Components/EvaporationComponent.cs # Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs # README.md # Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml # Resources/Prototypes/Maps/Pools/deathmatch.yml # Resources/Prototypes/Maps/arenas.yml
This commit is contained in:
@@ -127,6 +127,7 @@ csharp_indent_braces = false
|
||||
#csharp_indent_case_contents_when_block = true
|
||||
#csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
xmldoc_indent_text = zeroindent
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
|
||||
2
.github/workflows/close-master-pr.yml
vendored
2
.github/workflows/close-master-pr.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
|
||||
comment: "Thank you for your contribution! It appears you created a PR from your master branch, this is [something you should avoid doing](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/), and thus this PR has been automatically closed. \n \n We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html). \n \n You can move your current work from the master branch to another branch by following [these commands](https://ohshitgit.com/#accidental-commit-master). And then you may recreate your PR using the new branch."
|
||||
|
||||
# If you prefer to just comment on the pr and not close it, uncomment the bellow and comment the above
|
||||
|
||||
|
||||
44
.vscode/tasks.json
vendored
44
.vscode/tasks.json
vendored
@@ -32,6 +32,50 @@
|
||||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"test",
|
||||
"--no-build",
|
||||
"--configuration",
|
||||
"DebugOpt",
|
||||
"Content.Tests/Content.Tests.csproj",
|
||||
"--",
|
||||
"NUnit.ConsoleOut=0"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "integration-test",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"test",
|
||||
"--no-build",
|
||||
"--configuration",
|
||||
"DebugOpt",
|
||||
"Content.IntegrationTests/Content.IntegrationTests.csproj",
|
||||
"--",
|
||||
"NUnit.ConsoleOut=0",
|
||||
"NUnit.MapWarningTo=Failed.ConsoleOut=0",
|
||||
"NUnit.MapWarningTo=Failed"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
|
||||
@@ -174,7 +174,8 @@ namespace Content.Client.Access.UI
|
||||
new List<ProtoId<AccessLevelPrototype>>());
|
||||
|
||||
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
||||
// If the job index is < 0 that means they don't have a job registered in the station records.
|
||||
// If the job index is < 0 that means they don't have a job registered in the station records
|
||||
// or the IdCardComponent's JobPrototype field.
|
||||
// For example, a new ID from a box would have no job index.
|
||||
if (jobIndex < 0)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -22,6 +23,7 @@ namespace Content.Client.Actions
|
||||
{
|
||||
public delegate void OnActionReplaced(EntityUid actionId);
|
||||
|
||||
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
@@ -51,29 +53,6 @@ namespace Content.Client.Actions
|
||||
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
var worldActionQuery = EntityQueryEnumerator<WorldTargetActionComponent>();
|
||||
while (worldActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
|
||||
var instantActionQuery = EntityQueryEnumerator<InstantActionComponent>();
|
||||
while (instantActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
|
||||
var entityActionQuery = EntityQueryEnumerator<EntityTargetActionComponent>();
|
||||
while (entityActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InstantActionComponentState state)
|
||||
@@ -127,9 +106,6 @@ namespace Content.Client.Actions
|
||||
component.Toggled = state.Toggled;
|
||||
component.Cooldown = state.Cooldown;
|
||||
component.UseDelay = state.UseDelay;
|
||||
component.Charges = state.Charges;
|
||||
component.MaxCharges = state.MaxCharges;
|
||||
component.RenewCharges = state.RenewCharges;
|
||||
component.Container = EnsureEntity<T>(state.Container, uid);
|
||||
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
@@ -152,7 +128,8 @@ namespace Content.Client.Actions
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return;
|
||||
|
||||
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
// TODO: Decouple this.
|
||||
action.IconColor = _sharedCharges.GetCurrentCharges(actionId.Value) == 0 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
|
||||
base.UpdateAction(actionId, action);
|
||||
if (_playerManager.LocalEntity != action.AttachedEntity)
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -22,10 +23,11 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly SharedRoleSystem _roles;
|
||||
private readonly Font _font;
|
||||
private readonly Font _fontBold;
|
||||
private bool _overlayClassic;
|
||||
private bool _overlaySymbols;
|
||||
private AdminOverlayAntagFormat _overlayFormat;
|
||||
private AdminOverlayAntagSymbolStyle _overlaySymbolStyle;
|
||||
private bool _overlayPlaytime;
|
||||
private bool _overlayStartingJob;
|
||||
private float _ghostFadeDistance;
|
||||
@@ -33,9 +35,10 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private int _overlayStackMax;
|
||||
private float _overlayMergeDistance;
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
//TODO make this adjustable via GUI?
|
||||
private readonly ProtoId<RoleTypePrototype>[] _filter =
|
||||
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
|
||||
|
||||
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
|
||||
|
||||
public AdminNameOverlay(
|
||||
@@ -45,20 +48,22 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
IResourceCache resourceCache,
|
||||
EntityLookupSystem entityLookup,
|
||||
IUserInterfaceManager userInterfaceManager,
|
||||
IConfigurationManager config)
|
||||
IConfigurationManager config,
|
||||
SharedRoleSystem roles)
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
_roles = roles;
|
||||
ZIndex = 200;
|
||||
// Setting these to a specific ttf would break the antag symbols
|
||||
_font = resourceCache.NotoStack();
|
||||
_fontBold = resourceCache.NotoStack(variation: "Bold");
|
||||
|
||||
config.OnValueChanged(CCVars.AdminOverlayClassic, (show) => { _overlayClassic = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlaySymbols, (show) => { _overlaySymbols = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayAntagFormat, (show) => { _overlayFormat = UpdateOverlayFormat(show); }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlaySymbolStyle, (show) => { _overlaySymbolStyle = UpdateOverlaySymbolStyle(show); }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true);
|
||||
@@ -67,6 +72,22 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true);
|
||||
}
|
||||
|
||||
private AdminOverlayAntagFormat UpdateOverlayFormat(string formatString)
|
||||
{
|
||||
if (!Enum.TryParse<AdminOverlayAntagFormat>(formatString, out var format))
|
||||
format = AdminOverlayAntagFormat.Binary;
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
private AdminOverlayAntagSymbolStyle UpdateOverlaySymbolStyle(string symbolString)
|
||||
{
|
||||
if (!Enum.TryParse<AdminOverlayAntagSymbolStyle>(symbolString, out var symbolStyle))
|
||||
symbolStyle = AdminOverlayAntagSymbolStyle.Off;
|
||||
|
||||
return symbolStyle;
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -183,34 +204,56 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// Classic Antag Label
|
||||
if (_overlayClassic && playerInfo.Antag)
|
||||
// Determine antag symbol
|
||||
string? symbol;
|
||||
switch (_overlaySymbolStyle)
|
||||
{
|
||||
var symbol = _overlaySymbols ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
var label = _overlaySymbols
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol",
|
||||
("symbol", symbol),
|
||||
("name", _antagLabelClassic))
|
||||
: _antagLabelClassic;
|
||||
color = Color.OrangeRed;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
case AdminOverlayAntagSymbolStyle.Specific:
|
||||
symbol = playerInfo.RoleProto.Symbol;
|
||||
break;
|
||||
case AdminOverlayAntagSymbolStyle.Basic:
|
||||
symbol = Loc.GetString("player-tab-antag-prefix");
|
||||
break;
|
||||
default:
|
||||
case AdminOverlayAntagSymbolStyle.Off:
|
||||
symbol = string.Empty;
|
||||
break;
|
||||
}
|
||||
// Role Type
|
||||
else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto))
|
||||
|
||||
// Determine antag/role type name
|
||||
string? text;
|
||||
switch (_overlayFormat)
|
||||
{
|
||||
var symbol = _overlaySymbols && playerInfo.Antag ? playerInfo.RoleProto.Symbol : string.Empty;
|
||||
var role = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var label = _overlaySymbols
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", role))
|
||||
: role;
|
||||
color = playerInfo.RoleProto.Color;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
case AdminOverlayAntagFormat.Roletype:
|
||||
color = playerInfo.RoleProto.Color;
|
||||
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||
text = _filter.Contains(playerInfo.RoleProto)
|
||||
? Loc.GetString(playerInfo.RoleProto.Name).ToUpper()
|
||||
: string.Empty;
|
||||
break;
|
||||
case AdminOverlayAntagFormat.Subtype:
|
||||
color = playerInfo.RoleProto.Color;
|
||||
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||
text = _filter.Contains(playerInfo.RoleProto)
|
||||
? _roles.GetRoleSubtypeLabel(playerInfo.RoleProto.Name, playerInfo.Subtype).ToUpper()
|
||||
: string.Empty;
|
||||
break;
|
||||
default:
|
||||
case AdminOverlayAntagFormat.Binary:
|
||||
color = Color.OrangeRed;
|
||||
symbol = playerInfo.Antag ? symbol : string.Empty;
|
||||
text = playerInfo.Antag ? _antagLabelClassic : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
// Draw antag label
|
||||
color.A = alpha;
|
||||
var label = !string.IsNullOrEmpty(symbol)
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", text))
|
||||
: text;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
//Save the coordinates and size of the text block, for stack merge check
|
||||
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
|
||||
}
|
||||
|
||||
15
Content.Client/Administration/OverlayOptions.cs
Normal file
15
Content.Client/Administration/OverlayOptions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
public enum AdminOverlayAntagFormat
|
||||
{
|
||||
Binary,
|
||||
Roletype,
|
||||
Subtype
|
||||
}
|
||||
|
||||
public enum AdminOverlayAntagSymbolStyle
|
||||
{
|
||||
Off,
|
||||
Basic,
|
||||
Specific
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -15,6 +16,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -30,7 +32,8 @@ namespace Content.Client.Administration.Systems
|
||||
_resourceCache,
|
||||
_entityLookup,
|
||||
_userInterfaceManager,
|
||||
_configurationManager);
|
||||
_configurationManager,
|
||||
_roles);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
@@ -46,7 +49,8 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
public void AdminOverlayOn()
|
||||
{
|
||||
if (_overlayManager.HasOverlay<AdminNameOverlay>()) return;
|
||||
if (_overlayManager.HasOverlay<AdminNameOverlay>())
|
||||
return;
|
||||
_overlayManager.AddOverlay(_adminNameOverlay);
|
||||
OverlayEnabled?.Invoke();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Content.Client.Administration.Systems
|
||||
var verb = new VvVerb()
|
||||
{
|
||||
Text = Loc.GetString("view-variables"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Act = () => _clientConsoleHost.ExecuteCommand($"vv {GetNetEntity(args.Target)}"),
|
||||
ClientExclusive = true // opening VV window is client-side. Don't ask server to run this verb.
|
||||
};
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Threading;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Administration.UI;
|
||||
|
||||
public static class AdminUIHelpers
|
||||
{
|
||||
private static void ResetButton(Button button, ConfirmationData data)
|
||||
{
|
||||
data.Cancellation.Cancel();
|
||||
button.ModulateSelfOverride = null;
|
||||
button.Text = data.OriginalText;
|
||||
}
|
||||
|
||||
public static bool RemoveConfirm(Button button, Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
if (confirmations.Remove(button, out var data))
|
||||
{
|
||||
ResetButton(button, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RemoveAllConfirms(Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
foreach (var (button, confirmation) in confirmations)
|
||||
{
|
||||
ResetButton(button, confirmation);
|
||||
}
|
||||
|
||||
confirmations.Clear();
|
||||
}
|
||||
|
||||
public static bool TryConfirm(Button button, Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
if (RemoveConfirm(button, confirmations))
|
||||
return true;
|
||||
|
||||
var data = new ConfirmationData(new CancellationTokenSource(), button.Text);
|
||||
confirmations[button] = data;
|
||||
|
||||
Timer.Spawn(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
confirmations.Remove(button);
|
||||
button.ModulateSelfOverride = null;
|
||||
button.Text = data.OriginalText;
|
||||
}, data.Cancellation.Token);
|
||||
|
||||
button.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault;
|
||||
button.Text = Loc.GetString("admin-player-actions-confirm");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ConfirmationData(CancellationTokenSource Cancellation, string? OriginalText);
|
||||
@@ -1,6 +1,7 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<SplitContainer Orientation="Vertical" ResizeMode="NotResizable">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
@@ -18,9 +19,9 @@
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
public AdminAHelpUIHandler AHelpHelper = default!;
|
||||
|
||||
private PlayerInfo? _currentPlayer;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public BwoinkControl()
|
||||
{
|
||||
@@ -178,11 +177,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Kick.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(Kick, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Reason field
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"kick \"{_currentPlayer.Username}\"");
|
||||
@@ -196,11 +190,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Respawn.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(Respawn, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"respawn \"{_currentPlayer.Username}\"");
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Popup xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#18181B" BackgroundColor="#25252a"/>
|
||||
@@ -19,7 +20,7 @@
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EditButton" Text="{Loc admin-notes-edit}"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Button Name="DeleteButton" Text="{Loc admin-notes-delete}" HorizontalAlignment="Right"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc admin-notes-delete}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
<Label Name="SharedConnections"/>
|
||||
|
||||
<BoxContainer Align="Center">
|
||||
<GridContainer Rows="5">
|
||||
<GridContainer Rows="6">
|
||||
<Button Name="NotesButton" Text="{Loc player-panel-show-notes}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="AhelpButton" Text="{Loc player-panel-help}" Disabled="True"/>
|
||||
<Button Name="FreezeButton" Text = "{Loc player-panel-freeze}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="KickButton" Text="{Loc player-panel-kick}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc player-panel-delete}" Disabled="True"/>
|
||||
<Button Name="FollowButton" Text="{Loc player-panel-follow}"/>
|
||||
<Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
|
||||
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Client.Administration.Managers;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -21,6 +20,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
public event Action<string?>? OnKick;
|
||||
public event Action<NetUserId?>? OnOpenBanPanel;
|
||||
public event Action<NetUserId?, bool>? OnWhitelistToggle;
|
||||
public event Action? OnFollow;
|
||||
public event Action? OnFreezeAndMuteToggle;
|
||||
public event Action? OnFreeze;
|
||||
public event Action? OnLogs;
|
||||
@@ -36,7 +36,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
|
||||
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
|
||||
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
|
||||
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
|
||||
@@ -47,6 +47,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
|
||||
SetWhitelisted(!_isWhitelisted);
|
||||
};
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
|
||||
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
|
||||
LogsButton.OnPressed += _ => OnLogs?.Invoke();
|
||||
|
||||
@@ -38,6 +38,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
|
||||
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
|
||||
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
PlayerPanel.OnFollow += () => SendMessage(new PlayerPanelFollowMessage());
|
||||
|
||||
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<cc:UICommandButton Command="callshuttle" Text="{Loc admin-player-actions-window-shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
|
||||
<cc:CommandButton Command="adminlogs" Text="{Loc admin-player-actions-window-admin-logs}"/>
|
||||
<cc:CommandButton Command="faxui" Text="{Loc admin-player-actions-window-admin-fax}"/>
|
||||
<cc:CommandButton Command="achatwindow" Text="{Loc admin-player-actions-window-admin-chat}"/>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
@@ -10,9 +11,9 @@
|
||||
</BoxContainer>
|
||||
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
|
||||
<Button Name="SubmitAHelpButton" Text="{Loc admin-player-actions-ahelp}" Disabled="True"/>
|
||||
<Button Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
public sealed partial class PlayerActionsWindow : DefaultWindow
|
||||
{
|
||||
private PlayerInfo? _selectedPlayer;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public PlayerActionsWindow()
|
||||
{
|
||||
@@ -28,9 +27,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
|
||||
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
|
||||
{
|
||||
if (_selectedPlayer != obj)
|
||||
AdminUIHelpers.RemoveAllConfirms(_confirmations);
|
||||
|
||||
_selectedPlayer = obj;
|
||||
var disableButtons = _selectedPlayer == null;
|
||||
SubmitKickButton.Disabled = disableButtons;
|
||||
@@ -43,9 +39,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
if (_selectedPlayer == null)
|
||||
return;
|
||||
|
||||
if (!AdminUIHelpers.TryConfirm(SubmitKickButton, _confirmations))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"kick \"{_selectedPlayer.Username}\" \"{CommandParsing.Escape(ReasonLine.Text)}\"");
|
||||
}
|
||||
@@ -64,9 +57,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
if (_selectedPlayer == null)
|
||||
return;
|
||||
|
||||
if (!AdminUIHelpers.TryConfirm(SubmitRespawnButton, _confirmations))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"respawn \"{_selectedPlayer.Username}\"");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
|
||||
public Action<NetEntity>? OnTeleport;
|
||||
public Action<NetEntity>? OnDelete;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
@@ -28,13 +27,6 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
DeleteButton.Disabled = !manager.CanCommand("delete");
|
||||
|
||||
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
|
||||
DeleteButton.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
OnDelete?.Invoke(nent);
|
||||
};
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke(nent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ public sealed partial class PlayerTab : Control
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
|
||||
private AdminPlayerTabColorOption _playerTabColorSetting;
|
||||
private AdminPlayerTabRoleTypeOption _playerTabRoleSetting;
|
||||
private AdminPlayerTabSymbolOption _playerTabSymbolSetting;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
public PlayerTab()
|
||||
@@ -44,9 +48,10 @@ public sealed partial class PlayerTab : Control
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistSeparateSymbols, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistRoleTypeColor, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabRoleSetting, RoleSettingChanged, true);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true);
|
||||
|
||||
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
@@ -113,8 +118,27 @@ public sealed partial class PlayerTab : Control
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void PlayerListSettingsChanged(bool _)
|
||||
private void RoleSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabRoleTypeOption>(s, out var format))
|
||||
format = AdminPlayerTabRoleTypeOption.Subtype;
|
||||
_playerTabRoleSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void ColorSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabColorOption>(s, out var format))
|
||||
format = AdminPlayerTabColorOption.Both;
|
||||
_playerTabColorSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void SymbolSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabSymbolOption>(s, out var format))
|
||||
format = AdminPlayerTabSymbolOption.Specific;
|
||||
_playerTabSymbolSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
@@ -140,7 +164,12 @@ public sealed partial class PlayerTab : Control
|
||||
if (data is not PlayerListData { Info: var player})
|
||||
return;
|
||||
|
||||
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
|
||||
var entry = new PlayerTabEntry(
|
||||
player,
|
||||
new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor),
|
||||
_playerTabColorSetting,
|
||||
_playerTabRoleSetting,
|
||||
_playerTabSymbolSetting);
|
||||
button.AddChild(entry);
|
||||
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
|
||||
}
|
||||
|
||||
@@ -1,42 +1,99 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity? PlayerEntity;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
|
||||
public PlayerTabEntry(
|
||||
PlayerInfo player,
|
||||
StyleBoxFlat styleBoxFlat,
|
||||
AdminPlayerTabColorOption colorOption,
|
||||
AdminPlayerTabRoleTypeOption roleSetting,
|
||||
AdminPlayerTabSymbolOption symbolSetting)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
var config = IoCManager.Resolve<IConfigurationManager>();
|
||||
var roles = _entMan.System<SharedRoleSystem>();
|
||||
|
||||
UsernameLabel.Text = player.Username;
|
||||
if (!player.Connected)
|
||||
UsernameLabel.StyleClasses.Add("Disabled");
|
||||
JobLabel.Text = player.StartingJob;
|
||||
var separateAntagSymbols = config.GetCVar(CCVars.AdminPlayerlistSeparateSymbols);
|
||||
var genericAntagSymbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
var roleSymbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
|
||||
var symbol = separateAntagSymbols ? roleSymbol : genericAntagSymbol;
|
||||
|
||||
var colorAntags = false;
|
||||
var colorRoles = false;
|
||||
switch (colorOption)
|
||||
{
|
||||
case AdminPlayerTabColorOption.Off:
|
||||
break;
|
||||
case AdminPlayerTabColorOption.Character:
|
||||
colorAntags = true;
|
||||
break;
|
||||
case AdminPlayerTabColorOption.Roletype:
|
||||
colorRoles = true;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabColorOption.Both:
|
||||
colorAntags = true;
|
||||
colorRoles = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var symbol = string.Empty;
|
||||
switch (symbolSetting)
|
||||
{
|
||||
case AdminPlayerTabSymbolOption.Off:
|
||||
break;
|
||||
case AdminPlayerTabSymbolOption.Basic:
|
||||
symbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabSymbolOption.Specific:
|
||||
symbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
|
||||
|
||||
if (player.Antag && config.GetCVar(CCVars.AdminPlayerlistHighlightedCharacterColor))
|
||||
if (player.Antag && colorAntags)
|
||||
CharacterLabel.FontColorOverride = player.RoleProto.Color;
|
||||
if (player.IdentityName != player.CharacterName)
|
||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
if (config.GetCVar(CCVars.AdminPlayerlistRoleTypeColor))
|
||||
|
||||
var roletype = RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
var subtype = roles.GetRoleSubtypeLabel(player.RoleProto.Name, player.Subtype);
|
||||
switch (roleSetting)
|
||||
{
|
||||
case AdminPlayerTabRoleTypeOption.RoleTypeSubtype:
|
||||
RoleTypeLabel.Text = roletype != subtype
|
||||
? roletype + " - " +subtype
|
||||
: roletype;
|
||||
break;
|
||||
case AdminPlayerTabRoleTypeOption.SubtypeRoleType:
|
||||
RoleTypeLabel.Text = roletype != subtype
|
||||
? subtype + " - " + roletype
|
||||
: roletype;
|
||||
break;
|
||||
case AdminPlayerTabRoleTypeOption.RoleType:
|
||||
RoleTypeLabel.Text = roletype;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabRoleTypeOption.Subtype:
|
||||
RoleTypeLabel.Text = subtype;
|
||||
break;
|
||||
}
|
||||
|
||||
if (colorRoles)
|
||||
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||
PlayerEntity = player.NetEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
public enum AdminPlayerTabColorOption
|
||||
{
|
||||
Off,
|
||||
Character,
|
||||
Roletype,
|
||||
Both
|
||||
}
|
||||
|
||||
public enum AdminPlayerTabRoleTypeOption
|
||||
{
|
||||
RoleType,
|
||||
Subtype,
|
||||
RoleTypeSubtype,
|
||||
SubtypeRoleType
|
||||
}
|
||||
|
||||
public enum AdminPlayerTabSymbolOption
|
||||
{
|
||||
Off,
|
||||
Basic,
|
||||
Specific
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.Piping.Binary.Systems;
|
||||
|
||||
public sealed class GasVolumePumpSystem : SharedGasVolumePumpSystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, AfterAutoHandleStateEvent>(OnPumpState);
|
||||
}
|
||||
|
||||
protected override void UpdateUi(Entity<GasVolumePumpComponent> entity)
|
||||
{
|
||||
if (_ui.TryGetOpenUi(entity.Owner, GasVolumePumpUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPumpState(Entity<GasVolumePumpComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUi(ent);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Localizations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
@@ -14,7 +13,7 @@ namespace Content.Client.Atmos.UI
|
||||
public sealed class GasVolumePumpBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private const float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
private float _maxTransferRate;
|
||||
|
||||
[ViewVariables]
|
||||
private GasVolumePumpWindow? _window;
|
||||
@@ -29,38 +28,41 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
_window = this.CreateWindow<GasVolumePumpWindow>();
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out GasVolumePumpComponent? pump))
|
||||
{
|
||||
_maxTransferRate = pump.MaxTransferRate;
|
||||
}
|
||||
|
||||
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
|
||||
_window.PumpTransferRateChanged += OnPumpTransferRatePressed;
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
if (_window is null) return;
|
||||
SendMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
|
||||
|
||||
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
|
||||
}
|
||||
|
||||
private void OnPumpTransferRatePressed(string value)
|
||||
{
|
||||
var rate = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
|
||||
if (rate > MaxTransferRate)
|
||||
rate = MaxTransferRate;
|
||||
rate = Math.Clamp(rate, 0f, _maxTransferRate);
|
||||
|
||||
SendMessage(new GasVolumePumpChangeTransferRateMessage(rate));
|
||||
SendPredictedMessage(new GasVolumePumpChangeTransferRateMessage(rate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI state based on server-sent info
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public override void Update()
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not GasVolumePumpBoundUserInterfaceState cast)
|
||||
base.Update();
|
||||
|
||||
if (_window is null || !EntMan.TryGetComponent(Owner, out GasVolumePumpComponent? pump))
|
||||
return;
|
||||
|
||||
_window.Title = cast.PumpLabel;
|
||||
_window.SetPumpStatus(cast.Enabled);
|
||||
_window.SetTransferRate(cast.TransferRate);
|
||||
_window.Title = Identity.Name(Owner, EntMan);
|
||||
_window.SetPumpStatus(pump.Enabled);
|
||||
_window.SetTransferRate(pump.TransferRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="200 120" Title="Volume Pump">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
@@ -19,4 +20,4 @@
|
||||
<Button Name="SetTransferRateButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" HorizontalAlignment="Right" Disabled="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -16,7 +17,7 @@ namespace Content.Client.Atmos.UI
|
||||
/// Client-side UI used to control a gas volume pump.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasVolumePumpWindow : DefaultWindow
|
||||
public sealed partial class GasVolumePumpWindow : FancyWindow
|
||||
{
|
||||
public bool PumpStatus = true;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -14,6 +15,8 @@ namespace Content.Client.Cargo.BUI
|
||||
{
|
||||
public sealed class CargoOrderConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private readonly SharedCargoSystem _cargoSystem;
|
||||
|
||||
[ViewVariables]
|
||||
private CargoConsoleMenu? _menu;
|
||||
|
||||
@@ -43,6 +46,7 @@ namespace Content.Client.Cargo.BUI
|
||||
|
||||
public CargoOrderConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_cargoSystem = EntMan.System<SharedCargoSystem>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -57,7 +61,7 @@ namespace Content.Client.Cargo.BUI
|
||||
|
||||
string orderRequester;
|
||||
|
||||
if (EntMan.TryGetComponent<MetaDataComponent>(localPlayer, out var metadata))
|
||||
if (EntMan.EntityExists(localPlayer))
|
||||
orderRequester = Identity.Name(localPlayer.Value, EntMan);
|
||||
else
|
||||
orderRequester = string.Empty;
|
||||
@@ -96,41 +100,54 @@ namespace Content.Client.Cargo.BUI
|
||||
}
|
||||
};
|
||||
|
||||
_menu.OnAccountAction += (account, amount) =>
|
||||
{
|
||||
SendMessage(new CargoConsoleWithdrawFundsMessage(account, amount));
|
||||
};
|
||||
|
||||
_menu.OnToggleUnboundedLimit += _ =>
|
||||
{
|
||||
SendMessage(new CargoConsoleToggleLimitMessage());
|
||||
};
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
private void Populate(List<CargoOrderData> orders)
|
||||
{
|
||||
if (_menu == null) return;
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.PopulateProducts();
|
||||
_menu.PopulateCategories();
|
||||
_menu.PopulateOrders(orders);
|
||||
_menu.PopulateAccountActions();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not CargoConsoleInterfaceState cState)
|
||||
if (state is not CargoConsoleInterfaceState cState || !EntMan.TryGetComponent<CargoOrderConsoleComponent>(Owner, out var orderConsole))
|
||||
return;
|
||||
var station = EntMan.GetEntity(cState.Station);
|
||||
|
||||
OrderCapacity = cState.Capacity;
|
||||
OrderCount = cState.Count;
|
||||
BankBalance = cState.Balance;
|
||||
BankBalance = _cargoSystem.GetBalanceFromAccount(station, orderConsole.Account);
|
||||
|
||||
AccountName = cState.Name;
|
||||
|
||||
_menu?.UpdateStation(station);
|
||||
Populate(cState.Orders);
|
||||
_menu?.UpdateCargoCapacity(OrderCount, OrderCapacity);
|
||||
_menu?.UpdateBankData(AccountName, BankBalance);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing) return;
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
_orderMenu?.Dispose();
|
||||
@@ -170,8 +187,6 @@ namespace Content.Client.Cargo.BUI
|
||||
return;
|
||||
|
||||
SendMessage(new CargoConsoleApproveOrderMessage(row.Order.OrderId));
|
||||
// Most of the UI isn't predicted anyway so.
|
||||
// _menu?.UpdateCargoCapacity(OrderCount + row.Order.Amount, OrderCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Cargo.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class FundingAllocationConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private FundingAllocationMenu? _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<FundingAllocationMenu>();
|
||||
|
||||
_menu.OnSavePressed += (dicts, primary, lockbox) =>
|
||||
{
|
||||
SendMessage(new SetFundingAllocationBuiMessage(dicts, primary, lockbox));
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState message)
|
||||
{
|
||||
base.UpdateState(message);
|
||||
|
||||
if (message is not FundingAllocationConsoleBuiState state)
|
||||
return;
|
||||
|
||||
_menu?.Update(state);
|
||||
}
|
||||
}
|
||||
@@ -75,10 +75,9 @@ public sealed partial class CargoSystem
|
||||
switch (state)
|
||||
{
|
||||
case CargoTelepadState.Teleporting:
|
||||
if (_player.HasRunningAnimation(uid, TelepadBeamKey))
|
||||
return;
|
||||
_player.Stop(uid, player, TelepadIdleKey);
|
||||
_player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey);
|
||||
_player.Stop((uid, player), TelepadIdleKey);
|
||||
if (!_player.HasRunningAnimation(uid, TelepadBeamKey))
|
||||
_player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey);
|
||||
break;
|
||||
case CargoTelepadState.Unpowered:
|
||||
sprite.LayerSetVisible(CargoTelepadLayers.Beam, false);
|
||||
|
||||
@@ -3,66 +3,83 @@
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="600 600"
|
||||
MinSize="600 600">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 0 5 0">
|
||||
<BoxContainer Orientation="Vertical" Margin="15 5 15 10">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cargo-console-menu-account-name-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
<Label Name="AccountNameLabel"
|
||||
<RichTextLabel Name="AccountNameLabel"
|
||||
Text="{Loc 'cargo-console-menu-account-name-none-text'}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cargo-console-menu-points-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
<Label Name="PointsLabel"
|
||||
<RichTextLabel Name="PointsLabel"
|
||||
Text="$0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cargo-console-menu-order-capacity-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
<Label Name="ShuttleCapacityLabel"
|
||||
Text="0/20" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="Categories"
|
||||
Prefix="{Loc 'cargo-console-menu-categories-label'}"
|
||||
HorizontalExpand="True" />
|
||||
<LineEdit Name="SearchBar"
|
||||
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
<BoxContainer Name="Products"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<!-- Products get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<PanelContainer VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="{Loc 'cargo-console-menu-requests-label'}" />
|
||||
<BoxContainer Name="Requests"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
<!-- Requests are added here by code -->
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc 'cargo-console-menu-orders-label'}" />
|
||||
<BoxContainer Name="Orders"
|
||||
Orientation="Vertical"
|
||||
StyleClasses="transparentItemList"
|
||||
VerticalExpand="True">
|
||||
<!-- Orders are added here by code -->
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10"/>
|
||||
<TabContainer Name="TabContainer" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="Categories"
|
||||
Prefix="{Loc 'cargo-console-menu-categories-label'}"
|
||||
HorizontalExpand="True" />
|
||||
<LineEdit Name="SearchBar"
|
||||
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<TextureButton VerticalExpand="True" />
|
||||
<Control MinHeight="5"/>
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Name="Products"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<!-- Products get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<Control MinHeight="5"/>
|
||||
<PanelContainer VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="1">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="5">
|
||||
<Label Text="{Loc 'cargo-console-menu-requests-label'}" />
|
||||
<BoxContainer Name="Requests"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
<!-- Requests are added here by code -->
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<!-- Funds tab -->
|
||||
<BoxContainer Orientation="Vertical" Margin="15">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="TransferLimitLabel" Margin="0 0 15 0"/>
|
||||
<RichTextLabel Name="UnlimitedNotifier" Text="{Loc 'cargo-console-menu-account-action-transfer-limit-unlimited-notifier'}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Text="{Loc 'cargo-console-menu-account-action-select'}" Margin="0 0 10 0"/>
|
||||
<OptionButton Name="ActionOptions"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="5"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="AmountText" Text="{ Loc 'cargo-console-menu-account-action-amount'}"/>
|
||||
<SpinBox Name="TransferSpinBox" MinWidth="100" Value="10"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="15"/>
|
||||
<BoxContainer HorizontalAlignment="Center">
|
||||
<Button Name="AccountActionButton" Text="{ Loc 'cargo-console-menu-account-action-button'}" MinHeight="45" MinWidth="120"/>
|
||||
</BoxContainer>
|
||||
<Control VerticalExpand="True"/>
|
||||
<BoxContainer VerticalAlignment="Bottom" HorizontalAlignment="Center">
|
||||
<Button Name="AccountLimitToggleButton" Text="{ Loc 'cargo-console-menu-toggle-account-lock-button'}" MinHeight="45" MinWidth="120"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</TabContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Cargo.Systems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
@@ -8,6 +9,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Cargo.UI
|
||||
@@ -15,30 +17,83 @@ namespace Content.Client.Cargo.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CargoConsoleMenu : FancyWindow
|
||||
{
|
||||
private IEntityManager _entityManager;
|
||||
private IPrototypeManager _protoManager;
|
||||
private SpriteSystem _spriteSystem;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IPrototypeManager _protoManager;
|
||||
private readonly CargoSystem _cargoSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private EntityUid _owner;
|
||||
private EntityUid? _station;
|
||||
|
||||
private readonly EntityQuery<CargoOrderConsoleComponent> _orderConsoleQuery;
|
||||
private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
|
||||
|
||||
public event Action<ButtonEventArgs>? OnItemSelected;
|
||||
public event Action<ButtonEventArgs>? OnOrderApproved;
|
||||
public event Action<ButtonEventArgs>? OnOrderCanceled;
|
||||
|
||||
public event Action<ProtoId<CargoAccountPrototype>?, int>? OnAccountAction;
|
||||
|
||||
public event Action<ButtonEventArgs>? OnToggleUnboundedLimit;
|
||||
|
||||
private readonly List<string> _categoryStrings = new();
|
||||
private string? _category;
|
||||
|
||||
public CargoConsoleMenu(EntityUid owner, IEntityManager entMan, IPrototypeManager protoManager, SpriteSystem spriteSystem)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_entityManager = entMan;
|
||||
_protoManager = protoManager;
|
||||
_cargoSystem = entMan.System<CargoSystem>();
|
||||
_spriteSystem = spriteSystem;
|
||||
_owner = owner;
|
||||
|
||||
Title = Loc.GetString("cargo-console-menu-title");
|
||||
_orderConsoleQuery = _entityManager.GetEntityQuery<CargoOrderConsoleComponent>();
|
||||
_bankQuery = _entityManager.GetEntityQuery<StationBankAccountComponent>();
|
||||
|
||||
Title = entMan.GetComponent<MetaDataComponent>(owner).EntityName;
|
||||
|
||||
SearchBar.OnTextChanged += OnSearchBarTextChanged;
|
||||
Categories.OnItemSelected += OnCategoryItemSelected;
|
||||
|
||||
if (entMan.TryGetComponent<CargoOrderConsoleComponent>(owner, out var orderConsole))
|
||||
{
|
||||
var accountProto = _protoManager.Index(orderConsole.Account);
|
||||
AccountNameLabel.Text = Loc.GetString("cargo-console-menu-account-name-format",
|
||||
("color", accountProto.Color),
|
||||
("name", Loc.GetString(accountProto.Name)),
|
||||
("code", Loc.GetString(accountProto.Code)));
|
||||
}
|
||||
|
||||
TabContainer.SetTabTitle(0, Loc.GetString("cargo-console-menu-tab-title-orders"));
|
||||
TabContainer.SetTabTitle(1, Loc.GetString("cargo-console-menu-tab-title-funds"));
|
||||
|
||||
ActionOptions.OnItemSelected += idx =>
|
||||
{
|
||||
ActionOptions.SelectId(idx.Id);
|
||||
};
|
||||
|
||||
TransferSpinBox.IsValid = val =>
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<CargoOrderConsoleComponent>(owner, out var console) ||
|
||||
!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return true;
|
||||
|
||||
return val >= 0 && val <= (int) (console.TransferLimit * bank.Accounts[console.Account]);
|
||||
};
|
||||
|
||||
AccountActionButton.OnPressed += _ =>
|
||||
{
|
||||
var account = (ProtoId<CargoAccountPrototype>?) ActionOptions.SelectedMetadata;
|
||||
OnAccountAction?.Invoke(account, TransferSpinBox.Value);
|
||||
};
|
||||
|
||||
AccountLimitToggleButton.OnPressed += a =>
|
||||
{
|
||||
OnToggleUnboundedLimit?.Invoke(a);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs args)
|
||||
@@ -144,11 +199,13 @@ namespace Content.Client.Cargo.UI
|
||||
/// </summary>
|
||||
public void PopulateOrders(IEnumerable<CargoOrderData> orders)
|
||||
{
|
||||
Orders.DisposeAllChildren();
|
||||
Requests.DisposeAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
if (order.Approved)
|
||||
continue;
|
||||
|
||||
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
|
||||
var productName = product.Name;
|
||||
|
||||
@@ -164,35 +221,67 @@ namespace Content.Client.Cargo.UI
|
||||
("orderAmount", order.OrderQuantity),
|
||||
("orderRequester", order.Requester))
|
||||
},
|
||||
Description = {Text = Loc.GetString("cargo-console-menu-order-reason-description",
|
||||
("reason", order.Reason))}
|
||||
Description =
|
||||
{
|
||||
Text = Loc.GetString("cargo-console-menu-order-reason-description",
|
||||
("reason", order.Reason))
|
||||
}
|
||||
};
|
||||
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
|
||||
if (order.Approved)
|
||||
{
|
||||
row.Approve.Visible = false;
|
||||
row.Cancel.Visible = false;
|
||||
Orders.AddChild(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Disable based on access.
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||
Requests.AddChild(row);
|
||||
}
|
||||
|
||||
// TODO: Disable based on access.
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||
Requests.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCargoCapacity(int count, int capacity)
|
||||
public void PopulateAccountActions()
|
||||
{
|
||||
// TODO: Rename + Loc.
|
||||
ShuttleCapacityLabel.Text = $"{count}/{capacity}";
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank) ||
|
||||
!_entityManager.TryGetComponent<CargoOrderConsoleComponent>(_owner, out var console))
|
||||
return;
|
||||
|
||||
var i = 0;
|
||||
ActionOptions.Clear();
|
||||
ActionOptions.AddItem(Loc.GetString("cargo-console-menu-account-action-option-withdraw"), i);
|
||||
i++;
|
||||
foreach (var account in bank.Accounts.Keys)
|
||||
{
|
||||
if (account == console.Account)
|
||||
continue;
|
||||
var accountProto = _protoManager.Index(account);
|
||||
ActionOptions.AddItem(Loc.GetString("cargo-console-menu-account-action-option-transfer",
|
||||
("code", Loc.GetString(accountProto.Code))),
|
||||
i);
|
||||
ActionOptions.SetItemMetadata(i, account);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBankData(string name, int points)
|
||||
public void UpdateStation(EntityUid station)
|
||||
{
|
||||
AccountNameLabel.Text = name;
|
||||
PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", points.ToString()));
|
||||
_station = station;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_bankQuery.TryComp(_station, out var bankAccount) ||
|
||||
!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var balance = _cargoSystem.GetBalanceFromAccount((_station.Value, bankAccount), orderConsole.Account);
|
||||
PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", balance));
|
||||
TransferLimitLabel.Text = Loc.GetString("cargo-console-menu-account-action-transfer-limit",
|
||||
("limit", (int) (balance * orderConsole.TransferLimit)));
|
||||
|
||||
UnlimitedNotifier.Visible = orderConsole.TransferUnbounded;
|
||||
AccountActionButton.Disabled = TransferSpinBox.Value <= 0 ||
|
||||
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
|
||||
_timing.CurTime < orderConsole.NextAccountActionTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
HorizontalExpand="True">
|
||||
HorizontalExpand="True"
|
||||
Margin="0 1">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<TextureRect Name="Icon"
|
||||
Access="Public"
|
||||
MinSize="32 32"
|
||||
RectClipContent="True" />
|
||||
<Control MinWidth="5"/>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
@@ -23,10 +25,10 @@
|
||||
<Button Name="Approve"
|
||||
Access="Public"
|
||||
Text="{Loc 'cargo-console-menu-cargo-order-row-approve-button'}"
|
||||
StyleClasses="LabelSubText" />
|
||||
StyleClasses="OpenRight" />
|
||||
<Button Name="Cancel"
|
||||
Access="Public"
|
||||
Text="{Loc 'cargo-console-menu-cargo-order-row-cancel-button'}"
|
||||
StyleClasses="LabelSubText" />
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
ToolTip=""
|
||||
Access="Public"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True" />
|
||||
VerticalExpand="True"
|
||||
StyleClasses="OpenBoth"/>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<TextureRect Name="Icon"
|
||||
@@ -18,7 +19,8 @@
|
||||
<Label Name="PointCost"
|
||||
Access="Public"
|
||||
MinSize="52 32"
|
||||
Align="Right" />
|
||||
Align="Right"
|
||||
Margin="0 0 5 0"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
32
Content.Client/Cargo/UI/FundingAllocationMenu.xaml
Normal file
32
Content.Client/Cargo/UI/FundingAllocationMenu.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'cargo-funding-alloc-console-menu-title'}">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="10 5 10 10">
|
||||
<controls:TableContainer Columns="2" HorizontalExpand="True" VerticalExpand="True">
|
||||
<RichTextLabel Name="PrimaryCutLabel" Text="{Loc 'cargo-funding-alloc-console-label-primary-cut'}"/>
|
||||
<SpinBox Name="PrimaryCut"/>
|
||||
<RichTextLabel Name="LockboxCutLabel" Text="{Loc 'cargo-funding-alloc-console-label-lockbox-cut'}"/>
|
||||
<SpinBox Name="LockboxCut"/>
|
||||
</controls:TableContainer>
|
||||
<Label Name="HelpLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="0 10"/>
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Top" MaxHeight="250">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<controls:TableContainer Name="EntriesContainer" Columns="4" HorizontalExpand="True" VerticalExpand="True" Margin="5 0">
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-account'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-code'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-balance'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-cut'}" HorizontalAlignment="Center"/>
|
||||
</controls:TableContainer>
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 0">
|
||||
<Button Name="SaveButton" Text="{Loc 'cargo-funding-alloc-console-button-save'}" Disabled="True"/>
|
||||
<RichTextLabel Name="SaveAlertLabel" HorizontalExpand="True" HorizontalAlignment="Right" Visible="False"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
229
Content.Client/Cargo/UI/FundingAllocationMenu.xaml.cs
Normal file
229
Content.Client/Cargo/UI/FundingAllocationMenu.xaml.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class FundingAllocationMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
|
||||
|
||||
public event Action<Dictionary<ProtoId<CargoAccountPrototype>, int>, double, double>? OnSavePressed;
|
||||
|
||||
private EntityUid? _station;
|
||||
private bool _allowPrimaryAccountAllocation;
|
||||
private bool _allowPrimaryCutAdjustment;
|
||||
private bool _lockboxCutEnabled;
|
||||
|
||||
private double _primaryCut;
|
||||
private double _lockboxCut;
|
||||
|
||||
private readonly HashSet<Control> _addedControls = new();
|
||||
private readonly List<SpinBox> _spinBoxes = new();
|
||||
private readonly Dictionary<ProtoId<CargoAccountPrototype>, RichTextLabel> _balanceLabels = new();
|
||||
|
||||
public FundingAllocationMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_bankQuery = _entityManager.GetEntityQuery<StationBankAccountComponent>();
|
||||
|
||||
PrimaryCut.ValueChanged += args =>
|
||||
{
|
||||
_primaryCut = (double)args.Value / 100.0;
|
||||
UpdateButtonDisabled();
|
||||
};
|
||||
|
||||
LockboxCut.ValueChanged += args =>
|
||||
{
|
||||
_lockboxCut = 1.0 - (double)args.Value / 100.0;
|
||||
UpdateButtonDisabled();
|
||||
};
|
||||
|
||||
SaveButton.OnPressed += _ =>
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return;
|
||||
var accounts = EditableAccounts(bank).OrderBy(p => p.Key).Select(p => p.Key).ToList();
|
||||
var dicts = new Dictionary<ProtoId<CargoAccountPrototype>, int>();
|
||||
for (var i = 0; i< accounts.Count; i++)
|
||||
{
|
||||
dicts.Add(accounts[i], _spinBoxes[i].Value);
|
||||
}
|
||||
|
||||
OnSavePressed?.Invoke(dicts, _primaryCut, _lockboxCut);
|
||||
SaveButton.Disabled = true;
|
||||
};
|
||||
|
||||
_cfg.OnValueChanged(CCVars.AllowPrimaryAccountAllocation, enabled => { _allowPrimaryAccountAllocation = enabled; }, true);
|
||||
_cfg.OnValueChanged(CCVars.AllowPrimaryCutAdjustment, enabled => { _allowPrimaryCutAdjustment = enabled; }, true);
|
||||
_cfg.OnValueChanged(CCVars.LockboxCutEnabled, enabled => { _lockboxCutEnabled = enabled; }, true);
|
||||
|
||||
BuildEntries();
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<ProtoId<CargoAccountPrototype>, int>> EditableAccounts(StationBankAccountComponent bank)
|
||||
{
|
||||
foreach (var kvp in bank.Accounts)
|
||||
{
|
||||
if (_allowPrimaryAccountAllocation || kvp.Key != bank.PrimaryAccount)
|
||||
{
|
||||
yield return kvp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildEntries()
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return;
|
||||
|
||||
if (_allowPrimaryCutAdjustment)
|
||||
{
|
||||
HelpLabel.Text = Loc.GetString("cargo-funding-alloc-console-label-help-adjustible");
|
||||
}
|
||||
else
|
||||
{
|
||||
HelpLabel.Text = Loc.GetString("cargo-funding-alloc-console-label-help-non-adjustible",
|
||||
("percent", (int) (bank.PrimaryCut * 100)));
|
||||
}
|
||||
|
||||
foreach (var ctrl in _addedControls)
|
||||
{
|
||||
ctrl.Orphan();
|
||||
}
|
||||
|
||||
_addedControls.Clear();
|
||||
_spinBoxes.Clear();
|
||||
_balanceLabels.Clear();
|
||||
|
||||
_primaryCut = bank.PrimaryCut;
|
||||
_lockboxCut = bank.LockboxCut;
|
||||
|
||||
LockboxCut.OverrideValue(100 - (int)(_lockboxCut * 100));
|
||||
PrimaryCut.OverrideValue((int)(_primaryCut * 100));
|
||||
|
||||
LockboxCut.IsValid = val => val is >= 0 and <= 100;
|
||||
PrimaryCut.IsValid = val => val is >= 0 and <= 100;
|
||||
|
||||
LockboxCut.Visible = _lockboxCutEnabled;
|
||||
LockboxCutLabel.Visible = _lockboxCutEnabled;
|
||||
PrimaryCut.Visible = _allowPrimaryCutAdjustment;
|
||||
PrimaryCutLabel.Visible = _allowPrimaryCutAdjustment;
|
||||
|
||||
var accounts = EditableAccounts(bank).OrderBy(p => p.Key);
|
||||
foreach (var (account, balance) in accounts)
|
||||
{
|
||||
var accountProto = _prototypeManager.Index(account);
|
||||
|
||||
var accountNameLabel = new RichTextLabel
|
||||
{
|
||||
Modulate = accountProto.Color,
|
||||
Margin = new Thickness(0, 0, 10, 0)
|
||||
};
|
||||
accountNameLabel.SetMarkup($"[bold]{Loc.GetString(accountProto.Name)}[/bold]");
|
||||
EntriesContainer.AddChild(accountNameLabel);
|
||||
|
||||
var codeLabel = new RichTextLabel
|
||||
{
|
||||
Text = $"[font=\"Monospace\"]{Loc.GetString(accountProto.Code)}[/font]",
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(5, 0),
|
||||
};
|
||||
EntriesContainer.AddChild(codeLabel);
|
||||
|
||||
var balanceLabel = new RichTextLabel
|
||||
{
|
||||
Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", balance)),
|
||||
HorizontalExpand = true,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(5, 0),
|
||||
};
|
||||
EntriesContainer.AddChild(balanceLabel);
|
||||
|
||||
var box = new SpinBox
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
Value = (int) (bank.RevenueDistribution[account] * 100),
|
||||
IsValid = val => val is >= 0 and <= 100,
|
||||
};
|
||||
box.ValueChanged += _ => UpdateButtonDisabled();
|
||||
EntriesContainer.AddChild(box);
|
||||
|
||||
_spinBoxes.Add(box);
|
||||
_balanceLabels.Add(account, balanceLabel);
|
||||
_addedControls.Add(accountNameLabel);
|
||||
_addedControls.Add(codeLabel);
|
||||
_addedControls.Add(balanceLabel);
|
||||
_addedControls.Add(box);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateButtonDisabled()
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return;
|
||||
|
||||
var sum = _spinBoxes.Sum(s => s.Value);
|
||||
var incorrectSum = sum != 100;
|
||||
|
||||
var differs = false;
|
||||
var accounts = EditableAccounts(bank).OrderBy(p => p.Key).Select(p => p.Key).ToList();
|
||||
for (var i = 0; i < accounts.Count; i++)
|
||||
{
|
||||
var percent = _spinBoxes[i].Value;
|
||||
if (percent != (int) Math.Round(bank.RevenueDistribution[accounts[i]] * 100))
|
||||
{
|
||||
differs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
differs = differs || _primaryCut != bank.PrimaryCut || _lockboxCut != bank.LockboxCut;
|
||||
|
||||
SaveButton.Disabled = !differs || incorrectSum;
|
||||
|
||||
var diff = sum - 100;
|
||||
SaveAlertLabel.Visible = incorrectSum;
|
||||
SaveAlertLabel.SetMarkup(Loc.GetString("cargo-funding-alloc-console-label-save-fail",
|
||||
("pos", Math.Sign(diff)),
|
||||
("val", Math.Abs(diff))));
|
||||
}
|
||||
|
||||
public void Update(FundingAllocationConsoleBuiState state)
|
||||
{
|
||||
_station = _entityManager.GetEntity(state.Station);
|
||||
BuildEntries();
|
||||
UpdateButtonDisabled();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_bankQuery.TryComp(_station, out var bank))
|
||||
return;
|
||||
|
||||
foreach (var (account, label) in _balanceLabels)
|
||||
{
|
||||
label.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", bank.Accounts[account]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,12 @@ public sealed partial class NanoTaskItemPopup : DefaultWindow
|
||||
{
|
||||
if (item is NanoTaskItem task)
|
||||
{
|
||||
var button = task.Priority switch {
|
||||
var button = task.Priority switch
|
||||
{
|
||||
NanoTaskPriority.High => HighButton,
|
||||
NanoTaskPriority.Medium => MediumButton,
|
||||
NanoTaskPriority.Low => LowButton,
|
||||
_ => throw new ArgumentException("Invalid priority"),
|
||||
};
|
||||
button.Pressed = true;
|
||||
DescriptionInput.Text = task.Description;
|
||||
|
||||
@@ -38,10 +38,12 @@ public sealed partial class NanoTaskUiFragment : BoxContainer
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var container = task.Data.Priority switch {
|
||||
var container = task.Data.Priority switch
|
||||
{
|
||||
NanoTaskPriority.High => HighContainer,
|
||||
NanoTaskPriority.Medium => MediumContainer,
|
||||
NanoTaskPriority.Low => LowContainer,
|
||||
_ => throw new ArgumentException("Invalid priority"),
|
||||
};
|
||||
var control = new NanoTaskItemControl(task);
|
||||
container.AddChild(control);
|
||||
|
||||
52
Content.Client/Charges/ChargesSystem.cs
Normal file
52
Content.Client/Charges/ChargesSystem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
|
||||
namespace Content.Client.Charges;
|
||||
|
||||
public sealed class ChargesSystem : SharedChargesSystem
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
|
||||
private Dictionary<EntityUid, int> _lastCharges = new();
|
||||
private Dictionary<EntityUid, int> _tempLastCharges = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
// Technically this should probably be in frameupdate but no one will ever notice a tick of delay on this.
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
// Update recharging actions. Server doesn't actually care about this and it's a waste of performance, actions are immediate.
|
||||
var query = AllEntityQuery<AutoRechargeComponent, LimitedChargesComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var recharge, out var charges))
|
||||
{
|
||||
BaseActionComponent? actionComp = null;
|
||||
|
||||
if (!_actions.ResolveActionData(uid, ref actionComp, logError: false))
|
||||
continue;
|
||||
|
||||
var current = GetCurrentCharges((uid, charges, recharge));
|
||||
|
||||
if (!_lastCharges.TryGetValue(uid, out var last) || current != last)
|
||||
{
|
||||
_actions.UpdateAction(uid, actionComp);
|
||||
}
|
||||
|
||||
_tempLastCharges[uid] = current;
|
||||
}
|
||||
|
||||
_lastCharges.Clear();
|
||||
|
||||
foreach (var (uid, value) in _tempLastCharges)
|
||||
{
|
||||
_lastCharges[uid] = value;
|
||||
}
|
||||
|
||||
_tempLastCharges.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
using Content.Shared.Charges.Systems;
|
||||
|
||||
namespace Content.Client.Charges.Systems;
|
||||
|
||||
public sealed class ChargesSystem : SharedChargesSystem { }
|
||||
@@ -341,8 +341,11 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
|
||||
continue;
|
||||
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers))
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, out var displacementKey))
|
||||
{
|
||||
revealedLayers.Add(displacementKey);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
Content.Client/Configurable/ConfigurationSystem.cs
Normal file
25
Content.Client/Configurable/ConfigurationSystem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Client.Configurable.UI;
|
||||
using Content.Shared.Configurable;
|
||||
|
||||
namespace Content.Client.Configurable;
|
||||
|
||||
public sealed class ConfigurationSystem : SharedConfigurationSystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ConfigurationComponent, AfterAutoHandleStateEvent>(OnConfigurationState);
|
||||
}
|
||||
|
||||
private void OnConfigurationState(Entity<ConfigurationComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<ConfigurationBoundUserInterface>(ent.Owner,
|
||||
ConfigurationComponent.ConfigurationUiKey.Key,
|
||||
out var bui))
|
||||
{
|
||||
bui.Refresh(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Client.GameObjects;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared.Configurable;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
namespace Content.Client.Configurable.UI
|
||||
@@ -19,16 +21,53 @@ namespace Content.Client.Configurable.UI
|
||||
base.Open();
|
||||
_menu = this.CreateWindow<ConfigurationMenu>();
|
||||
_menu.OnConfiguration += SendConfiguration;
|
||||
if (EntMan.TryGetComponent(Owner, out ConfigurationComponent? component))
|
||||
Refresh((Owner, component));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public void Refresh(Entity<ConfigurationComponent> entity)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not ConfigurationBoundUserInterfaceState configurationState)
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu?.Populate(configurationState);
|
||||
_menu.Column.Children.Clear();
|
||||
_menu.Inputs.Clear();
|
||||
|
||||
foreach (var field in entity.Comp.Config)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Name = field.Key,
|
||||
Text = field.Key + ":",
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .2f,
|
||||
MinSize = new Vector2(60, 0)
|
||||
};
|
||||
|
||||
var input = new LineEdit
|
||||
{
|
||||
Name = field.Key + "-input",
|
||||
Text = field.Value ?? "",
|
||||
IsValid = _menu.Validate,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .8f
|
||||
};
|
||||
|
||||
_menu.Inputs.Add((field.Key, input));
|
||||
|
||||
var row = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal
|
||||
};
|
||||
|
||||
ConfigurationMenu.CopyProperties(_menu.Row, row);
|
||||
|
||||
row.AddChild(label);
|
||||
row.AddChild(input);
|
||||
_menu.Column.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
@@ -14,10 +10,10 @@ namespace Content.Client.Configurable.UI
|
||||
{
|
||||
public sealed class ConfigurationMenu : DefaultWindow
|
||||
{
|
||||
private readonly BoxContainer _column;
|
||||
private readonly BoxContainer _row;
|
||||
public readonly BoxContainer Column;
|
||||
public readonly BoxContainer Row;
|
||||
|
||||
private readonly List<(string name, LineEdit input)> _inputs;
|
||||
public readonly List<(string name, LineEdit input)> Inputs;
|
||||
|
||||
[ViewVariables]
|
||||
public Regex? Validation { get; internal set; }
|
||||
@@ -28,7 +24,7 @@ namespace Content.Client.Configurable.UI
|
||||
{
|
||||
MinSize = SetSize = new Vector2(300, 250);
|
||||
|
||||
_inputs = new List<(string name, LineEdit input)>();
|
||||
Inputs = new List<(string name, LineEdit input)>();
|
||||
|
||||
Title = Loc.GetString("configuration-menu-device-title");
|
||||
|
||||
@@ -39,14 +35,14 @@ namespace Content.Client.Configurable.UI
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
_column = new BoxContainer
|
||||
Column = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Margin = new Thickness(8),
|
||||
SeparationOverride = 16,
|
||||
};
|
||||
|
||||
_row = new BoxContainer
|
||||
Row = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 16,
|
||||
@@ -69,61 +65,20 @@ namespace Content.Client.Configurable.UI
|
||||
ModulateSelfOverride = Color.FromHex("#202025")
|
||||
};
|
||||
|
||||
outerColumn.AddChild(_column);
|
||||
outerColumn.AddChild(Column);
|
||||
baseContainer.AddChild(outerColumn);
|
||||
baseContainer.AddChild(confirmButton);
|
||||
Contents.AddChild(baseContainer);
|
||||
}
|
||||
|
||||
public void Populate(ConfigurationBoundUserInterfaceState state)
|
||||
{
|
||||
_column.Children.Clear();
|
||||
_inputs.Clear();
|
||||
|
||||
foreach (var field in state.Config)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Name = field.Key,
|
||||
Text = field.Key + ":",
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .2f,
|
||||
MinSize = new Vector2(60, 0)
|
||||
};
|
||||
|
||||
var input = new LineEdit
|
||||
{
|
||||
Name = field.Key + "-input",
|
||||
Text = field.Value ?? "",
|
||||
IsValid = Validate,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .8f
|
||||
};
|
||||
|
||||
_inputs.Add((field.Key, input));
|
||||
|
||||
var row = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
CopyProperties(_row, row);
|
||||
|
||||
row.AddChild(label);
|
||||
row.AddChild(input);
|
||||
_column.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfirm(ButtonEventArgs args)
|
||||
{
|
||||
var config = GenerateDictionary(_inputs, "Text");
|
||||
var config = GenerateDictionary(Inputs, "Text");
|
||||
OnConfiguration?.Invoke(config);
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool Validate(string value)
|
||||
public bool Validate(string value)
|
||||
{
|
||||
return Validation?.IsMatch(value) != false;
|
||||
}
|
||||
@@ -140,7 +95,7 @@ namespace Content.Client.Configurable.UI
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private static void CopyProperties<T>(T from, T to) where T : Control
|
||||
public static void CopyProperties<T>(T from, T to) where T : Control
|
||||
{
|
||||
foreach (var property in from.AllAttachedProperties)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'credits-window-title'}"
|
||||
SetSize="650 650" >
|
||||
<TabContainer>
|
||||
Title="{Loc 'credits-window-title'}"
|
||||
SetSize="650 650">
|
||||
<TabContainer Name="MasterTabContainer">
|
||||
<ScrollContainer Name="Ss14ContributorsTab"
|
||||
HScrollEnabled="False">
|
||||
<BoxContainer Name="Ss14ContributorsContainer"
|
||||
@@ -26,5 +26,11 @@
|
||||
<!-- Licenses get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer Name="AttributionsTab"
|
||||
HScrollEnabled="False">
|
||||
<BoxContainer Name="AttributionsContainer"
|
||||
Orientation="Vertical"
|
||||
Margin="2 2 0 0" />
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,184 +1,381 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Credits;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Credits
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CreditsWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
namespace Content.Client.Credits;
|
||||
|
||||
private static readonly Dictionary<string, int> PatronTierPriority = new()
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CreditsWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private static readonly Dictionary<string, int> PatronTierPriority = new()
|
||||
{
|
||||
["Nuclear Operative"] = 1,
|
||||
["Syndicate Agent"] = 2,
|
||||
["Revolutionary"] = 3,
|
||||
};
|
||||
|
||||
private readonly List<FormattedMessage> _attributions = [];
|
||||
private readonly ISawmill _sawmill = Logger.GetSawmill("Credits");
|
||||
|
||||
private const int AttributionsSourcesPerPage = 50;
|
||||
|
||||
public CreditsWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
TabContainer.SetTabTitle(Ss14ContributorsTab, Loc.GetString("credits-window-ss14contributorslist-tab"));
|
||||
TabContainer.SetTabTitle(PatronsTab, Loc.GetString("credits-window-patrons-tab"));
|
||||
TabContainer.SetTabTitle(LicensesTab, Loc.GetString("credits-window-licenses-tab"));
|
||||
TabContainer.SetTabTitle(AttributionsTab, Loc.GetString("credits-window-attributions-tab"));
|
||||
|
||||
_protoManager.PrototypesReloaded += _ =>
|
||||
{
|
||||
["Nuclear Operative"] = 1,
|
||||
["Syndicate Agent"] = 2,
|
||||
["Revolutionary"] = 3
|
||||
_attributions.Clear();
|
||||
};
|
||||
|
||||
public CreditsWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
MasterTabContainer.OnTabChanged += OnTabChanged;
|
||||
|
||||
TabContainer.SetTabTitle(Ss14ContributorsTab, Loc.GetString("credits-window-ss14contributorslist-tab"));
|
||||
TabContainer.SetTabTitle(PatronsTab, Loc.GetString("credits-window-patrons-tab"));
|
||||
TabContainer.SetTabTitle(LicensesTab, Loc.GetString("credits-window-licenses-tab"));
|
||||
PopulateContributors(Ss14ContributorsContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only populates the tab when they are selected, which reduces lagspike when not looking at attributions.
|
||||
/// </summary>
|
||||
private void OnTabChanged(int tab)
|
||||
{
|
||||
if (tab == Ss14ContributorsTab.GetPositionInParent())
|
||||
PopulateContributors(Ss14ContributorsContainer);
|
||||
else if (tab == PatronsTab.GetPositionInParent())
|
||||
PopulatePatrons(PatronsContainer);
|
||||
else if (tab == LicensesTab.GetPositionInParent())
|
||||
PopulateLicenses(LicensesContainer);
|
||||
else if (tab == AttributionsTab.GetPositionInParent())
|
||||
PopulateAttributions(AttributionsContainer, 0);
|
||||
}
|
||||
|
||||
private async void PopulateAttributions(BoxContainer attributionsContainer, int count)
|
||||
{
|
||||
attributionsContainer.DisposeAllChildren();
|
||||
|
||||
if (_attributions.Count == 0)
|
||||
{
|
||||
var rsi = await CollectRSiAttributions();
|
||||
var rga = await CollectRgaAttributions();
|
||||
|
||||
_attributions.AddRange(rsi);
|
||||
_attributions.AddRange(rga);
|
||||
}
|
||||
|
||||
private void PopulateLicenses(BoxContainer licensesContainer)
|
||||
foreach (var message in _attributions.Skip(count).Take(AttributionsSourcesPerPage))
|
||||
{
|
||||
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
|
||||
{
|
||||
licensesContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = entry.Name});
|
||||
var rich = new RichTextLabel();
|
||||
rich.SetMessage(message);
|
||||
attributionsContainer.AddChild(rich);
|
||||
}
|
||||
|
||||
// We split these line by line because otherwise
|
||||
// the LGPL causes Clyde to go out of bounds in the rendering code.
|
||||
foreach (var line in entry.License.Split("\n"))
|
||||
var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal };
|
||||
|
||||
var previousPageButton = new Button { Text = "Previous Page" };
|
||||
previousPageButton.OnPressed +=
|
||||
_ => PopulateAttributions(attributionsContainer, count - AttributionsSourcesPerPage);
|
||||
|
||||
var nextPageButton = new Button { Text = "Next Page" };
|
||||
nextPageButton.OnPressed +=
|
||||
_ => PopulateAttributions(attributionsContainer, count + AttributionsSourcesPerPage);
|
||||
|
||||
if (count - AttributionsSourcesPerPage >= 0)
|
||||
container.AddChild(previousPageButton);
|
||||
if (count + AttributionsSourcesPerPage < _attributions.Count)
|
||||
container.AddChild(nextPageButton);
|
||||
|
||||
attributionsContainer.AddChild(container);
|
||||
}
|
||||
|
||||
private Task<List<FormattedMessage>> CollectRSiAttributions()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var rsiStreams = _resourceManager.ContentFindFiles("/Textures/")
|
||||
.Where(p => p.ToString().EndsWith(".rsi/meta.json"));
|
||||
|
||||
var attrs = new List<FormattedMessage>();
|
||||
|
||||
foreach (var stream in rsiStreams)
|
||||
{
|
||||
try
|
||||
{
|
||||
licensesContainer.AddChild(new Label {Text = line, FontColorOverride = new Color(200, 200, 200)});
|
||||
var m = new FormattedMessage();
|
||||
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(stream);
|
||||
|
||||
if (yamlStream.Documents[0].RootNode.ToDataNode() is not MappingDataNode map)
|
||||
throw new Exception("meta.json is not a mapping.");
|
||||
|
||||
if (!map.TryGet("copyright", out var copyrightNode))
|
||||
throw new Exception("Missing the copyright field.");
|
||||
|
||||
if (!map.TryGet("states", out var statesNode))
|
||||
throw new Exception("Missing the states field.");
|
||||
|
||||
if (statesNode is not SequenceDataNode states)
|
||||
throw new Exception("Missing a list of states.");
|
||||
|
||||
var copyright = copyrightNode.ToString();
|
||||
var files = states.Select(n => (MappingDataNode)n)
|
||||
.Select(n => n.Get("name") + ".png");
|
||||
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-directory",
|
||||
("directory", stream.Directory.ToString())));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-files",
|
||||
("files", string.Join(", ", files))));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-copyright",
|
||||
("copyright", copyright)));
|
||||
m.AddText("\n");
|
||||
|
||||
attrs.Add(m);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var m = new FormattedMessage();
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-failed",
|
||||
("file", stream.ToString())));
|
||||
m.AddText("\n");
|
||||
_sawmill.Error($"{stream.ToString()}\n{e}");
|
||||
attrs.Add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulatePatrons(BoxContainer patronsContainer)
|
||||
return attrs;
|
||||
});
|
||||
}
|
||||
|
||||
private Task<List<FormattedMessage>> CollectRgaAttributions()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var patrons = LoadPatrons();
|
||||
var rgaStreams = _resourceManager.ContentFindFiles("/")
|
||||
.Where(p => p.Filename == "attributions.yml");
|
||||
|
||||
// Do not show "become a patron" button on Steam builds
|
||||
// since Patreon violates Valve's rules about alternative storefronts.
|
||||
var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
|
||||
if (!_cfg.GetCVar(CCVars.BrandingSteam) && linkPatreon != "")
|
||||
var attrs = new List<FormattedMessage>();
|
||||
|
||||
foreach (var stream in rgaStreams)
|
||||
{
|
||||
Button patronButton;
|
||||
patronsContainer.AddChild(patronButton = new Button
|
||||
try
|
||||
{
|
||||
Text = Loc.GetString("credits-window-become-patron-button"),
|
||||
HorizontalAlignment = HAlignment.Center
|
||||
});
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(stream);
|
||||
|
||||
patronButton.OnPressed +=
|
||||
_ => IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
|
||||
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
|
||||
throw new Exception("Attributions file is not a list of attributions.");
|
||||
|
||||
foreach (var attribution in sequence.Sequence)
|
||||
{
|
||||
var m = new FormattedMessage();
|
||||
|
||||
if (attribution is not MappingDataNode map)
|
||||
throw new Exception("Attribution is not a mapping.");
|
||||
|
||||
if (!map.TryGet("files", out var filesNode))
|
||||
throw new Exception("Attribution does not list files.");
|
||||
|
||||
if (!map.TryGet("copyright", out var copyrightNode))
|
||||
throw new Exception("Attribution does not copyright.");
|
||||
|
||||
if (!map.TryGet("license", out var licenseNode))
|
||||
throw new Exception("Attribution does not identify a license.");
|
||||
|
||||
if (!map.TryGet("source", out var sourceNode))
|
||||
throw new Exception("Attribution does not identify a source.");
|
||||
|
||||
var files = _serialization.Read<string[]>(filesNode, notNullableOverride: true);
|
||||
var copyright = copyrightNode.ToString();
|
||||
var license = licenseNode.ToString();
|
||||
var source = sourceNode.ToString();
|
||||
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-directory",
|
||||
("directory", stream.Directory.ToString())));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-files",
|
||||
("files", string.Join(", ", files))));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-copyright",
|
||||
("copyright", copyright)));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(
|
||||
_loc.GetString("credits-window-attributions-license", ("license", license)));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-source", ("source", source)));
|
||||
m.AddText("\n");
|
||||
|
||||
attrs.Add(m);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var m = new FormattedMessage();
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-failed",
|
||||
("file", stream.ToString())));
|
||||
m.AddText("\n");
|
||||
_sawmill.Error($"{stream.ToString()}\n{e}");
|
||||
attrs.Add(m);
|
||||
}
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var tier in patrons.GroupBy(p => p.Tier).OrderBy(p => PatronTierPriority[p.Key]))
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
patronsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
|
||||
}
|
||||
return attrs;
|
||||
});
|
||||
}
|
||||
|
||||
first = false;
|
||||
patronsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = $"{tier.Key}"});
|
||||
|
||||
var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
|
||||
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(msg);
|
||||
|
||||
patronsContainer.AddChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PatronEntry> LoadPatrons()
|
||||
private void PopulateLicenses(BoxContainer licensesContainer)
|
||||
{
|
||||
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
|
||||
{
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(new ("/Credits/Patrons.yml"));
|
||||
var sequence = (YamlSequenceNode) yamlStream.Documents[0].RootNode;
|
||||
licensesContainer.AddChild(new Label
|
||||
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = entry.Name });
|
||||
|
||||
return sequence
|
||||
.Cast<YamlMappingNode>()
|
||||
.Select(m => new PatronEntry(m["Name"].AsString(), m["Tier"].AsString()));
|
||||
}
|
||||
|
||||
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
|
||||
{
|
||||
Button contributeButton;
|
||||
|
||||
ss14ContributorsContainer.AddChild(new BoxContainer
|
||||
// We split these line by line because otherwise
|
||||
// the LGPL causes Clyde to go out of bounds in the rendering code.
|
||||
foreach (var line in entry.License.Split("\n"))
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
SeparationOverride = 20,
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("credits-window-contributor-encouragement-label") },
|
||||
(contributeButton = new Button {Text = Loc.GetString("credits-window-contribute-button")})
|
||||
}
|
||||
});
|
||||
|
||||
var first = true;
|
||||
|
||||
void AddSection(string title, string path, bool markup = false)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
ss14ContributorsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
|
||||
}
|
||||
|
||||
first = false;
|
||||
ss14ContributorsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = title});
|
||||
|
||||
var label = new RichTextLabel();
|
||||
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
|
||||
if (markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
}
|
||||
else
|
||||
{
|
||||
label.SetMessage(text);
|
||||
}
|
||||
|
||||
ss14ContributorsContainer.AddChild(label);
|
||||
}
|
||||
|
||||
AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
|
||||
AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
|
||||
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
|
||||
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
|
||||
|
||||
var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
|
||||
|
||||
contributeButton.OnPressed += _ =>
|
||||
IoCManager.Resolve<IUriOpener>().OpenUri(linkGithub);
|
||||
|
||||
if (linkGithub == "")
|
||||
contributeButton.Visible = false;
|
||||
}
|
||||
|
||||
private sealed class PatronEntry
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Tier { get; }
|
||||
|
||||
public PatronEntry(string name, string tier)
|
||||
{
|
||||
Name = name;
|
||||
Tier = tier;
|
||||
licensesContainer.AddChild(new Label { Text = line, FontColorOverride = new Color(200, 200, 200) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulatePatrons(BoxContainer patronsContainer)
|
||||
{
|
||||
var patrons = LoadPatrons();
|
||||
|
||||
// Do not show "become a patron" button on Steam builds
|
||||
// since Patreon violates Valve's rules about alternative storefronts.
|
||||
var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
|
||||
if (!_cfg.GetCVar(CCVars.BrandingSteam) && linkPatreon != "")
|
||||
{
|
||||
Button patronButton;
|
||||
patronsContainer.AddChild(patronButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("credits-window-become-patron-button"),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
});
|
||||
|
||||
patronButton.OnPressed +=
|
||||
_ => IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var tier in patrons.GroupBy(p => p.Tier).OrderBy(p => PatronTierPriority[p.Key]))
|
||||
{
|
||||
if (!first)
|
||||
patronsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
|
||||
|
||||
first = false;
|
||||
patronsContainer.AddChild(new Label
|
||||
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = $"{tier.Key}" });
|
||||
|
||||
var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
|
||||
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(msg);
|
||||
|
||||
patronsContainer.AddChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PatronEntry> LoadPatrons()
|
||||
{
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(new ResPath("/Credits/Patrons.yml"));
|
||||
var sequence = (YamlSequenceNode)yamlStream.Documents[0].RootNode;
|
||||
|
||||
return sequence
|
||||
.Cast<YamlMappingNode>()
|
||||
.Select(m => new PatronEntry(m["Name"].AsString(), m["Tier"].AsString()));
|
||||
}
|
||||
|
||||
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
|
||||
{
|
||||
Button contributeButton;
|
||||
|
||||
ss14ContributorsContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
SeparationOverride = 20,
|
||||
Children =
|
||||
{
|
||||
new Label { Text = Loc.GetString("credits-window-contributor-encouragement-label") },
|
||||
(contributeButton = new Button { Text = Loc.GetString("credits-window-contribute-button") }),
|
||||
},
|
||||
});
|
||||
|
||||
var first = true;
|
||||
|
||||
void AddSection(string title, string path, bool markup = false)
|
||||
{
|
||||
if (!first)
|
||||
ss14ContributorsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
|
||||
|
||||
first = false;
|
||||
ss14ContributorsContainer.AddChild(new Label
|
||||
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = title });
|
||||
|
||||
var label = new RichTextLabel();
|
||||
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
|
||||
if (markup)
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
else
|
||||
label.SetMessage(text);
|
||||
|
||||
ss14ContributorsContainer.AddChild(label);
|
||||
}
|
||||
|
||||
AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
|
||||
AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
|
||||
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
|
||||
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
|
||||
|
||||
var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
|
||||
|
||||
contributeButton.OnPressed += _ =>
|
||||
IoCManager.Resolve<IUriOpener>().OpenUri(linkGithub);
|
||||
|
||||
if (linkGithub == "")
|
||||
contributeButton.Visible = false;
|
||||
}
|
||||
|
||||
private sealed class PatronEntry
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Tier { get; }
|
||||
|
||||
public PatronEntry(string name, string tier)
|
||||
{
|
||||
Name = name;
|
||||
Tier = tier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,52 +213,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = listing.ToList();
|
||||
entries.Sort((a, b) => string.Compare(a.Value, b.Value, StringComparison.Ordinal));
|
||||
// `entries` now contains the definitive list of items which should be in
|
||||
// our list of records and is in the order we want to present those items.
|
||||
|
||||
// Walk through the existing items in RecordListing and in the updated listing
|
||||
// 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)
|
||||
{
|
||||
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
|
||||
if (strcmp == 0)
|
||||
{
|
||||
// This item exists in both RecordListing and `entries`. Nothing to do.
|
||||
i--;
|
||||
j--;
|
||||
}
|
||||
else if (strcmp > 0)
|
||||
{
|
||||
// Item exists in RecordListing, but not in `entries`. Remove it.
|
||||
RecordListing.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else if (strcmp < 0)
|
||||
{
|
||||
// A new entry which doesn't exist in RecordListing. Create it.
|
||||
RecordListing.Insert(i + 1, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
// Any remaining items in RecordListing don't exist in `entries`, so remove them
|
||||
while (i >= 0)
|
||||
{
|
||||
RecordListing.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// 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 });
|
||||
j--;
|
||||
}
|
||||
var entries = listing.Select(i => new ItemList.Item(RecordListing) {
|
||||
Text = i.Value,
|
||||
Metadata = i.Key
|
||||
}).ToList();
|
||||
entries.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.Ordinal));
|
||||
RecordListing.SetItems(entries, (a,b) => string.Compare(a.Text, b.Text));
|
||||
}
|
||||
|
||||
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown");
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Client.Decals
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprites = default!;
|
||||
|
||||
private DecalOverlay _overlay = default!;
|
||||
private DecalOverlay? _overlay;
|
||||
|
||||
private HashSet<uint> _removedUids = new();
|
||||
private readonly List<Vector2i> _removedChunks = new();
|
||||
@@ -31,6 +31,9 @@ namespace Content.Client.Decals
|
||||
|
||||
public void ToggleOverlay()
|
||||
{
|
||||
if (_overlay == null)
|
||||
return;
|
||||
|
||||
if (_overlayManager.HasOverlay<DecalOverlay>())
|
||||
{
|
||||
_overlayManager.RemoveOverlay(_overlay);
|
||||
@@ -44,6 +47,10 @@ namespace Content.Client.Decals
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
if (_overlay == null)
|
||||
return;
|
||||
|
||||
_overlayManager.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,3 +43,9 @@ public enum DeliveryVisualLayers : byte
|
||||
Breakage,
|
||||
Trash,
|
||||
}
|
||||
|
||||
public enum DeliverySpawnerVisualLayers : byte
|
||||
{
|
||||
Contents,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
|
||||
namespace Content.Client.DeviceNetwork.Systems;
|
||||
|
||||
public sealed class DeviceNetworkSystem : SharedDeviceNetworkSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -9,22 +9,36 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
public bool TryAddDisplacement(DisplacementData data, SpriteComponent sprite, int index, string key, HashSet<string> revealedLayers)
|
||||
/// <summary>
|
||||
/// Attempting to apply a displacement map to a specific layer of SpriteComponent
|
||||
/// </summary>
|
||||
/// <param name="data">Information package for applying the displacement map</param>
|
||||
/// <param name="sprite">SpriteComponent</param>
|
||||
/// <param name="index">Index of the layer where the new map layer will be added</param>
|
||||
/// <param name="key">Unique layer key, which will determine which layer to apply displacement map to</param>
|
||||
/// <param name="displacementKey">The key of the new displacement map layer added by this function.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryAddDisplacement(DisplacementData data,
|
||||
SpriteComponent sprite,
|
||||
int index,
|
||||
object key,
|
||||
out string displacementKey)
|
||||
{
|
||||
displacementKey = $"{key}-displacement";
|
||||
|
||||
if (key.ToString() is null)
|
||||
return false;
|
||||
|
||||
if (data.ShaderOverride != null)
|
||||
sprite.LayerSetShader(index, data.ShaderOverride);
|
||||
|
||||
var displacementKey = $"{key}-displacement";
|
||||
if (!revealedLayers.Add(displacementKey))
|
||||
{
|
||||
Log.Warning($"Duplicate key for DISPLACEMENT: {displacementKey}.");
|
||||
return false;
|
||||
}
|
||||
if (sprite.LayerMapTryGet(displacementKey, out var oldIndex))
|
||||
sprite.RemoveLayer(oldIndex);
|
||||
|
||||
//allows you not to write it every time in the YML
|
||||
foreach (var pair in data.SizeMaps)
|
||||
{
|
||||
pair.Value.CopyToShaderParameters??= new()
|
||||
pair.Value.CopyToShaderParameters ??= new()
|
||||
{
|
||||
LayerKey = "dummy",
|
||||
ParameterTexture = "displacementMap",
|
||||
@@ -45,21 +59,22 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
if (actualRSI is not null)
|
||||
{
|
||||
if (actualRSI.Size.X != actualRSI.Size.Y)
|
||||
Log.Warning($"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked");
|
||||
{
|
||||
Log.Warning(
|
||||
$"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked");
|
||||
}
|
||||
|
||||
var layerSize = actualRSI.Size.X;
|
||||
if (data.SizeMaps.ContainsKey(layerSize))
|
||||
displacementDataLayer = data.SizeMaps[layerSize];
|
||||
if (data.SizeMaps.TryGetValue(layerSize, out var map))
|
||||
displacementDataLayer = map;
|
||||
}
|
||||
|
||||
var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true);
|
||||
displacementLayer.CopyToShaderParameters!.LayerKey = key;
|
||||
displacementLayer.CopyToShaderParameters!.LayerKey = key.ToString() ?? "this is impossible";
|
||||
|
||||
sprite.AddLayer(displacementLayer, index);
|
||||
sprite.LayerMapSet(displacementKey, index);
|
||||
|
||||
revealedLayers.Add(displacementKey);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
|
||||
namespace Content.Client.Disposal;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DisposalUnitComponent : SharedDisposalUnitComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Content.Client.Disposal.Unit;
|
||||
using Content.Client.Power.EntitySystems;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing;
|
||||
|
||||
public sealed class MailingUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
public MailingUnitWindow? MailingUnitWindow;
|
||||
|
||||
public MailingUnitBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(DisposalUnitComponent.UiButton button)
|
||||
{
|
||||
SendMessage(new DisposalUnitComponent.UiButtonPressedMessage(button));
|
||||
// If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs
|
||||
// the pressure lerp up.
|
||||
}
|
||||
|
||||
private void TargetSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
var item = args.ItemList[args.ItemIndex];
|
||||
SendMessage(new TargetSelectedMessage(item.Text));
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
MailingUnitWindow = this.CreateWindow<MailingUnitWindow>();
|
||||
MailingUnitWindow.OpenCenteredRight();
|
||||
|
||||
MailingUnitWindow.Eject.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Eject);
|
||||
MailingUnitWindow.Engage.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Engage);
|
||||
MailingUnitWindow.Power.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Power);
|
||||
|
||||
MailingUnitWindow.TargetListContainer.OnItemSelected += TargetSelected;
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out MailingUnitComponent? component))
|
||||
Refresh((Owner, component));
|
||||
}
|
||||
|
||||
public void Refresh(Entity<MailingUnitComponent> entity)
|
||||
{
|
||||
if (MailingUnitWindow == null)
|
||||
return;
|
||||
|
||||
// TODO: This should be decoupled from disposals
|
||||
if (EntMan.TryGetComponent(entity.Owner, out DisposalUnitComponent? disposals))
|
||||
{
|
||||
var disposalSystem = EntMan.System<DisposalUnitSystem>();
|
||||
|
||||
var disposalState = disposalSystem.GetState(Owner, disposals);
|
||||
var fullPressure = disposalSystem.EstimatedFullPressure(Owner, disposals);
|
||||
|
||||
MailingUnitWindow.UnitState.Text = Loc.GetString($"disposal-unit-state-{disposalState}");
|
||||
MailingUnitWindow.FullPressure = fullPressure;
|
||||
MailingUnitWindow.PressureBar.UpdatePressure(fullPressure);
|
||||
MailingUnitWindow.Power.Pressed = EntMan.System<PowerReceiverSystem>().IsPowered(Owner);
|
||||
MailingUnitWindow.Engage.Pressed = disposals.Engaged;
|
||||
}
|
||||
|
||||
MailingUnitWindow.Title = Loc.GetString("ui-mailing-unit-window-title", ("tag", entity.Comp.Tag ?? " "));
|
||||
//UnitTag.Text = state.Tag;
|
||||
MailingUnitWindow.Target.Text = entity.Comp.Target;
|
||||
|
||||
MailingUnitWindow.TargetListContainer.Clear();
|
||||
foreach (var target in entity.Comp.TargetList)
|
||||
{
|
||||
MailingUnitWindow.TargetListContainer.AddItem(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Content.Client/Disposal/Mailing/MailingUnitSystem.cs
Normal file
22
Content.Client/Disposal/Mailing/MailingUnitSystem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.Disposal.Mailing;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing;
|
||||
|
||||
public sealed class MailingUnitSystem : SharedMailingUnitSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MailingUnitComponent, AfterAutoHandleStateEvent>(OnMailingState);
|
||||
}
|
||||
|
||||
private void OnMailingState(Entity<MailingUnitComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (UserInterfaceSystem.TryGetOpenUi<MailingUnitBoundUserInterface>(ent.Owner, MailingUnitUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Refresh(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Disposal.UI"
|
||||
MinSize="300 400"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:disposal="clr-namespace:Content.Client.Disposal"
|
||||
MinSize="300 400"
|
||||
SetSize="300 400"
|
||||
Resizable="False">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="8">
|
||||
<Label Text="{Loc 'ui-mailing-unit-target-label'}" />
|
||||
<Label Name="Target"
|
||||
Access="Public"
|
||||
Text="" />
|
||||
</BoxContainer>
|
||||
<ItemList Name="TargetListContainer"
|
||||
@@ -18,14 +20,15 @@
|
||||
</ItemList>
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-state'}" />
|
||||
<Label Name="UnitState"
|
||||
<Label Name="UnitState" Access="Public"
|
||||
Text="{Loc 'ui-disposal-unit-label-status'}" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 5" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-pressure'}" />
|
||||
<ui:PressureBar Name="PressureBar"
|
||||
<disposal:PressureBar Name="PressureBar"
|
||||
Access="Public"
|
||||
MinSize="190 20"
|
||||
HorizontalAlignment="Right"
|
||||
MinValue="0"
|
||||
@@ -50,4 +53,4 @@
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
27
Content.Client/Disposal/Mailing/MailingUnitWindow.xaml.cs
Normal file
27
Content.Client/Disposal/Mailing/MailingUnitWindow.xaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="Shared.Disposal.Components.MailingUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MailingUnitWindow : FancyWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public MailingUnitWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.UI;
|
||||
namespace Content.Client.Disposal;
|
||||
|
||||
public sealed class PressureBar : ProgressBar
|
||||
{
|
||||
@@ -1,187 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
namespace Content.Client.Disposal.Systems;
|
||||
|
||||
public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
private const string AnimationKey = "disposal_unit_animation";
|
||||
|
||||
private const string DefaultFlushState = "disposal-flush";
|
||||
private const string DefaultChargeState = "disposal-charging";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, PreventCollideEvent>(OnPreventCollide);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, CanDropTargetEvent>(OnCanDragDropOn);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, DisposalUnitComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not DisposalUnitComponentState state)
|
||||
return;
|
||||
|
||||
component.FlushSound = state.FlushSound;
|
||||
component.State = state.State;
|
||||
component.NextPressurized = state.NextPressurized;
|
||||
component.AutomaticEngageTime = state.AutomaticEngageTime;
|
||||
component.NextFlush = state.NextFlush;
|
||||
component.Powered = state.Powered;
|
||||
component.Engaged = state.Engaged;
|
||||
component.RecentlyEjected.Clear();
|
||||
component.RecentlyEjected.AddRange(EnsureEntityList<DisposalUnitComponent>(state.RecentlyEjected, uid));
|
||||
}
|
||||
|
||||
public override bool HasDisposals(EntityUid? uid)
|
||||
{
|
||||
return HasComp<DisposalUnitComponent>(uid);
|
||||
}
|
||||
|
||||
public override bool ResolveDisposals(EntityUid uid, [NotNullWhen(true)] ref SharedDisposalUnitComponent? component)
|
||||
{
|
||||
if (component != null)
|
||||
return true;
|
||||
|
||||
TryComp<DisposalUnitComponent>(uid, out var storage);
|
||||
component = storage;
|
||||
return component != null;
|
||||
}
|
||||
|
||||
public override void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, SharedDisposalUnitComponent? disposal = null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, SharedDisposalUnitComponent sharedDisposalUnit, ComponentInit args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite) || !TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
return;
|
||||
|
||||
UpdateState(uid, sharedDisposalUnit, sprite, appearance);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, SharedDisposalUnitComponent unit, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
UpdateState(uid, unit, args.Sprite, args.Component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update visuals and tick animation
|
||||
/// </summary>
|
||||
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
|
||||
{
|
||||
if (!_appearanceSystem.TryGetData<VisualState>(uid, Visuals.VisualState, out var state, appearance))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state is VisualState.OverlayFlushing or VisualState.OverlayCharging);
|
||||
|
||||
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
|
||||
? sprite.LayerGetState(chargingLayer)
|
||||
: new RSI.StateId(DefaultChargeState);
|
||||
|
||||
// This is a transient state so not too worried about replaying in range.
|
||||
if (state == VisualState.OverlayFlushing)
|
||||
{
|
||||
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
|
||||
{
|
||||
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
|
||||
? sprite.LayerGetState(flushLayer)
|
||||
: new RSI.StateId(DefaultFlushState);
|
||||
|
||||
// Setup the flush animation to play
|
||||
var anim = new Animation
|
||||
{
|
||||
Length = unit.FlushDelay,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DisposalUnitVisualLayers.OverlayFlush,
|
||||
KeyFrames =
|
||||
{
|
||||
// Play the flush animation
|
||||
new AnimationTrackSpriteFlick.KeyFrame(flushState, 0),
|
||||
// Return to base state (though, depending on how the unit is
|
||||
// configured we might get an appearance change event telling
|
||||
// us to go to charging state)
|
||||
new AnimationTrackSpriteFlick.KeyFrame(chargingState, (float) unit.FlushDelay.TotalSeconds)
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (unit.FlushSound != null)
|
||||
{
|
||||
anim.AnimationTracks.Add(
|
||||
new AnimationTrackPlaySound
|
||||
{
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(unit.FlushSound), 0)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_animationSystem.Play(uid, anim, AnimationKey);
|
||||
}
|
||||
}
|
||||
else if (state == VisualState.OverlayCharging)
|
||||
sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, chargingState);
|
||||
else
|
||||
_animationSystem.Stop(uid, AnimationKey);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState, appearance))
|
||||
handleState = HandleState.Normal;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState, appearance))
|
||||
lightState = LightStates.Off;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
|
||||
(lightState & LightStates.Charging) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayReady,
|
||||
(lightState & LightStates.Ready) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFull,
|
||||
(lightState & LightStates.Full) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DisposalUnitVisualLayers : byte
|
||||
{
|
||||
Unanchored,
|
||||
Base,
|
||||
BaseCharging,
|
||||
OverlayFlush,
|
||||
OverlayCharging,
|
||||
OverlayReady,
|
||||
OverlayFull,
|
||||
OverlayEngaged
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalRouterComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="DisposalRouterWindow"/> and updates it when new server messages are received.
|
||||
@@ -1,11 +1,10 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalRouterComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalRouterComponent"/>
|
||||
@@ -1,9 +1,8 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalTaggerComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="DisposalTaggerWindow"/> and updates it when new server messages are received.
|
||||
@@ -1,11 +1,10 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalTaggerComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalTaggerComponent"/>
|
||||
8
Content.Client/Disposal/Tube/DisposalTubeSystem.cs
Normal file
8
Content.Client/Disposal/Tube/DisposalTubeSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Disposal.Unit;
|
||||
|
||||
namespace Content.Client.Disposal.Tube;
|
||||
|
||||
public sealed class DisposalTubeSystem : SharedDisposalTubeSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
using Content.Client.Disposal.Systems;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="MailingUnitWindow"/> or a <see cref="DisposalUnitWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class DisposalUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
// What are you doing here
|
||||
[ViewVariables]
|
||||
public MailingUnitWindow? MailingUnitWindow;
|
||||
|
||||
[ViewVariables]
|
||||
public DisposalUnitWindow? DisposalUnitWindow;
|
||||
|
||||
public DisposalUnitBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(UiButton button)
|
||||
{
|
||||
SendMessage(new UiButtonPressedMessage(button));
|
||||
// If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs
|
||||
// the pressure lerp up.
|
||||
}
|
||||
|
||||
private void TargetSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
var item = args.ItemList[args.ItemIndex];
|
||||
SendMessage(new TargetSelectedMessage(item.Text));
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if (UiKey is MailingUnitUiKey)
|
||||
{
|
||||
MailingUnitWindow = new MailingUnitWindow();
|
||||
|
||||
MailingUnitWindow.OpenCenteredRight();
|
||||
MailingUnitWindow.OnClose += Close;
|
||||
|
||||
MailingUnitWindow.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||
MailingUnitWindow.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
|
||||
MailingUnitWindow.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
|
||||
|
||||
MailingUnitWindow.TargetListContainer.OnItemSelected += TargetSelected;
|
||||
}
|
||||
else if (UiKey is DisposalUnitUiKey)
|
||||
{
|
||||
DisposalUnitWindow = new DisposalUnitWindow();
|
||||
|
||||
DisposalUnitWindow.OpenCenteredRight();
|
||||
DisposalUnitWindow.OnClose += Close;
|
||||
|
||||
DisposalUnitWindow.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||
DisposalUnitWindow.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
|
||||
DisposalUnitWindow.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not MailingUnitBoundUserInterfaceState && state is not DisposalUnitBoundUserInterfaceState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case MailingUnitBoundUserInterfaceState mailingUnitState:
|
||||
MailingUnitWindow?.UpdateState(mailingUnitState);
|
||||
break;
|
||||
|
||||
case DisposalUnitBoundUserInterfaceState disposalUnitState:
|
||||
DisposalUnitWindow?.UpdateState(disposalUnitState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
MailingUnitWindow?.Dispose();
|
||||
DisposalUnitWindow?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DisposalUnitWindow : DefaultWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public DisposalUnitWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the interface state for the disposals window.
|
||||
/// </summary>
|
||||
/// <returns>true if we should stop updating every frame.</returns>
|
||||
public void UpdateState(DisposalUnitBoundUserInterfaceState state)
|
||||
{
|
||||
Title = state.UnitName;
|
||||
UnitState.Text = state.UnitState;
|
||||
Power.Pressed = state.Powered;
|
||||
Engage.Pressed = state.Engaged;
|
||||
FullPressure = state.FullPressureTime;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="MailingUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MailingUnitWindow : DefaultWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public MailingUnitWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the interface state for the disposals window.
|
||||
/// </summary>
|
||||
/// <returns>true if we should stop updating every frame.</returns>
|
||||
public bool UpdateState(MailingUnitBoundUserInterfaceState state)
|
||||
{
|
||||
var disposalState = state.DisposalState;
|
||||
|
||||
Title = Loc.GetString("ui-mailing-unit-window-title", ("tag", state.Tag ?? " "));
|
||||
UnitState.Text = disposalState.UnitState;
|
||||
FullPressure = disposalState.FullPressureTime;
|
||||
var pressureReached = PressureBar.UpdatePressure(disposalState.FullPressureTime);
|
||||
Power.Pressed = disposalState.Powered;
|
||||
Engage.Pressed = disposalState.Engaged;
|
||||
|
||||
//UnitTag.Text = state.Tag;
|
||||
Target.Text = state.Target;
|
||||
|
||||
TargetListContainer.Clear();
|
||||
foreach (var target in state.TargetList)
|
||||
{
|
||||
TargetListContainer.AddItem(target);
|
||||
}
|
||||
|
||||
return !disposalState.Powered || pressureReached;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Content.Client.Disposal.Mailing;
|
||||
using Content.Client.Power.EntitySystems;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Disposal.Unit
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="MailingUnitWindow"/> or a <see cref="_disposalUnitWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class DisposalUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables] private DisposalUnitWindow? _disposalUnitWindow;
|
||||
|
||||
public DisposalUnitBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(DisposalUnitComponent.UiButton button)
|
||||
{
|
||||
SendPredictedMessage(new DisposalUnitComponent.UiButtonPressedMessage(button));
|
||||
// If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs
|
||||
// the pressure lerp up.
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_disposalUnitWindow = this.CreateWindow<DisposalUnitWindow>();
|
||||
|
||||
_disposalUnitWindow.OpenCenteredRight();
|
||||
|
||||
_disposalUnitWindow.Eject.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Eject);
|
||||
_disposalUnitWindow.Engage.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Engage);
|
||||
_disposalUnitWindow.Power.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Power);
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out DisposalUnitComponent? component))
|
||||
{
|
||||
Refresh((Owner, component));
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh(Entity<DisposalUnitComponent> entity)
|
||||
{
|
||||
if (_disposalUnitWindow == null)
|
||||
return;
|
||||
|
||||
var disposalSystem = EntMan.System<DisposalUnitSystem>();
|
||||
|
||||
_disposalUnitWindow.Title = EntMan.GetComponent<MetaDataComponent>(entity.Owner).EntityName;
|
||||
|
||||
var state = disposalSystem.GetState(entity.Owner, entity.Comp);
|
||||
|
||||
_disposalUnitWindow.UnitState.Text = Loc.GetString($"disposal-unit-state-{state}");
|
||||
_disposalUnitWindow.Power.Pressed = EntMan.System<PowerReceiverSystem>().IsPowered(Owner);
|
||||
_disposalUnitWindow.Engage.Pressed = entity.Comp.Engaged;
|
||||
_disposalUnitWindow.FullPressure = disposalSystem.EstimatedFullPressure(entity.Owner, entity.Comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Content.Client/Disposal/Unit/DisposalUnitSystem.cs
Normal file
151
Content.Client/Disposal/Unit/DisposalUnitSystem.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Client.Disposal.Unit;
|
||||
|
||||
public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
private const string AnimationKey = "disposal_unit_animation";
|
||||
|
||||
private const string DefaultFlushState = "disposal-flush";
|
||||
private const string DefaultChargeState = "disposal-charging";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, DisposalUnitComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUI((uid, component));
|
||||
}
|
||||
|
||||
protected override void UpdateUI(Entity<DisposalUnitComponent> entity)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<DisposalUnitBoundUserInterface>(entity.Owner, DisposalUnitComponent.DisposalUnitUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Refresh(entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisposalInit(Entity<DisposalUnitComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
base.OnDisposalInit(ent, ref args);
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite) || !TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
return;
|
||||
|
||||
UpdateState(ent, sprite, appearance);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(Entity<DisposalUnitComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
UpdateState(ent, args.Sprite, args.Component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update visuals and tick animation
|
||||
/// </summary>
|
||||
private void UpdateState(Entity<DisposalUnitComponent> ent, SpriteComponent sprite, AppearanceComponent appearance)
|
||||
{
|
||||
if (!_appearanceSystem.TryGetData<DisposalUnitComponent.VisualState>(ent, DisposalUnitComponent.Visuals.VisualState, out var state, appearance))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == DisposalUnitComponent.VisualState.UnAnchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == DisposalUnitComponent.VisualState.Anchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state == DisposalUnitComponent.VisualState.OverlayFlushing);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseCharging, state == DisposalUnitComponent.VisualState.OverlayCharging);
|
||||
|
||||
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
|
||||
? sprite.LayerGetState(chargingLayer)
|
||||
: new RSI.StateId(DefaultChargeState);
|
||||
|
||||
// This is a transient state so not too worried about replaying in range.
|
||||
if (state == DisposalUnitComponent.VisualState.OverlayFlushing)
|
||||
{
|
||||
if (!_animationSystem.HasRunningAnimation(ent, AnimationKey))
|
||||
{
|
||||
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
|
||||
? sprite.LayerGetState(flushLayer)
|
||||
: new RSI.StateId(DefaultFlushState);
|
||||
|
||||
// Setup the flush animation to play
|
||||
var anim = new Animation
|
||||
{
|
||||
Length = ent.Comp.FlushDelay,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DisposalUnitVisualLayers.OverlayFlush,
|
||||
KeyFrames =
|
||||
{
|
||||
// Play the flush animation
|
||||
new AnimationTrackSpriteFlick.KeyFrame(flushState, 0),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (ent.Comp.FlushSound != null)
|
||||
{
|
||||
anim.AnimationTracks.Add(
|
||||
new AnimationTrackPlaySound
|
||||
{
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.FlushSound), 0)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_animationSystem.Play(ent, anim, AnimationKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
_animationSystem.Stop(ent.Owner, AnimationKey);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<DisposalUnitComponent.HandleState>(ent, DisposalUnitComponent.Visuals.Handle, out var handleState, appearance))
|
||||
handleState = DisposalUnitComponent.HandleState.Normal;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != DisposalUnitComponent.HandleState.Normal);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<DisposalUnitComponent.LightStates>(ent, DisposalUnitComponent.Visuals.Light, out var lightState, appearance))
|
||||
lightState = DisposalUnitComponent.LightStates.Off;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
|
||||
(lightState & DisposalUnitComponent.LightStates.Charging) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayReady,
|
||||
(lightState & DisposalUnitComponent.LightStates.Ready) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFull,
|
||||
(lightState & DisposalUnitComponent.LightStates.Full) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DisposalUnitVisualLayers : byte
|
||||
{
|
||||
Unanchored,
|
||||
Base,
|
||||
BaseCharging,
|
||||
OverlayFlush,
|
||||
OverlayCharging,
|
||||
OverlayReady,
|
||||
OverlayFull,
|
||||
OverlayEngaged
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Disposal.UI"
|
||||
MinSize="300 140"
|
||||
SetSize="300 140"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:disposal="clr-namespace:Content.Client.Disposal"
|
||||
MinSize="300 140"
|
||||
SetSize="300 160"
|
||||
Resizable="False">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-state'}" />
|
||||
<Label Name="UnitState"
|
||||
<Label Name="UnitState" Access="Public"
|
||||
Text="{Loc 'ui-disposal-unit-label-status'}" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 5" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-pressure'}" />
|
||||
<ui:PressureBar Name="PressureBar"
|
||||
<disposal:PressureBar Name="PressureBar"
|
||||
MinSize="190 20"
|
||||
HorizontalAlignment="Right"
|
||||
MinValue="0"
|
||||
@@ -23,7 +24,7 @@
|
||||
Value="0.5" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 10" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Button Name="Engage"
|
||||
Access="Public"
|
||||
Text="{Loc 'ui-disposal-unit-button-flush'}"
|
||||
@@ -39,4 +40,4 @@
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
28
Content.Client/Disposal/Unit/DisposalUnitWindow.xaml.cs
Normal file
28
Content.Client/Disposal/Unit/DisposalUnitWindow.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.Unit
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="Shared.Disposal.Components.DisposalUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DisposalUnitWindow : FancyWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public DisposalUnitWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
@@ -14,6 +15,30 @@ public sealed class FirelockSystem : SharedFirelockSystem
|
||||
SubscribeLocalEvent<FirelockComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
protected override void OnComponentStartup(Entity<FirelockComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
base.OnComponentStartup(ent, ref args);
|
||||
if(!TryComp<DoorComponent>(ent.Owner, out var door))
|
||||
return;
|
||||
|
||||
door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, ent.Comp.WarningLightSpriteState));
|
||||
door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, ent.Comp.WarningLightSpriteState));
|
||||
|
||||
((Animation)door.OpeningAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
|
||||
{
|
||||
LayerKey = DoorVisualLayers.BaseUnlit,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.OpeningLightSpriteState, 0f) },
|
||||
}
|
||||
);
|
||||
|
||||
((Animation)door.ClosingAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
|
||||
{
|
||||
LayerKey = DoorVisualLayers.BaseUnlit,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.ClosingLightSpriteState, 0f) },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, FirelockComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
|
||||
75
Content.Client/Doors/TurnstileSystem.cs
Normal file
75
Content.Client/Doors/TurnstileSystem.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class TurnstileSystem : SharedTurnstileSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
|
||||
private static EntProtoId _examineArrow = "TurnstileArrow";
|
||||
|
||||
private const string AnimationKey = "Turnstile";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TurnstileComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<TurnstileComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(Entity<TurnstileComponent> ent, ref AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key != AnimationKey)
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
sprite.LayerSetState(TurnstileVisualLayers.Base, new RSI.StateId(ent.Comp.DefaultState));
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<TurnstileComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
Spawn(_examineArrow, new EntityCoordinates(ent, 0, 0));
|
||||
}
|
||||
|
||||
protected override void PlayAnimation(EntityUid uid, string stateId)
|
||||
{
|
||||
if (!TryComp<AnimationPlayerComponent>(uid, out var animation) || !TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
var ent = (uid, animation);
|
||||
|
||||
if (_animationPlayer.HasRunningAnimation(animation, AnimationKey))
|
||||
_animationPlayer.Stop(ent, AnimationKey);
|
||||
|
||||
if (sprite.BaseRSI == null || !sprite.BaseRSI.TryGetState(stateId, out var state))
|
||||
return;
|
||||
var animLength = state.AnimationLength;
|
||||
|
||||
var anim = new Animation
|
||||
{
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = TurnstileVisualLayers.Base,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(state.StateId, 0f),
|
||||
},
|
||||
},
|
||||
},
|
||||
Length = TimeSpan.FromSeconds(animLength),
|
||||
};
|
||||
|
||||
_animationPlayer.Play(ent, anim, AnimationKey);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ namespace Content.Client.Examine
|
||||
[Dependency] private readonly VerbSystem _verbSystem = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
private List<Verb> _verbList = new();
|
||||
|
||||
public const string StyleClassEntityTooltip = "entity-tooltip";
|
||||
|
||||
private EntityUid _examinedEntity;
|
||||
@@ -158,13 +160,13 @@ namespace Content.Client.Examine
|
||||
var entity = GetEntity(ev.EntityUid);
|
||||
|
||||
OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
|
||||
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs);
|
||||
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs, getVerbs: false);
|
||||
}
|
||||
|
||||
public override void SendExamineTooltip(EntityUid player, EntityUid target, FormattedMessage message, bool getVerbs, bool centerAtCursor)
|
||||
{
|
||||
OpenTooltip(player, target, centerAtCursor, false);
|
||||
UpdateTooltipInfo(player, target, message);
|
||||
OpenTooltip(player, target, centerAtCursor);
|
||||
UpdateTooltipInfo(player, target, message, getVerbs: getVerbs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,7 +261,7 @@ namespace Content.Client.Examine
|
||||
/// <summary>
|
||||
/// Fills the examine tooltip with a message and buttons if applicable.
|
||||
/// </summary>
|
||||
public void UpdateTooltipInfo(EntityUid player, EntityUid target, FormattedMessage message, List<Verb>? verbs=null)
|
||||
public void UpdateTooltipInfo(EntityUid player, EntityUid target, FormattedMessage message, List<Verb>? verbs=null, bool getVerbs = true)
|
||||
{
|
||||
var vBox = _examineTooltipOpen?.GetChild(0).GetChild(0);
|
||||
if (vBox == null)
|
||||
@@ -283,9 +285,29 @@ namespace Content.Client.Examine
|
||||
break;
|
||||
}
|
||||
|
||||
verbs ??= new List<Verb>();
|
||||
var totalVerbs = _verbSystem.GetLocalVerbs(target, player, typeof(ExamineVerb));
|
||||
totalVerbs.UnionWith(verbs);
|
||||
|
||||
// We still need client-exclusive verbs even when the server sends its data in so if that's the case
|
||||
// we remove any non-client-exclusive verbs.
|
||||
if (!getVerbs)
|
||||
{
|
||||
_verbList.AddRange(totalVerbs);
|
||||
|
||||
foreach (var verb in _verbList)
|
||||
{
|
||||
if (!verb.ClientExclusive)
|
||||
{
|
||||
totalVerbs.Remove(verb);
|
||||
}
|
||||
}
|
||||
|
||||
_verbList.Clear();
|
||||
}
|
||||
|
||||
if (verbs != null)
|
||||
{
|
||||
totalVerbs.UnionWith(verbs);
|
||||
}
|
||||
|
||||
AddVerbsToTooltip(totalVerbs);
|
||||
}
|
||||
|
||||
@@ -348,14 +348,16 @@ namespace Content.Client.Hands.Systems
|
||||
|
||||
sprite.LayerSetData(index, layerData);
|
||||
|
||||
//Add displacement maps
|
||||
if (hand.Location == HandLocation.Left && handComp.LeftHandDisplacement is not null)
|
||||
_displacement.TryAddDisplacement(handComp.LeftHandDisplacement, sprite, index, key, revealedLayers);
|
||||
else if (hand.Location == HandLocation.Right && handComp.RightHandDisplacement is not null)
|
||||
_displacement.TryAddDisplacement(handComp.RightHandDisplacement, sprite, index, key, revealedLayers);
|
||||
//Fallback to default displacement map
|
||||
else if (handComp.HandDisplacement is not null)
|
||||
_displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers);
|
||||
// Add displacement maps
|
||||
var displacement = hand.Location switch
|
||||
{
|
||||
HandLocation.Left => handComp.LeftHandDisplacement,
|
||||
HandLocation.Right => handComp.RightHandDisplacement,
|
||||
_ => handComp.HandDisplacement
|
||||
};
|
||||
|
||||
if (displacement is not null && _displacement.TryAddDisplacement(displacement, sprite, index, key, out var displacementKey))
|
||||
revealedLayers.Add(displacementKey);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.DisplacementMap;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
@@ -16,6 +17,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -380,6 +382,11 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
sprite.LayerSetColor(layerId, Color.White);
|
||||
}
|
||||
|
||||
if (humanoid.MarkingsDisplacement.TryGetValue(markingPrototype.BodyPart, out var displacementData) && markingPrototype.CanBeDisplaced)
|
||||
{
|
||||
_displacement.TryAddDisplacement(displacementData, sprite, targetLayer + j + 1, layerId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:info="clr-namespace:Content.Client.Info"
|
||||
VerticalExpand="True" HorizontalExpand="True"
|
||||
MouseFilter="Stop">
|
||||
<parallax:ParallaxControl />
|
||||
<parallax:ParallaxControl SpeedX="20"/>
|
||||
<Control VerticalExpand="True"
|
||||
MaxWidth="800"
|
||||
MaxHeight="900">
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Item;
|
||||
|
||||
namespace Content.Client.Items.Systems;
|
||||
|
||||
public sealed class MultiHandedItemSystem : SharedMultiHandedItemSystem
|
||||
{
|
||||
protected override void OnEquipped(EntityUid uid, MultiHandedItemComponent component, GotEquippedHandEvent args)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnUnequipped(EntityUid uid, MultiHandedItemComponent component, GotUnequippedHandEvent args)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
|
||||
namespace Content.Client.Labels;
|
||||
|
||||
public sealed partial class LabelSystem : SharedLabelSystem
|
||||
{
|
||||
}
|
||||
@@ -127,12 +127,17 @@
|
||||
HorizontalExpand="True"
|
||||
Orientation="Vertical">
|
||||
<Label Text="{Loc 'lathe-menu-materials-title'}" Margin="5 5 5 5" HorizontalAlignment="Center"/>
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
<ui:MaterialStorageControl Name="MaterialsList" SizeFlagsStretchRatio="8"/>
|
||||
</BoxContainer>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
<ui:MaterialStorageControl Name="MaterialsList" SizeFlagsStretchRatio="8"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:parallax="clr-namespace:Content.Client.Parallax"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<parallax:ParallaxControl />
|
||||
<parallax:ParallaxControl SpeedX="20"/>
|
||||
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Orientation="Vertical" MinSize="300 200">
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:pllax="clr-namespace:Content.Client.Parallax"
|
||||
xmlns:clog="clr-namespace:Content.Client.Changelog">
|
||||
<pllax:ParallaxControl />
|
||||
<pllax:ParallaxControl
|
||||
ParallaxPrototype="TrainStation"
|
||||
SpeedX="5"
|
||||
SpeedY="20"
|
||||
ScaleX="3"
|
||||
ScaleY="3"/>
|
||||
<BoxContainer Name="VBox"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
|
||||
6
Content.Client/Materials/OreSiloSystem.cs
Normal file
6
Content.Client/Materials/OreSiloSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Materials.OreSilo;
|
||||
|
||||
namespace Content.Client.Materials;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class OreSiloSystem : SharedOreSiloSystem;
|
||||
@@ -2,7 +2,10 @@
|
||||
SizeFlagsStretchRatio="8"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer Name="MaterialList" Orientation="Vertical">
|
||||
<Label Name="NoMatsLabel" Text="{Loc 'lathe-menu-no-materials-message'}" Align="Center"/>
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Name="MaterialList" Orientation="Vertical" VerticalExpand="True">
|
||||
<Label Name="NoMatsLabel" Text="{Loc 'lathe-menu-no-materials-message'}" HorizontalAlignment="Center" VerticalAlignment="Center" VerticalExpand="True"/>
|
||||
</BoxContainer>
|
||||
<Label Name="SiloLinkedLabel" Text="{Loc 'lathe-menu-silo-linked-message'}" StyleClasses="LabelSubText" Visible="False" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Materials.OreSilo;
|
||||
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.Materials.UI;
|
||||
@@ -14,15 +16,18 @@ namespace Content.Client.Materials.UI;
|
||||
public sealed partial class MaterialStorageControl : ScrollContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
private readonly MaterialStorageSystem _materialStorage;
|
||||
|
||||
private EntityUid? _owner;
|
||||
|
||||
private Dictionary<string, int> _currentMaterials = new();
|
||||
private Dictionary<ProtoId<MaterialPrototype>, int> _currentMaterials = new();
|
||||
|
||||
public MaterialStorageControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_materialStorage = _entityManager.System<MaterialStorageSystem>();
|
||||
}
|
||||
|
||||
public void SetOwner(EntityUid owner)
|
||||
@@ -44,7 +49,8 @@ public sealed partial class MaterialStorageControl : ScrollContainer
|
||||
}
|
||||
|
||||
var canEject = materialStorage.CanEjectStoredMaterials;
|
||||
var mats = materialStorage.Storage.Select(pair => (pair.Key.Id, pair.Value)).ToDictionary();
|
||||
var mats = _materialStorage.GetStoredMaterials((_owner.Value, materialStorage));
|
||||
|
||||
if (_currentMaterials.Equals(mats))
|
||||
return;
|
||||
|
||||
@@ -88,5 +94,6 @@ public sealed partial class MaterialStorageControl : ScrollContainer
|
||||
|
||||
_currentMaterials = mats;
|
||||
NoMatsLabel.Visible = MaterialList.ChildCount == 1;
|
||||
SiloLinkedLabel.Visible = _entityManager.TryGetComponent<OreSiloClientComponent>(_owner.Value, out var client) && client.Silo != null;
|
||||
}
|
||||
}
|
||||
|
||||
34
Content.Client/Materials/UI/OreSiloBoundUserInterface.cs
Normal file
34
Content.Client/Materials/UI/OreSiloBoundUserInterface.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Materials.OreSilo;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Materials.UI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class OreSiloBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private OreSiloMenu? _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<OreSiloMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
|
||||
_menu.OnClientEntryPressed += netEnt =>
|
||||
{
|
||||
SendPredictedMessage(new ToggleOreSiloClientMessage(netEnt));
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not OreSiloBuiState msg)
|
||||
return;
|
||||
_menu?.Update(msg);
|
||||
}
|
||||
}
|
||||
42
Content.Client/Materials/UI/OreSiloMenu.xaml
Normal file
42
Content.Client/Materials/UI/OreSiloMenu.xaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:ui="clr-namespace:Content.Client.Materials.UI"
|
||||
Title="{Loc 'ore-silo-ui-title'}"
|
||||
MinSize="400 260"
|
||||
SetSize="400 460">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
Margin="10 10 10 10">
|
||||
<BoxContainer VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Orientation="Vertical"
|
||||
SizeFlagsStretchRatio="3">
|
||||
<Label Text="{Loc 'ore-silo-ui-label-clients'}" Margin="5 5 5 5" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ItemList Name="ClientList" SelectMode="Button" VerticalExpand="True"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Orientation="Vertical"
|
||||
SizeFlagsStretchRatio="2">
|
||||
<Label Text="{Loc 'ore-silo-ui-label-mats'}" Margin="5 5 5 5" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
|
||||
<PanelContainer VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
<ui:MaterialStorageControl Name="Materials"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
64
Content.Client/Materials/UI/OreSiloMenu.xaml.cs
Normal file
64
Content.Client/Materials/UI/OreSiloMenu.xaml.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Materials.OreSilo;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Materials.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class OreSiloMenu : FancyWindow
|
||||
{
|
||||
public event Action<NetEntity>? OnClientEntryPressed;
|
||||
|
||||
public OreSiloMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ClientList.OnItemSelected += args =>
|
||||
{
|
||||
var item = ClientList[args.ItemIndex];
|
||||
// a little bit of null suppression makes me feel great! :-)
|
||||
OnClientEntryPressed?.Invoke((NetEntity) item.Metadata!);
|
||||
};
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid uid)
|
||||
{
|
||||
Materials.SetOwner(uid);
|
||||
}
|
||||
|
||||
public void Update(OreSiloBuiState state)
|
||||
{
|
||||
var items = new List<ItemList.Item>();
|
||||
var orderedClients = state.Clients.OrderBy(t => t.Item3).ThenBy(t => t.Item1.Id);
|
||||
foreach (var (ent, _, _) in orderedClients)
|
||||
{
|
||||
items.Add(new ItemList.Item(ClientList)
|
||||
{
|
||||
Metadata = ent
|
||||
});
|
||||
}
|
||||
|
||||
ClientList.SetItems(items,
|
||||
(item1, item2) =>
|
||||
{
|
||||
var ent1 = (NetEntity) item1.Metadata!;
|
||||
var ent2 = (NetEntity) item2.Metadata!;
|
||||
return ent1.CompareTo(ent2);
|
||||
});
|
||||
|
||||
var entTextDict = state.Clients.Select(t => (t.Item1, t.Item2)).ToDictionary();
|
||||
using var enumerator = ClientList.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (enumerator.Current.Metadata is not NetEntity ent)
|
||||
continue;
|
||||
|
||||
if (entTextDict.TryGetValue(ent, out var text))
|
||||
enumerator.Current.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,28 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
|
||||
NoServerLabel.Visible = false;
|
||||
|
||||
// Collect one status per user, using the sensor with the most data available.
|
||||
Dictionary<NetEntity, SuitSensorStatus> uniqueSensorsMap = new();
|
||||
foreach (var sensor in sensors)
|
||||
{
|
||||
if (uniqueSensorsMap.TryGetValue(sensor.OwnerUid, out var existingSensor))
|
||||
{
|
||||
// Skip if we already have a sensor with more data for this mob.
|
||||
if (existingSensor.Coordinates != null && sensor.Coordinates == null)
|
||||
continue;
|
||||
|
||||
if (existingSensor.DamagePercentage != null && sensor.DamagePercentage == null)
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueSensorsMap[sensor.OwnerUid] = sensor;
|
||||
}
|
||||
var uniqueSensors = uniqueSensorsMap.Values.ToList();
|
||||
|
||||
// Order sensor data
|
||||
var orderedSensors = sensors.OrderBy(n => n.Name).OrderBy(j => j.Job);
|
||||
var orderedSensors = uniqueSensors.OrderBy(n => n.Name).OrderBy(j => j.Job);
|
||||
var assignedSensors = new HashSet<SuitSensorStatus>();
|
||||
var departments = sensors.SelectMany(d => d.JobDepartments).Distinct().OrderBy(n => n);
|
||||
var departments = uniqueSensors.SelectMany(d => d.JobDepartments).Distinct().OrderBy(n => n);
|
||||
|
||||
// Create department labels and populate lists
|
||||
foreach (var department in departments)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
|
||||
namespace Content.Client.NetworkConfigurator.Systems;
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
<BoxContainer Orientation="Vertical" Margin="8">
|
||||
<Label Text="{Loc 'ui-options-admin-player-panel'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="PlayerlistSeparateSymbolsCheckBox" Text="{Loc 'ui-options-admin-playerlist-separate-symbols'}" />
|
||||
<CheckBox Name="PlayerlistCharacterColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-character-color'}" />
|
||||
<CheckBox Name="PlayerlistRoleTypeColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-roletype-color'}" />
|
||||
<ui:OptionDropDown Name="DropDownPlayerTabSymbolSetting" Title="{Loc 'ui-options-admin-player-tab-symbol-setting'}" />
|
||||
<ui:OptionDropDown Name="DropDownPlayerTabRoleSetting" Title="{Loc 'ui-options-admin-player-tab-role-setting'}" />
|
||||
<ui:OptionDropDown Name="DropDownPlayerTabColorSetting" Title="{Loc 'ui-options-admin-player-tab-color-setting'}" />
|
||||
<Label Text="{Loc 'ui-options-admin-overlay-title'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="EnableClassicOverlayCheckBox" Text="{Loc 'ui-options-enable-classic-overlay'}" />
|
||||
<CheckBox Name="EnableOverlaySymbolsCheckBox" Text="{Loc 'ui-options-enable-overlay-symbols'}" />
|
||||
<CheckBox Name="EnableOverlayPlaytimeCheckBox" Text="{Loc 'ui-options-enable-overlay-playtime'}" />
|
||||
<CheckBox Name="EnableOverlayStartingJobCheckBox" Text="{Loc 'ui-options-enable-overlay-starting-job'}" />
|
||||
<ui:OptionSlider Name="OverlayMergeDistanceSlider" Title="{Loc 'ui-options-overlay-merge-distance'}"/>
|
||||
<ui:OptionSlider Name="OverlayGhostFadeSlider" Title="{Loc 'ui-options-overlay-ghost-fade-distance'}"/>
|
||||
<ui:OptionSlider Name="OverlayGhostHideSlider" Title="{Loc 'ui-options-overlay-ghost-hide-distance'}"/>
|
||||
<ui:OptionDropDown Name="DropDownOverlayAntagFormat" Title="{Loc 'ui-options-admin-overlay-antag-format'}" />
|
||||
<ui:OptionDropDown Name="DropDownOverlayAntagSymbol" Title="{Loc 'ui-options-admin-overlay-antag-symbol'}" />
|
||||
<CheckBox Name="EnableOverlayPlaytimeCheckBox" Text="{Loc 'ui-options-admin-enable-overlay-playtime'}" />
|
||||
<CheckBox Name="EnableOverlayStartingJobCheckBox" Text="{Loc 'ui-options-admin-enable-overlay-starting-job'}" />
|
||||
<ui:OptionSlider Name="OverlayMergeDistanceSlider" Title="{Loc 'ui-options-admin-overlay-merge-distance'}"/>
|
||||
<ui:OptionSlider Name="OverlayGhostFadeSlider" Title="{Loc 'ui-options-admin-overlay-ghost-fade-distance'}"/>
|
||||
<ui:OptionSlider Name="OverlayGhostHideSlider" Title="{Loc 'ui-options-admin-overlay-ghost-hide-distance'}"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Content.Client.Administration;
|
||||
using Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -19,12 +21,43 @@ public sealed partial class AdminOptionsTab : Control
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AdminPlayerlistSeparateSymbols, PlayerlistSeparateSymbolsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerlistCharacterColorCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminPlayerlistRoleTypeColor, PlayerlistRoleTypeColorCheckBox);
|
||||
var antagFormats = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var format in Enum.GetValues(typeof(AdminOverlayAntagFormat)))
|
||||
{
|
||||
antagFormats.Add(new OptionDropDownCVar<string>.ValueOption(format.ToString()!, Loc.GetString($"ui-options-admin-overlay-antag-format-{format.ToString()!.ToLower()}")));
|
||||
}
|
||||
|
||||
var antagSymbolStyles = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var symbol in Enum.GetValues(typeof(AdminOverlayAntagSymbolStyle)))
|
||||
{
|
||||
antagSymbolStyles.Add(new OptionDropDownCVar<string>.ValueOption(symbol.ToString()!, Loc.GetString($"ui-options-admin-overlay-antag-symbol-{symbol.ToString()!.ToLower()}")));
|
||||
}
|
||||
|
||||
var playerTabColorSettings = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var setting in Enum.GetValues(typeof(AdminPlayerTabColorOption)))
|
||||
{
|
||||
playerTabColorSettings.Add(new OptionDropDownCVar<string>.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-color-setting-{setting.ToString()!.ToLower()}")));
|
||||
}
|
||||
|
||||
var playerTabRoleSettings = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var setting in Enum.GetValues(typeof(AdminPlayerTabRoleTypeOption)))
|
||||
{
|
||||
playerTabRoleSettings.Add(new OptionDropDownCVar<string>.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-role-setting-{setting.ToString()!.ToLower()}")));
|
||||
}
|
||||
|
||||
var playerTabSymbolSettings = new List<OptionDropDownCVar<string>.ValueOption>();
|
||||
foreach (var setting in Enum.GetValues(typeof(AdminPlayerTabSymbolOption)))
|
||||
{
|
||||
playerTabSymbolSettings.Add(new OptionDropDownCVar<string>.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-symbol-setting-{setting.ToString()!.ToLower()}")));
|
||||
}
|
||||
|
||||
Control.AddOptionDropDown(CCVars.AdminPlayerTabSymbolSetting, DropDownPlayerTabSymbolSetting, playerTabSymbolSettings);
|
||||
Control.AddOptionDropDown(CCVars.AdminPlayerTabRoleSetting, DropDownPlayerTabRoleSetting, playerTabRoleSettings);
|
||||
Control.AddOptionDropDown(CCVars.AdminPlayerTabColorSetting, DropDownPlayerTabColorSetting, playerTabColorSettings);
|
||||
|
||||
Control.AddOptionDropDown(CCVars.AdminOverlayAntagFormat, DropDownOverlayAntagFormat, antagFormats);
|
||||
Control.AddOptionDropDown(CCVars.AdminOverlaySymbolStyle, DropDownOverlayAntagSymbol, antagSymbolStyles);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayClassic, EnableClassicOverlayCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlaySymbols, EnableOverlaySymbolsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayPlaytime, EnableOverlayPlaytimeCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayStartingJob, EnableOverlayStartingJobCheckBox);
|
||||
|
||||
|
||||
@@ -7,40 +7,21 @@ using Robust.Shared.Timing;
|
||||
namespace Content.Client.PDA.Ringer
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class RingerBoundUserInterface : BoundUserInterface
|
||||
public sealed class RingerBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private RingtoneMenu? _menu;
|
||||
|
||||
public RingerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_menu = this.CreateWindow<RingtoneMenu>();
|
||||
_menu.OpenToLeft();
|
||||
|
||||
_menu.TestRingerButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new RingerPlayRingtoneMessage());
|
||||
};
|
||||
_menu.TestRingtoneButtonPressed += OnTestRingtoneButtonPressed;
|
||||
_menu.SetRingtoneButtonPressed += OnSetRingtoneButtonPressed;
|
||||
|
||||
_menu.SetRingerButton.OnPressed += _ =>
|
||||
{
|
||||
if (!TryGetRingtone(out var ringtone))
|
||||
return;
|
||||
|
||||
SendMessage(new RingerSetRingtoneMessage(ringtone));
|
||||
_menu.SetRingerButton.Disabled = true;
|
||||
|
||||
Timer.Spawn(333, () =>
|
||||
{
|
||||
if (_menu is { Disposed: false, SetRingerButton: { Disposed: false } ringer})
|
||||
ringer.Disabled = false;
|
||||
});
|
||||
};
|
||||
Update();
|
||||
}
|
||||
|
||||
private bool TryGetRingtone(out Note[] ringtone)
|
||||
@@ -63,36 +44,59 @@ namespace Content.Client.PDA.Ringer
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public override void Update()
|
||||
{
|
||||
base.UpdateState(state);
|
||||
base.Update();
|
||||
|
||||
if (_menu == null || state is not RingerUpdateState msg)
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _menu.RingerNoteInputs.Length; i++)
|
||||
if (!EntMan.TryGetComponent(Owner, out RingerComponent? ringer))
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _menu.RingerNoteInputs.Length; i++)
|
||||
{
|
||||
var note = ringer.Ringtone[i].ToString();
|
||||
|
||||
var note = msg.Ringtone[i].ToString();
|
||||
if (RingtoneMenu.IsNote(note))
|
||||
{
|
||||
_menu.PreviousNoteInputs[i] = note.Replace("sharp", "#");
|
||||
_menu.RingerNoteInputs[i].Text = _menu.PreviousNoteInputs[i];
|
||||
}
|
||||
if (!RingtoneMenu.IsNote(note))
|
||||
continue;
|
||||
|
||||
_menu.PreviousNoteInputs[i] = note.Replace("sharp", "#");
|
||||
_menu.RingerNoteInputs[i].Text = _menu.PreviousNoteInputs[i];
|
||||
}
|
||||
|
||||
_menu.TestRingerButton.Disabled = msg.IsPlaying;
|
||||
_menu.TestRingerButton.Disabled = ringer.Active;
|
||||
}
|
||||
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
private void OnTestRingtoneButtonPressed()
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
if (_menu is null)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
SendPredictedMessage(new RingerPlayRingtoneMessage());
|
||||
|
||||
// We disable it instantly to remove the delay before the client receives the next compstate
|
||||
// Makes the UI feel responsive, will be re-enabled by ringer.Active once it gets an update.
|
||||
_menu.TestRingerButton.Disabled = true;
|
||||
}
|
||||
|
||||
private void OnSetRingtoneButtonPressed()
|
||||
{
|
||||
if (_menu is null)
|
||||
return;
|
||||
|
||||
if (!TryGetRingtone(out var ringtone))
|
||||
return;
|
||||
|
||||
SendPredictedMessage(new RingerSetRingtoneMessage(ringtone));
|
||||
_menu.SetRingerButton.Disabled = true;
|
||||
|
||||
Timer.Spawn(333,
|
||||
() =>
|
||||
{
|
||||
if (_menu is { Disposed: false, SetRingerButton: { Disposed: false } ringer} )
|
||||
ringer.Disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
Content.Client/PDA/Ringer/RingerSystem.cs
Normal file
36
Content.Client/PDA/Ringer/RingerSystem.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.PDA.Ringer;
|
||||
using Content.Shared.Store.Components;
|
||||
|
||||
namespace Content.Client.PDA.Ringer;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the client-side logic for <see cref="SharedRingerSystem"/>.
|
||||
/// </summary>
|
||||
public sealed class RingerSystem : SharedRingerSystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RingerComponent, AfterAutoHandleStateEvent>(OnRingerUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the UI whenever we get a new component state from the server.
|
||||
/// </summary>
|
||||
private void OnRingerUpdate(Entity<RingerComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateRingerUi(ent);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void UpdateRingerUi(Entity<RingerComponent> ent)
|
||||
{
|
||||
if (UI.TryGetOpenUi(ent.Owner, RingerUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'comp-ringer-ui-menu-title'}"
|
||||
MinSize="320 128"
|
||||
SetSize="320 128">
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'comp-ringer-ui-menu-title'}"
|
||||
MinSize="320 100"
|
||||
SetSize="320 100">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
@@ -90,4 +91,4 @@
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -8,49 +9,68 @@ using Robust.Client.UserInterface.Controls;
|
||||
namespace Content.Client.PDA.Ringer
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RingtoneMenu : DefaultWindow
|
||||
public sealed partial class RingtoneMenu : FancyWindow
|
||||
{
|
||||
public string[] PreviousNoteInputs = new[] { "A", "A", "A", "A", "A", "A" };
|
||||
public LineEdit[] RingerNoteInputs = default!;
|
||||
public LineEdit[] RingerNoteInputs;
|
||||
|
||||
public event Action? SetRingtoneButtonPressed;
|
||||
public event Action? TestRingtoneButtonPressed;
|
||||
|
||||
public RingtoneMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
SetRingerButton.OnPressed += _ => SetRingtoneButtonPressed?.Invoke();
|
||||
TestRingerButton.OnPressed += _ => TestRingtoneButtonPressed?.Invoke();
|
||||
|
||||
RingerNoteInputs = new[] { RingerNoteOneInput, RingerNoteTwoInput, RingerNoteThreeInput, RingerNoteFourInput, RingerNoteFiveInput, RingerNoteSixInput };
|
||||
|
||||
for (var i = 0; i < RingerNoteInputs.Length; ++i)
|
||||
{
|
||||
var input = RingerNoteInputs[i];
|
||||
var index = i;
|
||||
var foo = () => // Prevents unauthorized characters from being entered into the LineEdit
|
||||
{
|
||||
input.Text = input.Text.ToUpper();
|
||||
|
||||
input.OnTextChanged += args =>
|
||||
{
|
||||
if (input.Text.Length <= 0)
|
||||
return;
|
||||
|
||||
input.Text = args.Text.ToUpper();
|
||||
|
||||
var isValid = IsNote(input.Text);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
input.AddStyleClass("Caution");
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousNoteInputs[index] = input.Text;
|
||||
input.RemoveStyleClass("Caution");
|
||||
}
|
||||
|
||||
input.CursorPosition = input.Text.Length;
|
||||
};
|
||||
|
||||
input.OnFocusExit += _ =>
|
||||
{
|
||||
if (!IsNote(input.Text))
|
||||
{
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
input.RemoveStyleClass("Caution");
|
||||
}
|
||||
else
|
||||
PreviousNoteInputs[index] = input.Text;
|
||||
|
||||
input.RemoveStyleClass("Caution");
|
||||
};
|
||||
|
||||
input.OnFocusExit += _ => foo();
|
||||
input.OnTextEntered += _ =>
|
||||
{
|
||||
foo();
|
||||
input.CursorPosition = input.Text.Length; // Resets caret position to the end of the typed input
|
||||
};
|
||||
input.OnTextChanged += _ =>
|
||||
{
|
||||
input.Text = input.Text.ToUpper();
|
||||
|
||||
if (!IsNote(input.Text))
|
||||
input.AddStyleClass("Caution");
|
||||
else
|
||||
{
|
||||
input.Text = PreviousNoteInputs[index];
|
||||
input.RemoveStyleClass("Caution");
|
||||
}
|
||||
input.CursorPosition = input.Text.Length;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -66,6 +86,9 @@ namespace Content.Client.PDA.Ringer
|
||||
/// </summary>
|
||||
public static bool IsNote(string input)
|
||||
{
|
||||
if (input.Any(char.IsDigit))
|
||||
return false;
|
||||
|
||||
input = input.Replace("#", "sharp");
|
||||
|
||||
return Enum.TryParse(input, true, out Note _);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Content.Client.Parallax.Data;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Content.Client.Parallax
|
||||
{
|
||||
private readonly Color InnerColor = Color.White;
|
||||
private readonly Color OuterColor = Color.Black;
|
||||
private readonly NoiseGenerator.NoiseType NoiseType = NoiseGenerator.NoiseType.Fbm;
|
||||
private readonly FastNoiseLite.FractalType NoiseType = FastNoiseLite.FractalType.FBm;
|
||||
private readonly uint Seed = 1234;
|
||||
private readonly float Persistence = 0.5f;
|
||||
private readonly float Lacunarity = (float) (Math.PI / 3);
|
||||
@@ -204,10 +204,10 @@ namespace Content.Client.Parallax
|
||||
switch (((TomlValue<string>) tomlObject).Value)
|
||||
{
|
||||
case "fbm":
|
||||
NoiseType = NoiseGenerator.NoiseType.Fbm;
|
||||
NoiseType = FastNoiseLite.FractalType.FBm;
|
||||
break;
|
||||
case "ridged":
|
||||
NoiseType = NoiseGenerator.NoiseType.Ridged;
|
||||
NoiseType = FastNoiseLite.FractalType.Ridged;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
@@ -217,14 +217,11 @@ namespace Content.Client.Parallax
|
||||
|
||||
public override void Apply(Image<Rgba32> bitmap)
|
||||
{
|
||||
var noise = new NoiseGenerator(NoiseType);
|
||||
noise.SetSeed(Seed);
|
||||
var noise = new FastNoiseLite((int)Seed);
|
||||
noise.SetFractalType(NoiseType);
|
||||
noise.SetFrequency(Frequency);
|
||||
noise.SetPersistence(Persistence);
|
||||
noise.SetLacunarity(Lacunarity);
|
||||
noise.SetOctaves(Octaves);
|
||||
noise.SetPeriodX(bitmap.Width);
|
||||
noise.SetPeriodY(bitmap.Height);
|
||||
noise.SetFractalLacunarity(Lacunarity);
|
||||
noise.SetFractalOctaves((int)Octaves);
|
||||
var threshVal = 1 / (1 - Threshold);
|
||||
var powFactor = 1 / Power;
|
||||
|
||||
@@ -268,7 +265,7 @@ namespace Content.Client.Parallax
|
||||
|
||||
// Noise mask stuff.
|
||||
private readonly bool Masked;
|
||||
private readonly NoiseGenerator.NoiseType MaskNoiseType = NoiseGenerator.NoiseType.Fbm;
|
||||
private readonly FastNoiseLite.FractalType MaskNoiseType = FastNoiseLite.FractalType.FBm;
|
||||
private readonly uint MaskSeed = 1234;
|
||||
private readonly float MaskPersistence = 0.5f;
|
||||
private readonly float MaskLacunarity = (float) (Math.PI * 2 / 3);
|
||||
@@ -357,10 +354,10 @@ namespace Content.Client.Parallax
|
||||
switch (((TomlValue<string>) tomlObject).Value)
|
||||
{
|
||||
case "fbm":
|
||||
MaskNoiseType = NoiseGenerator.NoiseType.Fbm;
|
||||
MaskNoiseType = FastNoiseLite.FractalType.FBm;
|
||||
break;
|
||||
case "ridged":
|
||||
MaskNoiseType = NoiseGenerator.NoiseType.Ridged;
|
||||
MaskNoiseType = FastNoiseLite.FractalType.Ridged;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
@@ -439,14 +436,10 @@ namespace Content.Client.Parallax
|
||||
{
|
||||
var o = PointSize - 1;
|
||||
var random = new Random(Seed);
|
||||
var noise = new NoiseGenerator(MaskNoiseType);
|
||||
noise.SetSeed(MaskSeed);
|
||||
noise.SetFrequency(MaskFrequency);
|
||||
noise.SetPersistence(MaskPersistence);
|
||||
noise.SetLacunarity(MaskLacunarity);
|
||||
noise.SetOctaves(MaskOctaves);
|
||||
noise.SetPeriodX(buffer.Width);
|
||||
noise.SetPeriodY(buffer.Height);
|
||||
var noise = new FastNoiseLite((int)MaskSeed);
|
||||
noise.SetFractalType(MaskNoiseType);
|
||||
noise.SetFractalLacunarity(MaskLacunarity);
|
||||
noise.SetFractalOctaves((int)MaskOctaves);
|
||||
|
||||
var threshVal = 1 / (1 - MaskThreshold);
|
||||
var powFactor = 1 / MaskPower;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user