Compare commits

..

1 Commits

Author SHA1 Message Date
Ed
fd3f222e7d Update CP14BiomeSpawnerSystem.cs 2024-07-30 13:21:06 +03:00
7219 changed files with 696527 additions and 710624 deletions

2
.github/CODEOWNERS vendored
View File

@@ -19,7 +19,7 @@
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/Prototypes/Maps/** @Emisse /Resources/Prototypes/Maps/ @Emisse
/Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf

14
.github/FUNDING.yml vendored
View File

@@ -1,14 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: ['https://boosty.to/theshued']

View File

@@ -85,7 +85,7 @@
"CC-BY-NC-SA-3.0", "CC-BY-NC-SA-3.0",
"CC-BY-NC-SA-4.0", "CC-BY-NC-SA-4.0",
"CC0-1.0", "CC0-1.0",
"CLA" "All rights reserved for the CrystallPunk14 project only"
], ],
"examples":[ "examples":[
"CC-BY-SA-3.0" "CC-BY-SA-3.0"

View File

@@ -1,20 +0,0 @@
name: Publish
concurrency:
group: publish
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Send POST-request
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.BUILD_HOST }}
username: ${{ secrets.BUILD_USER }}
password: ${{ secrets.BUILD_PASS }}
port: 22
script: sh update.sh &> /dev/null

View File

@@ -5,8 +5,8 @@ concurrency:
on: on:
workflow_dispatch: workflow_dispatch:
# schedule: schedule:
# - cron: '0 10 * * *' - cron: '0 10 * * *'
jobs: jobs:
build: build:
@@ -41,10 +41,21 @@ jobs:
- name: Package client - name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Upload build artifact
id: artifact-upload-step
uses: actions/upload-artifact@v4
with:
name: build
path: release/*.zip
compression-level: 0
retention-days: 0
- name: Publish version - name: Publish version
run: Tools/publish_multi_request.py run: Tools/publish_github_artifact.py
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }}
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
- name: Publish changelog (Discord) - name: Publish changelog (Discord)
@@ -57,3 +68,8 @@ jobs:
run: Tools/actions_changelog_rss.py run: Tools/actions_changelog_rss.py
env: env:
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }} CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
- uses: geekyeggo/delete-artifact@v5
if: always()
with:
name: build

View File

@@ -19,8 +19,6 @@ jobs:
- name: Get this week's Contributors - name: Get this week's Contributors
shell: pwsh shell: pwsh
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt
# TODO # TODO

6
.vscode/tasks.json vendored
View File

@@ -10,7 +10,7 @@
"args": [ "args": [
"build", "build",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names. "/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:'ForceNoAlign;NoSummary'" // Do not generate summary otherwise it leads to duplicate errors in Problems panel "/consoleloggerparameters:NoSummary" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
], ],
"group": { "group": {
"kind": "build", "kind": "build",
@@ -29,9 +29,9 @@
"build", "build",
"${workspaceFolder}/Content.YAMLLinter/Content.YAMLLinter.csproj", "${workspaceFolder}/Content.YAMLLinter/Content.YAMLLinter.csproj",
"/property:GenerateFullPaths=true", "/property:GenerateFullPaths=true",
"/consoleloggerparameters:'ForceNoAlign;NoSummary'" "/consoleloggerparameters:NoSummary"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
} }
] ]
} }

View File

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

View File

@@ -38,7 +38,7 @@ namespace Content.Client.Access.UI
SendMessage(new AgentIDCardJobChangedMessage(newJob)); SendMessage(new AgentIDCardJobChangedMessage(newJob));
} }
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId) public void OnJobIconChanged(ProtoId<StatusIconPrototype> newJobIconId)
{ {
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId)); SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
} }
@@ -55,7 +55,7 @@ namespace Content.Client.Access.UI
_window.SetCurrentName(cast.CurrentName); _window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob); _window.SetCurrentJob(cast.CurrentJob);
_window.SetAllowedIcons(cast.CurrentJobIconId); _window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId);
} }
} }
} }

View File

@@ -6,9 +6,12 @@
<LineEdit Name="NameLineEdit" /> <LineEdit Name="NameLineEdit" />
<Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" /> <Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" />
<LineEdit Name="JobLineEdit" /> <LineEdit Name="JobLineEdit" />
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/> <BoxContainer Orientation="Horizontal">
<GridContainer Name="IconGrid" Columns="10"> <Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
<!-- Job icon buttons are generated in the code --> <Control HorizontalExpand="True" MinSize="50 0"/>
</GridContainer> <GridContainer Name="IconGrid" Columns="10">
<!-- Job icon buttons are generated in the code -->
</GridContainer>
</BoxContainer>
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Numerics; using System.Numerics;
using System.Linq;
namespace Content.Client.Access.UI namespace Content.Client.Access.UI
{ {
@@ -24,7 +23,7 @@ namespace Content.Client.Access.UI
public event Action<string>? OnNameChanged; public event Action<string>? OnNameChanged;
public event Action<string>? OnJobChanged; public event Action<string>? OnJobChanged;
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged; public event Action<ProtoId<StatusIconPrototype>>? OnJobIconChanged;
public AgentIDCardWindow() public AgentIDCardWindow()
{ {
@@ -39,16 +38,17 @@ namespace Content.Client.Access.UI
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text); JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
} }
public void SetAllowedIcons(string currentJobIconId) public void SetAllowedIcons(HashSet<ProtoId<StatusIconPrototype>> icons, string currentJobIconId)
{ {
IconGrid.DisposeAllChildren(); IconGrid.DisposeAllChildren();
var jobIconButtonGroup = new ButtonGroup(); var jobIconGroup = new ButtonGroup();
var i = 0; var i = 0;
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList(); foreach (var jobIconId in icons)
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
foreach (var jobIcon in icons)
{ {
if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
continue;
String styleBase = StyleBase.ButtonOpenBoth; String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount; var modulo = i % JobIconColumnCount;
if (modulo == 0) if (modulo == 0)
@@ -62,9 +62,8 @@ namespace Content.Client.Access.UI
Access = AccessLevel.Public, Access = AccessLevel.Public,
StyleClasses = { styleBase }, StyleClasses = { styleBase },
MaxSize = new Vector2(42, 28), MaxSize = new Vector2(42, 28),
Group = jobIconButtonGroup, Group = jobIconGroup,
Pressed = currentJobIconId == jobIcon.ID, Pressed = i == 0,
ToolTip = jobIcon.LocalizedJobName
}; };
// Generate buttons textures // Generate buttons textures
@@ -79,6 +78,9 @@ namespace Content.Client.Access.UI
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID); jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
IconGrid.AddChild(jobIconButton); IconGrid.AddChild(jobIconButton);
if (jobIconId.Equals(currentJobIconId))
jobIconButton.Pressed = true;
i++; i++;
} }
} }

View File

@@ -48,30 +48,6 @@ namespace Content.Client.Actions
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState); SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState); SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState); SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
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) private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
@@ -100,26 +76,12 @@ namespace Content.Client.Actions
BaseHandleState<WorldTargetActionComponent>(uid, component, state); BaseHandleState<WorldTargetActionComponent>(uid, component, state);
} }
private void OnEntityWorldTargetHandleState(EntityUid uid,
EntityWorldTargetActionComponent component,
ref ComponentHandleState args)
{
if (args.Current is not EntityWorldTargetActionComponentState state)
return;
component.Whitelist = state.Whitelist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
}
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{ {
// TODO ACTIONS use auto comp states // TODO ACTIONS use auto comp states
component.Icon = state.Icon; component.Icon = state.Icon;
component.IconOn = state.IconOn; component.IconOn = state.IconOn;
component.IconColor = state.IconColor; component.IconColor = state.IconColor;
component.OriginalIconColor = state.OriginalIconColor;
component.DisabledIconColor = state.DisabledIconColor;
component.Keywords.Clear(); component.Keywords.Clear();
component.Keywords.UnionWith(state.Keywords); component.Keywords.UnionWith(state.Keywords);
component.Enabled = state.Enabled; component.Enabled = state.Enabled;
@@ -150,8 +112,6 @@ namespace Content.Client.Actions
if (!ResolveActionData(actionId, ref action)) if (!ResolveActionData(actionId, ref action))
return; return;
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
base.UpdateAction(actionId, action); base.UpdateAction(actionId, action);
if (_playerManager.LocalEntity != action.AttachedEntity) if (_playerManager.LocalEntity != action.AttachedEntity)
return; return;
@@ -286,6 +246,12 @@ namespace Content.Client.Actions
if (action.ClientExclusive) if (action.ClientExclusive)
{ {
if (instantAction.Event != null)
{
instantAction.Event.Performer = user;
instantAction.Event.Action = actionId;
}
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
} }
else else
@@ -327,7 +293,7 @@ namespace Content.Client.Actions
continue; continue;
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true); var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
var actionId = Spawn(); var actionId = Spawn(null);
AddComp(actionId, action); AddComp(actionId, action);
AddActionDirect(user, actionId); AddActionDirect(user, actionId);

View File

@@ -101,7 +101,7 @@ namespace Content.Client.Actions.UI
{ {
var duration = Cooldown.Value.End - Cooldown.Value.Start; var duration = Cooldown.Value.End - Cooldown.Value.Start;
if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup)) if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
return; return;
_cooldownLabel.SetMessage(markup); _cooldownLabel.SetMessage(markup);

View File

@@ -2,75 +2,72 @@ using System.Numerics;
using Content.Client.Administration.Systems; using Content.Client.Administration.Systems;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Configuration; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Administration; namespace Content.Client.Administration
internal sealed class AdminNameOverlay : Overlay
{ {
private readonly AdminSystem _system; internal sealed class AdminNameOverlay : Overlay
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup;
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly Font _font;
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
{ {
_system = system; private readonly AdminSystem _system;
_entityManager = entityManager; private readonly IEntityManager _entityManager;
_eyeManager = eyeManager; private readonly IEyeManager _eyeManager;
_entityLookup = entityLookup; private readonly EntityLookupSystem _entityLookup;
_userInterfaceManager = userInterfaceManager; private readonly Font _font;
ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
public override OverlaySpace Space => OverlaySpace.ScreenSpace; public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.WorldAABB;
foreach (var playerInfo in _system.PlayerList)
{ {
var entity = _entityManager.GetEntity(playerInfo.NetEntity); _system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_entityLookup = entityLookup;
ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
// Otherwise the entity can not exist yet public override OverlaySpace Space => OverlaySpace.ScreenSpace;
if (entity == null || !_entityManager.EntityExists(entity))
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.WorldAABB;
foreach (var playerInfo in _system.PlayerList)
{ {
continue; var entity = _entityManager.GetEntity(playerInfo.NetEntity);
}
// if not on the same map, continue // Otherwise the entity can not exist yet
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId) if (entity == null || !_entityManager.EntityExists(entity))
{ {
continue; continue;
} }
var aabb = _entityLookup.GetWorldAABB(entity.Value); // if not on the same map, continue
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != _eyeManager.CurrentMap)
{
continue;
}
// if not on screen, continue var aabb = _entityLookup.GetWorldAABB(entity.Value);
if (!aabb.Intersects(in viewport))
{
continue;
}
var uiScale = _userInterfaceManager.RootControl.UIScale; // if not on screen, continue
var lineoffset = new Vector2(0f, 11f) * uiScale; if (!aabb.Intersects(in viewport))
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center + {
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec( continue;
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f); }
if (playerInfo.Antag)
{ var lineoffset = new Vector2(0f, 11f);
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", uiScale, Color.OrangeRed); var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
; new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
if (playerInfo.Antag)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
}
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
} }
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
} }
} }
} }

View File

@@ -1,8 +1,6 @@
using Content.Client.Administration.Managers; using Content.Client.Administration.Managers;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
namespace Content.Client.Administration.Systems namespace Content.Client.Administration.Systems
{ {
@@ -13,7 +11,6 @@ namespace Content.Client.Administration.Systems
[Dependency] private readonly IClientAdminManager _adminManager = default!; [Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
private AdminNameOverlay _adminNameOverlay = default!; private AdminNameOverlay _adminNameOverlay = default!;
@@ -22,7 +19,7 @@ namespace Content.Client.Administration.Systems
private void InitializeOverlay() private void InitializeOverlay()
{ {
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager); _adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated; _adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
} }

View File

@@ -1,4 +1,4 @@
using Content.Shared.Administration.Notes; using Content.Shared.Administration.Notes;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -13,7 +13,7 @@ public sealed partial class AdminMessagePopupMessage : Control
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
Admin.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString( Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
"admin-notes-message-admin", "admin-notes-message-admin",
("admin", message.AdminName), ("admin", message.AdminName),
("date", message.AddedOn.ToLocalTime())))); ("date", message.AddedOn.ToLocalTime()))));

View File

@@ -49,7 +49,7 @@ public sealed partial class AdminMessagePopupWindow : Control
MessageContainer.AddChild(new AdminMessagePopupMessage(message)); MessageContainer.AddChild(new AdminMessagePopupMessage(message));
} }
Description.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length)))); Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
} }
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj) private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)

View File

@@ -1,5 +1,4 @@
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;

View File

@@ -11,8 +11,9 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Configuration;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Timing;
using Robust.Shared.Configuration;
namespace Content.Client.Administration.UI.Bwoink namespace Content.Client.Administration.UI.Bwoink
{ {
@@ -87,51 +88,26 @@ namespace Content.Client.Administration.UI.Bwoink
var ach = AHelpHelper.EnsurePanel(a.SessionId); var ach = AHelpHelper.EnsurePanel(a.SessionId);
var bch = AHelpHelper.EnsurePanel(b.SessionId); var bch = AHelpHelper.EnsurePanel(b.SessionId);
// Pinned players first // First, sort by unread. Any chat with unread messages appears first. We just sort based on unread
if (a.IsPinned != b.IsPinned) // status, not number of unread messages, so that more recent unread messages take priority.
return a.IsPinned ? -1 : 1;
// First, sort by unread. Any chat with unread messages appears first.
var aUnread = ach.Unread > 0; var aUnread = ach.Unread > 0;
var bUnread = bch.Unread > 0; var bUnread = bch.Unread > 0;
if (aUnread != bUnread) if (aUnread != bUnread)
return aUnread ? -1 : 1; return aUnread ? -1 : 1;
// Sort by recent messages during the current round.
var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
if (aRecent != bRecent)
return aRecent ? -1 : 1;
// Next, sort by connection status. Any disconnected players are grouped towards the end. // Next, sort by connection status. Any disconnected players are grouped towards the end.
if (a.Connected != b.Connected) if (a.Connected != b.Connected)
return a.Connected ? -1 : 1; return a.Connected ? -1 : 1;
// Sort connected players by New Player status, then by Antag status // Next, group by whether or not the players have participated in this round.
if (a.Connected && b.Connected) // The ahelp window shows all players that have connected since server restart, this groups them all towards the bottom.
{ if (a.ActiveThisRound != b.ActiveThisRound)
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)); return a.ActiveThisRound ? -1 : 1;
var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
if (aNewPlayer != bNewPlayer)
return aNewPlayer ? -1 : 1;
if (a.Antag != b.Antag)
return a.Antag ? -1 : 1;
}
// Sort disconnected players by participation in the round
if (!a.Connected && !b.Connected)
{
if (a.ActiveThisRound != b.ActiveThisRound)
return a.ActiveThisRound ? -1 : 1;
}
// Finally, sort by the most recent message. // Finally, sort by the most recent message.
return bch.LastMessage.CompareTo(ach.LastMessage); return bch.LastMessage.CompareTo(ach.LastMessage);
}; };
Bans.OnPressed += _ => Bans.OnPressed += _ =>
{ {
if (_currentPlayer is not null) if (_currentPlayer is not null)
@@ -277,20 +253,7 @@ namespace Content.Client.Administration.UI.Bwoink
public void PopulateList() public void PopulateList()
{ {
// Maintain existing pin statuses
var pinnedPlayers = ChannelSelector.PlayerInfo.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
ChannelSelector.PopulateList(); ChannelSelector.PopulateList();
// Restore pin statuses
foreach (var player in ChannelSelector.PlayerInfo)
{
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
{
player.IsPinned = pinnedPlayer.IsPinned;
}
}
UpdateButtons(); UpdateButtons();
} }
} }

View File

@@ -59,7 +59,7 @@ namespace Content.Client.Administration.UI.Bwoink
Unread++; Unread++;
var formatted = new FormattedMessage(1); var formatted = new FormattedMessage(1);
formatted.AddMarkupOrThrow($"[color=gray]{message.SentAt.ToShortTimeString()}[/color] {message.Text}"); formatted.AddMarkup($"[color=gray]{message.SentAt.ToShortTimeString()}[/color] {message.Text}");
TextOutput.AddMessage(formatted); TextOutput.AddMessage(formatted);
LastMessage = message.SentAt; LastMessage = message.SentAt;
} }

View File

@@ -30,11 +30,7 @@ namespace Content.Client.Administration.UI.Bwoink
} }
}; };
OnOpen += () => OnOpen += () => Bwoink.PopulateList();
{
Bwoink.ChannelSelector.StopFiltering();
Bwoink.PopulateList();
};
} }
} }
} }

View File

@@ -4,166 +4,154 @@ using Content.Client.UserInterface.Controls;
using Content.Client.Verbs.UI; using Content.Client.Verbs.UI;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls; namespace Content.Client.Administration.UI.CustomControls
[GenerateTypedNameReferences]
public sealed partial class PlayerListControl : BoxContainer
{ {
private readonly AdminSystem _adminSystem; [GenerateTypedNameReferences]
public sealed partial class PlayerListControl : BoxContainer
private readonly IEntityManager _entManager;
private readonly IUserInterfaceManager _uiManager;
private PlayerInfo? _selectedPlayer;
private List<PlayerInfo> _playerList = new();
private List<PlayerInfo> _sortedPlayerList = new();
public Comparison<PlayerInfo>? Comparison;
public Func<PlayerInfo, string, string>? OverrideText;
public PlayerListControl()
{ {
_entManager = IoCManager.Resolve<IEntityManager>(); private readonly AdminSystem _adminSystem;
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
_adminSystem = _entManager.System<AdminSystem>();
RobustXamlLoader.Load(this);
// Fill the Option data
PlayerListContainer.ItemPressed += PlayerListItemPressed;
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
PopulateList(_adminSystem.PlayerList);
FilterLineEdit.OnTextChanged += _ => FilterList();
_adminSystem.PlayerListChanged += PopulateList;
BackgroundPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40) };
}
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList; private List<PlayerInfo> _playerList = new();
private readonly List<PlayerInfo> _sortedPlayerList = new();
public event Action<PlayerInfo?>? OnSelectionChanged; public event Action<PlayerInfo?>? OnSelectionChanged;
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
private void PlayerListNoItemSelected() public Func<PlayerInfo, string, string>? OverrideText;
{ public Comparison<PlayerInfo>? Comparison;
_selectedPlayer = null;
OnSelectionChanged?.Invoke(null);
}
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data) private IEntityManager _entManager;
{ private IUserInterfaceManager _uiManager;
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (selectedPlayer == _selectedPlayer) private PlayerInfo? _selectedPlayer;
return;
if (args.Event.Function != EngineKeyFunctions.UIClick) public PlayerListControl()
return;
OnSelectionChanged?.Invoke(selectedPlayer);
_selectedPlayer = selectedPlayer;
// update label text. Only required if there is some override (e.g. unread bwoink count).
if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
label.Text = GetText(selectedPlayer);
}
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
return;
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
args.Handle();
}
public void StopFiltering()
{
FilterLineEdit.Text = string.Empty;
}
private void FilterList()
{
_sortedPlayerList.Clear();
foreach (var info in _playerList)
{ {
var displayName = $"{info.CharacterName} ({info.Username})"; _entManager = IoCManager.Resolve<IEntityManager>();
if (info.IdentityName != info.CharacterName) _uiManager = IoCManager.Resolve<IUserInterfaceManager>();
displayName += $" [{info.IdentityName}]"; _adminSystem = _entManager.System<AdminSystem>();
if (!string.IsNullOrEmpty(FilterLineEdit.Text) RobustXamlLoader.Load(this);
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant())) // Fill the Option data
continue; PlayerListContainer.ItemPressed += PlayerListItemPressed;
_sortedPlayerList.Add(info); PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
PopulateList(_adminSystem.PlayerList);
FilterLineEdit.OnTextChanged += _ => FilterList();
_adminSystem.PlayerListChanged += PopulateList;
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
} }
if (Comparison != null) private void PlayerListNoItemSelected()
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
if (_selectedPlayer != null)
PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
}
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
{
// Maintain existing pin statuses
var pinnedPlayers = _playerList.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
players ??= _adminSystem.PlayerList;
_playerList = players.ToList();
// Restore pin statuses
foreach (var player in _playerList)
{ {
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
{
player.IsPinned = pinnedPlayer.IsPinned;
}
}
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
_selectedPlayer = null; _selectedPlayer = null;
OnSelectionChanged?.Invoke(null);
}
FilterList(); private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
}
private string GetText(PlayerInfo info)
{
var text = $"{info.CharacterName} ({info.Username})";
if (OverrideText != null)
text = OverrideText.Invoke(info, text);
return text;
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var info })
return;
var entry = new PlayerListEntry();
entry.Setup(info, OverrideText);
entry.OnPinStatusChanged += _ =>
{ {
if (args == null || data is not PlayerListData {Info: var selectedPlayer})
return;
if (selectedPlayer == _selectedPlayer)
return;
if (args.Event.Function != EngineKeyFunctions.UIClick)
return;
OnSelectionChanged?.Invoke(selectedPlayer);
_selectedPlayer = selectedPlayer;
// update label text. Only required if there is some override (e.g. unread bwoink count).
if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label)
label.Text = GetText(selectedPlayer);
}
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null)
return;
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
args.Handle();
}
public void StopFiltering()
{
FilterLineEdit.Text = string.Empty;
}
private void FilterList()
{
_sortedPlayerList.Clear();
foreach (var info in _playerList)
{
var displayName = $"{info.CharacterName} ({info.Username})";
if (info.IdentityName != info.CharacterName)
displayName += $" [{info.IdentityName}]";
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
continue;
_sortedPlayerList.Add(info);
}
if (Comparison != null)
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
if (_selectedPlayer != null)
PlayerListContainer.Select(new PlayerListData(_selectedPlayer));
}
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
{
players ??= _adminSystem.PlayerList;
_playerList = players.ToList();
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
_selectedPlayer = null;
FilterList(); FilterList();
}; }
button.AddChild(entry); private string GetText(PlayerInfo info)
button.AddStyleClass(ListContainer.StyleClassListContainerButton); {
var text = $"{info.CharacterName} ({info.Username})";
if (OverrideText != null)
text = OverrideText.Invoke(info, text);
return text;
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var info })
return;
button.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
new Label
{
ClipText = true,
Text = GetText(info)
}
}
});
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
}
} }
}
public record PlayerListData(PlayerInfo Info) : ListData; public record PlayerListData(PlayerInfo Info) : ListData;
}

View File

@@ -1,6 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal" HorizontalExpand="true">
<Label Name="PlayerEntryLabel" Text="" ClipText="True" HorizontalExpand="True" />
<TextureButton Name="PlayerEntryPinButton"
HorizontalAlignment="Right" />
</BoxContainer>

View File

@@ -1,58 +0,0 @@
using Content.Client.Stylesheets;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
[GenerateTypedNameReferences]
public sealed partial class PlayerListEntry : BoxContainer
{
public PlayerListEntry()
{
RobustXamlLoader.Load(this);
}
public event Action<PlayerInfo>? OnPinStatusChanged;
public void Setup(PlayerInfo info, Func<PlayerInfo, string, string>? overrideText)
{
Update(info, overrideText);
PlayerEntryPinButton.OnPressed += HandlePinButtonPressed(info);
}
private Action<BaseButton.ButtonEventArgs> HandlePinButtonPressed(PlayerInfo info)
{
return args =>
{
info.IsPinned = !info.IsPinned;
UpdatePinButtonTexture(info.IsPinned);
OnPinStatusChanged?.Invoke(info);
};
}
private void Update(PlayerInfo info, Func<PlayerInfo, string, string>? overrideText)
{
PlayerEntryLabel.Text = overrideText?.Invoke(info, $"{info.CharacterName} ({info.Username})") ??
$"{info.CharacterName} ({info.Username})";
UpdatePinButtonTexture(info.IsPinned);
}
private void UpdatePinButtonTexture(bool isPinned)
{
if (isPinned)
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned);
}
else
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned);
}
}
}

View File

@@ -1,36 +0,0 @@
<ui:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc ban-panel-title}" MinSize="300 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Name="PlayerName"/>
<Button Name="UsernameCopyButton" Text="{Loc player-panel-copy-username}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Name="Whitelisted"/>
<controls:ConfirmButton Name="WhitelistToggle" Text="{Loc 'player-panel-false'}" Visible="False"></controls:ConfirmButton>
</BoxContainer>
<Label Name="Playtime"/>
<Label Name="Notes"/>
<Label Name="Bans"/>
<Label Name="RoleBans"/>
<Label Name="SharedConnections"/>
<BoxContainer Align="Center">
<GridContainer Rows="5">
<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="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"/>
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
</GridContainer>
</BoxContainer>
</BoxContainer>
</ui:FancyWindow>

View File

@@ -1,132 +0,0 @@
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;
namespace Content.Client.Administration.UI.PlayerPanel;
[GenerateTypedNameReferences]
public sealed partial class PlayerPanel : FancyWindow
{
private readonly IClientAdminManager _adminManager;
public event Action<string>? OnUsernameCopy;
public event Action<NetUserId?>? OnOpenNotes;
public event Action<NetUserId?>? OnOpenBans;
public event Action<NetUserId?>? OnAhelp;
public event Action<string?>? OnKick;
public event Action<NetUserId?>? OnOpenBanPanel;
public event Action<NetUserId?, bool>? OnWhitelistToggle;
public event Action? OnFreezeAndMuteToggle;
public event Action? OnFreeze;
public event Action? OnLogs;
public event Action? OnDelete;
public event Action? OnRejuvenate;
public NetUserId? TargetPlayer;
public string? TargetUsername;
private bool _isWhitelisted;
public PlayerPanel(IClientAdminManager adminManager)
{
RobustXamlLoader.Load(this);
_adminManager = adminManager;
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
WhitelistToggle.OnPressed += _ =>
{
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
SetWhitelisted(!_isWhitelisted);
};
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
LogsButton.OnPressed += _ => OnLogs?.Invoke();
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
}
public void SetUsername(string player)
{
Title = Loc.GetString("player-panel-title", ("player", player));
PlayerName.Text = Loc.GetString("player-panel-username", ("player", player));
}
public void SetWhitelisted(bool? whitelisted)
{
if (whitelisted == null)
{
Whitelisted.Text = null;
WhitelistToggle.Visible = false;
}
else
{
Whitelisted.Text = Loc.GetString("player-panel-whitelisted");
WhitelistToggle.Text = whitelisted.Value ? Loc.GetString("player-panel-true") : Loc.GetString("player-panel-false");
WhitelistToggle.Visible = true;
_isWhitelisted = whitelisted.Value;
}
}
public void SetBans(int? totalBans, int? totalRoleBans)
{
// If one value exists then so should the other.
DebugTools.Assert(totalBans.HasValue && totalRoleBans.HasValue || totalBans == null && totalRoleBans == null);
Bans.Text = totalBans != null ? Loc.GetString("player-panel-bans", ("totalBans", totalBans)) : null;
RoleBans.Text = totalRoleBans != null ? Loc.GetString("player-panel-rolebans", ("totalRoleBans", totalRoleBans)) : null;
}
public void SetNotes(int? totalNotes)
{
Notes.Text = totalNotes != null ? Loc.GetString("player-panel-notes", ("totalNotes", totalNotes)) : null;
}
public void SetSharedConnections(int sharedConnections)
{
SharedConnections.Text = Loc.GetString("player-panel-shared-connections", ("sharedConnections", sharedConnections));
}
public void SetPlaytime(TimeSpan playtime)
{
Playtime.Text = Loc.GetString("player-panel-playtime",
("days", playtime.Days),
("hours", playtime.Hours % 24),
("minutes", playtime.Minutes % (24 * 60)));
}
public void SetFrozen(bool canFreeze, bool frozen)
{
FreezeAndMuteToggleButton.Disabled = !canFreeze;
FreezeButton.Disabled = !canFreeze || frozen;
FreezeAndMuteToggleButton.Text = Loc.GetString(!frozen ? "player-panel-freeze-and-mute" : "player-panel-unfreeze");
}
public void SetAhelp(bool canAhelp)
{
AhelpButton.Disabled = !canAhelp;
}
public void SetButtons()
{
BanButton.Disabled = !_adminManager.CanCommand("banpanel");
KickButton.Disabled = !_adminManager.CanCommand("kick");
NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
WhitelistToggle.Disabled =
!(_adminManager.CanCommand("whitelistadd") && _adminManager.CanCommand("whitelistremove"));
LogsButton.Disabled = !_adminManager.CanCommand("adminlogs");
RejuvenateButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
DeleteButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
}
}

View File

@@ -1,72 +0,0 @@
using Content.Client.Administration.Managers;
using Content.Client.Eui;
using Content.Shared.Administration;
using Content.Shared.Eui;
using JetBrains.Annotations;
using Robust.Client.Console;
using Robust.Client.UserInterface;
namespace Content.Client.Administration.UI.PlayerPanel;
[UsedImplicitly]
public sealed class PlayerPanelEui : BaseEui
{
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IClipboardManager _clipboard = default!;
private PlayerPanel PlayerPanel { get; }
public PlayerPanelEui()
{
PlayerPanel = new PlayerPanel(_admin);
PlayerPanel.OnUsernameCopy += username => _clipboard.SetText(username);
PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
// Kick command does not support GUIDs
PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
PlayerPanel.OnWhitelistToggle += (id, whitelisted) =>
{
_console.ExecuteCommand(whitelisted ? $"whitelistremove \"{id}\"" : $"whitelistadd \"{id}\"");
};
PlayerPanel.OnFreezeAndMuteToggle += () => SendMessage(new PlayerPanelFreezeMessage(true));
PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
}
public override void Opened()
{
PlayerPanel.OpenCentered();
}
public override void Closed()
{
PlayerPanel.Close();
}
public override void HandleState(EuiStateBase state)
{
if (state is not PlayerPanelEuiState s)
return;
PlayerPanel.TargetPlayer = s.Guid;
PlayerPanel.TargetUsername = s.Username;
PlayerPanel.SetUsername(s.Username);
PlayerPanel.SetPlaytime(s.Playtime);
PlayerPanel.SetBans(s.TotalBans, s.TotalRoleBans);
PlayerPanel.SetNotes(s.TotalNotes);
PlayerPanel.SetWhitelisted(s.Whitelisted);
PlayerPanel.SetSharedConnections(s.SharedConnections);
PlayerPanel.SetFrozen(s.CanFreeze, s.Frozen);
PlayerPanel.SetAhelp(s.CanAhelp);
PlayerPanel.SetButtons();
}
}

View File

@@ -1,13 +1,13 @@
using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Client.Administration.UI.SetOutfit namespace Content.Client.Administration.UI.SetOutfit
@@ -64,18 +64,9 @@ namespace Content.Client.Administration.UI.SetOutfit
PopulateByFilter(SearchBar.Text); PopulateByFilter(SearchBar.Text);
} }
private IEnumerable<StartingGearPrototype> GetPrototypes()
{
// Filter out any StartingGearPrototypes that belong to loadouts
var loadouts = _prototypeManager.EnumeratePrototypes<LoadoutPrototype>();
var loadoutGears = loadouts.Select(l => l.StartingGear);
return _prototypeManager.EnumeratePrototypes<StartingGearPrototype>()
.Where(p => !loadoutGears.Contains(p.ID));
}
private void PopulateList() private void PopulateList()
{ {
foreach (var gear in GetPrototypes()) foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
{ {
OutfitList.Add(GetItem(gear, OutfitList)); OutfitList.Add(GetItem(gear, OutfitList));
} }
@@ -84,7 +75,7 @@ namespace Content.Client.Administration.UI.SetOutfit
private void PopulateByFilter(string filter) private void PopulateByFilter(string filter)
{ {
OutfitList.Clear(); OutfitList.Clear();
foreach (var gear in GetPrototypes()) foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
{ {
if (!string.IsNullOrEmpty(filter) && if (!string.IsNullOrEmpty(filter) &&
gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant())) gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))

View File

@@ -1,21 +1,19 @@
using Content.Client.Administration.Managers;
using Content.Client.Station; using Content.Client.Station;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab; namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control public sealed partial class ObjectsTab : Control
{ {
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
private readonly Color _altColor = Color.FromHex("#292B38"); private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
@@ -51,20 +49,10 @@ public sealed partial class ObjectsTab : Control
RefreshListButton.OnPressed += _ => RefreshObjectList(); RefreshListButton.OnPressed += _ => RefreshObjectList();
var defaultSelection = ObjectsTabSelection.Grids; var defaultSelection = ObjectsTabSelection.Grids;
ObjectTypeOptions.SelectId((int)defaultSelection); ObjectTypeOptions.SelectId((int) defaultSelection);
RefreshObjectList(defaultSelection); RefreshObjectList(defaultSelection);
} }
private void TeleportTo(NetEntity nent)
{
_console.ExecuteCommand($"tpto {nent}");
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
}
public void RefreshObjectList() public void RefreshObjectList()
{ {
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]); RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
@@ -128,9 +116,9 @@ public sealed partial class ObjectsTab : Control
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor }) if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
return; return;
var entry = new ObjectsTabEntry(_admin, info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor }); var entry = new ObjectsTabEntry(info.Name,
entry.OnTeleport += TeleportTo; info.Entity,
entry.OnDelete += Delete; new StyleBoxFlat { BackgroundColor = backgroundColor });
button.ToolTip = $"{info.Name}, {info.Entity}"; button.ToolTip = $"{info.Name}, {info.Entity}";
button.AddChild(entry); button.AddChild(entry);

View File

@@ -5,25 +5,13 @@
HorizontalExpand="True" HorizontalExpand="True"
SeparationOverride="4"> SeparationOverride="4">
<Label Name="NameLabel" <Label Name="NameLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="EIDLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="TeleportButton"
Text="{Loc object-tab-entity-teleport}"
SizeFlagsStretchRatio="3" SizeFlagsStretchRatio="3"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True"/> ClipText="True"/>
<customControls:VSeparator/> <customControls:VSeparator/>
<Button Name="DeleteButton" <Label Name="EIDLabel"
Text="{Loc object-tab-entity-delete}" SizeFlagsStretchRatio="3"
SizeFlagsStretchRatio="3" HorizontalExpand="True"
HorizontalExpand="True" ClipText="True"/>
ClipText="True"/>
</BoxContainer> </BoxContainer>
</PanelContainer> </PanelContainer>

View File

@@ -1,5 +1,4 @@
using Content.Client.Administration.Managers; using Robust.Client.AutoGenerated;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -11,30 +10,12 @@ public sealed partial class ObjectsTabEntry : PanelContainer
{ {
public NetEntity AssocEntity; public NetEntity AssocEntity;
public Action<NetEntity>? OnTeleport; public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
public Action<NetEntity>? OnDelete;
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
AssocEntity = nent; AssocEntity = nent;
EIDLabel.Text = nent.ToString(); EIDLabel.Text = nent.ToString();
NameLabel.Text = name; NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox; BackgroundColorPanel.PanelOverride = styleBox;
TeleportButton.Disabled = !manager.CanCommand("tpto");
DeleteButton.Disabled = !manager.CanCommand("delete");
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
DeleteButton.OnPressed += _ =>
{
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
{
return;
}
OnDelete?.Invoke(nent);
};
} }
} }

View File

@@ -5,23 +5,17 @@
HorizontalExpand="True" HorizontalExpand="True"
SeparationOverride="4"> SeparationOverride="4">
<Label Name="ObjectNameLabel" <Label Name="ObjectNameLabel"
SizeFlagsStretchRatio="5" SizeFlagsStretchRatio="3"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True" ClipText="True"
Text="{Loc object-tab-object-name}" Text="{Loc object-tab-object-name}"
MouseFilter="Pass"/> MouseFilter="Pass"/>
<cc:VSeparator/> <cc:VSeparator/>
<Label Name="EntityIDLabel" <Label Name="EntityIDLabel"
SizeFlagsStretchRatio="5" SizeFlagsStretchRatio="3"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True" ClipText="True"
Text="{Loc object-tab-entity-id}" Text="{Loc object-tab-entity-id}"
MouseFilter="Pass"/> MouseFilter="Pass"/>
<Label Name="EntityTeleportLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"/>
<Label Name="EntityDeleteLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"/>
</BoxContainer> </BoxContainer>
</Control> </Control>

View File

@@ -10,219 +10,220 @@ using Robust.Client.UserInterface.XAML;
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader; using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
using static Robust.Client.UserInterface.Controls.BaseButton; using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.Tabs.PlayerTab; namespace Content.Client.Administration.UI.Tabs.PlayerTab
[GenerateTypedNameReferences]
public sealed partial class PlayerTab : Control
{ {
[Dependency] private readonly IEntityManager _entManager = default!; [GenerateTypedNameReferences]
[Dependency] private readonly IPlayerManager _playerMan = default!; public sealed partial class PlayerTab : Control
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
private Header _headerClicked = Header.Username;
private bool _ascending = true;
private bool _showDisconnected;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
public PlayerTab()
{ {
IoCManager.InjectDependencies(this); [Dependency] private readonly IEntityManager _entManager = default!;
RobustXamlLoader.Load(this); [Dependency] private readonly IPlayerManager _playerMan = default!;
_adminSystem = _entManager.System<AdminSystem>(); private const string ArrowUp = "↑";
_adminSystem.PlayerListChanged += RefreshPlayerList; private const string ArrowDown = "↓";
_adminSystem.OverlayEnabled += OverlayEnabled; private readonly Color _altColor = Color.FromHex("#292B38");
_adminSystem.OverlayDisabled += OverlayDisabled; private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
OverlayButton.OnPressed += OverlayButtonPressed; private Header _headerClicked = Header.Username;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed; private bool _ascending = true;
private bool _showDisconnected;
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor); public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit; public PlayerTab()
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
private void OverlayEnabled()
{
OverlayButton.Pressed = true;
}
private void OverlayDisabled()
{
OverlayButton.Pressed = false;
}
private void OverlayButtonPressed(ButtonEventArgs args)
{
if (args.Button.Pressed)
{ {
_adminSystem.AdminOverlayOn(); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_adminSystem = _entManager.System<AdminSystem>();
_adminSystem.PlayerListChanged += RefreshPlayerList;
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
} }
else
#region Antag Overlay
private void OverlayEnabled()
{ {
_adminSystem.AdminOverlayOff(); OverlayButton.Pressed = true;
} }
}
#endregion private void OverlayDisabled()
private void ShowDisconnectedPressed(ButtonEventArgs args)
{
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{ {
_adminSystem.PlayerListChanged -= RefreshPlayerList; OverlayButton.Pressed = false;
_adminSystem.OverlayEnabled -= OverlayEnabled;
_adminSystem.OverlayDisabled -= OverlayDisabled;
OverlayButton.OnPressed -= OverlayButtonPressed;
ListHeader.OnHeaderClicked -= HeaderClicked;
} }
}
#region ListContainer private void OverlayButtonPressed(ButtonEventArgs args)
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var player})
return;
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
}
/// <summary>
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
/// If all characters are lowercase, the comparison ignores case.
/// If there is an uppercase character, the comparison is case sensitive.
/// </summary>
/// <param name="filter"></param>
/// <param name="listData"></param>
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
return false;
if (!_showDisconnected && !info.Connected)
return false;
if (IsAllLower(filter))
{ {
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase)) if (args.Button.Pressed)
{
_adminSystem.AdminOverlayOn();
}
else
{
_adminSystem.AdminOverlayOff();
}
}
#endregion
private void ShowDisconnectedPressed(ButtonEventArgs args)
{
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_adminSystem.PlayerListChanged -= RefreshPlayerList;
_adminSystem.OverlayEnabled -= OverlayEnabled;
_adminSystem.OverlayDisabled -= OverlayDisabled;
OverlayButton.OnPressed -= OverlayButtonPressed;
ListHeader.OnHeaderClicked -= HeaderClicked;
}
}
#region ListContainer
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var player})
return;
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
}
/// <summary>
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
/// If all characters are lowercase, the comparison ignores case.
/// If there is an uppercase character, the comparison is case sensitive.
/// </summary>
/// <param name="filter"></param>
/// <param name="listData"></param>
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
return false; return false;
}
else if (!_showDisconnected && !info.Connected)
{
if (!playerString.Contains(filter))
return false; return false;
if (IsAllLower(filter))
{
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
return false;
}
else
{
if (!playerString.Contains(filter))
return false;
}
return true;
} }
return true; private bool IsAllLower(string input)
}
private bool IsAllLower(string input)
{
foreach (var c in input)
{ {
if (char.IsLetter(c) && !char.IsLower(c)) foreach (var c in input)
return false; {
if (char.IsLetter(c) && !char.IsLower(c))
return false;
}
return true;
} }
return true; #endregion
}
#endregion #region Header
#region Header private void UpdateHeaderSymbols()
private void UpdateHeaderSymbols()
{
ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
}
private int Compare(PlayerInfo x, PlayerInfo y)
{
if (!_ascending)
{ {
(x, y) = (y, x); ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
} }
return _headerClicked switch private int Compare(PlayerInfo x, PlayerInfo y)
{ {
Header.Username => Compare(x.Username, y.Username), if (!_ascending)
Header.Character => Compare(x.CharacterName, y.CharacterName), {
Header.Job => Compare(x.StartingJob, y.StartingJob), (x, y) = (y, x);
Header.Antagonist => x.Antag.CompareTo(y.Antag), }
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
_ => 1
};
}
private int Compare(string x, string y) return _headerClicked switch
{ {
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); Header.Username => Compare(x.Username, y.Username),
} Header.Character => Compare(x.CharacterName, y.CharacterName),
Header.Job => Compare(x.StartingJob, y.StartingJob),
private void HeaderClicked(Header header) Header.Antagonist => x.Antag.CompareTo(y.Antag),
{ Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
if (_headerClicked == header) _ => 1
{ };
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
} }
RefreshPlayerList(_adminSystem.PlayerList); private int Compare(string x, string y)
{
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}
private void HeaderClicked(Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
RefreshPlayerList(_adminSystem.PlayerList);
}
#endregion
} }
#endregion public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;
} }
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;

View File

@@ -93,6 +93,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
public void AlertClicked(ProtoId<AlertPrototype> alertType) public void AlertClicked(ProtoId<AlertPrototype> alertType)
{ {
RaisePredictiveEvent(new ClickAlertEvent(alertType)); RaiseNetworkEvent(new ClickAlertEvent(alertType));
} }
} }

View File

@@ -20,9 +20,8 @@ public sealed class AnomalySystem : SharedAnomalySystem
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged); SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup); SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete); SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete);
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
} }
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args) private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
{ {
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime); _floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
@@ -76,13 +75,4 @@ public sealed class AnomalySystem : SharedAnomalySystem
} }
} }
} }
private void OnShutdown(Entity<AnomalySupercriticalComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
sprite.Scale = Vector2.One;
sprite.Color = sprite.Color.WithAlpha(1f);
}
} }

View File

@@ -1,50 +0,0 @@
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects;
using Content.Shared.Body.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Anomaly.Effects;
public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
{
public override void Initialize()
{
SubscribeLocalEvent<InnerBodyAnomalyComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
}
private void OnAfterHandleState(Entity<InnerBodyAnomalyComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (ent.Comp.FallbackSprite is null)
return;
if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index))
index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap);
if (TryComp<BodyComponent>(ent, out var body) &&
body.Prototype is not null &&
ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite))
{
sprite.LayerSetSprite(index, speciesSprite);
}
else
{
sprite.LayerSetSprite(index, ent.Comp.FallbackSprite);
}
sprite.LayerSetVisible(index, true);
sprite.LayerSetShader(index, "unshaded");
}
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
var index = sprite.LayerMapGet(ent.Comp.LayerMap);
sprite.LayerSetVisible(index, false);
}
}

View File

@@ -1,5 +1,7 @@
using Content.Shared.Anomaly; using Content.Shared.Anomaly;
using Content.Shared.Gravity;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
namespace Content.Client.Anomaly.Ui; namespace Content.Client.Anomaly.Ui;

View File

@@ -31,7 +31,7 @@ public sealed partial class AnomalyScannerMenu : FancyWindow
msg.PushNewline(); msg.PushNewline();
var time = NextPulseTime.Value - _timing.CurTime; var time = NextPulseTime.Value - _timing.CurTime;
var timestring = $"{time.Minutes:00}:{time.Seconds:00}"; var timestring = $"{time.Minutes:00}:{time.Seconds:00}";
msg.AddMarkupOrThrow(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring))); msg.AddMarkup(Loc.GetString("anomaly-scanner-pulse-timer", ("time", timestring)));
} }
TextDisplay.SetMarkup(msg.ToMarkup()); TextDisplay.SetMarkup(msg.ToMarkup());

View File

@@ -1,81 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:Content.Client.Stylesheets"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Orientation="Vertical" HorizontalExpand ="True" Margin="0 0 0 3">
<!-- Device selection button -->
<Button Name="FocusButton" HorizontalExpand="True" SetHeight="32" Margin="12 0 0 0" StyleClasses="OpenBoth" Access="Public">
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
<!-- Alarm state -->
<TextureRect Stretch="Keep" HorizontalAlignment="Left" Margin="-20 -2 0 0" ModulateSelfOverride="#25252a" TexturePath="/Textures/Interface/AtmosMonitoring/status_bg.png">
<BoxContainer VerticalExpand="True" HorizontalExpand="True" Orientation="Horizontal" Margin="8 0">
<TextureRect Name="ArrowTexture" VerticalAlignment="Center" SetSize="12 12" Stretch="KeepAspectCentered" Margin="3 0" TexturePath="/Textures/Interface/Nano/triangle_right.png"></TextureRect>
<Label Name="AlarmStateLabel" HorizontalExpand="True" HorizontalAlignment="Center" FontColorOverride="#5A5A5A" Text="{Loc 'atmos-alerts-window-invalid-state'}"></Label>
</BoxContainer>
</TextureRect>
<!-- Alarm name -->
<Label Name="AlarmNameLabel" Text="???" HorizontalExpand="True" HorizontalAlignment="Center" Margin="5 0"></Label>
</BoxContainer>
</Button>
<!-- Panel that appears on selecting the device -->
<PanelContainer Name="FocusContainer" HorizontalExpand="True" Margin="1 -1 1 0" ReservesSpace="False" Visible="False" Access="Public">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252a"/>
</PanelContainer.PanelOverride>
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical">
<!-- Atmosphere status -->
<Control>
<!-- Main container for displaying atmospheric data -->
<BoxContainer Name="MainDataContainer" HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical" ReservesSpace="False" Visible="False">
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
<Label Name="TemperatureHeaderLabel" Text="{Loc 'atmos-alerts-window-temperature-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="PressureHeaderLabel" Text="{Loc 'atmos-alerts-window-pressure-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="OxygenationHeaderLabel" Text="{Loc 'atmos-alerts-window-oxygenation-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 2 0 0" SetHeight="24"></Label>
</BoxContainer>
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#202023"/>
</PanelContainer.PanelOverride>
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
<Label Name="TemperatureLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#5A5A5A" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="PressureLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#5A5A5A" Margin="0 2 0 0" SetHeight="24"></Label>
<Label Name="OxygenationLabel" Text="???" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#5A5A5A" Margin="0 2 0 0" SetHeight="24"></Label>
</BoxContainer>
</PanelContainer>
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
<Label Name="GasesHeaderLabel" Text="{Loc 'atmos-alerts-window-other-gases-label'}" HorizontalAlignment="Center" HorizontalExpand="True" FontColorOverride="#a9a9a9" Margin="0 4 0 0" SetHeight="24"></Label>
</BoxContainer>
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#202023"/>
</PanelContainer.PanelOverride>
<!-- Gas entries added via C# code -->
<GridContainer Name="GasGridContainer" HorizontalExpand="True" Columns = "4"></GridContainer>
</PanelContainer>
</BoxContainer>
<!-- If the alarm is inactive, this is label is diplayed instead -->
<Label Name="NoDataLabel" Text="{Loc 'atmos-alerts-window-no-data-available'}" HorizontalAlignment="Center" Margin="0 15" FontColorOverride="#a9a9a9" ReservesSpace="False" Visible="False"></Label>
<!-- Silencing progress bar -->
<controls:StripeBack Name="SilenceAlarmProgressBar" ReservesSpace="False" Visible="False" Access="Public">
<PanelContainer>
<Label Text="{Loc 'atmos-alerts-window-alerts-being-silenced'}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5 5 5 5"/>
</PanelContainer>
</controls:StripeBack>
</Control>
<!-- Check box for silencing this alarm -->
<CheckBox Name="SilenceCheckBox" Text="{Loc 'atmos-alerts-window-silence-alerts'}" HorizontalAlignment="Left" Margin="5 5 5 5" Access="Public"></CheckBox>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -1,215 +0,0 @@
using Content.Client.Stylesheets;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.FixedPoint;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
using System.Linq;
namespace Content.Client.Atmos.Consoles;
[GenerateTypedNameReferences]
public sealed partial class AtmosAlarmEntryContainer : BoxContainer
{
public NetEntity NetEntity;
public EntityCoordinates? Coordinates;
private readonly IEntityManager _entManager;
private readonly IResourceCache _cache;
private Dictionary<AtmosAlarmType, string> _alarmStrings = new Dictionary<AtmosAlarmType, string>()
{
[AtmosAlarmType.Invalid] = "atmos-alerts-window-invalid-state",
[AtmosAlarmType.Normal] = "atmos-alerts-window-normal-state",
[AtmosAlarmType.Warning] = "atmos-alerts-window-warning-state",
[AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state",
};
private Dictionary<Gas, string> _gasShorthands = new Dictionary<Gas, string>()
{
[Gas.Ammonia] = "NH₃",
[Gas.CarbonDioxide] = "CO₂",
[Gas.Frezon] = "F",
[Gas.Nitrogen] = "N₂",
[Gas.NitrousOxide] = "N₂O",
[Gas.Oxygen] = "O₂",
[Gas.Plasma] = "P",
[Gas.Tritium] = "T",
[Gas.WaterVapor] = "H₂O",
};
public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_cache = IoCManager.Resolve<IResourceCache>();
NetEntity = uid;
Coordinates = coordinates;
// Load fonts
var headerFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11);
var normalFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
var smallFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
// Set fonts
TemperatureHeaderLabel.FontOverride = headerFont;
PressureHeaderLabel.FontOverride = headerFont;
OxygenationHeaderLabel.FontOverride = headerFont;
GasesHeaderLabel.FontOverride = headerFont;
TemperatureLabel.FontOverride = normalFont;
PressureLabel.FontOverride = normalFont;
OxygenationLabel.FontOverride = normalFont;
NoDataLabel.FontOverride = headerFont;
SilenceCheckBox.Label.FontOverride = smallFont;
SilenceCheckBox.Label.FontColorOverride = Color.DarkGray;
}
public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlertsFocusDeviceData? focusData = null)
{
NetEntity = entry.NetEntity;
Coordinates = _entManager.GetCoordinates(entry.Coordinates);
// Load fonts
var normalFont = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11);
// Update alarm state
if (!_alarmStrings.TryGetValue(entry.AlarmState, out var alarmString))
alarmString = "atmos-alerts-window-invalid-state";
AlarmStateLabel.Text = Loc.GetString(alarmString);
AlarmStateLabel.FontColorOverride = GetAlarmStateColor(entry.AlarmState);
// Update alarm name
AlarmNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", entry.EntityName), ("address", entry.Address));
// Focus updates
FocusContainer.Visible = isFocus;
if (isFocus)
SetAsFocus();
else
RemoveAsFocus();
if (isFocus && entry.Group == AtmosAlertsComputerGroup.AirAlarm)
{
MainDataContainer.Visible = (entry.AlarmState != AtmosAlarmType.Invalid);
NoDataLabel.Visible = (entry.AlarmState == AtmosAlarmType.Invalid);
if (focusData != null)
{
// Update temperature
var tempK = (FixedPoint2)focusData.Value.TemperatureData.Item1;
var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float());
TemperatureLabel.Text = Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK));
TemperatureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.TemperatureData.Item2);
// Update pressure
PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)focusData.Value.PressureData.Item1));
PressureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.PressureData.Item2);
// Update oxygenation
var oxygenPercent = (FixedPoint2)0f;
var oxygenAlert = AtmosAlarmType.Invalid;
if (focusData.Value.GasData.TryGetValue(Gas.Oxygen, out var oxygenData))
{
oxygenPercent = oxygenData.Item2 * 100f;
oxygenAlert = oxygenData.Item3;
}
OxygenationLabel.Text = Loc.GetString("atmos-alerts-window-oxygenation-value", ("value", oxygenPercent));
OxygenationLabel.FontColorOverride = GetAlarmStateColor(oxygenAlert);
// Update other present gases
GasGridContainer.RemoveAllChildren();
var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen);
if (gasData.Count() == 0)
{
// No other gases
var gasLabel = new Label()
{
Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"),
FontOverride = normalFont,
FontColorOverride = StyleNano.DisabledFore,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
Margin = new Thickness(0, 2, 0, 0),
SetHeight = 24f,
};
GasGridContainer.AddChild(gasLabel);
}
else
{
// Add an entry for each gas
foreach ((var gas, (var mol, var percent, var alert)) in gasData)
{
var gasPercent = (FixedPoint2)0f;
gasPercent = percent * 100f;
if (!_gasShorthands.TryGetValue(gas, out var gasShorthand))
gasShorthand = "X";
var gasLabel = new Label()
{
Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)),
FontOverride = normalFont,
FontColorOverride = GetAlarmStateColor(alert),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
Margin = new Thickness(0, 2, 0, 0),
SetHeight = 24f,
};
GasGridContainer.AddChild(gasLabel);
}
}
}
}
}
public void SetAsFocus()
{
FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen);
ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png";
}
public void RemoveAsFocus()
{
FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen);
ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png";
FocusContainer.Visible = false;
}
private Color GetAlarmStateColor(AtmosAlarmType alarmType)
{
switch (alarmType)
{
case AtmosAlarmType.Normal:
return StyleNano.GoodGreenFore;
case AtmosAlarmType.Warning:
return StyleNano.ConcerningOrangeFore;
case AtmosAlarmType.Danger:
return StyleNano.DangerousRedFore;
}
return StyleNano.DisabledFore;
}
}

View File

@@ -1,52 +0,0 @@
using Content.Shared.Atmos.Components;
namespace Content.Client.Atmos.Consoles;
public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private AtmosAlertsComputerWindow? _menu;
public AtmosAlertsComputerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
protected override void Open()
{
_menu = new AtmosAlertsComputerWindow(this, Owner);
_menu.OpenCentered();
_menu.OnClose += Close;
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (AtmosAlertsComputerBoundInterfaceState) state;
if (castState == null)
return;
EntMan.TryGetComponent<TransformComponent>(Owner, out var xform);
_menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData);
}
public void SendFocusChangeMessage(NetEntity? netEntity)
{
SendMessage(new AtmosAlertsComputerFocusChangeMessage(netEntity));
}
public void SendDeviceSilencedMessage(NetEntity netEntity, bool silenceDevice)
{
SendMessage(new AtmosAlertsComputerDeviceSilencedMessage(netEntity, silenceDevice));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
}
}

View File

@@ -1,108 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'atmos-alerts-window-title'}"
Resizable="False"
SetSize="1120 750"
MinSize="1120 750">
<BoxContainer Orientation="Vertical">
<!-- Main display -->
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
<!-- Nav map -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<ui:NavMapControl Name="NavMap" Margin="5 5" VerticalExpand="True" HorizontalExpand="True">
<!-- System warning -->
<PanelContainer Name="SystemWarningPanel"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalExpand="True"
Margin="0 48 0 0"
Visible="False">
<RichTextLabel Name="SystemWarningLabel" Margin="12 8 12 8"/>
</PanelContainer>
</ui:NavMapControl>
<!-- Nav map legend -->
<BoxContainer Orientation="Horizontal" Margin="0 10 0 10">
<Label Text="{Loc 'atmos-alerts-window-label-alert-types'}"
Margin="20 0 5 0"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
Modulate="#5A5A5A"
SetSize="16 16"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-alerts-window-invalid-state'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
Modulate="#32cd32"
SetSize="16 16"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-alerts-window-normal-state'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
SetSize="16 16"
Modulate="#ffb648"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-alerts-window-warning-state'}"/>
<TextureRect Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
SetSize="16 16"
Modulate="#ff4343"
Margin="20 0 5 0"/>
<Label Text="{Loc 'atmos-alerts-window-danger-state'}"/>
</BoxContainer>
</BoxContainer>
<!-- Atmosphere status -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" SetWidth="440" Margin="0 0 10 10">
<!-- Station name -->
<controls:StripeBack>
<PanelContainer>
<RichTextLabel Name="StationName" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 5 0 3"/>
</PanelContainer>
</controls:StripeBack>
<!-- Alarm status (entries added by C# code) -->
<TabContainer Name="MasterTabContainer" VerticalExpand="True" HorizontalExpand="True" Margin="0 10 0 0">
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
<BoxContainer Name="AlertsTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
</ScrollContainer>
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
<BoxContainer Name="AirAlarmsTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
</ScrollContainer>
<ScrollContainer HorizontalExpand="True" Margin="8, 8, 8, 8">
<BoxContainer Name="FireAlarmsTable" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="0 0 0 10"/>
</ScrollContainer>
</TabContainer>
<!-- Overlay toggles -->
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
<Label Text="{Loc 'atmos-alerts-window-toggle-overlays'}" Margin="0 0 0 5"/>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<CheckBox Name="ShowInactiveAlarms" Text="{Loc 'atmos-alerts-window-invalid-state'}" Pressed="False" HorizontalExpand="True"/>
<CheckBox Name="ShowNormalAlarms" Text="{Loc 'atmos-alerts-window-normal-state'}" Pressed="False" HorizontalExpand="True"/>
<CheckBox Name="ShowWarningAlarms" Text="{Loc 'atmos-alerts-window-warning-state'}" Pressed="True" HorizontalExpand="True"/>
<CheckBox Name="ShowDangerAlarms" Text="{Loc 'atmos-alerts-window-danger-state'}" Pressed="True" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'atmos-alerts-window-flavor-left'}" StyleClasses="WindowFooterText" />
<Label Text="{Loc 'atmos-alerts-window-flavor-right'}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -1,611 +0,0 @@
using Content.Client.Message;
using Content.Client.Pinpointer.UI;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Pinpointer;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Client.Atmos.Consoles;
[GenerateTypedNameReferences]
public sealed partial class AtmosAlertsComputerWindow : FancyWindow
{
private readonly IEntityManager _entManager;
private readonly SpriteSystem _spriteSystem;
private readonly SharedNavMapSystem _navMapSystem;
private EntityUid? _owner;
private NetEntity? _trackedEntity;
private AtmosAlertsComputerEntry[]? _airAlarms = null;
private AtmosAlertsComputerEntry[]? _fireAlarms = null;
private IEnumerable<AtmosAlertsComputerEntry>? _allAlarms = null;
private IEnumerable<AtmosAlertsComputerEntry>? _activeAlarms = null;
private Dictionary<NetEntity, float> _deviceSilencingProgress = new();
public event Action<NetEntity?>? SendFocusChangeMessageAction;
public event Action<NetEntity, bool>? SendDeviceSilencedMessageAction;
private bool _autoScrollActive = false;
private bool _autoScrollAwaitsUpdate = false;
private const float SilencingDuration = 2.5f;
// Colors
private Color _wallColor = new Color(64, 64, 64);
private Color _tileColor = new Color(28, 28, 28);
private Color _monitorBlipColor = Color.Cyan;
private Color _untrackedEntColor = Color.DimGray;
private Color _regionBaseColor = new Color(154, 154, 154);
private Color _inactiveColor = StyleNano.DisabledFore;
private Color _statusTextColor = StyleNano.GoodGreenFore;
private Color _goodColor = Color.LimeGreen;
private Color _warningColor = new Color(255, 182, 72);
private Color _dangerColor = new Color(255, 67, 67);
public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_spriteSystem = _entManager.System<SpriteSystem>();
_navMapSystem = _entManager.System<SharedNavMapSystem>();
// Pass the owner to nav map
_owner = owner;
NavMap.Owner = _owner;
// Set nav map colors
NavMap.WallColor = _wallColor;
NavMap.TileColor = _tileColor;
// Set nav map grid uid
var stationName = Loc.GetString("atmos-alerts-window-unknown-location");
if (_entManager.TryGetComponent<TransformComponent>(owner, out var xform))
{
NavMap.MapUid = xform.GridUid;
// Assign station name
if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
stationName = stationMetaData.EntityName;
var msg = new FormattedMessage();
msg.TryAddMarkup(Loc.GetString("atmos-alerts-window-station-name", ("stationName", stationName)), out _);
StationName.SetMessage(msg);
}
else
{
StationName.SetMessage(stationName);
NavMap.Visible = false;
}
// Set trackable entity selected action
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
// Update nav map
NavMap.ForceNavMapUpdate();
// Set tab container headers
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
MasterTabContainer.SetTabTitle(1, Loc.GetString("atmos-alerts-window-tab-air-alarms"));
MasterTabContainer.SetTabTitle(2, Loc.GetString("atmos-alerts-window-tab-fire-alarms"));
// Set UI toggles
ShowInactiveAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowInactiveAlarms, AtmosAlarmType.Invalid);
ShowNormalAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowNormalAlarms, AtmosAlarmType.Normal);
ShowWarningAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowWarningAlarms, AtmosAlarmType.Warning);
ShowDangerAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowDangerAlarms, AtmosAlarmType.Danger);
// Set atmos monitoring message action
SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage;
SendDeviceSilencedMessageAction += userInterface.SendDeviceSilencedMessage;
}
#region Toggle handling
private void OnShowAlarmsToggled(CheckBox toggle, AtmosAlarmType toggledAlarmState)
{
if (_owner == null)
return;
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner.Value, out var console))
return;
foreach (var device in console.AtmosDevices)
{
var alarmState = GetAlarmState(device.NetEntity);
if (toggledAlarmState != alarmState)
continue;
if (toggle.Pressed)
AddTrackedEntityToNavMap(device, alarmState);
else
NavMap.TrackedEntities.Remove(device.NetEntity);
}
}
private void OnSilenceAlertsToggled(NetEntity netEntity, bool toggleState)
{
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner, out var console))
return;
if (toggleState)
_deviceSilencingProgress[netEntity] = SilencingDuration;
else
_deviceSilencingProgress.Remove(netEntity);
foreach (AtmosAlarmEntryContainer entryContainer in AlertsTable.Children)
{
if (entryContainer.NetEntity == netEntity)
entryContainer.SilenceAlarmProgressBar.Visible = toggleState;
}
SendDeviceSilencedMessageAction?.Invoke(netEntity, toggleState);
}
#endregion
public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData)
{
if (_owner == null)
return;
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner.Value, out var console))
return;
if (_trackedEntity != focusData?.NetEntity)
{
SendFocusChangeMessageAction?.Invoke(_trackedEntity);
focusData = null;
}
// Retain alarm data for use inbetween updates
_airAlarms = airAlarms;
_fireAlarms = fireAlarms;
_allAlarms = airAlarms.Concat(fireAlarms);
var silenced = console.SilencedDevices;
_activeAlarms = _allAlarms.Where(x => x.AlarmState > AtmosAlarmType.Normal &&
(!silenced.Contains(x.NetEntity) || _deviceSilencingProgress.ContainsKey(x.NetEntity)));
// Reset nav map data
NavMap.TrackedCoordinates.Clear();
NavMap.TrackedEntities.Clear();
// Add tracked entities to the nav map
foreach (var device in console.AtmosDevices)
{
if (!device.NetEntity.Valid)
continue;
if (!NavMap.Visible)
continue;
var alarmState = GetAlarmState(device.NetEntity);
if (_trackedEntity != device.NetEntity)
{
// Skip air alarms if the appropriate overlay is off
if (!ShowInactiveAlarms.Pressed && alarmState == AtmosAlarmType.Invalid)
continue;
if (!ShowNormalAlarms.Pressed && alarmState == AtmosAlarmType.Normal)
continue;
if (!ShowWarningAlarms.Pressed && alarmState == AtmosAlarmType.Warning)
continue;
if (!ShowDangerAlarms.Pressed && alarmState == AtmosAlarmType.Danger)
continue;
}
AddTrackedEntityToNavMap(device, alarmState);
}
// Show the monitor location
var consoleUid = _entManager.GetNetEntity(_owner);
if (consoleCoords != null && consoleUid != null)
{
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
var blip = new NavMapBlip(consoleCoords.Value, texture, _monitorBlipColor, true, false);
NavMap.TrackedEntities[consoleUid.Value] = blip;
}
// Update the nav map
NavMap.ForceNavMapUpdate();
// Clear excess children from the tables
var activeAlarmCount = _activeAlarms.Count();
while (AlertsTable.ChildCount > activeAlarmCount)
AlertsTable.RemoveChild(AlertsTable.GetChild(AlertsTable.ChildCount - 1));
while (AirAlarmsTable.ChildCount > airAlarms.Length)
AirAlarmsTable.RemoveChild(AirAlarmsTable.GetChild(AirAlarmsTable.ChildCount - 1));
while (FireAlarmsTable.ChildCount > fireAlarms.Length)
FireAlarmsTable.RemoveChild(FireAlarmsTable.GetChild(FireAlarmsTable.ChildCount - 1));
// Update all entries in each table
for (int index = 0; index < _activeAlarms.Count(); index++)
{
var entry = _activeAlarms.ElementAt(index);
UpdateUIEntry(entry, index, AlertsTable, console, focusData);
}
for (int index = 0; index < airAlarms.Count(); index++)
{
var entry = airAlarms.ElementAt(index);
UpdateUIEntry(entry, index, AirAlarmsTable, console, focusData);
}
for (int index = 0; index < fireAlarms.Count(); index++)
{
var entry = fireAlarms.ElementAt(index);
UpdateUIEntry(entry, index, FireAlarmsTable, console, focusData);
}
// If no alerts are active, display a message
if (MasterTabContainer.CurrentTab == 0 && activeAlarmCount == 0)
{
var label = new RichTextLabel()
{
HorizontalExpand = true,
VerticalExpand = true,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
};
label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", _statusTextColor.ToHexNoAlpha())));
AlertsTable.AddChild(label);
}
// Update the alerts tab with the number of active alerts
if (activeAlarmCount == 0)
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts"));
else
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
// Update sensor regions
NavMap.RegionOverlays.Clear();
var prioritizedRegionOverlays = new Dictionary<NavMapRegionOverlay, int>();
if (_owner != null &&
_entManager.TryGetComponent<TransformComponent>(_owner, out var xform) &&
_entManager.TryGetComponent<NavMapComponent>(xform.GridUid, out var navMap))
{
var regionOverlays = _navMapSystem.GetNavMapRegionOverlays(_owner.Value, navMap, AtmosAlertsComputerUiKey.Key);
foreach (var (regionOwner, regionOverlay) in regionOverlays)
{
var alarmState = GetAlarmState(regionOwner);
if (!TryGetSensorRegionColor(regionOwner, alarmState, out var regionColor))
continue;
regionOverlay.Color = regionColor;
var priority = (_trackedEntity == regionOwner) ? 999 : (int)alarmState;
prioritizedRegionOverlays.Add(regionOverlay, priority);
}
// Sort overlays according to their priority
var sortedOverlays = prioritizedRegionOverlays.OrderBy(x => x.Value).Select(x => x.Key).ToList();
NavMap.RegionOverlays = sortedOverlays;
}
// Auto-scroll re-enable
if (_autoScrollAwaitsUpdate)
{
_autoScrollActive = true;
_autoScrollAwaitsUpdate = false;
}
}
private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, AtmosAlarmType alarmState)
{
var data = GetBlipTexture(alarmState);
if (data == null)
return;
var texture = data.Value.Item1;
var color = data.Value.Item2;
var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
if (_trackedEntity != null && _trackedEntity != metaData.NetEntity)
color *= _untrackedEntColor;
var selectable = true;
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable);
NavMap.TrackedEntities[metaData.NetEntity] = blip;
}
private bool TryGetSensorRegionColor(NetEntity regionOwner, AtmosAlarmType alarmState, out Color color)
{
color = Color.White;
var blip = GetBlipTexture(alarmState);
if (blip == null)
return false;
// Color the region based on alarm state and entity tracking
color = blip.Value.Item2 * _regionBaseColor;
if (_trackedEntity != null && _trackedEntity != regionOwner)
color *= _untrackedEntColor;
return true;
}
private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
{
// Make new UI entry if required
if (index >= table.ChildCount)
{
var newEntryContainer = new AtmosAlarmEntryContainer(entry.NetEntity, _entManager.GetCoordinates(entry.Coordinates));
// On click
newEntryContainer.FocusButton.OnButtonUp += args =>
{
if (_trackedEntity == newEntryContainer.NetEntity)
{
_trackedEntity = null;
}
else
{
_trackedEntity = newEntryContainer.NetEntity;
if (newEntryContainer.Coordinates != null)
NavMap.CenterToCoordinates(newEntryContainer.Coordinates.Value);
}
// Send message to console that the focus has changed
SendFocusChangeMessageAction?.Invoke(_trackedEntity);
// Update affected UI elements across all tables
UpdateConsoleTable(console, AlertsTable, _trackedEntity);
UpdateConsoleTable(console, AirAlarmsTable, _trackedEntity);
UpdateConsoleTable(console, FireAlarmsTable, _trackedEntity);
};
// On toggling the silence check box
newEntryContainer.SilenceCheckBox.OnToggled += _ => OnSilenceAlertsToggled(newEntryContainer.NetEntity, newEntryContainer.SilenceCheckBox.Pressed);
// Add the entry to the current table
table.AddChild(newEntryContainer);
}
// Update values and UI elements
var tableChild = table.GetChild(index);
if (tableChild is not AtmosAlarmEntryContainer)
{
table.RemoveChild(tableChild);
UpdateUIEntry(entry, index, table, console, focusData);
return;
}
var entryContainer = (AtmosAlarmEntryContainer)tableChild;
entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData);
if (_trackedEntity != entry.NetEntity)
{
var silenced = console.SilencedDevices;
entryContainer.SilenceCheckBox.Pressed = (silenced.Contains(entry.NetEntity) || _deviceSilencingProgress.ContainsKey(entry.NetEntity));
}
entryContainer.SilenceAlarmProgressBar.Visible = (table == AlertsTable && _deviceSilencingProgress.ContainsKey(entry.NetEntity));
}
private void UpdateConsoleTable(AtmosAlertsComputerComponent console, Control table, NetEntity? currTrackedEntity)
{
foreach (var tableChild in table.Children)
{
if (tableChild is not AtmosAlarmEntryContainer)
continue;
var entryContainer = (AtmosAlarmEntryContainer)tableChild;
if (entryContainer.NetEntity != currTrackedEntity)
entryContainer.RemoveAsFocus();
else if (entryContainer.NetEntity == currTrackedEntity)
entryContainer.SetAsFocus();
}
}
private void SetTrackedEntityFromNavMap(NetEntity? netEntity)
{
if (netEntity == null)
return;
if (!_entManager.TryGetComponent<AtmosAlertsComputerComponent>(_owner, out var console))
return;
_trackedEntity = netEntity;
if (netEntity != null)
{
// Tab switching
if (MasterTabContainer.CurrentTab != 0 || _activeAlarms?.Any(x => x.NetEntity == netEntity) == false)
{
var device = console.AtmosDevices.FirstOrNull(x => x.NetEntity == netEntity);
switch (device?.Group)
{
case AtmosAlertsComputerGroup.AirAlarm:
MasterTabContainer.CurrentTab = 1; break;
case AtmosAlertsComputerGroup.FireAlarm:
MasterTabContainer.CurrentTab = 2; break;
}
}
// Get the scroll position of the selected entity on the selected button the UI
ActivateAutoScrollToFocus();
}
// Send message to console that the focus has changed
SendFocusChangeMessageAction?.Invoke(_trackedEntity);
}
protected override void FrameUpdate(FrameEventArgs args)
{
AutoScrollToFocus();
// Device silencing update
foreach ((var device, var remainingTime) in _deviceSilencingProgress)
{
var t = remainingTime - args.DeltaSeconds;
if (t <= 0)
{
_deviceSilencingProgress.Remove(device);
if (device == _trackedEntity)
_trackedEntity = null;
}
else
_deviceSilencingProgress[device] = t;
}
}
private void ActivateAutoScrollToFocus()
{
_autoScrollActive = false;
_autoScrollAwaitsUpdate = true;
}
private void AutoScrollToFocus()
{
if (!_autoScrollActive)
return;
var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
if (scroll == null)
return;
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
return;
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
return;
vScrollbar.ValueTarget = nextScrollPosition.Value;
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
_autoScrollActive = false;
}
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
{
vScrollBar = null;
foreach (var child in scroll.Children)
{
if (child is not VScrollBar)
continue;
var castChild = child as VScrollBar;
if (castChild != null)
{
vScrollBar = castChild;
return true;
}
}
return false;
}
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
{
nextScrollPosition = null;
var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer;
if (scroll == null)
return false;
var container = scroll.Children.ElementAt(0) as BoxContainer;
if (container == null || container.Children.Count() == 0)
return false;
// Exit if the heights of the children haven't been initialized yet
if (!container.Children.Any(x => x.Height > 0))
return false;
nextScrollPosition = 0;
foreach (var control in container.Children)
{
if (control == null || control is not AtmosAlarmEntryContainer)
continue;
if (((AtmosAlarmEntryContainer)control).NetEntity == _trackedEntity)
return true;
nextScrollPosition += control.Height;
}
// Failed to find control
nextScrollPosition = null;
return false;
}
private AtmosAlarmType GetAlarmState(NetEntity netEntity)
{
var alarmState = _allAlarms?.FirstOrNull(x => x.NetEntity == netEntity)?.AlarmState;
if (alarmState == null)
return AtmosAlarmType.Invalid;
return alarmState.Value;
}
private (SpriteSpecifier.Texture, Color)? GetBlipTexture(AtmosAlarmType alarmState)
{
(SpriteSpecifier.Texture, Color)? output = null;
switch (alarmState)
{
case AtmosAlarmType.Invalid:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _inactiveColor); break;
case AtmosAlarmType.Normal:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _goodColor); break;
case AtmosAlarmType.Warning:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), _warningColor); break;
case AtmosAlarmType.Danger:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), _dangerColor); break;
}
return output;
}
}

View File

@@ -1,10 +0,0 @@
using Content.Shared.Atmos.EntitySystems;
using JetBrains.Annotations;
namespace Content.Client.Atmos.EntitySystems;
[UsedImplicitly]
public sealed class GasMinerSystem : SharedGasMinerSystem
{
}

View File

@@ -30,6 +30,7 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
_window.AirAlarmModeChanged += OnAirAlarmModeChanged; _window.AirAlarmModeChanged += OnAirAlarmModeChanged;
_window.AutoModeChanged += OnAutoModeChanged; _window.AutoModeChanged += OnAutoModeChanged;
_window.ResyncAllRequested += ResyncAllDevices; _window.ResyncAllRequested += ResyncAllDevices;
_window.AirAlarmTabChange += OnTabChanged;
} }
private void ResyncAllDevices() private void ResyncAllDevices()
@@ -62,6 +63,11 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas)); SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas));
} }
private void OnTabChanged(AirAlarmTab tab)
{
SendMessage(new AirAlarmTabSetMessage(tab));
}
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
{ {
base.UpdateState(state); base.UpdateState(state);

View File

@@ -23,6 +23,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
public event Action<AirAlarmMode>? AirAlarmModeChanged; public event Action<AirAlarmMode>? AirAlarmModeChanged;
public event Action<bool>? AutoModeChanged; public event Action<bool>? AutoModeChanged;
public event Action? ResyncAllRequested; public event Action? ResyncAllRequested;
public event Action<AirAlarmTab>? AirAlarmTabChange;
private RichTextLabel _address => CDeviceAddress; private RichTextLabel _address => CDeviceAddress;
private RichTextLabel _deviceTotal => CDeviceTotal; private RichTextLabel _deviceTotal => CDeviceTotal;
@@ -79,6 +80,11 @@ public sealed partial class AirAlarmWindow : FancyWindow
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers")); _tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors")); _tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
_tabContainer.OnTabChanged += idx =>
{
AirAlarmTabChange!((AirAlarmTab) idx);
};
_resyncDevices.OnPressed += _ => _resyncDevices.OnPressed += _ =>
{ {
_ventDevices.RemoveAllChildren(); _ventDevices.RemoveAllChildren();
@@ -111,6 +117,8 @@ public sealed partial class AirAlarmWindow : FancyWindow
{ {
UpdateDeviceData(addr, dev); UpdateDeviceData(addr, dev);
} }
_tabContainer.CurrentTab = (int) state.Tab;
} }
public void UpdateModeSelector(AirAlarmMode mode) public void UpdateModeSelector(AirAlarmMode mode)

View File

@@ -16,8 +16,6 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class GasAnalyzerWindow : DefaultWindow public sealed partial class GasAnalyzerWindow : DefaultWindow
{ {
private NetEntity _currentEntity = NetEntity.Invalid;
public GasAnalyzerWindow() public GasAnalyzerWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -57,13 +55,6 @@ namespace Content.Client.Atmos.UI
// Device Tab // Device Tab
if (msg.NodeGasMixes.Length > 1) if (msg.NodeGasMixes.Length > 1)
{ {
if (_currentEntity != msg.DeviceUid)
{
// when we get new device data switch to the device tab
CTabContainer.CurrentTab = 0;
_currentEntity = msg.DeviceUid;
}
CTabContainer.SetTabVisible(0, true); CTabContainer.SetTabVisible(0, true);
CTabContainer.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName))); CTabContainer.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName)));
// Set up Grid // Set up Grid
@@ -152,7 +143,6 @@ namespace Content.Client.Atmos.UI
CTabContainer.SetTabVisible(0, false); CTabContainer.SetTabVisible(0, false);
CTabContainer.CurrentTab = 1; CTabContainer.CurrentTab = 1;
minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y); minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y);
_currentEntity = NetEntity.Invalid;
} }
MinSize = minSize; MinSize = minSize;

View File

@@ -57,7 +57,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
/// </summary> /// </summary>
private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f)); private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f));
private readonly Dictionary<Entity<AmbientSoundComponent>, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new(); private readonly Dictionary<AmbientSoundComponent, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
private readonly Dictionary<string, int> _playingCount = new(); private readonly Dictionary<string, int> _playingCount = new();
public bool OverlayEnabled public bool OverlayEnabled
@@ -107,7 +107,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args) private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args)
{ {
if (!_playingSounds.Remove((uid, component), out var sound)) if (!_playingSounds.Remove(component, out var sound))
return; return;
_audio.Stop(sound.Stream); _audio.Stop(sound.Stream);
@@ -120,13 +120,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
{ {
_ambienceVolume = SharedAudioSystem.GainToVolume(value); _ambienceVolume = SharedAudioSystem.GainToVolume(value);
foreach (var (ent, values) in _playingSounds) foreach (var (comp, values) in _playingSounds)
{ {
if (values.Stream == null) if (values.Stream == null)
continue; continue;
var stream = values.Stream; var stream = values.Stream;
_audio.SetVolume(stream, _params.Volume + ent.Comp.Volume + _ambienceVolume); _audio.SetVolume(stream, _params.Volume + comp.Volume + _ambienceVolume);
} }
} }
private void SetCooldown(float value) => _cooldown = value; private void SetCooldown(float value) => _cooldown = value;
@@ -165,7 +165,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
if (_gameTiming.CurTime < _targetTime) if (_gameTiming.CurTime < _targetTime)
return; return;
_targetTime = _gameTiming.CurTime + TimeSpan.FromSeconds(_cooldown); _targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown);
var player = _playerManager.LocalEntity; var player = _playerManager.LocalEntity;
if (!EntityManager.TryGetComponent(player, out TransformComponent? xform)) if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
@@ -190,7 +190,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
private readonly struct QueryState private readonly struct QueryState
{ {
public readonly Dictionary<string, List<(float Importance, Entity<AmbientSoundComponent>)>> SourceDict = new(); public readonly Dictionary<string, List<(float Importance, AmbientSoundComponent)>> SourceDict = new();
public readonly Vector2 MapPos; public readonly Vector2 MapPos;
public readonly TransformComponent Player; public readonly TransformComponent Player;
public readonly SharedTransformSystem TransformSystem; public readonly SharedTransformSystem TransformSystem;
@@ -224,11 +224,11 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
if (ambientComp.Sound is SoundPathSpecifier path) if (ambientComp.Sound is SoundPathSpecifier path)
key = path.Path.ToString(); key = path.Path.ToString();
else else
key = ((SoundCollectionSpecifier)ambientComp.Sound).Collection ?? string.Empty; key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
// Prioritize far away & loud sounds. // Prioritize far away & loud sounds.
var importance = range * (ambientComp.Volume + 32); var importance = range * (ambientComp.Volume + 32);
state.SourceDict.GetOrNew(key).Add((importance, (value.Uid, ambientComp))); state.SourceDict.GetOrNew(key).Add((importance, ambientComp));
return true; return true;
} }
@@ -242,18 +242,16 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
var mapPos = _xformSystem.GetMapCoordinates(playerXform); var mapPos = _xformSystem.GetMapCoordinates(playerXform);
// Remove out-of-range ambiences // Remove out-of-range ambiences
foreach (var (ent, sound) in _playingSounds) foreach (var (comp, sound) in _playingSounds)
{ {
//var entity = comp.Owner; var entity = comp.Owner;
var owner = ent.Owner;
var comp = ent.Comp;
if (comp.Enabled && if (comp.Enabled &&
// Don't keep playing sounds that have changed since. // Don't keep playing sounds that have changed since.
sound.Sound == comp.Sound && sound.Sound == comp.Sound &&
query.TryGetComponent(owner, out var xform) && query.TryGetComponent(entity, out var xform) &&
xform.MapID == playerXform.MapID && xform.MapID == playerXform.MapID &&
!metaQuery.GetComponent(owner).EntityPaused) !metaQuery.GetComponent(entity).EntityPaused)
{ {
// TODO: This is just trydistance for coordinates. // TODO: This is just trydistance for coordinates.
var distance = (xform.ParentUid == playerXform.ParentUid) var distance = (xform.ParentUid == playerXform.ParentUid)
@@ -265,7 +263,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
} }
_audio.Stop(sound.Stream); _audio.Stop(sound.Stream);
_playingSounds.Remove(ent); _playingSounds.Remove(comp);
_playingCount[sound.Path] -= 1; _playingCount[sound.Path] -= 1;
if (_playingCount[sound.Path] == 0) if (_playingCount[sound.Path] == 0)
_playingCount.Remove(sound.Path); _playingCount.Remove(sound.Path);
@@ -280,7 +278,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
_treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb); _treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb);
// Add in range ambiences // Add in range ambiences
foreach (var (key, sourceList) in state.SourceDict) foreach (var (key, sources) in state.SourceDict)
{ {
if (_playingSounds.Count >= _maxAmbientCount) if (_playingSounds.Count >= _maxAmbientCount)
break; break;
@@ -288,14 +286,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
if (_playingCount.TryGetValue(key, out var playingCount) && playingCount >= MaxSingleSound) if (_playingCount.TryGetValue(key, out var playingCount) && playingCount >= MaxSingleSound)
continue; continue;
sourceList.Sort(static (a, b) => b.Importance.CompareTo(a.Importance)); sources.Sort(static (a, b) => b.Importance.CompareTo(a.Importance));
foreach (var (_, sourceEntity) in sourceList) foreach (var (_, comp) in sources)
{ {
var uid = sourceEntity.Owner; var uid = comp.Owner;
var comp = sourceEntity.Comp;
if (_playingSounds.ContainsKey(sourceEntity) || if (_playingSounds.ContainsKey(comp) ||
metaQuery.GetComponent(uid).EntityPaused) metaQuery.GetComponent(uid).EntityPaused)
continue; continue;
@@ -306,10 +303,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
.WithMaxDistance(comp.Range); .WithMaxDistance(comp.Range);
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams); var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
if (stream == null) _playingSounds[comp] = (stream.Value.Entity, comp.Sound, key);
continue;
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
playingCount++; playingCount++;
if (_playingSounds.Count >= _maxAmbientCount) if (_playingSounds.Count >= _maxAmbientCount)

View File

@@ -67,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
if(!_adminAudioEnabled) return; if(!_adminAudioEnabled) return;
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams); var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
_adminAudio.Add(stream?.Entity); _adminAudio.Add(stream.Value.Entity);
} }
private void PlayStationEventMusic(StationEventMusicEvent soundEvent) private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
@@ -76,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return; if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams); var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
_eventAudio.Add(soundEvent.Type, stream?.Entity); _eventAudio.Add(soundEvent.Type, stream.Value.Entity);
} }
private void PlayGameSound(GameGlobalSoundEvent soundEvent) private void PlayGameSound(GameGlobalSoundEvent soundEvent)

View File

@@ -214,9 +214,9 @@ public sealed partial class ContentAudioSystem
false, false,
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider)); AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
_ambientMusicStream = strim?.Entity; _ambientMusicStream = strim.Value.Entity;
if (_musicProto.FadeIn && strim != null) if (_musicProto.FadeIn)
{ {
FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime); FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime);
} }

View File

@@ -43,8 +43,6 @@ public sealed partial class ContentAudioSystem
private void CP14UpdateAmbientLoops() private void CP14UpdateAmbientLoops()
{ {
return; //DISABLED UNTIL CLIENT ERROR SPAM FIXED
if (_timing.CurTime <= _nextUpdateTime) if (_timing.CurTime <= _nextUpdateTime)
return; return;
@@ -82,10 +80,6 @@ public sealed partial class ContentAudioSystem
.WithLoop(true) .WithLoop(true)
.WithVolume(proto.Sound.Params.Volume + _volumeSlider) .WithVolume(proto.Sound.Params.Volume + _volumeSlider)
.WithPlayOffset(_random.NextFloat(0f, 100f))); .WithPlayOffset(_random.NextFloat(0f, 100f)));
if (newLoop is null)
return;
_loopStreams.Add(proto, newLoop.Value.Entity); _loopStreams.Add(proto, newLoop.Value.Entity);
FadeIn(newLoop.Value.Entity, newLoop.Value.Component, AmbientLoopFadeInTime); FadeIn(newLoop.Value.Entity, newLoop.Value.Component, AmbientLoopFadeInTime);

View File

@@ -20,6 +20,7 @@ public sealed partial class ContentAudioSystem
{ {
[Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly ClientGameTicker _gameTicker = default!; [Dependency] private readonly ClientGameTicker _gameTicker = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f); private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f);
@@ -70,7 +71,7 @@ public sealed partial class ContentAudioSystem
Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged); Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged); Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
_state.OnStateChanged += StateManagerOnStateChanged; _stateManager.OnStateChanged += StateManagerOnStateChanged;
_client.PlayerLeaveServer += OnLeave; _client.PlayerLeaveServer += OnLeave;
@@ -114,7 +115,7 @@ public sealed partial class ContentAudioSystem
private void LobbyMusicCVarChanged(bool musicEnabled) private void LobbyMusicCVarChanged(bool musicEnabled)
{ {
if (musicEnabled && _state.CurrentState is LobbyState) if (musicEnabled && _stateManager.CurrentState is LobbyState)
{ {
StartLobbyMusic(); StartLobbyMusic();
} }
@@ -184,7 +185,7 @@ public sealed partial class ContentAudioSystem
false, false,
_lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume))) _lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
); );
if (playResult == null) if (playResult.Value.Entity == default)
{ {
_sawmill.Warning( _sawmill.Warning(
$"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!", $"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!",
@@ -233,7 +234,7 @@ public sealed partial class ContentAudioSystem
private void ShutdownLobbyMusic() private void ShutdownLobbyMusic()
{ {
_state.OnStateChanged -= StateManagerOnStateChanged; _stateManager.OnStateChanged -= StateManagerOnStateChanged;
_client.PlayerLeaveServer -= OnLeave; _client.PlayerLeaveServer -= OnLeave;

View File

@@ -15,6 +15,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange); SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent); SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
} }
@@ -56,6 +57,21 @@ internal sealed class BuckleSystem : SharedBuckleSystem
} }
} }
private void OnHandleState(Entity<BuckleComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not BuckleState state)
return;
ent.Comp.DontCollide = state.DontCollide;
ent.Comp.BuckleTime = state.BuckleTime;
var strapUid = EnsureEntity<BuckleComponent>(state.BuckledTo, ent);
SetBuckledTo(ent, strapUid == null ? null : new (strapUid.Value, null));
var (uid, component) = ent;
}
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args) private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
{ {
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals)) if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))

View File

@@ -1,21 +0,0 @@
using Content.Shared.Timing;
using Content.Shared.Cargo.Systems;
namespace Content.Client.Cargo.Systems;
/// <summary>
/// This handles...
/// </summary>
public sealed class ClientPriceGunSystem : SharedPriceGunSystem
{
[Dependency] private readonly UseDelaySystem _useDelay = default!;
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
{
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
return false;
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
return true;
}
}

View File

@@ -1,5 +1,4 @@
using Content.Client.CrewManifest.UI; using Content.Shared.CrewManifest;
using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;

View File

@@ -26,7 +26,7 @@
Text="{Loc 'news-read-ui-next-text'}" Text="{Loc 'news-read-ui-next-text'}"
ToolTip="{Loc 'news-read-ui-next-tooltip'}"/> ToolTip="{Loc 'news-read-ui-next-tooltip'}"/>
</BoxContainer> </BoxContainer>
<controls:StripeBack Name="ArticleNameContainer"> <controls:StripeBack Name="АrticleNameContainer">
<PanelContainer> <PanelContainer>
<Label Name="PageNum" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="4,0,0,0"/> <Label Name="PageNum" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="4,0,0,0"/>
<Label Name="PageName" Align="Center"/> <Label Name="PageName" Align="Center"/>

View File

@@ -1,30 +0,0 @@
using Content.Client.UserInterface.Fragments;
using Content.Shared.CartridgeLoader.Cartridges;
using Robust.Client.UserInterface;
namespace Content.Client.CartridgeLoader.Cartridges;
public sealed partial class WantedListUi : UIFragment
{
private WantedListUiFragment? _fragment;
public override Control GetUIFragmentRoot()
{
return _fragment!;
}
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
{
_fragment = new WantedListUiFragment();
}
public override void UpdateState(BoundUserInterfaceState state)
{
switch (state)
{
case WantedListUiState cast:
_fragment?.UpdateState(cast.Records);
break;
}
}
}

View File

@@ -1,240 +0,0 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared.CriminalRecords.Systems;
using Content.Shared.Security;
using Content.Shared.StatusIcon;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.CartridgeLoader.Cartridges;
[GenerateTypedNameReferences]
public sealed partial class WantedListUiFragment : BoxContainer
{
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SpriteSystem _spriteSystem;
private string? _selectedTargetName;
private List<WantedRecord> _wantedRecords = new();
public WantedListUiFragment()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
SearchBar.OnTextChanged += OnSearchBarTextChanged;
}
private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
{
var found = !String.IsNullOrWhiteSpace(args.Text)
? _wantedRecords.FindAll(r =>
r.TargetInfo.Name.Contains(args.Text) ||
r.Status.ToString().Contains(args.Text, StringComparison.OrdinalIgnoreCase))
: _wantedRecords;
UpdateState(found, false);
}
public void UpdateState(List<WantedRecord> records, bool refresh = true)
{
if (records.Count == 0)
{
NoRecords.Visible = true;
RecordsList.Visible = false;
RecordUnselected.Visible = false;
PersonContainer.Visible = false;
_selectedTargetName = null;
if (refresh)
_wantedRecords.Clear();
RecordsList.PopulateList(new List<ListData>());
return;
}
NoRecords.Visible = false;
RecordsList.Visible = true;
RecordUnselected.Visible = true;
PersonContainer.Visible = false;
var dataList = records.Select(r => new StatusListData(r)).ToList();
RecordsList.GenerateItem = GenerateItem;
RecordsList.ItemPressed = OnItemSelected;
RecordsList.PopulateList(dataList);
if (refresh)
_wantedRecords = records;
}
private void OnItemSelected(BaseButton.ButtonEventArgs args, ListData data)
{
if (data is not StatusListData(var record))
return;
FormattedMessage GetLoc(string fluentId, params (string,object)[] args)
{
var msg = new FormattedMessage();
var fluent = Loc.GetString(fluentId, args);
msg.AddMarkupPermissive(fluent);
return msg;
}
// Set personal info
PersonName.Text = record.TargetInfo.Name;
TargetAge.SetMessage(GetLoc(
"wanted-list-age-label",
("age", record.TargetInfo.Age)
));
TargetJob.SetMessage(GetLoc(
"wanted-list-job-label",
("job", record.TargetInfo.JobTitle.ToLower())
));
TargetSpecies.SetMessage(GetLoc(
"wanted-list-species-label",
("species", record.TargetInfo.Species.ToLower())
));
TargetGender.SetMessage(GetLoc(
"wanted-list-gender-label",
("gender", record.TargetInfo.Gender)
));
// Set reason
WantedReason.SetMessage(GetLoc(
"wanted-list-reason-label",
("reason", record.Reason ?? Loc.GetString("wanted-list-unknown-reason-label"))
));
// Set status
PersonState.SetMessage(GetLoc(
"wanted-list-status-label",
("status", record.Status.ToString().ToLower())
));
// Set initiator
InitiatorName.SetMessage(GetLoc(
"wanted-list-initiator-label",
("initiator", record.Initiator ?? Loc.GetString("wanted-list-unknown-initiator-label"))
));
// History table
// Clear table if it exists
HistoryTable.RemoveAllChildren();
HistoryTable.AddChild(new Label()
{
Text = Loc.GetString("wanted-list-history-table-time-col"),
StyleClasses = { "LabelSmall" },
HorizontalAlignment = HAlignment.Center,
});
HistoryTable.AddChild(new Label()
{
Text = Loc.GetString("wanted-list-history-table-reason-col"),
StyleClasses = { "LabelSmall" },
HorizontalAlignment = HAlignment.Center,
HorizontalExpand = true,
});
HistoryTable.AddChild(new Label()
{
Text = Loc.GetString("wanted-list-history-table-initiator-col"),
StyleClasses = { "LabelSmall" },
HorizontalAlignment = HAlignment.Center,
});
if (record.History.Count > 0)
{
HistoryTable.Visible = true;
foreach (var history in record.History.OrderByDescending(h => h.AddTime))
{
HistoryTable.AddChild(new Label()
{
Text = $"{history.AddTime.Hours:00}:{history.AddTime.Minutes:00}:{history.AddTime.Seconds:00}",
StyleClasses = { "LabelSmall" },
VerticalAlignment = VAlignment.Top,
});
HistoryTable.AddChild(new RichTextLabel()
{
Text = $"[color=white]{history.Crime}[/color]",
HorizontalExpand = true,
VerticalAlignment = VAlignment.Top,
StyleClasses = { "LabelSubText" },
Margin = new(10f, 0f),
});
HistoryTable.AddChild(new RichTextLabel()
{
Text = $"[color=white]{history.InitiatorName}[/color]",
StyleClasses = { "LabelSubText" },
VerticalAlignment = VAlignment.Top,
});
}
}
RecordUnselected.Visible = false;
PersonContainer.Visible = true;
// Save selected item
_selectedTargetName = record.TargetInfo.Name;
}
private void GenerateItem(ListData data, ListContainerButton button)
{
if (data is not StatusListData(var record))
return;
var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal, HorizontalExpand = true };
var label = new Label() { Text = record.TargetInfo.Name };
var rect = new TextureRect()
{
TextureScale = new(2.2f),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Margin = new(0f, 0f, 6f, 0f),
};
if (record.Status is not SecurityStatus.None)
{
var proto = "SecurityIcon" + record.Status switch
{
SecurityStatus.Detained => "Incarcerated",
_ => record.Status.ToString(),
};
if (_prototypeManager.TryIndex<SecurityIconPrototype>(proto, out var prototype))
{
rect.Texture = _spriteSystem.Frame0(prototype.Icon);
}
}
box.AddChild(rect);
box.AddChild(label);
button.AddChild(box);
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
if (record.TargetInfo.Name.Equals(_selectedTargetName))
{
button.Pressed = true;
// For some reason the event is not called when `Pressed` changed, call it manually.
OnItemSelected(
new(button, new(new(), BoundKeyState.Down, new(), false, new(), new())),
data);
}
}
}
internal record StatusListData(WantedRecord Record) : ListData;

View File

@@ -1,50 +0,0 @@
<cartridges:WantedListUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True">
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'wanted-list-search-placeholder'}"/>
<BoxContainer Name="MainContainer" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<Label Name="NoRecords" Text="{Loc 'wanted-list-label-no-records'}" Align="Center" VAlign="Center" HorizontalExpand="True" FontColorOverride="DarkGray"/>
<!-- Any attempts to set dimensions for ListContainer breaks the renderer, I have to roughly set sizes and margins in other controllers. -->
<controls:ListContainer
Name="RecordsList"
HorizontalAlignment="Left"
VerticalExpand="True"
Visible="False"
Toggle="True"
Group="True"
SetWidth="192" />
<Label Name="RecordUnselected"
Text="{Loc 'criminal-records-console-select-record-info'}"
Align="Center"
FontColorOverride="DarkGray"
Visible="False"
HorizontalExpand="True" />
<BoxContainer Name="PersonContainer" Orientation="Vertical" HorizontalExpand="True" SetWidth="334" Margin="5 0 77 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Name="PersonName" StyleClasses="LabelBig" />
<RichTextLabel Name="PersonState" HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Name="DataContainer" Orientation="Vertical">
<RichTextLabel Name="TargetAge" />
<RichTextLabel Name="TargetJob" />
<RichTextLabel Name="TargetSpecies" />
<RichTextLabel Name="TargetGender" />
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
<RichTextLabel Name="InitiatorName" VerticalAlignment="Stretch"/>
<RichTextLabel Name="WantedReason" VerticalAlignment="Stretch"/>
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
<controls:TableContainer Name="HistoryTable" Columns="3" Visible="False" HorizontalAlignment="Stretch" />
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</BoxContainer>
</cartridges:WantedListUiFragment>

View File

@@ -131,13 +131,13 @@ public sealed partial class ChangelogTab : Control
Margin = new Thickness(6, 0, 0, 0), Margin = new Thickness(6, 0, 0, 0),
}; };
authorLabel.SetMessage( authorLabel.SetMessage(
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", author)))); FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
ChangelogBody.AddChild(authorLabel); ChangelogBody.AddChild(authorLabel);
foreach (var change in groupedEntry.SelectMany(c => c.Changes)) foreach (var change in groupedEntry.SelectMany(c => c.Changes))
{ {
var text = new RichTextLabel(); var text = new RichTextLabel();
text.SetMessage(FormattedMessage.FromMarkupOrThrow(change.Message)); text.SetMessage(FormattedMessage.FromMarkup(change.Message));
ChangelogBody.AddChild(new BoxContainer ChangelogBody.AddChild(new BoxContainer
{ {
Orientation = LayoutOrientation.Horizontal, Orientation = LayoutOrientation.Horizontal,

View File

@@ -5,80 +5,71 @@ using Content.Shared.Chat;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.Chat.Managers; namespace Content.Client.Chat.Managers
internal sealed class ChatManager : IChatManager
{ {
[Dependency] private readonly IClientConsoleHost _consoleHost = default!; internal sealed class ChatManager : IChatManager
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
private ISawmill _sawmill = default!;
public void Initialize()
{ {
_sawmill = Logger.GetSawmill("chat"); [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
_sawmill.Level = LogLevel.Info; [Dependency] private readonly IClientAdminManager _adminMgr = default!;
} [Dependency] private readonly IEntitySystemManager _systems = default!;
public void SendAdminAlert(string message) private ISawmill _sawmill = default!;
{
// See server-side manager. This just exists for shared code.
}
public void SendAdminAlert(EntityUid player, string message) public void Initialize()
{
// See server-side manager. This just exists for shared code.
}
public void SendMessage(string text, ChatSelectChannel channel)
{
var str = text.ToString();
switch (channel)
{ {
case ChatSelectChannel.Console: _sawmill = Logger.GetSawmill("chat");
// run locally _sawmill.Level = LogLevel.Info;
_consoleHost.ExecuteCommand(text); }
break;
case ChatSelectChannel.LOOC: public void SendMessage(string text, ChatSelectChannel channel)
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\""); {
break; var str = text.ToString();
switch (channel)
{
case ChatSelectChannel.Console:
// run locally
_consoleHost.ExecuteCommand(text);
break;
case ChatSelectChannel.OOC: case ChatSelectChannel.LOOC:
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\""); _consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
break; break;
case ChatSelectChannel.Admin: case ChatSelectChannel.OOC:
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\""); _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
break; break;
case ChatSelectChannel.Emotes: case ChatSelectChannel.Admin:
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\""); _consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
break; break;
case ChatSelectChannel.Dead: case ChatSelectChannel.Emotes:
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true}) _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
goto case ChatSelectChannel.Local; break;
if (_adminMgr.HasFlag(AdminFlags.Admin)) case ChatSelectChannel.Dead:
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\""); if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
else goto case ChatSelectChannel.Local;
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
break;
// TODO sepearate radio and say into separate commands. if (_adminMgr.HasFlag(AdminFlags.Admin))
case ChatSelectChannel.Radio: _consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
case ChatSelectChannel.Local: else
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\""); _sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
break; break;
case ChatSelectChannel.Whisper: // TODO sepearate radio and say into separate commands.
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\""); case ChatSelectChannel.Radio:
break; case ChatSelectChannel.Local:
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
break;
default: case ChatSelectChannel.Whisper:
throw new ArgumentOutOfRangeException(nameof(channel), channel, null); _consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
break;
default:
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
}
} }
} }
} }

View File

@@ -2,8 +2,10 @@ using Content.Shared.Chat;
namespace Content.Client.Chat.Managers namespace Content.Client.Chat.Managers
{ {
public interface IChatManager : ISharedChatManager public interface IChatManager
{ {
void Initialize();
public void SendMessage(string text, ChatSelectChannel channel); public void SendMessage(string text, ChatSelectChannel channel);
} }
} }

View File

@@ -19,6 +19,9 @@ public sealed partial class EmotesMenu : RadialMenu
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly EntityWhitelistSystem _whitelistSystem;
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote; public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
public EmotesMenu() public EmotesMenu()
@@ -26,8 +29,8 @@ public sealed partial class EmotesMenu : RadialMenu
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
var spriteSystem = _entManager.System<SpriteSystem>(); _spriteSystem = _entManager.System<SpriteSystem>();
var whitelistSystem = _entManager.System<EntityWhitelistSystem>(); _whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main"); var main = FindControl<RadialContainer>("Main");
@@ -37,8 +40,8 @@ public sealed partial class EmotesMenu : RadialMenu
var player = _playerManager.LocalSession?.AttachedEntity; var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid || if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 || emote.ChatTriggers.Count == 0 ||
!(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue; continue;
if (!emote.Available && if (!emote.Available &&
@@ -60,7 +63,7 @@ public sealed partial class EmotesMenu : RadialMenu
{ {
VerticalAlignment = VAlignment.Center, VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center, HorizontalAlignment = HAlignment.Center,
Texture = spriteSystem.Frame0(emote.Icon), Texture = _spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f), TextureScale = new Vector2(2f, 2f),
}; };

View File

@@ -16,7 +16,6 @@ namespace Content.Client.Chat.UI
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigManager = default!; [Dependency] protected readonly IConfigurationManager ConfigManager = default!;
private readonly SharedTransformSystem _transformSystem;
public enum SpeechType : byte public enum SpeechType : byte
{ {
@@ -84,7 +83,6 @@ namespace Content.Client.Chat.UI
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_senderEntity = senderEntity; _senderEntity = senderEntity;
_transformSystem = _entityManager.System<SharedTransformSystem>();
// Use text clipping so new messages don't overlap old ones being pushed up. // Use text clipping so new messages don't overlap old ones being pushed up.
RectClipContent = true; RectClipContent = true;
@@ -142,7 +140,7 @@ namespace Content.Client.Chat.UI
} }
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset; var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset;
var worldPos = _transformSystem.GetWorldPosition(xform) + offset; var worldPos = xform.WorldPosition + offset;
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale; var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved); var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);
@@ -180,7 +178,7 @@ namespace Content.Client.Chat.UI
var msg = new FormattedMessage(); var msg = new FormattedMessage();
if (fontColor != null) if (fontColor != null)
msg.PushColor(fontColor.Value); msg.PushColor(fontColor.Value);
msg.AddMarkupOrThrow(message); msg.AddMarkup(message);
return msg; return msg;
} }

View File

@@ -1,5 +1,5 @@
using System.Linq; using System.Linq;
using Content.Shared.Chemistry.EntitySystems; using Content.Client.Chemistry.Containers.EntitySystems;
using Content.Shared.Atmos.Prototypes; using Content.Shared.Atmos.Prototypes;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
@@ -16,7 +16,7 @@ namespace Content.Client.Chemistry.EntitySystems;
/// <inheritdoc/> /// <inheritdoc/>
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
{ {
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
[ValidatePrototypeId<MixingCategoryPrototype>] [ValidatePrototypeId<MixingCategoryPrototype>]
private const string DefaultMixingCategory = "DummyMix"; private const string DefaultMixingCategory = "DummyMix";

View File

@@ -1,5 +1,4 @@
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
@@ -10,15 +9,11 @@ namespace Content.Client.Chemistry.UI
[UsedImplicitly] [UsedImplicitly]
public sealed class TransferAmountBoundUserInterface : BoundUserInterface public sealed class TransferAmountBoundUserInterface : BoundUserInterface
{ {
private IEntityManager _entManager;
private EntityUid _owner;
[ViewVariables] [ViewVariables]
private TransferAmountWindow? _window; private TransferAmountWindow? _window;
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{ {
_owner = owner;
_entManager = IoCManager.Resolve<IEntityManager>();
} }
protected override void Open() protected override void Open()
@@ -26,9 +21,6 @@ namespace Content.Client.Chemistry.UI
base.Open(); base.Open();
_window = this.CreateWindow<TransferAmountWindow>(); _window = this.CreateWindow<TransferAmountWindow>();
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ => _window.ApplyButton.OnPressed += _ =>
{ {
if (int.TryParse(_window.AmountLineEdit.Text, out var i)) if (int.TryParse(_window.AmountLineEdit.Text, out var i))

View File

@@ -6,10 +6,6 @@
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<LineEdit Name="AmountLineEdit" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc 'ui-transfer-amount-line-edit-placeholder'}"/> <LineEdit Name="AmountLineEdit" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc 'ui-transfer-amount-line-edit-placeholder'}"/>
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Name="MinimumAmount" Access="Public" HorizontalExpand="True" />
<Label Name="MaximumAmount" Access="Public" />
</BoxContainer>
<Button Name="ApplyButton" Access="Public" Text="{Loc 'ui-transfer-amount-apply'}"/> <Button Name="ApplyButton" Access="Public" Text="{Loc 'ui-transfer-amount-apply'}"/>
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -8,29 +8,9 @@ namespace Content.Client.Chemistry.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow public sealed partial class TransferAmountWindow : DefaultWindow
{ {
private int _max = Int32.MaxValue;
private int _min = 1;
public TransferAmountWindow() public TransferAmountWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
AmountLineEdit.OnTextChanged += OnValueChanged;
}
public void SetBounds(int min, int max)
{
_min = min;
_max = max;
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
}
private void OnValueChanged(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
ApplyButton.Disabled = true;
else
ApplyButton.Disabled = false;
} }
} }
} }

View File

@@ -2,8 +2,6 @@ using Content.Client.Items.Systems;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Hands; using Content.Shared.Hands;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Rounding; using Content.Shared.Rounding;
@@ -22,7 +20,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SolutionContainerVisualsComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<SolutionContainerVisualsComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals); SubscribeLocalEvent<SolutionContainerVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals);
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetEquipmentVisualsEvent>(OnGetClothingVisuals);
} }
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
@@ -177,41 +174,4 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
args.Layers.Add((key, layer)); args.Layers.Add((key, layer));
} }
} }
private void OnGetClothingVisuals(Entity<SolutionContainerVisualsComponent> ent, ref GetEquipmentVisualsEvent args)
{
if (ent.Comp.EquippedFillBaseName == null)
return;
if (!TryComp<AppearanceComponent>(ent, out var appearance))
return;
if (!TryComp<ClothingComponent>(ent, out var clothing))
return;
if (!AppearanceSystem.TryGetData<float>(ent, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
return;
var closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, ent.Comp.EquippedMaxFillLevels + 1);
if (closestFillSprite > 0)
{
var layer = new PrototypeLayerData();
var equippedPrefix = clothing.EquippedPrefix == null ? $"equipped-{args.Slot}" : $" {clothing.EquippedPrefix}-equipped-{args.Slot}";
var key = equippedPrefix + ent.Comp.EquippedFillBaseName + closestFillSprite;
// Make sure the sprite state is valid so we don't show a big red error message
// This saves us from having to make fill level sprites for every possible slot the item could be in (including pockets).
if (!TryComp<SpriteComponent>(ent, out var sprite) || sprite.BaseRSI == null || !sprite.BaseRSI.TryGetState(key, out _))
return;
layer.State = key;
if (ent.Comp.ChangeColor && AppearanceSystem.TryGetData<Color>(ent, SolutionContainerVisuals.Color, out var color, appearance))
layer.Color = color;
args.Layers.Add((key, layer));
}
}
} }

View File

@@ -1,17 +1,148 @@
namespace Content.Client.Clickable; using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using static Robust.Client.GameObjects.SpriteComponent;
using Direction = Robust.Shared.Maths.Direction;
[RegisterComponent] namespace Content.Client.Clickable
public sealed partial class ClickableComponent : Component
{ {
[DataField] public DirBoundData? Bounds; [RegisterComponent]
public sealed partial class ClickableComponent : Component
[DataDefinition]
public sealed partial class DirBoundData
{ {
[DataField] public Box2 All; [Dependency] private readonly IClickMapManager _clickMapManager = default!;
[DataField] public Box2 North;
[DataField] public Box2 South; [DataField("bounds")] public DirBoundData? Bounds;
[DataField] public Box2 East;
[DataField] public Box2 West; /// <summary>
/// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
/// boxes (see <see cref="Bounds"/>). If that fails, attempts to use automatically generated click maps.
/// </summary>
/// <param name="worldPos">The world position that was clicked.</param>
/// <param name="drawDepth">
/// The draw depth for the sprite that captured the click.
/// </param>
/// <returns>True if the click worked, false otherwise.</returns>
public bool CheckClick(SpriteComponent sprite, TransformComponent transform, EntityQuery<TransformComponent> xformQuery, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
{
if (!sprite.Visible)
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
drawDepth = sprite.DrawDepth;
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
// Check explicitly defined click-able bounds
if (CheckDirBound(sprite, relativeRotation, localPos))
return true;
// Next check each individual sprite layer using automatically computed click maps.
foreach (var spriteLayer in sprite.AllLayers)
{
// TODO: Move this to a system and also use SpriteSystem.IsVisible instead.
if (!spriteLayer.Visible || spriteLayer is not Layer layer || layer.CopyToShaderParameters != null)
{
continue;
}
// Check the layer's texture, if it has one
if (layer.Texture != null)
{
// Convert to image coordinates
var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
return true;
}
// Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
continue;
var dir = Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
Matrix3x2.Invert(matrix, out var inverseMatrix);
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
// Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
// This **can** differ from the dir defined before, but can also just be the same.
if (sprite.EnableDirectionOverride)
dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
dir = dir.OffsetRsiDir(layer.DirOffset);
if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
return true;
}
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
public bool CheckDirBound(SpriteComponent sprite, Angle relativeRotation, Vector2 localPos)
{
if (Bounds == null)
return false;
// These explicit bounds only work for either 1 or 4 directional sprites.
// This would be the orientation of a 4-directional sprite.
var direction = relativeRotation.GetCardinalDir();
var modLocalPos = sprite.NoRotation
? localPos
: direction.ToAngle().RotateVec(localPos);
// First, check the bounding box that is valid for all orientations
if (Bounds.All.Contains(modLocalPos))
return true;
// Next, get and check the appropriate bounding box for the current sprite orientation
var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
{
Direction.East => Bounds.East,
Direction.North => Bounds.North,
Direction.South => Bounds.South,
Direction.West => Bounds.West,
_ => throw new InvalidOperationException()
};
return boundsForDir.Contains(modLocalPos);
}
[DataDefinition]
public sealed partial class DirBoundData
{
[DataField("all")] public Box2 All;
[DataField("north")] public Box2 North;
[DataField("south")] public Box2 South;
[DataField("east")] public Box2 East;
[DataField("west")] public Box2 West;
}
} }
} }

View File

@@ -1,168 +0,0 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
namespace Content.Client.Clickable;
/// <summary>
/// Handles click detection for sprites.
/// </summary>
public sealed class ClickableSystem : EntitySystem
{
[Dependency] private readonly IClickMapManager _clickMapManager = default!;
[Dependency] private readonly SharedTransformSystem _transforms = default!;
[Dependency] private readonly SpriteSystem _sprites = default!;
private EntityQuery<ClickableComponent> _clickableQuery;
private EntityQuery<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
_clickableQuery = GetEntityQuery<ClickableComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
}
/// <summary>
/// Used to check whether a click worked. Will first check if the click falls inside of some explicit bounding
/// boxes (see <see cref="Bounds"/>). If that fails, attempts to use automatically generated click maps.
/// </summary>
/// <param name="worldPos">The world position that was clicked.</param>
/// <param name="drawDepth">
/// The draw depth for the sprite that captured the click.
/// </param>
/// <returns>True if the click worked, false otherwise.</returns>
public bool CheckClick(Entity<ClickableComponent?, SpriteComponent, TransformComponent?> entity, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
{
if (!_clickableQuery.Resolve(entity.Owner, ref entity.Comp1, false))
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
if (!_xformQuery.Resolve(entity.Owner, ref entity.Comp3))
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
var sprite = entity.Comp2;
var transform = entity.Comp3;
if (!sprite.Visible)
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
drawDepth = sprite.DrawDepth;
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = _transforms.GetWorldPositionRotation(transform);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
var cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
// Check explicitly defined click-able bounds
if (CheckDirBound((entity.Owner, entity.Comp1, entity.Comp2), relativeRotation, localPos))
return true;
// Next check each individual sprite layer using automatically computed click maps.
foreach (var spriteLayer in sprite.AllLayers)
{
if (spriteLayer is not SpriteComponent.Layer layer || !_sprites.IsVisible(layer))
{
continue;
}
// Check the layer's texture, if it has one
if (layer.Texture != null)
{
// Convert to image coordinates
var imagePos = (Vector2i) (localPos * EyeManager.PixelsPerMeter * new Vector2(1, -1) + layer.Texture.Size / 2f);
if (_clickMapManager.IsOccluding(layer.Texture, imagePos))
return true;
}
// Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next
if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState))
continue;
var dir = SpriteComponent.Layer.GetDirection(rsiState.RsiDirections, relativeRotation);
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
Matrix3x2.Invert(matrix, out var inverseMatrix);
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);
// Next, to get the right click map we need the "direction" of this layer that is actually being used to draw the sprite on the screen.
// This **can** differ from the dir defined before, but can also just be the same.
if (sprite.EnableDirectionOverride)
dir = sprite.DirectionOverride.Convert(rsiState.RsiDirections);
dir = dir.OffsetRsiDir(layer.DirOffset);
if (_clickMapManager.IsOccluding(layer.ActualRsi!, layer.State, dir, layer.AnimationFrame, layerImagePos))
return true;
}
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
public bool CheckDirBound(Entity<ClickableComponent, SpriteComponent> entity, Angle relativeRotation, Vector2 localPos)
{
var clickable = entity.Comp1;
var sprite = entity.Comp2;
if (clickable.Bounds == null)
return false;
// These explicit bounds only work for either 1 or 4 directional sprites.
// This would be the orientation of a 4-directional sprite.
var direction = relativeRotation.GetCardinalDir();
var modLocalPos = sprite.NoRotation
? localPos
: direction.ToAngle().RotateVec(localPos);
// First, check the bounding box that is valid for all orientations
if (clickable.Bounds.All.Contains(modLocalPos))
return true;
// Next, get and check the appropriate bounding box for the current sprite orientation
var boundsForDir = (sprite.EnableDirectionOverride ? sprite.DirectionOverride : direction) switch
{
Direction.East => clickable.Bounds.East,
Direction.North => clickable.Bounds.North,
Direction.South => clickable.Bounds.South,
Direction.West => clickable.Bounds.West,
_ => throw new InvalidOperationException()
};
return boundsForDir.Contains(modLocalPos);
}
}

View File

@@ -57,6 +57,7 @@ public sealed class ClientClothingSystem : ClothingSystem
}; };
[Dependency] private readonly IResourceCache _cache = default!; [Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly DisplacementMapSystem _displacement = default!; [Dependency] private readonly DisplacementMapSystem _displacement = default!;
@@ -327,8 +328,7 @@ public sealed class ClientClothingSystem : ClothingSystem
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId)) if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
continue; continue;
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers)) _displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
index++;
} }
} }

View File

@@ -1,6 +0,0 @@
using Content.Shared.Clothing;
namespace Content.Client.Clothing.Systems;
/// <inheritdoc/>
public sealed class CursedMaskSystem : SharedCursedMaskSystem;

View File

@@ -1,4 +1,5 @@
using Content.Client.Actions; using Content.Client.Actions;
using Content.Client.Mapping;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
@@ -60,3 +61,27 @@ public sealed class LoadActionsCommand : LocalizedCommands
} }
} }
} }
[AnyCommand]
public sealed class LoadMappingActionsCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
public const string CommandName = "loadmapacts";
public override string Command => CommandName;
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
try
{
_entitySystemManager.GetEntitySystem<MappingSystem>().LoadMappingActions();
}
catch
{
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
}
}
}

View File

@@ -30,7 +30,7 @@ public sealed class HideMechanismsCommand : LocalizedCommands
sprite.ContainerOccluded = false; sprite.ContainerOccluded = false;
var tempParent = uid; var tempParent = uid;
while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container)) while (containerSys.TryGetContainingContainer(tempParent, out var container))
{ {
if (!container.ShowContents) if (!container.ShowContents)
{ {

View File

@@ -1,9 +1,6 @@
using Content.Client.Actions;
using Content.Client.Mapping;
using Content.Client.Markers; using Content.Client.Markers;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Shared.Console; using Robust.Shared.Console;
namespace Content.Client.Commands; namespace Content.Client.Commands;
@@ -24,8 +21,8 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
{ {
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true; _entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
_lightManager.Enabled = false; _lightManager.Enabled = false;
shell.ExecuteCommand("showsubfloorforever"); shell.ExecuteCommand(ShowSubFloorForever.CommandName);
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false); shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
} }
} }
} }

View File

@@ -1,5 +1,4 @@
using Content.Client.Verbs; using Content.Client.Verbs;
using Content.Shared.Verbs;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Console; using Robust.Shared.Console;

View File

@@ -1,8 +1,6 @@
using Content.Shared.Damage.Prototypes;
using Content.Shared.Overlays; using Content.Shared.Overlays;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
namespace Content.Client.Commands; namespace Content.Client.Commands;
@@ -36,7 +34,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands
{ {
var showHealthBarsComponent = new ShowHealthBarsComponent var showHealthBarsComponent = new ShowHealthBarsComponent
{ {
DamageContainers = args.Select(arg => new ProtoId<DamageContainerPrototype>(arg)).ToList(), DamageContainers = args.ToList(),
HealthStatusIcon = null, HealthStatusIcon = null,
NetSyncEnabled = false NetSyncEnabled = false
}; };

View File

@@ -4,14 +4,14 @@
MinSize="400 225"> MinSize="400 225">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5"> <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
<TextEdit Name="MessageInput" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 0 5" MinHeight="100" /> <TextEdit Name="MessageInput" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 0 5" MinHeight="100" />
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" /> <Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" StyleClasses="OpenLeft" Access="Public" />
<Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" /> <Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" StyleClasses="OpenLeft" Access="Public" />
<OptionButton Name="AlertLevelButton" ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}" StyleClasses="OpenRight" Access="Public" /> <OptionButton Name="AlertLevelButton" StyleClasses="OpenRight" Access="Public" />
<Control MinSize="10 10" /> <Control MinSize="10 10" />
<RichTextLabel Name="CountdownLabel" VerticalExpand="True" /> <RichTextLabel Name="CountdownLabel" VerticalExpand="True" />
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}" Access="Public" /> <Button Name="EmergencyShuttleButton" Text="Placeholder Text" Access="Public" />
</BoxContainer> </BoxContainer>
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -130,7 +130,7 @@ namespace Content.Client.Communications.UI
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle"); EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
var infoText = Loc.GetString($"comms-console-menu-time-remaining", var infoText = Loc.GetString($"comms-console-menu-time-remaining",
("time", diff.ToString(@"hh\:mm\:ss", CultureInfo.CurrentCulture))); ("time", diff.TotalSeconds.ToString(CultureInfo.CurrentCulture)));
CountdownLabel.SetMessage(infoText); CountdownLabel.SetMessage(infoText);
} }
} }

View File

@@ -11,6 +11,8 @@ namespace Content.Client.Computer
[Virtual] [Virtual]
public class ComputerBoundUserInterface<TWindow, TState> : ComputerBoundUserInterfaceBase where TWindow : BaseWindow, IComputerWindow<TState>, new() where TState : BoundUserInterfaceState public class ComputerBoundUserInterface<TWindow, TState> : ComputerBoundUserInterfaceBase where TWindow : BaseWindow, IComputerWindow<TState>, new() where TState : BoundUserInterfaceState
{ {
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
[ViewVariables] [ViewVariables]
private TWindow? _window; private TWindow? _window;

View File

@@ -1,6 +1,5 @@
using System.Linq; using System.Linq;
using Content.Client.Materials; using Content.Client.Materials;
using Content.Client.Materials.UI;
using Content.Client.Message; using Content.Client.Message;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Components; using Content.Shared.Construction.Components;

View File

@@ -23,6 +23,9 @@
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" /> <ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" /> <ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Spawners\" />
</ItemGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" /> <Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" /> <Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
</Project> </Project>

View File

@@ -2,7 +2,6 @@ using System.Numerics;
using System.Threading; using System.Threading;
using Content.Client.CombatMode; using Content.Client.CombatMode;
using Content.Client.Gameplay; using Content.Client.Gameplay;
using Content.Client.Mapping;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers; using Robust.Client.UserInterface.Controllers;
using Timer = Robust.Shared.Timing.Timer; using Timer = Robust.Shared.Timing.Timer;
@@ -17,7 +16,7 @@ namespace Content.Client.ContextMenu.UI
/// <remarks> /// <remarks>
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements. /// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
/// </remarks> /// </remarks>
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>, IOnStateEntered<MappingState>, IOnStateExited<MappingState> public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>
{ {
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2); public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
@@ -43,51 +42,18 @@ namespace Content.Client.ContextMenu.UI
public Action<ContextMenuElement>? OnSubMenuOpened; public Action<ContextMenuElement>? OnSubMenuOpened;
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent; public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
private bool _setup;
public void OnStateEntered(GameplayState state) public void OnStateEntered(GameplayState state)
{ {
Setup();
}
public void OnStateExited(GameplayState state)
{
Shutdown();
}
public void OnStateEntered(MappingState state)
{
Setup();
}
public void OnStateExited(MappingState state)
{
Shutdown();
}
public void Setup()
{
if (_setup)
return;
_setup = true;
RootMenu = new(this, null); RootMenu = new(this, null);
RootMenu.OnPopupHide += Close; RootMenu.OnPopupHide += Close;
Menus.Push(RootMenu); Menus.Push(RootMenu);
} }
public void Shutdown() public void OnStateExited(GameplayState state)
{ {
if (!_setup)
return;
_setup = false;
Close(); Close();
RootMenu.OnPopupHide -= Close; RootMenu.OnPopupHide -= Close;
RootMenu.Dispose(); RootMenu.Dispose();
RootMenu = default!;
} }
/// <summary> /// <summary>

View File

@@ -9,7 +9,6 @@ using Content.Shared.CCVar;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Verbs;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
@@ -195,20 +194,8 @@ namespace Content.Client.ContextMenu.UI
return; return;
// Do we need to do in-range unOccluded checks? // Do we need to do in-range unOccluded checks?
var visibility = _verbSystem.Visibility; var ignoreFov = !_eyeManager.CurrentEye.DrawFov ||
(_verbSystem.Visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov;
if (!_eyeManager.CurrentEye.DrawFov)
{
visibility &= ~MenuVisibility.NoFov;
}
var ev = new MenuVisibilityEvent()
{
Visibility = visibility,
};
_entityManager.EventBus.RaiseLocalEvent(player, ref ev);
visibility = ev.Visibility;
_entityManager.TryGetComponent(player, out ExaminerComponent? examiner); _entityManager.TryGetComponent(player, out ExaminerComponent? examiner);
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>(); var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
@@ -222,7 +209,7 @@ namespace Content.Client.ContextMenu.UI
continue; continue;
} }
if ((visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov) if (ignoreFov)
continue; continue;
var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID); var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID);

View File

@@ -145,7 +145,7 @@ namespace Content.Client.Credits
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}"); var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
if (markup) if (markup)
{ {
label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim())); label.SetMessage(FormattedMessage.FromMarkup(text.Trim()));
} }
else else
{ {

View File

@@ -1,4 +1,3 @@
using Content.Client.CrewManifest.UI;
using Content.Shared.CrewManifest; using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;

View File

@@ -51,7 +51,7 @@ public sealed class CrewManifestSection : BoxContainer
title.SetMessage(entry.JobTitle); title.SetMessage(entry.JobTitle);
if (prototypeManager.TryIndex<JobIconPrototype>(entry.JobIcon, out var jobIcon)) if (prototypeManager.TryIndex<StatusIconPrototype>(entry.JobIcon, out var jobIcon))
{ {
var icon = new TextureRect() var icon = new TextureRect()
{ {

View File

@@ -7,12 +7,10 @@ using Content.Shared.Security;
using Content.Shared.StationRecords; using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq;
namespace Content.Client.CriminalRecords; namespace Content.Client.CriminalRecords;
@@ -38,6 +36,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
public Action<SecurityStatus, string>? OnDialogConfirmed; public Action<SecurityStatus, string>? OnDialogConfirmed;
private uint _maxLength; private uint _maxLength;
private bool _isPopulating;
private bool _access; private bool _access;
private uint? _selectedKey; private uint? _selectedKey;
private CriminalRecord? _selectedRecord; private CriminalRecord? _selectedRecord;
@@ -75,7 +74,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
RecordListing.OnItemSelected += args => RecordListing.OnItemSelected += args =>
{ {
if (RecordListing[args.ItemIndex].Metadata is not uint cast) if (_isPopulating || RecordListing[args.ItemIndex].Metadata is not uint cast)
return; return;
OnKeySelected?.Invoke(cast); OnKeySelected?.Invoke(cast);
@@ -83,7 +82,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
RecordListing.OnItemDeselected += _ => RecordListing.OnItemDeselected += _ =>
{ {
OnKeySelected?.Invoke(null); if (!_isPopulating)
OnKeySelected?.Invoke(null);
}; };
FilterType.OnItemSelected += eventArgs => FilterType.OnItemSelected += eventArgs =>
@@ -133,8 +133,13 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
FilterType.SelectId((int)_currentFilterType); FilterType.SelectId((int)_currentFilterType);
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0; // set up the records listing panel
PopulateRecordListing(state.RecordListing); RecordListing.Clear();
var hasRecords = state.RecordListing != null && state.RecordListing.Count > 0;
NoRecords.Visible = !hasRecords;
if (hasRecords)
PopulateRecordListing(state.RecordListing!);
// set up the selected person's record // set up the selected person's record
var selected = _selectedKey != null; var selected = _selectedKey != null;
@@ -162,59 +167,19 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
} }
} }
private void PopulateRecordListing(Dictionary<uint, string>? listing) private void PopulateRecordListing(Dictionary<uint, string> listing)
{ {
if (listing == null) _isPopulating = true;
{
RecordListing.Clear();
return;
}
var entries = listing.ToList(); foreach (var (key, name) in listing)
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); var item = RecordListing.AddItem(name);
if (strcmp == 0) item.Metadata = key;
{ item.Selected = key == _selectedKey;
// 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--;
}
} }
_isPopulating = false;
// Any remaining items in RecordListing don't exist in `entries`, so remove them RecordListing.SortItemsByText();
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) private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
@@ -227,7 +192,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
StatusOptionButton.SelectId((int) criminalRecord.Status); StatusOptionButton.SelectId((int) criminalRecord.Status);
if (criminalRecord.Reason is {} reason) if (criminalRecord.Reason is {} reason)
{ {
var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason")); var message = FormattedMessage.FromMarkup(Loc.GetString("criminal-records-console-wanted-reason"));
message.AddText($": {reason}"); message.AddText($": {reason}");
WantedReason.SetMessage(message); WantedReason.SetMessage(message);
WantedReason.Visible = true; WantedReason.Visible = true;
@@ -246,7 +211,10 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private void FilterListingOfRecords(string text = "") private void FilterListingOfRecords(string text = "")
{ {
OnFiltersChanged?.Invoke(_currentFilterType, text); if (!_isPopulating)
{
OnFiltersChanged?.Invoke(_currentFilterType, text);
}
} }
private void SetStatus(SecurityStatus status) private void SetStatus(SecurityStatus status)

View File

@@ -4,7 +4,6 @@ using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Client.Decals.Overlays; namespace Content.Client.Decals.Overlays;
@@ -17,7 +16,7 @@ public sealed class DecalPlacementOverlay : Overlay
private readonly SharedTransformSystem _transform; private readonly SharedTransformSystem _transform;
private readonly SpriteSystem _sprite; private readonly SpriteSystem _sprite;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite) public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
{ {
@@ -25,7 +24,6 @@ public sealed class DecalPlacementOverlay : Overlay
_placement = placement; _placement = placement;
_transform = transform; _transform = transform;
_sprite = sprite; _sprite = sprite;
ZIndex = 1000;
} }
protected override void Draw(in OverlayDrawArgs args) protected override void Draw(in OverlayDrawArgs args)
@@ -57,7 +55,7 @@ public sealed class DecalPlacementOverlay : Overlay
if (snap) if (snap)
{ {
localPos = localPos.Floored() + grid.TileSizeHalfVector; localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector;
} }
// Nothing uses snap cardinals so probably don't need preview? // Nothing uses snap cardinals so probably don't need preview?

View File

@@ -1,7 +1,6 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface" xmlns:ui="clr-namespace:Content.Client.UserInterface"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
Title="{Loc door-electronics-configuration-title}">
<Control Name="AccessLevelControlContainer" /> <Control Name="AccessLevelControlContainer" />
</controls:FancyWindow> </controls:FancyWindow>

View File

@@ -1,80 +0,0 @@
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Drowsiness;
public sealed class DrowsinessOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
private readonly ShaderInstance _drowsinessShader;
public float CurrentPower = 0.0f;
private const float PowerDivisor = 250.0f;
private const float Intensity = 0.2f; // for adjusting the visual scale
private float _visualScale = 0; // between 0 and 1
public DrowsinessOverlay()
{
IoCManager.InjectDependencies(this);
_drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
}
protected override void FrameUpdate(FrameEventArgs args)
{
var playerEntity = _playerManager.LocalEntity;
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<DrowsinessComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status))
return;
var curTime = _timing.CurTime;
var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds;
CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
return false;
if (args.Viewport.Eye != eyeComp.Eye)
return false;
_visualScale = Math.Clamp(CurrentPower / PowerDivisor, 0.0f, 1.0f);
return _visualScale > 0;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)
return;
var handle = args.WorldHandle;
_drowsinessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_drowsinessShader.SetParameter("Strength", _visualScale * Intensity);
handle.UseShader(_drowsinessShader);
handle.DrawRect(args.WorldBounds, Color.White);
handle.UseShader(null);
}
}

View File

@@ -1,53 +0,0 @@
using Content.Shared.Drowsiness;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
namespace Content.Client.Drowsiness;
public sealed class DrowsinessSystem : SharedDrowsinessSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private DrowsinessOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DrowsinessComponent, ComponentInit>(OnDrowsinessInit);
SubscribeLocalEvent<DrowsinessComponent, ComponentShutdown>(OnDrowsinessShutdown);
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args)
{
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args)
{
if (_player.LocalEntity == uid)
_overlayMan.AddOverlay(_overlay);
}
private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args)
{
if (_player.LocalEntity == uid)
{
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}
}

View File

@@ -2,7 +2,7 @@ using Content.Shared.Ensnaring;
using Content.Shared.Ensnaring.Components; using Content.Shared.Ensnaring.Components;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
namespace Content.Client.Ensnaring; namespace Content.Client.Ensnaring.Visualizers;
public sealed class EnsnareableSystem : SharedEnsnareableSystem public sealed class EnsnareableSystem : SharedEnsnareableSystem
{ {
@@ -12,14 +12,13 @@ public sealed class EnsnareableSystem : SharedEnsnareableSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<EnsnareableComponent, AppearanceChangeEvent>(OnAppearanceChange); SubscribeLocalEvent<EnsnareableComponent, AppearanceChangeEvent>(OnAppearanceChange);
} }
protected override void OnEnsnareInit(Entity<EnsnareableComponent> ent, ref ComponentInit args) private void OnComponentInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
{ {
base.OnEnsnareInit(ent, ref args); if(!TryComp<SpriteComponent>(uid, out var sprite))
if(!TryComp<SpriteComponent>(ent.Owner, out var sprite))
return; return;
// TODO remove this, this should just be in yaml. // TODO remove this, this should just be in yaml.

View File

@@ -4,7 +4,6 @@ using Content.Client.Chat.Managers;
using Content.Client.DebugMon; using Content.Client.DebugMon;
using Content.Client.Eui; using Content.Client.Eui;
using Content.Client.Fullscreen; using Content.Client.Fullscreen;
using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick; using Content.Client.GhostKick;
using Content.Client.Guidebook; using Content.Client.Guidebook;
using Content.Client.Input; using Content.Client.Input;
@@ -71,8 +70,8 @@ namespace Content.Client.Entry
[Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IReplayLoadManager _replayLoad = default!; [Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!; [Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
public override void Init() public override void Init()
{ {
@@ -110,7 +109,6 @@ namespace Content.Client.Entry
_prototypeManager.RegisterIgnore("lobbyBackground"); _prototypeManager.RegisterIgnore("lobbyBackground");
_prototypeManager.RegisterIgnore("gamePreset"); _prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel"); _prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("playerConnectionWhitelist");
_prototypeManager.RegisterIgnore("spaceBiome"); _prototypeManager.RegisterIgnore("spaceBiome");
_prototypeManager.RegisterIgnore("worldgenConfig"); _prototypeManager.RegisterIgnore("worldgenConfig");
_prototypeManager.RegisterIgnore("gameRule"); _prototypeManager.RegisterIgnore("gameRule");
@@ -142,12 +140,6 @@ namespace Content.Client.Entry
_configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f); _configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f);
} }
public override void Shutdown()
{
base.Shutdown();
_titleWindowManager.Shutdown();
}
public override void PostInit() public override void PostInit()
{ {
base.PostInit(); base.PostInit();
@@ -168,7 +160,6 @@ namespace Content.Client.Entry
_userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme)); _userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme));
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme)); _userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize(); _documentParsingManager.Initialize();
_titleWindowManager.Initialize();
_baseClient.RunLevelChanged += (_, args) => _baseClient.RunLevelChanged += (_, args) =>
{ {
@@ -200,7 +191,7 @@ namespace Content.Client.Entry
_resourceManager, _resourceManager,
ReplayConstants.ReplayZipFolder.ToRootedPath()); ReplayConstants.ReplayZipFolder.ToRootedPath());
_playbackMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath()); _replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
_replayLoad.LoadAndStartReplay(reader); _replayLoad.LoadAndStartReplay(reader);
} }
else if (_gameController.LaunchState.FromLauncher) else if (_gameController.LaunchState.FromLauncher)

View File

@@ -1,12 +1,7 @@
using System.Linq;
using System.Numerics;
using System.Threading;
using Content.Client.Verbs; using Content.Client.Verbs;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
@@ -17,8 +12,13 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
using System.Threading;
using static Content.Shared.Interaction.SharedInteractionSystem; using static Content.Shared.Interaction.SharedInteractionSystem;
using static Robust.Client.UserInterface.Controls.BoxContainer; using static Robust.Client.UserInterface.Controls.BoxContainer;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Direction = Robust.Shared.Maths.Direction; using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Examine namespace Content.Client.Examine
@@ -35,6 +35,7 @@ namespace Content.Client.Examine
private EntityUid _examinedEntity; private EntityUid _examinedEntity;
private EntityUid _lastExaminedEntity; private EntityUid _lastExaminedEntity;
private EntityUid _playerEntity;
private Popup? _examineTooltipOpen; private Popup? _examineTooltipOpen;
private ScreenCoordinates _popupPos; private ScreenCoordinates _popupPos;
private CancellationTokenSource? _requestCancelTokenSource; private CancellationTokenSource? _requestCancelTokenSource;
@@ -73,9 +74,9 @@ namespace Content.Client.Examine
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
if (_examineTooltipOpen is not {Visible: true}) return; if (_examineTooltipOpen is not {Visible: true}) return;
if (!_examinedEntity.Valid || _playerManager.LocalEntity is not { } player) return; if (!_examinedEntity.Valid || !_playerEntity.Valid) return;
if (!CanExamine(player, _examinedEntity)) if (!CanExamine(_playerEntity, _examinedEntity))
CloseTooltip(); CloseTooltip();
} }
@@ -113,8 +114,9 @@ namespace Content.Client.Examine
return false; return false;
} }
if (_playerManager.LocalEntity is not { } player || _playerEntity = _playerManager.LocalEntity ?? default;
!CanExamine(player, entity))
if (_playerEntity == default || !CanExamine(_playerEntity, entity))
{ {
return false; return false;
} }

View File

@@ -16,19 +16,15 @@ public sealed class ExplosionOverlay : Overlay
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPrototypeManager _proto = default!;
private readonly SharedTransformSystem _transformSystem;
private SharedAppearanceSystem _appearance;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private ShaderInstance _shader; private ShaderInstance _shader;
public ExplosionOverlay(SharedAppearanceSystem appearanceSystem) public ExplosionOverlay()
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_shader = _proto.Index<ShaderPrototype>("unshaded").Instance(); _shader = _proto.Index<ShaderPrototype>("unshaded").Instance();
_transformSystem = _entMan.System<SharedTransformSystem>();
_appearance = appearanceSystem;
} }
protected override void Draw(in OverlayDrawArgs args) protected override void Draw(in OverlayDrawArgs args)
@@ -37,14 +33,15 @@ public sealed class ExplosionOverlay : Overlay
drawHandle.UseShader(_shader); drawHandle.UseShader(_shader);
var xforms = _entMan.GetEntityQuery<TransformComponent>(); var xforms = _entMan.GetEntityQuery<TransformComponent>();
var query = _entMan.EntityQueryEnumerator<ExplosionVisualsComponent, ExplosionVisualsTexturesComponent>(); var query = _entMan
.EntityQuery<ExplosionVisualsComponent, ExplosionVisualsTexturesComponent, AppearanceComponent>(true);
while (query.MoveNext(out var uid, out var visuals, out var textures)) foreach (var (visuals, textures, appearance) in query)
{ {
if (visuals.Epicenter.MapId != args.MapId) if (visuals.Epicenter.MapId != args.MapId)
continue; continue;
if (!_appearance.TryGetData(uid, ExplosionAppearanceData.Progress, out int index)) if (!appearance.TryGetData(ExplosionAppearanceData.Progress, out int index))
continue; continue;
index = Math.Min(index, visuals.Intensity.Count - 1); index = Math.Min(index, visuals.Intensity.Count - 1);
@@ -70,7 +67,7 @@ public sealed class ExplosionOverlay : Overlay
continue; continue;
var xform = xforms.GetComponent(gridId); var xform = xforms.GetComponent(gridId);
var (_, _, worldMatrix, invWorldMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(xform, xforms); var (_, _, worldMatrix, invWorldMatrix) = xform.GetWorldPositionRotationMatrixWithInv(xforms);
gridBounds = invWorldMatrix.TransformBox(worldBounds).Enlarged(grid.TileSize * 2); gridBounds = invWorldMatrix.TransformBox(worldBounds).Enlarged(grid.TileSize * 2);
drawHandle.SetTransform(worldMatrix); drawHandle.SetTransform(worldMatrix);

View File

@@ -20,7 +20,6 @@ public sealed class ExplosionOverlaySystem : EntitySystem
[Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedPointLightSystem _lights = default!; [Dependency] private readonly SharedPointLightSystem _lights = default!;
[Dependency] private readonly IMapManager _mapMan = default!; [Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -29,7 +28,7 @@ public sealed class ExplosionOverlaySystem : EntitySystem
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentInit>(OnExplosionInit); SubscribeLocalEvent<ExplosionVisualsComponent, ComponentInit>(OnExplosionInit);
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentRemove>(OnCompRemove); SubscribeLocalEvent<ExplosionVisualsComponent, ComponentRemove>(OnCompRemove);
SubscribeLocalEvent<ExplosionVisualsComponent, ComponentHandleState>(OnExplosionHandleState); SubscribeLocalEvent<ExplosionVisualsComponent, ComponentHandleState>(OnExplosionHandleState);
_overlayMan.AddOverlay(new ExplosionOverlay(_appearance)); _overlayMan.AddOverlay(new ExplosionOverlay());
} }
private void OnExplosionHandleState(EntityUid uid, ExplosionVisualsComponent component, ref ComponentHandleState args) private void OnExplosionHandleState(EntityUid uid, ExplosionVisualsComponent component, ref ComponentHandleState args)

View File

@@ -1,5 +0,0 @@
using Content.Shared.Explosion.EntitySystems;
namespace Content.Client.Explosion.EntitySystems;
public sealed class ExplosionSystem : SharedExplosionSystem;

View File

@@ -16,7 +16,6 @@ namespace Content.Client.Flash
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
private readonly SharedFlashSystem _flash;
private readonly StatusEffectsSystem _statusSys; private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
@@ -28,7 +27,6 @@ namespace Content.Client.Flash
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").InstanceUnique(); _shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").InstanceUnique();
_flash = _entityManager.System<SharedFlashSystem>();
_statusSys = _entityManager.System<StatusEffectsSystem>(); _statusSys = _entityManager.System<StatusEffectsSystem>();
} }
@@ -43,7 +41,7 @@ namespace Content.Client.Flash
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status)) || !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return; return;
if (!_statusSys.TryGetTime(playerEntity.Value, _flash.FlashedKey, out var time, status)) if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status))
return; return;
var curTime = _timing.CurTime; var curTime = _timing.CurTime;

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