Signed-off-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: Samuka-C <47865393+Samuka-C@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: Partmedia <kevinz5000@gmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: themias <89101928+themias@users.noreply.github.com>
Co-authored-by: Victor Shen <71985089+Vexerot@users.noreply.github.com>
Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Milon <milonpl.git@proton.me>
Co-authored-by: Kirus59 <145689588+Kirus59@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Stomf <5dorkydorks@gmail.com>
Co-authored-by: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com>
Co-authored-by: PraxisMapper <praxismapper@gmail.com>
Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com>
Co-authored-by: abadaba695 <spacestation13thingy@gmail.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: ActiveMammmoth <140334666+ActiveMammmoth@users.noreply.github.com>
Co-authored-by: Myra <vasilis@pikachu.systems>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com>
Co-authored-by: Gentleman-Bird <dcgreen406@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: BIGZi0348 <svalker0348@gmail.com>
Co-authored-by: LaCumbiaDelCoronavirus <90893484+LaCumbiaDelCoronavirus@users.noreply.github.com>
Co-authored-by: imatsoup <93290208+imatsoup@users.noreply.github.com>
Co-authored-by: Matthew Herber <32679887+happyrobot33@users.noreply.github.com>
Co-authored-by: Ertanic <36124833+Ertanic@users.noreply.github.com>
Co-authored-by: MissKay1994 <15877268+MissKay1994@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: eoineoineoin <helloworld@eoinrul.es>
Co-authored-by: Tiniest Shark <head.rebel@yahoo.com>
Co-authored-by: nikitosych <boriszyn@gmail.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
Co-authored-by: YoungThug <ramialanbagy@gmail.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: Vladislav Suchkov <20380250+murolem@users.noreply.github.com>
Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
Co-authored-by: 3nderall <101940324+3nderall@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: J <billsmith116@gmail.com>
Co-authored-by: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com>
Co-authored-by: Minemoder5000 <minemoder50000@gmail.com>
Co-authored-by: Spanky <scott@wearejacob.com>
Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Co-authored-by: Deerstop <edainturner@gmail.com>
Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com>
Co-authored-by: archee1 <archee3@hotmail.co.uk>
Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: poklj <compgeek223@gmail.com>
Co-authored-by: Krunklehorn <42424291+Krunklehorn@users.noreply.github.com>
Co-authored-by: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com>
Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: Zalycon <84675130+Zalycon@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Dmitry <57028746+dimm00n@users.noreply.github.com>
This commit is contained in:
Dmitry
2025-05-24 15:00:17 +07:00
committed by GitHub
parent b41feb23e9
commit 25c5e59248
3429 changed files with 267307 additions and 99006 deletions

View File

@@ -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

View File

@@ -2,6 +2,7 @@
concurrency:
group: publish-testing
cancel-in-progress: true
on:
workflow_dispatch:

View File

@@ -2,6 +2,7 @@ name: Publish
concurrency:
group: publish
cancel-in-progress: true
on:
workflow_dispatch:
@@ -66,12 +67,14 @@ jobs:
FORK_ID: ${{ vars.FORK_ID }}
# - name: Publish changelog (Discord)
# continue-on-error: true
# run: Tools/actions_changelogs_since_last_run.py
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}
# - name: Publish changelog (RSS)
# continue-on-error: true
# run: Tools/actions_changelog_rss.py
# env:
# CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}

44
.vscode/tasks.json vendored
View File

@@ -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"
}
]
}

View File

@@ -5,6 +5,7 @@ import subprocess
import sys
import os
import shutil
import time
from pathlib import Path
from typing import List
@@ -104,7 +105,21 @@ def reset_solution():
with SOLUTION_PATH.open("w") as f:
f.write(content)
def check_for_zip_download():
# Check if .git exists,
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if not os.path.isdir(os.path.join(cur_dir, ".git")):
print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n"
"When downloading straight from GitHub, it leaves out important information that git needs to function. "
"Such as information to download the engine or even the ability to even be able to create contributions. \n"
"Please read and follow https://docs.spacestation14.com/en/general-development/setup/setting-up-a-development-environment.html \n"
"If you just want a Sandbox Server, you are following the wrong guide! You can download a premade server following the instructions here:"
"https://docs.spacestation14.com/en/general-development/setup/server-hosting-tutorial.html \n"
"Closing automatically in 30 seconds.")
time.sleep(30)
exit(1)
if __name__ == '__main__':
check_for_zip_download()
install_hooks()
update_submodules()

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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)
@@ -225,6 +202,7 @@ namespace Content.Client.Actions
return;
OnActionAdded?.Invoke(actionId);
ActionsUpdated?.Invoke();
}
protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
@@ -233,6 +211,7 @@ namespace Content.Client.Actions
return;
OnActionRemoved?.Invoke(actionId);
ActionsUpdated?.Invoke();
}
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()

View File

@@ -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,33 +204,55 @@ 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;
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));

View File

@@ -0,0 +1,15 @@
namespace Content.Client.Administration;
public enum AdminOverlayAntagFormat
{
Binary,
Roletype,
Subtype
}
public enum AdminOverlayAntagSymbolStyle
{
Off,
Basic,
Specific
}

View File

@@ -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();
}

View File

@@ -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.
};

View File

@@ -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);

View File

@@ -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>

View File

@@ -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}\"");
};

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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();

View File

@@ -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());
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}\"");
}

View File

@@ -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);
}
}

View File

@@ -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}";
}

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Shared.Alert;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
@@ -15,6 +16,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
public event EventHandler? ClearAlerts;
public event EventHandler<IReadOnlyDictionary<AlertKey, AlertState>>? SyncAlerts;
@@ -27,6 +29,12 @@ public sealed class ClientAlertsSystem : AlertsSystem
SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(OnHandleState);
}
protected override void HandledAlert()
{
_ui.ClickSound();
}
protected override void LoadPrototypes()
{
base.LoadPrototypes();
@@ -52,8 +60,24 @@ public sealed class ClientAlertsSystem : AlertsSystem
if (args.Current is not AlertComponentState cast)
return;
// Save all client-sided alerts to later put back in
var clientAlerts = new Dictionary<AlertKey, AlertState>();
foreach (var alert in alerts.Comp.Alerts)
{
if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto))
{
if (alertProto.ClientHandled)
clientAlerts[alert.Key] = alert.Value;
}
}
alerts.Comp.Alerts = new(cast.Alerts);
foreach (var alert in clientAlerts)
{
alerts.Comp.Alerts[alert.Key] = alert.Value;
}
UpdateHud(alerts);
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
namespace Content.Client.Atmos.EntitySystems;
public sealed class GasTankSystem : SharedGasTankSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasTankComponent, AfterAutoHandleStateEvent>(OnGasTankState);
}
private void OnGasTankState(Entity<GasTankComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui))
{
bui.Update<GasTankBoundUserInterfaceState>();
}
}
public override void UpdateUserInterface(Entity<GasTankComponent> ent)
{
if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui))
{
bui.Update<GasTankBoundUserInterfaceState>();
}
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,32 @@
using Content.Client.Atmos.UI;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Piping.Unary.Systems;
using Content.Shared.NodeContainer;
namespace Content.Client.Atmos.Piping.Unary.Systems;
public sealed class GasCanisterSystem : SharedGasCanisterSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasCanisterComponent, AfterAutoHandleStateEvent>(OnGasState);
}
private void OnGasState(Entity<GasCanisterComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (UI.TryGetOpenUi<GasCanisterBoundUserInterface>(ent.Owner, GasCanisterUiKey.Key, out var bui))
{
bui.Update<GasCanisterBoundUserInterfaceState>();
}
}
protected override void DirtyUI(EntityUid uid, GasCanisterComponent? component = null, NodeContainerComponent? nodes = null)
{
if (UI.TryGetOpenUi<GasCanisterBoundUserInterface>(uid, GasCanisterUiKey.Key, out var bui))
{
bui.Update<GasCanisterBoundUserInterfaceState>();
}
}
}

View File

@@ -1,4 +1,7 @@
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.IdentityManagement;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
@@ -32,22 +35,22 @@ namespace Content.Client.Atmos.UI
private void OnTankEjectPressed()
{
SendMessage(new GasCanisterHoldingTankEjectMessage());
SendPredictedMessage(new GasCanisterHoldingTankEjectMessage());
}
private void OnReleasePressureSet(float value)
{
SendMessage(new GasCanisterChangeReleasePressureMessage(value));
SendPredictedMessage(new GasCanisterChangeReleasePressureMessage(value));
}
private void OnReleaseValveOpenPressed()
{
SendMessage(new GasCanisterChangeReleaseValveMessage(true));
SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(true));
}
private void OnReleaseValveClosePressed()
{
SendMessage(new GasCanisterChangeReleaseValveMessage(false));
SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(false));
}
/// <summary>
@@ -57,17 +60,21 @@ namespace Content.Client.Atmos.UI
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast)
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast || !EntMan.TryGetComponent(Owner, out GasCanisterComponent? component))
return;
_window.SetCanisterLabel(cast.CanisterLabel);
var canisterLabel = Identity.Name(Owner, EntMan);
var tankLabel = component.GasTankSlot.Item != null ? Identity.Name(component.GasTankSlot.Item.Value, EntMan) : null;
_window.SetCanisterLabel(canisterLabel);
_window.SetCanisterPressure(cast.CanisterPressure);
_window.SetPortStatus(cast.PortStatus);
_window.SetTankLabel(cast.TankLabel);
_window.SetTankLabel(tankLabel);
_window.SetTankPressure(cast.TankPressure);
_window.SetReleasePressureRange(cast.ReleasePressureMin, cast.ReleasePressureMax);
_window.SetReleasePressure(cast.ReleasePressure);
_window.SetReleaseValve(cast.ReleaseValve);
_window.SetReleasePressureRange(component.MinReleasePressure, component.MaxReleasePressure);
_window.SetReleasePressure(component.ReleasePressure);
_window.SetReleaseValve(component.ReleaseValve);
}
protected override void Dispose(bool disposing)

View File

@@ -13,9 +13,6 @@ namespace Content.Client.Atmos.UI;
[UsedImplicitly]
public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private const float MaxPressure = Atmospherics.MaxOutputPressure;
[ViewVariables]
private GasPressurePumpWindow? _window;

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -0,0 +1,24 @@
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
namespace Content.Client.Body.Systems;
public sealed class InternalsSystem : SharedInternalsSystem
{
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<InternalsComponent, AfterAutoHandleStateEvent>(OnInternalsAfterState);
}
private void OnInternalsAfterState(Entity<InternalsComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (ent.Comp.GasTankEntity != null && _ui.TryGetOpenUi(ent.Comp.GasTankEntity.Value, SharedGasTankUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -75,9 +75,8 @@ public sealed partial class CargoSystem
switch (state)
{
case CargoTelepadState.Teleporting:
if (_player.HasRunningAnimation(uid, TelepadBeamKey))
return;
_player.Stop(uid, player, TelepadIdleKey);
_player.Stop((uid, player), TelepadIdleKey);
if (!_player.HasRunningAnimation(uid, TelepadBeamKey))
_player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey);
break;
case CargoTelepadState.Unpowered:

View File

@@ -3,25 +3,22 @@
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>
<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'}"
@@ -30,9 +27,10 @@
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
HorizontalExpand="True" />
</BoxContainer>
<Control MinHeight="5"/>
<ScrollContainer HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="6">
SizeFlagsStretchRatio="2">
<BoxContainer Name="Products"
Orientation="Vertical"
HorizontalExpand="True"
@@ -40,29 +38,49 @@
<!-- Products get added here by code -->
</BoxContainer>
</ScrollContainer>
<Control MinHeight="5" Name="OrdersSpacer"/>
<PanelContainer VerticalExpand="True"
SizeFlagsStretchRatio="6">
SizeFlagsStretchRatio="1"
Name="Orders">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#000000" />
</PanelContainer.PanelOverride>
<ScrollContainer VerticalExpand="True">
<BoxContainer Orientation="Vertical">
<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>
<Label Text="{Loc 'cargo-console-menu-orders-label'}" />
<BoxContainer Name="Orders"
Orientation="Vertical"
StyleClasses="transparentItemList"
VerticalExpand="True">
<!-- Orders are added here by code -->
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</PanelContainer>
<TextureButton VerticalExpand="True" />
</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>

View File

@@ -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,13 +199,16 @@ 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;
var account = _protoManager.Index(order.Account);
var row = new CargoOrderRow
{
@@ -162,37 +220,74 @@ namespace Content.Client.Cargo.UI
"cargo-console-menu-populate-orders-cargo-order-row-product-name-text",
("productName", productName),
("orderAmount", order.OrderQuantity),
("orderRequester", order.Requester))
("orderRequester", order.Requester),
("accountColor", account.Color),
("account", Loc.GetString(account.Code)))
},
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);
}
}
public void PopulateAccountActions()
{
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 UpdateCargoCapacity(int count, int capacity)
public void UpdateStation(EntityUid station)
{
// TODO: Rename + Loc.
ShuttleCapacityLabel.Text = $"{count}/{capacity}";
_station = station;
}
public void UpdateBankData(string name, int points)
protected override void FrameUpdate(FrameEventArgs args)
{
AccountNameLabel.Text = name;
PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", points.ToString()));
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;
OrdersSpacer.Visible = !orderConsole.SlipPrinter;
Orders.Visible = !orderConsole.SlipPrinter;
}
}
}

View File

@@ -1,19 +1,20 @@
<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">
<Label Name="ProductName"
<RichTextLabel Name="ProductName"
Access="Public"
HorizontalExpand="True"
StyleClasses="LabelSubText"
ClipText="True" />
StyleClasses="LabelSubText" />
<Label Name="Description"
Access="Public"
HorizontalExpand="True"
@@ -23,10 +24,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>

View File

@@ -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>

View File

@@ -36,6 +36,7 @@ namespace Content.Client.Cargo.UI
{
var product = protoManager.Index<EntityPrototype>(order.ProductId);
var productName = product.Name;
var account = protoManager.Index(order.Account);
var row = new CargoOrderRow
{
@@ -47,7 +48,9 @@ namespace Content.Client.Cargo.UI
"cargo-console-menu-populate-orders-cargo-order-row-product-name-text",
("productName", productName),
("orderAmount", order.OrderQuantity - order.NumDispatched),
("orderRequester", order.Requester))
("orderRequester", order.Requester),
("accountColor", account.Color),
("account", Loc.GetString(account.Code)))
},
Description = {Text = Loc.GetString("cargo-console-menu-order-reason-description",
("reason", order.Reason))}

View 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>

View 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]));
}
}
}

View File

@@ -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;

View File

@@ -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);

View 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();
}
}

View File

@@ -1,5 +0,0 @@
using Content.Shared.Charges.Systems;
namespace Content.Client.Charges.Systems;
public sealed class ChargesSystem : SharedChargesSystem { }

View File

@@ -16,7 +16,7 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
private readonly TimeSpan _typingTimeout = TimeSpan.FromSeconds(2);
private TimeSpan _lastTextChange;
private bool _isClientTyping;
private bool _isClientChatFocused; // Corvax-TypingIndicator
private bool _isClientChatFocused;
public override void Initialize()
{
@@ -32,10 +32,8 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
return;
// client typed something - show typing indicator
// Corvax-TypingIndicator-Start
_isClientTyping = true;
ClientUpdateTyping();
// Corvax-TypingIndicator-End
_lastTextChange = _time.CurTime;
}
@@ -46,14 +44,10 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
return;
// client submitted text - hide typing indicator
// Corvax-TypingIndicator-Start
_isClientTyping = false;
_isClientChatFocused = false;
ClientUpdateTyping();
// Corvax-TypingIndicator-End
}
// Corvax-TypingIndicator-Start
public void ClientChangedChatFocus(bool isFocused)
{
// don't update it if player don't want to show typing
@@ -64,7 +58,6 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
_isClientChatFocused = isFocused;
ClientUpdateTyping();
}
// Corvax-TypingIndicator-End
public override void Update(float frameTime)
{
@@ -76,34 +69,25 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
var dif = _time.CurTime - _lastTextChange;
if (dif > _typingTimeout)
{
// client didn't typed anything for a long time - hide indicator
// Corvax-TypingIndicator-Start
// client didn't typed anything for a long time - change indicator
_isClientTyping = false;
ClientUpdateTyping();
// Corvax-TypingIndicator-End
}
}
}
private void ClientUpdateTyping() // Corvax-TypingIndicator
private void ClientUpdateTyping()
{
// Corvax-TypingIndicator-Start
// if (_isClientTyping == isClientTyping)
// return;
// Corvax-TypingIndicator-End
// check if player controls any entity.
// check if player controls any pawn
if (_playerManager.LocalEntity == null)
return;
// Corvax-TypingIndicator-Start
// _isClientTyping = isClientTyping;
var state = TypingIndicatorState.None;
if (_isClientChatFocused)
state = _isClientTyping ? TypingIndicatorState.Typing : TypingIndicatorState.Idle;
// Corvax-TypingIndicator-End
// send a networked event to server
RaisePredictiveEvent(new TypingChangedEvent(state)); // Corvax-TypingIndicator
RaisePredictiveEvent(new TypingChangedEvent(state));
}
private void OnShowTypingChanged(bool showTyping)
@@ -111,10 +95,8 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
// hide typing indicator immediately if player don't want to show it anymore
if (!showTyping)
{
// Corvax-TypingIndicator-Start
_isClientTyping = false;
ClientUpdateTyping();
// Corvax-TypingIndicator-End
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Chat.TypingIndicator;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Prototypes;
@@ -35,7 +35,6 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
return;
}
//AppearanceSystem.TryGetData<bool>(uid, TypingIndicatorVisuals.IsTyping, out var isTyping, args.Component); // Corvax-TypingIndicator
var layerExists = args.Sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer);
if (!layerExists)
layer = args.Sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base);
@@ -44,8 +43,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
args.Sprite.LayerSetState(layer, proto.TypingState);
args.Sprite.LayerSetShader(layer, proto.Shader);
args.Sprite.LayerSetOffset(layer, proto.Offset);
// args.Sprite.LayerSetVisible(layer, isTyping); // Corvax-TypingIndicator
// Corvax-TypingIndicator-Start
AppearanceSystem.TryGetData<TypingIndicatorState>(uid, TypingIndicatorVisuals.State, out var state);
args.Sprite.LayerSetVisible(layer, state != TypingIndicatorState.None);
switch (state)
@@ -57,6 +55,5 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
args.Sprite.LayerSetState(layer, proto.TypingState);
break;
}
// Corvax-TypingIndicator-End
}
}

View File

@@ -116,7 +116,10 @@ namespace Content.Client.Chemistry.UI
("1", ChemMasterReagentAmount.U1, StyleBase.ButtonOpenBoth),
("5", ChemMasterReagentAmount.U5, StyleBase.ButtonOpenBoth),
("10", ChemMasterReagentAmount.U10, StyleBase.ButtonOpenBoth),
("15", ChemMasterReagentAmount.U15, StyleBase.ButtonOpenBoth),
("20", ChemMasterReagentAmount.U20, StyleBase.ButtonOpenBoth),
("25", ChemMasterReagentAmount.U25, StyleBase.ButtonOpenBoth),
("30", ChemMasterReagentAmount.U30, StyleBase.ButtonOpenBoth),
("50", ChemMasterReagentAmount.U50, StyleBase.ButtonOpenBoth),
("100", ChemMasterReagentAmount.U100, StyleBase.ButtonOpenBoth),
(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleBase.ButtonOpenLeft),

View File

@@ -44,7 +44,7 @@ public sealed class HyposprayStatusControl : Control
PrevMaxVolume = solution.MaxVolume;
PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
var modeStringLocalized = Loc.GetString(_parent.Comp.OnlyAffectsMobs switch
var modeStringLocalized = Loc.GetString((_parent.Comp.OnlyAffectsMobs && _parent.Comp.CanContainerDraw) switch
{
false => "hypospray-all-mode-text",
true => "hypospray-mobs-only-mode-text",

View File

@@ -1,4 +1,5 @@
using Content.Client.Guidebook.Components;
using Content.Client.UserInterface.Controls;
using Content.Shared.Chemistry;
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
@@ -31,8 +32,7 @@ namespace Content.Client.Chemistry.UI
// Setup window layout/elements
_window = this.CreateWindow<ReagentDispenserWindow>();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
_window.HelpGuidebookIds = EntMan.GetComponent<GuideHelpComponent>(Owner).Guides;
_window.SetInfoFromEntity(EntMan, Owner);
// Setup static button actions.
_window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName));

View File

@@ -334,10 +334,13 @@ 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++;
}
}
}
RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true);
}

View 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);
}
}
}

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -1,8 +1,10 @@
using System.Linq;
using Content.Shared.Construction.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Placement;
using Robust.Client.Utility;
using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Client.Construction
{
@@ -45,7 +47,14 @@ namespace Content.Client.Construction
public override void StartHijack(PlacementManager manager)
{
base.StartHijack(manager);
manager.CurrentTextures = _prototype?.Layers.Select(sprite => sprite.DirFrame0()).ToList();
if (_prototype is null || !_constructionSystem.TryGetRecipePrototype(_prototype.ID, out var targetProtoId))
return;
if (!IoCManager.Resolve<IPrototypeManager>().TryIndex(targetProtoId, out EntityPrototype? proto))
return;
manager.CurrentTextures = SpriteComponent.GetPrototypeTextures(proto, IoCManager.Resolve<IResourceCache>()).ToList();
}
}
}

View File

@@ -1,11 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Popups;
using Content.Shared.Construction;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps;
using Content.Shared.Examine;
using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Wall;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -15,6 +14,7 @@ using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Construction
{
@@ -25,7 +25,6 @@ namespace Content.Client.Construction
public sealed class ConstructionSystem : SharedConstructionSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -33,6 +32,8 @@ namespace Content.Client.Construction
private readonly Dictionary<int, EntityUid> _ghosts = new();
private readonly Dictionary<string, ConstructionGuide> _guideCache = new();
private readonly Dictionary<string, string> _recipesMetadataCache = [];
public bool CraftingEnabled { get; private set; }
/// <inheritdoc />
@@ -40,6 +41,8 @@ namespace Content.Client.Construction
{
base.Initialize();
WarmupRecipesCache();
UpdatesOutsidePrediction = true;
SubscribeLocalEvent<LocalPlayerAttachedEvent>(HandlePlayerAttached);
SubscribeNetworkEvent<AckStructureConstructionMessage>(HandleAckStructure);
@@ -63,6 +66,77 @@ namespace Content.Client.Construction
ClearGhost(component.GhostId);
}
public bool TryGetRecipePrototype(string constructionProtoId, [NotNullWhen(true)] out string? targetProtoId)
{
if (_recipesMetadataCache.TryGetValue(constructionProtoId, out targetProtoId))
return true;
targetProtoId = null;
return false;
}
private void WarmupRecipesCache()
{
foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{
if (!PrototypeManager.TryIndex(constructionProto.Graph, out var graphProto))
continue;
if (constructionProto.TargetNode is not { } targetNodeId)
continue;
if (!graphProto.Nodes.TryGetValue(targetNodeId, out var targetNode))
continue;
// Recursion is for wimps.
var stack = new Stack<ConstructionGraphNode>();
stack.Push(targetNode);
do
{
var node = stack.Pop();
// I never realized if this uid affects anything...
// EntityUid? userUid = args.SenderSession.State.ControlledEntity.HasValue
// ? GetEntity(args.SenderSession.State.ControlledEntity.Value)
// : null;
// We try to get the id of the target prototype, if it fails, we try going through the edges.
if (node.Entity.GetId(null, null, new(EntityManager)) is not { } entityId)
{
// If the stack is not empty, there is a high probability that the loop will go to infinity.
if (stack.Count == 0)
{
foreach (var edge in node.Edges)
{
if (graphProto.Nodes.TryGetValue(edge.Target, out var graphNode))
stack.Push(graphNode);
}
}
continue;
}
// If we got the id of the prototype, we exit the “recursion” by clearing the stack.
stack.Clear();
if (!PrototypeManager.TryIndex(constructionProto.ID, out ConstructionPrototype? recipe))
continue;
if (!PrototypeManager.TryIndex(entityId, out var proto))
continue;
var name = recipe.SetName.HasValue ? Loc.GetString(recipe.SetName) : proto.Name;
var desc = recipe.SetDescription.HasValue ? Loc.GetString(recipe.SetDescription) : proto.Description;
recipe.Name = name;
recipe.Description = desc;
_recipesMetadataCache.Add(constructionProto.ID, entityId);
} while (stack.Count > 0);
}
}
private void OnConstructionGuideReceived(ResponseConstructionGuide ev)
{
_guideCache[ev.ConstructionId] = ev.Guide;
@@ -88,7 +162,7 @@ namespace Content.Client.Construction
private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
{
if (component.Prototype == null)
if (component.Prototype?.Name is null)
return;
using (args.PushGroup(nameof(ConstructionGhostComponent)))
@@ -97,7 +171,7 @@ namespace Content.Client.Construction
"construction-ghost-examine-message",
("name", component.Prototype.Name)));
if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
if (!PrototypeManager.TryIndex(component.Prototype.Graph, out var graph))
return;
var startNode = graph.Nodes[component.Prototype.StartNode];
@@ -198,6 +272,9 @@ namespace Content.Client.Construction
return false;
}
if (!TryGetRecipePrototype(prototype.ID, out var targetProtoId) || !PrototypeManager.TryIndex(targetProtoId, out EntityPrototype? targetProto))
return false;
if (GhostPresent(loc))
return false;
@@ -214,17 +291,44 @@ namespace Content.Client.Construction
comp.GhostId = ghost.GetHashCode();
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
_ghosts.Add(comp.GhostId, ghost.Value);
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
sprite.Color = new Color(48, 255, 48, 128);
for (int i = 0; i < prototype.Layers.Count; i++)
if (targetProto.TryGetComponent(out IconComponent? icon, EntityManager.ComponentFactory))
{
sprite.AddBlankLayer(i); // There is no way to actually check if this already exists, so we blindly insert a new one
sprite.LayerSetSprite(i, prototype.Layers[i]);
sprite.AddBlankLayer(0);
sprite.LayerSetSprite(0, icon.Icon);
sprite.LayerSetShader(0, "unshaded");
sprite.LayerSetVisible(0, true);
}
else if (targetProto.Components.TryGetValue("Sprite", out _))
{
var dummy = EntityManager.SpawnEntity(targetProtoId, MapCoordinates.Nullspace);
var targetSprite = EntityManager.EnsureComponent<SpriteComponent>(dummy);
EntityManager.System<AppearanceSystem>().OnChangeData(dummy, targetSprite);
for (var i = 0; i < targetSprite.AllLayers.Count(); i++)
{
if (!targetSprite[i].Visible || !targetSprite[i].RsiState.IsValid)
continue;
var rsi = targetSprite[i].Rsi ?? targetSprite.BaseRSI;
if (rsi is null || !rsi.TryGetState(targetSprite[i].RsiState, out var state) ||
state.StateId.Name is null)
continue;
sprite.AddBlankLayer(i);
sprite.LayerSetSprite(i, new SpriteSpecifier.Rsi(rsi.Path, state.StateId.Name));
sprite.LayerSetShader(i, "unshaded");
sprite.LayerSetVisible(i, true);
}
EntityManager.DeleteEntity(dummy);
}
else
return false;
if (prototype.CanBuildInImpassable)
EnsureComp<WallMountComponent>(ghost.Value).Arc = new(Math.Tau);

View File

@@ -1,11 +1,12 @@
<DefaultWindow xmlns="https://spacestation14.io">
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" MinWidth="243" Margin="0 0 5 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5">
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/>
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'construction-menu-search'}" HorizontalExpand="True"/>
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
</BoxContainer>
<ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/>
<controls:ListContainer Name="Recipes" Access="Public" Group="True" Toggle="True" VerticalExpand="True" />
<ScrollContainer Name="RecipesGridScrollContainer" VerticalExpand="True" Access="Public" Visible="False">
<GridContainer Name="RecipesGrid" Columns="5" Access="Public"/>
</ScrollContainer>
@@ -18,7 +19,7 @@
<Control>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
<BoxContainer Orientation="Horizontal" Align="Center">
<TextureRect Name="TargetTexture" HorizontalAlignment="Right" Stretch="Keep" Margin="0 0 10 0"/>
<EntityPrototypeView Name="TargetTexture" HorizontalAlignment="Right" Stretch="Fill" Margin="0 0 10 0"/>
<BoxContainer Orientation="Vertical">
<RichTextLabel Name="TargetName"/>
<RichTextLabel Name="TargetDesc"/>

View File

@@ -1,14 +1,11 @@
using System;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
namespace Content.Client.Construction.UI
{
@@ -28,7 +25,7 @@ namespace Content.Client.Construction.UI
bool GridViewButtonPressed { get; set; }
bool BuildButtonPressed { get; set; }
ItemList Recipes { get; }
ListContainer Recipes { get; }
ItemList RecipeStepList { get; }
@@ -36,14 +33,14 @@ namespace Content.Client.Construction.UI
GridContainer RecipesGrid { get; }
event EventHandler<(string search, string catagory)> PopulateRecipes;
event EventHandler<ItemList.Item?> RecipeSelected;
event EventHandler<ConstructionMenu.ConstructionMenuListData?> RecipeSelected;
event EventHandler RecipeFavorited;
event EventHandler<bool> BuildButtonToggled;
event EventHandler<bool> EraseButtonToggled;
event EventHandler ClearAllGhosts;
void ClearRecipeInfo();
void SetRecipeInfo(string name, string description, Texture iconTexture, bool isItem, bool isFavorite);
void SetRecipeInfo(string name, string description, EntityPrototype? targetPrototype, bool isItem, bool isFavorite);
void ResetPlacement();
#region Window Control
@@ -94,8 +91,36 @@ namespace Content.Client.Construction.UI
Title = Loc.GetString("construction-menu-title");
BuildButton.Text = Loc.GetString("construction-menu-place-ghost");
Recipes.OnItemSelected += obj => RecipeSelected?.Invoke(this, obj.ItemList[obj.ItemIndex]);
Recipes.OnItemDeselected += _ => RecipeSelected?.Invoke(this, null);
Recipes.ItemPressed += (_, data) => RecipeSelected?.Invoke(this, data as ConstructionMenuListData);
Recipes.NoItemSelected += () => RecipeSelected?.Invoke(this, null);
Recipes.GenerateItem += (data, button) =>
{
if (data is not ConstructionMenuListData (var prototype, var targetPrototype))
return;
var entProtoView = new EntityPrototypeView()
{
SetSize = new(32f),
Stretch = SpriteView.StretchMode.Fill,
Scale = new(2),
Margin = new(0, 2),
};
entProtoView.SetPrototype(targetPrototype);
var label = new Label()
{
Text = prototype.Name,
Margin = new(5, 0),
};
var box = new BoxContainer();
box.AddChild(entProtoView);
box.AddChild(label);
button.AddChild(box);
button.ToolTip = prototype.Description;
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
};
SearchBar.OnTextChanged += _ =>
PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
@@ -121,7 +146,7 @@ namespace Content.Client.Construction.UI
public event EventHandler? ClearAllGhosts;
public event EventHandler<(string search, string catagory)>? PopulateRecipes;
public event EventHandler<ItemList.Item?>? RecipeSelected;
public event EventHandler<ConstructionMenuListData?>? RecipeSelected;
public event EventHandler? RecipeFavorited;
public event EventHandler<bool>? BuildButtonToggled;
public event EventHandler<bool>? EraseButtonToggled;
@@ -133,13 +158,17 @@ namespace Content.Client.Construction.UI
}
public void SetRecipeInfo(
string name, string description, Texture iconTexture, bool isItem, bool isFavorite)
string name,
string description,
EntityPrototype? targetPrototype,
bool isItem,
bool isFavorite)
{
BuildButton.Disabled = false;
BuildButton.Text = Loc.GetString(isItem ? "construction-menu-place-ghost" : "construction-menu-craft");
TargetName.SetMessage(name);
TargetDesc.SetMessage(description);
TargetTexture.Texture = iconTexture;
TargetTexture.SetPrototype(targetPrototype?.ID);
FavoriteButton.Visible = true;
FavoriteButton.Text = Loc.GetString(
isFavorite ? "construction-add-favorite-button" : "construction-remove-from-favorite-button");
@@ -150,9 +179,11 @@ namespace Content.Client.Construction.UI
BuildButton.Disabled = true;
TargetName.SetMessage(string.Empty);
TargetDesc.SetMessage(string.Empty);
TargetTexture.Texture = null;
TargetTexture.SetPrototype(null);
FavoriteButton.Visible = false;
RecipeStepList.Clear();
}
public sealed record ConstructionMenuListData(ConstructionPrototype Prototype, EntityPrototype TargetPrototype) : ListData;
}
}

View File

@@ -10,10 +10,8 @@ using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Construction.UI
{
@@ -30,18 +28,20 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
private readonly SpriteSystem _spriteSystem;
private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected;
private List<ConstructionPrototype> _favoritedRecipes = [];
private Dictionary<string, TextureButton> _recipeButtons = new();
private Dictionary<string, ContainerButton> _recipeButtons = new();
private string _selectedCategory = string.Empty;
private string _favoriteCatName = "construction-category-favorites";
private string _forAllCategoryName = "construction-category-all";
private const string FavoriteCatName = "construction-category-favorites";
private const string ForAllCategoryName = "construction-category-all";
private bool CraftingAvailable
{
get => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Visible;
@@ -98,15 +98,18 @@ namespace Content.Client.Construction.UI
_placementManager.PlacementChanged += OnPlacementChanged;
_constructionView.OnClose += () => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Pressed = false;
_constructionView.OnClose +=
() => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Pressed = false;
_constructionView.ClearAllGhosts += (_, _) => _constructionSystem?.ClearAllGhosts();
_constructionView.PopulateRecipes += OnViewPopulateRecipes;
_constructionView.RecipeSelected += OnViewRecipeSelected;
_constructionView.BuildButtonToggled += (_, b) => BuildButtonToggled(b);
_constructionView.EraseButtonToggled += (_, b) =>
{
if (_constructionSystem is null) return;
if (b) _placementManager.Clear();
if (_constructionSystem is null)
return;
if (b)
_placementManager.Clear();
_placementManager.ToggleEraserHijacked(new ConstructionPlacementHijack(_constructionSystem, null));
_constructionView.EraseButtonPressed = b;
};
@@ -117,7 +120,7 @@ namespace Content.Client.Construction.UI
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
}
public void OnHudCraftingButtonToggled(ButtonToggledEventArgs args)
public void OnHudCraftingButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
WindowOpen = args.Pressed;
}
@@ -139,7 +142,7 @@ namespace Content.Client.Construction.UI
_constructionView.ResetPlacement();
}
private void OnViewRecipeSelected(object? sender, ItemList.Item? item)
private void OnViewRecipeSelected(object? sender, ConstructionMenu.ConstructionMenuListData? item)
{
if (item is null)
{
@@ -148,12 +151,15 @@ namespace Content.Client.Construction.UI
return;
}
_selected = (ConstructionPrototype) item.Metadata!;
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
_selected = item.Prototype;
if (_placementManager is { IsActive: true, Eraser: false })
UpdateGhostPlacement();
PopulateInfo(_selected);
}
private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
private void OnGridViewRecipeSelected(object? _, ConstructionPrototype? recipe)
{
if (recipe is null)
{
@@ -163,62 +169,21 @@ namespace Content.Client.Construction.UI
}
_selected = recipe;
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
if (_placementManager is { IsActive: true, Eraser: false })
UpdateGhostPlacement();
PopulateInfo(_selected);
}
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
{
var (search, category) = args;
if (_constructionSystem is null)
return;
var recipes = new List<ConstructionPrototype>();
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
if (isEmptyCategory)
_selectedCategory = string.Empty;
else
_selectedCategory = category;
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{
if (recipe.Hide)
continue;
if (_playerManager.LocalSession == null
|| _playerManager.LocalEntity == null
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
continue;
if (!string.IsNullOrEmpty(search))
{
if (!recipe.Name.ToLowerInvariant().Contains(search.Trim().ToLowerInvariant()))
continue;
}
if (!isEmptyCategory)
{
if (category == _favoriteCatName)
{
if (!_favoritedRecipes.Contains(recipe))
{
continue;
}
}
else if (recipe.Category != category)
{
continue;
}
}
recipes.Add(recipe);
}
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
var actualRecipes = GetAndSortRecipes(args);
var recipesList = _constructionView.Recipes;
recipesList.Clear();
var recipesGrid = _constructionView.RecipesGrid;
recipesGrid.RemoveAllChildren();
@@ -227,17 +192,35 @@ namespace Content.Client.Construction.UI
if (_constructionView.GridViewButtonPressed)
{
foreach (var recipe in recipes)
recipesList.PopulateList([]);
PopulateGrid(recipesGrid, actualRecipes);
}
else
{
var itemButton = new TextureButton
recipesList.PopulateList(actualRecipes);
}
}
private void PopulateGrid(GridContainer recipesGrid,
IEnumerable<ConstructionMenu.ConstructionMenuListData> actualRecipes)
{
TextureNormal = _spriteSystem.Frame0(recipe.Icon),
VerticalAlignment = Control.VAlignment.Center,
Name = recipe.Name,
ToolTip = recipe.Name,
Scale = new Vector2(1.35f),
ToggleMode = true,
foreach (var recipe in actualRecipes)
{
var protoView = new EntityPrototypeView()
{
Scale = new Vector2(1.2f),
};
protoView.SetPrototype(recipe.TargetPrototype);
var itemButton = new ContainerButton()
{
VerticalAlignment = Control.VAlignment.Center,
Name = recipe.TargetPrototype.Name,
ToolTip = recipe.TargetPrototype.Name,
ToggleMode = true,
Children = { protoView },
};
var itemButtonPanelContainer = new PanelContainer
{
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
@@ -250,37 +233,79 @@ namespace Content.Client.Construction.UI
if (buttonToggledEventArgs.Pressed &&
_selected != null &&
_recipeButtons.TryGetValue(_selected.Name, out var oldButton))
_recipeButtons.TryGetValue(_selected.Name!, out var oldButton))
{
oldButton.Pressed = false;
SelectGridButton(oldButton, false);
}
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe.Prototype : null);
};
recipesGrid.AddChild(itemButtonPanelContainer);
_recipeButtons[recipe.Name] = itemButton;
var isCurrentButtonSelected = _selected == recipe;
_recipeButtons[recipe.Prototype.Name!] = itemButton;
var isCurrentButtonSelected = _selected == recipe.Prototype;
itemButton.Pressed = isCurrentButtonSelected;
SelectGridButton(itemButton, isCurrentButtonSelected);
}
}
else
private List<ConstructionMenu.ConstructionMenuListData> GetAndSortRecipes((string, string) args)
{
foreach (var recipe in recipes)
var recipes = new List<ConstructionMenu.ConstructionMenuListData>();
var (search, category) = args;
var isEmptyCategory = string.IsNullOrEmpty(category) || category == ForAllCategoryName;
_selectedCategory = isEmptyCategory ? string.Empty : category;
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{
recipesList.Add(GetItem(recipe, recipesList));
}
}
if (recipe.Hide)
continue;
if (_playerManager.LocalSession == null
|| _playerManager.LocalEntity == null
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
continue;
if (!string.IsNullOrEmpty(search) && (recipe.Name is { } name &&
!name.Contains(search.Trim(),
StringComparison.InvariantCultureIgnoreCase)))
continue;
if (!isEmptyCategory)
{
if ((category != FavoriteCatName || !_favoritedRecipes.Contains(recipe)) &&
recipe.Category != category)
continue;
}
private void SelectGridButton(TextureButton button, bool select)
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
{
Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
recipe.ID,
nameof(ConstructionPrototype));
continue;
}
if (!_prototypeManager.TryIndex(targetProtoId, out EntityPrototype? proto))
continue;
recipes.Add(new(recipe, proto));
}
recipes.Sort(
(a, b) => string.Compare(a.Prototype.Name, b.Prototype.Name, StringComparison.InvariantCulture));
return recipes;
}
private void SelectGridButton(BaseButton button, bool select)
{
if (button.Parent is not PanelContainer buttonPanel)
return;
button.Modulate = select ? Color.Green : Color.White;
button.Modulate = select ? Color.Green : Color.Transparent;
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
}
@@ -302,12 +327,12 @@ namespace Content.Client.Construction.UI
// hard-coded to show all recipes
var idx = 0;
categoriesArray[idx++] = _forAllCategoryName;
categoriesArray[idx++] = ForAllCategoryName;
// hard-coded to show favorites if it need
if (isFavorites)
{
categoriesArray[idx++] = _favoriteCatName;
categoriesArray[idx++] = FavoriteCatName;
}
var sortedProtoCategories = uniqueCategories.OrderBy(Loc.GetString);
@@ -325,18 +350,31 @@ namespace Content.Client.Construction.UI
if (!string.IsNullOrEmpty(selectCategory) && selectCategory == categoriesArray[i])
_constructionView.OptionCategories.SelectId(i);
}
_constructionView.Categories = categoriesArray;
}
private void PopulateInfo(ConstructionPrototype prototype)
private void PopulateInfo(ConstructionPrototype? prototype)
{
if (_constructionSystem is null)
return;
_constructionView.ClearRecipeInfo();
if (prototype is null)
return;
if (!_constructionSystem.TryGetRecipePrototype(prototype.ID, out var targetProtoId))
return;
if (!_prototypeManager.TryIndex(targetProtoId, out EntityPrototype? proto))
return;
_constructionView.SetRecipeInfo(
prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
prototype.Name!,
prototype.Description!,
proto,
prototype.Type != ConstructionType.Item,
!_favoritedRecipes.Contains(prototype));
@@ -349,16 +387,17 @@ namespace Content.Client.Construction.UI
if (_constructionSystem?.GetGuide(prototype) is not { } guide)
return;
foreach (var entry in guide.Entries)
{
var text = entry.Arguments != null
? Loc.GetString(entry.Localization, entry.Arguments) : Loc.GetString(entry.Localization);
? Loc.GetString(entry.Localization, entry.Arguments)
: Loc.GetString(entry.Localization);
if (entry.EntryNumber is { } number)
{
text = Loc.GetString("construction-presenter-step-wrapper",
("step-number", number), ("text", text));
("step-number", number),
("text", text));
}
// The padding needs to be applied regardless of text length... (See PadLeft documentation)
@@ -369,23 +408,12 @@ namespace Content.Client.Construction.UI
}
}
private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
{
return new(itemList)
{
Metadata = recipe,
Text = recipe.Name,
Icon = _spriteSystem.Frame0(recipe.Icon),
TooltipEnabled = true,
TooltipText = recipe.Description,
};
}
private void BuildButtonToggled(bool pressed)
{
if (pressed)
{
if (_selected == null) return;
if (_selected == null)
return;
// not bound to a construction system
if (_constructionSystem is null)
@@ -405,7 +433,8 @@ namespace Content.Client.Construction.UI
{
IsTile = false,
PlacementOption = _selected.PlacementMode
}, new ConstructionPlacementHijack(_constructionSystem, _selected));
},
new ConstructionPlacementHijack(_constructionSystem, _selected));
UpdateGhostPlacement();
}
@@ -432,35 +461,36 @@ namespace Content.Client.Construction.UI
{
IsTile = false,
PlacementOption = _selected.PlacementMode,
}, new ConstructionPlacementHijack(constructSystem, _selected));
},
new ConstructionPlacementHijack(constructSystem, _selected));
_constructionView.BuildButtonPressed = true;
}
private void OnSystemLoaded(object? sender, SystemChangedArgs args)
{
if (args.System is ConstructionSystem system) SystemBindingChanged(system);
if (args.System is ConstructionSystem system)
SystemBindingChanged(system);
}
private void OnSystemUnloaded(object? sender, SystemChangedArgs args)
{
if (args.System is ConstructionSystem) SystemBindingChanged(null);
if (args.System is ConstructionSystem)
SystemBindingChanged(null);
}
private void OnViewFavoriteRecipe()
{
if (_selected is not ConstructionPrototype recipe)
if (_selected is null)
return;
if (!_favoritedRecipes.Remove(_selected))
_favoritedRecipes.Add(_selected);
if (_selectedCategory == _favoriteCatName)
if (_selectedCategory == FavoriteCatName)
{
if (_favoritedRecipes.Count > 0)
OnViewPopulateRecipes(_constructionView, (string.Empty, _favoriteCatName));
else
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
OnViewPopulateRecipes(_constructionView,
_favoritedRecipes.Count > 0 ? (string.Empty, FavoriteCatName) : (string.Empty, string.Empty));
}
PopulateInfo(_selected);
@@ -492,6 +522,9 @@ namespace Content.Client.Construction.UI
private void BindToSystem(ConstructionSystem system)
{
_constructionSystem = system;
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
system.ToggleCraftingWindow += SystemOnToggleMenu;
system.FlipConstructionPrototype += SystemFlipConstructionPrototype;
system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged;
@@ -533,7 +566,8 @@ namespace Content.Client.Construction.UI
if (IsAtFront)
{
WindowOpen = false;
_uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
_uiManager.GetActiveUIWidget<GameTopMenuBar>()
.CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
}
else
_constructionView.MoveToFront();
@@ -541,7 +575,8 @@ namespace Content.Client.Construction.UI
else
{
WindowOpen = true;
_uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
_uiManager.GetActiveUIWidget<GameTopMenuBar>()
.CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
}
}

View File

@@ -1,7 +1,7 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'credits-window-title'}"
SetSize="650 650" >
<TabContainer>
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>

View File

@@ -1,39 +1,48 @@
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
namespace Content.Client.Credits;
[GenerateTypedNameReferences]
public sealed partial class CreditsWindow : DefaultWindow
{
[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
["Revolutionary"] = 3,
};
private readonly List<FormattedMessage> _attributions = [];
private readonly ISawmill _sawmill = Logger.GetSawmill("Credits");
private const int AttributionsSourcesPerPage = 50;
public CreditsWindow()
{
IoCManager.InjectDependencies(this);
@@ -42,23 +51,218 @@ namespace Content.Client.Credits
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 += _ =>
{
_attributions.Clear();
};
MasterTabContainer.OnTabChanged += OnTabChanged;
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);
}
foreach (var message in _attributions.Skip(count).Take(AttributionsSourcesPerPage))
{
var rich = new RichTextLabel();
rich.SetMessage(message);
attributionsContainer.AddChild(rich);
}
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
{
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);
}
}
return attrs;
});
}
private Task<List<FormattedMessage>> CollectRgaAttributions()
{
return Task.Run(() =>
{
var rgaStreams = _resourceManager.ContentFindFiles("/")
.Where(p => p.Filename == "attributions.yml");
var attrs = new List<FormattedMessage>();
foreach (var stream in rgaStreams)
{
try
{
var yamlStream = _resourceManager.ContentFileReadYaml(stream);
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);
}
}
return attrs;
});
}
private void PopulateLicenses(BoxContainer licensesContainer)
{
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
{
licensesContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = entry.Name});
licensesContainer.AddChild(new Label
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = entry.Name });
// 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"))
{
licensesContainer.AddChild(new Label {Text = line, FontColorOverride = new Color(200, 200, 200)});
licensesContainer.AddChild(new Label { Text = line, FontColorOverride = new Color(200, 200, 200) });
}
}
}
@@ -76,7 +280,7 @@ namespace Content.Client.Credits
patronsContainer.AddChild(patronButton = new Button
{
Text = Loc.GetString("credits-window-become-patron-button"),
HorizontalAlignment = HAlignment.Center
HorizontalAlignment = HAlignment.Center,
});
patronButton.OnPressed +=
@@ -87,12 +291,11 @@ namespace Content.Client.Credits
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)});
}
patronsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
first = false;
patronsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = $"{tier.Key}"});
patronsContainer.AddChild(new Label
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = $"{tier.Key}" });
var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
@@ -105,8 +308,8 @@ namespace Content.Client.Credits
private IEnumerable<PatronEntry> LoadPatrons()
{
var yamlStream = _resourceManager.ContentFileReadYaml(new ("/Credits/Patrons.yml"));
var sequence = (YamlSequenceNode) yamlStream.Documents[0].RootNode;
var yamlStream = _resourceManager.ContentFileReadYaml(new ResPath("/Credits/Patrons.yml"));
var sequence = (YamlSequenceNode)yamlStream.Documents[0].RootNode;
return sequence
.Cast<YamlMappingNode>()
@@ -124,9 +327,9 @@ namespace Content.Client.Credits
SeparationOverride = 20,
Children =
{
new Label {Text = Loc.GetString("credits-window-contributor-encouragement-label") },
(contributeButton = new Button {Text = Loc.GetString("credits-window-contribute-button")})
}
new Label { Text = Loc.GetString("credits-window-contributor-encouragement-label") },
(contributeButton = new Button { Text = Loc.GetString("credits-window-contribute-button") }),
},
});
var first = true;
@@ -134,30 +337,25 @@ namespace Content.Client.Credits
void AddSection(string title, string path, bool markup = false)
{
if (!first)
{
ss14ContributorsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
}
ss14ContributorsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
first = false;
ss14ContributorsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = title});
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-tts-title"), "TTS.txt"); // Corvax-TTS
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-tts-title"), "TTS.txt"); // Corvax-TTS
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
@@ -181,5 +379,4 @@ namespace Content.Client.Credits
Tier = tier;
}
}
}
}

View File

@@ -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--;
}
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));
}
// 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--;
}
}
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
{
var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown");

View File

@@ -0,0 +1,7 @@
using Content.Shared.Damage.Systems;
namespace Content.Client.Damage.Systems;
public sealed partial class StaminaSystem : SharedStaminaSystem
{
}

View File

@@ -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);
}

View File

@@ -43,3 +43,9 @@ public enum DeliveryVisualLayers : byte
Breakage,
Trash,
}
public enum DeliverySpawnerVisualLayers : byte
{
Contents,
}

View File

@@ -0,0 +1,8 @@
using Content.Shared.DeviceNetwork.Systems;
namespace Content.Client.DeviceNetwork.Systems;
public sealed class DeviceNetworkSystem : SharedDeviceNetworkSystem
{
}

View File

@@ -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;
}
}

View File

@@ -1,9 +0,0 @@
using Content.Shared.Disposal.Components;
namespace Content.Client.Disposal;
[RegisterComponent]
public sealed partial class DisposalUnitComponent : SharedDisposalUnitComponent
{
}

View File

@@ -0,0 +1,80 @@
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;
using System.Linq;
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;
var entries = entity.Comp.TargetList.Select(target => new ItemList.Item(MailingUnitWindow.TargetListContainer) {
Text = target,
Selected = target == entity.Comp.Target
}).ToList();
MailingUnitWindow.TargetListContainer.SetItems(entries);
}
}

View 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);
}
}
}

View File

@@ -1,5 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Disposal.UI"
<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">
@@ -7,6 +8,7 @@
<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>

View 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);
}
}
}

View File

@@ -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
{

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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"/>

View File

@@ -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.

View File

@@ -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"/>

View File

@@ -0,0 +1,8 @@
using Content.Shared.Disposal.Unit;
namespace Content.Client.Disposal.Tube;
public sealed class DisposalTubeSystem : SharedDisposalTubeSystem
{
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View 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
}

View File

@@ -1,20 +1,21 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Disposal.UI"
<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 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>

View 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);
}
}
}

View File

@@ -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)

View 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);
}
}

View File

@@ -21,6 +21,7 @@ using Content.Client.Replay;
using Content.Client.Screenshot;
using Content.Client.Singularity;
using Content.Client.Stylesheets;
using Content.Client.UserInterface;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Shared.Ame.Components;
@@ -74,6 +75,7 @@ namespace Content.Client.Entry
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
public override void Init()
{
@@ -227,6 +229,15 @@ namespace Content.Client.Entry
{
_debugMonitorManager.FrameUpdate();
}
if (level == ModUpdateLevel.PreEngine)
{
if (_baseClient.RunLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame)
{
var updateSystem = _entitySystemManager.GetEntitySystem<BuiPreTickUpdateSystem>();
updateSystem.RunUpdates();
}
}
}
}
}

View File

@@ -32,10 +32,11 @@ 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;
private EntityUid _lastExaminedEntity;
private Popup? _examineTooltipOpen;
private ScreenCoordinates _popupPos;
private CancellationTokenSource? _requestCancelTokenSource;
@@ -158,13 +159,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 +260,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 +284,29 @@ namespace Content.Client.Examine
break;
}
verbs ??= new List<Verb>();
var totalVerbs = _verbSystem.GetLocalVerbs(target, player, typeof(ExamineVerb));
// 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);
}
@@ -394,15 +415,14 @@ namespace Content.Client.Examine
if (!IsClientSide(entity))
{
// Ask server for extra examine info.
if (entity != _lastExaminedEntity)
unchecked
{
_idCounter += 1;
if (_idCounter == int.MaxValue)
_idCounter = 0;
}
RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(GetNetEntity(entity), _idCounter, true));
}
RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value));
_lastExaminedEntity = entity;
}
private void CloseTooltip()

View File

@@ -93,7 +93,7 @@ public sealed partial class TriggerSystem
break;
case ProximityTriggerVisuals.Active:
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
_player.Play(uid, player, _flasherAnimation, AnimKey);
_player.Play((uid, player), _flasherAnimation, AnimKey);
break;
case ProximityTriggerVisuals.Off:
default:

View File

@@ -34,7 +34,7 @@ public sealed class EyeLerpingSystem : EntitySystem
SubscribeLocalEvent<LerpingEyeComponent, LocalPlayerDetachedEvent>(OnDetached);
UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(PhysicsSystem));
UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem));
UpdatesBefore.Add(typeof(SharedEyeSystem));
UpdatesOutsidePrediction = true;
}

View File

@@ -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);

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