Compare commits
1 Commits
demiplanes
...
TheShuEd-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3f222e7d |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -19,7 +19,7 @@
|
||||
/Resources/engineCommandPerms.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/Entities/Mobs/Player/ @DrSmugleaf
|
||||
|
||||
14
.github/FUNDING.yml
vendored
14
.github/FUNDING.yml
vendored
@@ -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']
|
||||
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@@ -16,7 +16,7 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.swsl'
|
||||
|
||||
"Changes: No C#":
|
||||
"No C#":
|
||||
- changed-files:
|
||||
# Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.
|
||||
- all-globs-to-all-files: "!**/*.cs"
|
||||
|
||||
2
.github/rsi-schema.json
vendored
2
.github/rsi-schema.json
vendored
@@ -85,7 +85,7 @@
|
||||
"CC-BY-NC-SA-3.0",
|
||||
"CC-BY-NC-SA-4.0",
|
||||
"CC0-1.0",
|
||||
"CLA"
|
||||
"All rights reserved for the CrystallPunk14 project only"
|
||||
],
|
||||
"examples":[
|
||||
"CC-BY-SA-3.0"
|
||||
|
||||
20
.github/workflows/CP14Publish.yml
vendored
20
.github/workflows/CP14Publish.yml
vendored
@@ -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
|
||||
21
.github/workflows/conflict-labeler.yml
vendored
Normal file
21
.github/workflows/conflict-labeler.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Check Merge Conflicts
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
Label:
|
||||
if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' )
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for Merge Conflicts
|
||||
uses: eps1lon/actions-label-merge-conflict@v3.0.0
|
||||
with:
|
||||
dirtyLabel: "Merge Conflict"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
|
||||
21
.github/workflows/labeler-conflict.yml
vendored
21
.github/workflows/labeler-conflict.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Check Merge Conflicts
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
Label:
|
||||
if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' )
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for Merge Conflicts
|
||||
uses: eps1lon/actions-label-merge-conflict@v3.0.0
|
||||
with:
|
||||
dirtyLabel: "S: Merge Conflict"
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
|
||||
4
.github/workflows/labeler-needsreview.yml
vendored
4
.github/workflows/labeler-needsreview.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: "S: Needs Review"
|
||||
labels: "Status: Needs Review"
|
||||
- uses: actions-ecosystem/action-remove-labels@v1
|
||||
with:
|
||||
labels: "S: Awaiting Changes"
|
||||
labels: "Status: Awaiting Changes"
|
||||
|
||||
23
.github/workflows/labeler-review.yml
vendored
23
.github/workflows/labeler-review.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: "Labels: Approved"
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
jobs:
|
||||
add_label:
|
||||
# Change the repository name after you've made sure the team name is correct for your fork!
|
||||
if: ${{ (github.repository == 'space-wizards/space-station-14') && (github.event.review.state == 'APPROVED') }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tspascoal/get-user-teams-membership@v3
|
||||
id: checkUserMember
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
team: "content-maintainers,junior-maintainers"
|
||||
GITHUB_TOKEN: ${{ secrets.LABELER_PAT }}
|
||||
- if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }}
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: "S: Approved"
|
||||
20
.github/workflows/labeler-size.yml
vendored
20
.github/workflows/labeler-size.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: "Labels: Size"
|
||||
on: pull_request_target
|
||||
jobs:
|
||||
size-label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: size-label
|
||||
uses: "pascalgn/size-label-action@v0.5.5"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
with:
|
||||
# Custom size configuration
|
||||
sizes: >
|
||||
{
|
||||
"0": "XS",
|
||||
"10": "S",
|
||||
"30": "M",
|
||||
"100": "L",
|
||||
"1000": "XL"
|
||||
}
|
||||
16
.github/workflows/labeler-stable.yml
vendored
16
.github/workflows/labeler-stable.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: "Labels: Branch stable"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
branches:
|
||||
- 'stable'
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: "Branch: Stable"
|
||||
16
.github/workflows/labeler-staging.yml
vendored
16
.github/workflows/labeler-staging.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: "Labels: Branch staging"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
branches:
|
||||
- 'staging'
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: "Branch: Staging"
|
||||
4
.github/workflows/labeler-untriaged.yml
vendored
4
.github/workflows/labeler-untriaged.yml
vendored
@@ -3,8 +3,6 @@
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
@@ -13,4 +11,4 @@ jobs:
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
if: join(github.event.issue.labels) == ''
|
||||
with:
|
||||
labels: "S: Untriaged"
|
||||
labels: "Status: Untriaged"
|
||||
|
||||
22
.github/workflows/publish.yml
vendored
22
.github/workflows/publish.yml
vendored
@@ -5,8 +5,8 @@ concurrency:
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '0 10 * * *'
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -41,10 +41,21 @@ jobs:
|
||||
- name: Package client
|
||||
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
|
||||
run: Tools/publish_multi_request.py
|
||||
run: Tools/publish_github_artifact.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }}
|
||||
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
|
||||
|
||||
- name: Publish changelog (Discord)
|
||||
@@ -57,3 +68,8 @@ jobs:
|
||||
run: Tools/actions_changelog_rss.py
|
||||
env:
|
||||
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
|
||||
|
||||
- uses: geekyeggo/delete-artifact@v5
|
||||
if: always()
|
||||
with:
|
||||
name: build
|
||||
|
||||
2
.github/workflows/update-credits.yml
vendored
2
.github/workflows/update-credits.yml
vendored
@@ -19,8 +19,6 @@ jobs:
|
||||
|
||||
- name: Get this week's Contributors
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt
|
||||
|
||||
# TODO
|
||||
|
||||
6
.vscode/tasks.json
vendored
6
.vscode/tasks.json
vendored
@@ -10,7 +10,7 @@
|
||||
"args": [
|
||||
"build",
|
||||
"/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": {
|
||||
"kind": "build",
|
||||
@@ -29,9 +29,9 @@
|
||||
"build",
|
||||
"${workspaceFolder}/Content.YAMLLinter/Content.YAMLLinter.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog" };
|
||||
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry", "Oasis" };
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Content.Client.Access.UI
|
||||
SendMessage(new AgentIDCardJobChangedMessage(newJob));
|
||||
}
|
||||
|
||||
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId)
|
||||
public void OnJobIconChanged(ProtoId<StatusIconPrototype> newJobIconId)
|
||||
{
|
||||
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
_window.SetCurrentName(cast.CurrentName);
|
||||
_window.SetCurrentJob(cast.CurrentJob);
|
||||
_window.SetAllowedIcons(cast.CurrentJobIconId);
|
||||
_window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
<LineEdit Name="NameLineEdit" />
|
||||
<Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" />
|
||||
<LineEdit Name="JobLineEdit" />
|
||||
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
|
||||
<GridContainer Name="IconGrid" Columns="10">
|
||||
<!-- Job icon buttons are generated in the code -->
|
||||
</GridContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
|
||||
<Control HorizontalExpand="True" MinSize="50 0"/>
|
||||
<GridContainer Name="IconGrid" Columns="10">
|
||||
<!-- Job icon buttons are generated in the code -->
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Access.UI
|
||||
{
|
||||
@@ -24,7 +23,7 @@ namespace Content.Client.Access.UI
|
||||
public event Action<string>? OnNameChanged;
|
||||
public event Action<string>? OnJobChanged;
|
||||
|
||||
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged;
|
||||
public event Action<ProtoId<StatusIconPrototype>>? OnJobIconChanged;
|
||||
|
||||
public AgentIDCardWindow()
|
||||
{
|
||||
@@ -39,16 +38,17 @@ namespace Content.Client.Access.UI
|
||||
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
|
||||
}
|
||||
|
||||
public void SetAllowedIcons(string currentJobIconId)
|
||||
public void SetAllowedIcons(HashSet<ProtoId<StatusIconPrototype>> icons, string currentJobIconId)
|
||||
{
|
||||
IconGrid.DisposeAllChildren();
|
||||
|
||||
var jobIconButtonGroup = new ButtonGroup();
|
||||
var jobIconGroup = new ButtonGroup();
|
||||
var i = 0;
|
||||
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList();
|
||||
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
|
||||
foreach (var jobIcon in icons)
|
||||
foreach (var jobIconId in icons)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
|
||||
continue;
|
||||
|
||||
String styleBase = StyleBase.ButtonOpenBoth;
|
||||
var modulo = i % JobIconColumnCount;
|
||||
if (modulo == 0)
|
||||
@@ -62,9 +62,8 @@ namespace Content.Client.Access.UI
|
||||
Access = AccessLevel.Public,
|
||||
StyleClasses = { styleBase },
|
||||
MaxSize = new Vector2(42, 28),
|
||||
Group = jobIconButtonGroup,
|
||||
Pressed = currentJobIconId == jobIcon.ID,
|
||||
ToolTip = jobIcon.LocalizedJobName
|
||||
Group = jobIconGroup,
|
||||
Pressed = i == 0,
|
||||
};
|
||||
|
||||
// Generate buttons textures
|
||||
@@ -79,6 +78,9 @@ namespace Content.Client.Access.UI
|
||||
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
|
||||
IconGrid.AddChild(jobIconButton);
|
||||
|
||||
if (jobIconId.Equals(currentJobIconId))
|
||||
jobIconButton.Pressed = true;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,30 +48,6 @@ namespace Content.Client.Actions
|
||||
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
|
||||
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)
|
||||
@@ -100,26 +76,12 @@ namespace Content.Client.Actions
|
||||
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
|
||||
{
|
||||
// TODO ACTIONS use auto comp states
|
||||
component.Icon = state.Icon;
|
||||
component.IconOn = state.IconOn;
|
||||
component.IconColor = state.IconColor;
|
||||
component.OriginalIconColor = state.OriginalIconColor;
|
||||
component.DisabledIconColor = state.DisabledIconColor;
|
||||
component.Keywords.Clear();
|
||||
component.Keywords.UnionWith(state.Keywords);
|
||||
component.Enabled = state.Enabled;
|
||||
@@ -150,8 +112,6 @@ namespace Content.Client.Actions
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return;
|
||||
|
||||
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
|
||||
base.UpdateAction(actionId, action);
|
||||
if (_playerManager.LocalEntity != action.AttachedEntity)
|
||||
return;
|
||||
@@ -258,13 +218,13 @@ namespace Content.Client.Actions
|
||||
|
||||
public void LinkAllActions(ActionsComponent? actions = null)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } user ||
|
||||
!Resolve(user, ref actions, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_playerManager.LocalEntity is not { } user ||
|
||||
!Resolve(user, ref actions, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LinkActions?.Invoke(actions);
|
||||
LinkActions?.Invoke(actions);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -286,6 +246,12 @@ namespace Content.Client.Actions
|
||||
|
||||
if (action.ClientExclusive)
|
||||
{
|
||||
if (instantAction.Event != null)
|
||||
{
|
||||
instantAction.Event.Performer = user;
|
||||
instantAction.Event.Action = actionId;
|
||||
}
|
||||
|
||||
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
|
||||
}
|
||||
else
|
||||
@@ -327,7 +293,7 @@ namespace Content.Client.Actions
|
||||
continue;
|
||||
|
||||
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||
var actionId = Spawn();
|
||||
var actionId = Spawn(null);
|
||||
AddComp(actionId, action);
|
||||
AddActionDirect(user, actionId);
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Content.Client.Actions.UI
|
||||
{
|
||||
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;
|
||||
|
||||
_cooldownLabel.SetMessage(markup);
|
||||
|
||||
@@ -2,75 +2,72 @@ using System.Numerics;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
namespace Content.Client.Administration
|
||||
{
|
||||
private readonly AdminSystem _system;
|
||||
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)
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly Font _font;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
|
||||
{
|
||||
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
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
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
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
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
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var aabb = _entityLookup.GetWorldAABB(entity.Value);
|
||||
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 11f) * uiScale;
|
||||
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", uiScale, Color.OrangeRed);
|
||||
;
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lineoffset = new Vector2(0f, 11f);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.Systems
|
||||
{
|
||||
@@ -13,7 +11,6 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -22,7 +19,7 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
private void InitializeOverlay()
|
||||
{
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -13,7 +13,7 @@ public sealed partial class AdminMessagePopupMessage : Control
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Admin.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString(
|
||||
Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
|
||||
"admin-notes-message-admin",
|
||||
("admin", message.AdminName),
|
||||
("date", message.AddedOn.ToLocalTime()))));
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed partial class AdminMessagePopupWindow : Control
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ namespace Content.Client.Administration.UI.BanPanel;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
|
||||
public event Action<string?, (IPAddress, int)?, bool, byte[]?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
|
||||
public event Action<string>? PlayerChanged;
|
||||
private string? PlayerUsername { get; set; }
|
||||
private (IPAddress, int)? IpAddress { get; set; }
|
||||
private ImmutableTypedHwid? Hwid { get; set; }
|
||||
private byte[]? Hwid { get; set; }
|
||||
private double TimeEntered { get; set; }
|
||||
private uint Multiplier { get; set; }
|
||||
private bool HasBanFlag { get; set; }
|
||||
@@ -371,8 +371,9 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
private void OnHwidChanged()
|
||||
{
|
||||
var hwidString = HwidLine.Text;
|
||||
ImmutableTypedHwid? hwid = null;
|
||||
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !ImmutableTypedHwid.TryParse(hwidString, out hwid))
|
||||
var length = 3 * (hwidString.Length / 4) - hwidString.TakeLast(2).Count(c => c == '=');
|
||||
Hwid = new byte[length];
|
||||
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !Convert.TryFromBase64String(hwidString, Hwid, out _))
|
||||
{
|
||||
ErrorLevel |= ErrorLevelEnum.Hwid;
|
||||
HwidLine.ModulateSelfOverride = Color.Red;
|
||||
@@ -389,7 +390,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
Hwid = null;
|
||||
return;
|
||||
}
|
||||
Hwid = hwid;
|
||||
Hwid = Convert.FromHexString(hwidString);
|
||||
}
|
||||
|
||||
private void OnTypeChanged()
|
||||
|
||||
@@ -11,8 +11,9 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.UI.Bwoink
|
||||
{
|
||||
@@ -87,51 +88,26 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
var ach = AHelpHelper.EnsurePanel(a.SessionId);
|
||||
var bch = AHelpHelper.EnsurePanel(b.SessionId);
|
||||
|
||||
// Pinned players first
|
||||
if (a.IsPinned != b.IsPinned)
|
||||
return a.IsPinned ? -1 : 1;
|
||||
|
||||
// First, sort by unread. Any chat with unread messages appears first.
|
||||
// First, sort by unread. Any chat with unread messages appears first. We just sort based on unread
|
||||
// status, not number of unread messages, so that more recent unread messages take priority.
|
||||
var aUnread = ach.Unread > 0;
|
||||
var bUnread = bch.Unread > 0;
|
||||
if (aUnread != bUnread)
|
||||
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.
|
||||
if (a.Connected != b.Connected)
|
||||
return a.Connected ? -1 : 1;
|
||||
|
||||
// Sort connected players by New Player status, then by Antag status
|
||||
if (a.Connected && b.Connected)
|
||||
{
|
||||
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
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;
|
||||
}
|
||||
// Next, group by whether or not the players have participated in this round.
|
||||
// The ahelp window shows all players that have connected since server restart, this groups them all towards the bottom.
|
||||
if (a.ActiveThisRound != b.ActiveThisRound)
|
||||
return a.ActiveThisRound ? -1 : 1;
|
||||
|
||||
// Finally, sort by the most recent message.
|
||||
return bch.LastMessage.CompareTo(ach.LastMessage);
|
||||
};
|
||||
|
||||
|
||||
Bans.OnPressed += _ =>
|
||||
{
|
||||
if (_currentPlayer is not null)
|
||||
@@ -277,20 +253,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
public void PopulateList()
|
||||
{
|
||||
// Maintain existing pin statuses
|
||||
var pinnedPlayers = ChannelSelector.PlayerInfo.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
|
||||
|
||||
ChannelSelector.PopulateList();
|
||||
|
||||
// Restore pin statuses
|
||||
foreach (var player in ChannelSelector.PlayerInfo)
|
||||
{
|
||||
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
|
||||
{
|
||||
player.IsPinned = pinnedPlayer.IsPinned;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
Unread++;
|
||||
|
||||
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);
|
||||
LastMessage = message.SentAt;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
}
|
||||
};
|
||||
|
||||
OnOpen += () =>
|
||||
{
|
||||
Bwoink.ChannelSelector.StopFiltering();
|
||||
Bwoink.PopulateList();
|
||||
};
|
||||
OnOpen += () => Bwoink.PopulateList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,166 +4,154 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Verbs.UI;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.CustomControls;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerListControl : BoxContainer
|
||||
namespace Content.Client.Administration.UI.CustomControls
|
||||
{
|
||||
private readonly AdminSystem _adminSystem;
|
||||
|
||||
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()
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerListControl : BoxContainer
|
||||
{
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_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) };
|
||||
}
|
||||
private readonly AdminSystem _adminSystem;
|
||||
|
||||
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()
|
||||
{
|
||||
_selectedPlayer = null;
|
||||
OnSelectionChanged?.Invoke(null);
|
||||
}
|
||||
public Func<PlayerInfo, string, string>? OverrideText;
|
||||
public Comparison<PlayerInfo>? Comparison;
|
||||
|
||||
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
|
||||
{
|
||||
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
|
||||
return;
|
||||
private IEntityManager _entManager;
|
||||
private IUserInterfaceManager _uiManager;
|
||||
|
||||
if (selectedPlayer == _selectedPlayer)
|
||||
return;
|
||||
private PlayerInfo? _selectedPlayer;
|
||||
|
||||
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)
|
||||
public PlayerListControl()
|
||||
{
|
||||
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);
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_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)};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
private void PlayerListNoItemSelected()
|
||||
{
|
||||
if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
|
||||
{
|
||||
player.IsPinned = pinnedPlayer.IsPinned;
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer))
|
||||
_selectedPlayer = null;
|
||||
OnSelectionChanged?.Invoke(null);
|
||||
}
|
||||
|
||||
FilterList();
|
||||
}
|
||||
|
||||
|
||||
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 += _ =>
|
||||
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
|
||||
{
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
button.AddChild(entry);
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
|
||||
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
|
||||
Visible="False" HorizontalExpand="True" />
|
||||
<OptionButton Name="ExpiryLengthDropdown" Visible="False" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<OptionButton Name="TypeOption" HorizontalAlignment="Center" />
|
||||
|
||||
@@ -17,17 +17,6 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
private enum Multipliers
|
||||
{
|
||||
Minutes,
|
||||
Hours,
|
||||
Days,
|
||||
Weeks,
|
||||
Months,
|
||||
Years,
|
||||
Centuries
|
||||
}
|
||||
|
||||
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed;
|
||||
|
||||
public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit)
|
||||
@@ -42,20 +31,6 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
|
||||
ResetSubmitButton();
|
||||
|
||||
// It's weird to use minutes as the IDs, but it works and makes sense kind of :)
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-minutes"), (int) Multipliers.Minutes);
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-hours"), (int) Multipliers.Hours);
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-days"), (int) Multipliers.Days);
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-weeks"), (int) Multipliers.Weeks);
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-months"), (int) Multipliers.Months);
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-years"), (int) Multipliers.Years);
|
||||
ExpiryLengthDropdown.AddItem(Loc.GetString("admin-note-button-centuries"), (int) Multipliers.Centuries);
|
||||
ExpiryLengthDropdown.OnItemSelected += OnLengthChanged;
|
||||
|
||||
ExpiryLengthDropdown.SelectId((int) Multipliers.Weeks);
|
||||
|
||||
ExpiryLineEdit.OnTextChanged += OnTextChanged;
|
||||
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note);
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message);
|
||||
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist);
|
||||
@@ -159,7 +134,6 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
SecretCheckBox.Pressed = false;
|
||||
SeverityOption.Disabled = false;
|
||||
PermanentCheckBox.Pressed = true;
|
||||
SubmitButton.Disabled = true;
|
||||
UpdatePermanentCheckboxFields();
|
||||
break;
|
||||
case (int) NoteType.Message: // Message: these are shown to the player when they log on
|
||||
@@ -198,9 +172,8 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
{
|
||||
ExpiryLabel.Visible = !PermanentCheckBox.Pressed;
|
||||
ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed;
|
||||
ExpiryLengthDropdown.Visible = !PermanentCheckBox.Pressed;
|
||||
|
||||
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? 1.ToString() : string.Empty;
|
||||
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty;
|
||||
}
|
||||
|
||||
private void OnSecretPressed(BaseButton.ButtonEventArgs _)
|
||||
@@ -214,16 +187,6 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
SeverityOption.SelectId(args.Id);
|
||||
}
|
||||
|
||||
private void OnLengthChanged(OptionButton.ItemSelectedEventArgs args)
|
||||
{
|
||||
ExpiryLengthDropdown.SelectId(args.Id);
|
||||
}
|
||||
|
||||
private void OnTextChanged(HistoryLineEdit.LineEditEventArgs args)
|
||||
{
|
||||
ParseExpiryTime();
|
||||
}
|
||||
|
||||
private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
if (!ParseExpiryTime())
|
||||
@@ -300,24 +263,13 @@ public sealed partial class NoteEdit : FancyWindow
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !uint.TryParse(ExpiryLineEdit.Text, out var inputInt))
|
||||
if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !DateTime.TryParse(ExpiryLineEdit.Text, out var result) || DateTime.UtcNow > result)
|
||||
{
|
||||
ExpiryLineEdit.ModulateSelfOverride = Color.Red;
|
||||
return false;
|
||||
}
|
||||
|
||||
var mult = ExpiryLengthDropdown.SelectedId switch
|
||||
{
|
||||
(int) Multipliers.Minutes => TimeSpan.FromMinutes(1).TotalMinutes,
|
||||
(int) Multipliers.Hours => TimeSpan.FromHours(1).TotalMinutes,
|
||||
(int) Multipliers.Days => TimeSpan.FromDays(1).TotalMinutes,
|
||||
(int) Multipliers.Weeks => TimeSpan.FromDays(7).TotalMinutes,
|
||||
(int) Multipliers.Months => TimeSpan.FromDays(30).TotalMinutes,
|
||||
(int) Multipliers.Years => TimeSpan.FromDays(365).TotalMinutes,
|
||||
(int) Multipliers.Centuries => TimeSpan.FromDays(36525).TotalMinutes,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ExpiryLengthDropdown.SelectedId), "Multiplier out of range :(")
|
||||
};
|
||||
ExpiryTime = DateTime.UtcNow.AddMinutes(inputInt * mult);
|
||||
ExpiryTime = result.ToUniversalTime();
|
||||
ExpiryLineEdit.ModulateSelfOverride = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration.UI.SetOutfit
|
||||
@@ -64,18 +64,9 @@ namespace Content.Client.Administration.UI.SetOutfit
|
||||
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()
|
||||
{
|
||||
foreach (var gear in GetPrototypes())
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
{
|
||||
OutfitList.Add(GetItem(gear, OutfitList));
|
||||
}
|
||||
@@ -84,7 +75,7 @@ namespace Content.Client.Administration.UI.SetOutfit
|
||||
private void PopulateByFilter(string filter)
|
||||
{
|
||||
OutfitList.Clear();
|
||||
foreach (var gear in GetPrototypes())
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filter) &&
|
||||
gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Station;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTab : Control
|
||||
{
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
@@ -51,20 +49,10 @@ public sealed partial class ObjectsTab : Control
|
||||
RefreshListButton.OnPressed += _ => RefreshObjectList();
|
||||
|
||||
var defaultSelection = ObjectsTabSelection.Grids;
|
||||
ObjectTypeOptions.SelectId((int)defaultSelection);
|
||||
ObjectTypeOptions.SelectId((int) defaultSelection);
|
||||
RefreshObjectList(defaultSelection);
|
||||
}
|
||||
|
||||
private void TeleportTo(NetEntity nent)
|
||||
{
|
||||
_console.ExecuteCommand($"tpto {nent}");
|
||||
}
|
||||
|
||||
private void Delete(NetEntity nent)
|
||||
{
|
||||
_console.ExecuteCommand($"delete {nent}");
|
||||
}
|
||||
|
||||
public void RefreshObjectList()
|
||||
{
|
||||
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 })
|
||||
return;
|
||||
|
||||
var entry = new ObjectsTabEntry(_admin, info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
|
||||
entry.OnTeleport += TeleportTo;
|
||||
entry.OnDelete += Delete;
|
||||
var entry = new ObjectsTabEntry(info.Name,
|
||||
info.Entity,
|
||||
new StyleBoxFlat { BackgroundColor = backgroundColor });
|
||||
button.ToolTip = $"{info.Name}, {info.Entity}";
|
||||
|
||||
button.AddChild(entry);
|
||||
|
||||
@@ -5,25 +5,13 @@
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<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"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Button Name="DeleteButton"
|
||||
Text="{Loc object-tab-entity-delete}"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<Label Name="EIDLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -11,30 +10,12 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity AssocEntity;
|
||||
|
||||
public Action<NetEntity>? OnTeleport;
|
||||
public Action<NetEntity>? OnDelete;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
|
||||
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
AssocEntity = nent;
|
||||
EIDLabel.Text = nent.ToString();
|
||||
NameLabel.Text = name;
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,17 @@
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<Label Name="ObjectNameLabel"
|
||||
SizeFlagsStretchRatio="5"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-object-name}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="EntityIDLabel"
|
||||
SizeFlagsStretchRatio="5"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-entity-id}"
|
||||
MouseFilter="Pass"/>
|
||||
<Label Name="EntityTeleportLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"/>
|
||||
<Label Name="EntityDeleteLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -10,219 +10,220 @@ using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTab : Control
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
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()
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTab : Control
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
_adminSystem = _entManager.System<AdminSystem>();
|
||||
_adminSystem.PlayerListChanged += RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
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>();
|
||||
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
private Header _headerClicked = Header.Username;
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
|
||||
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
SearchList.SearchBar = SearchLineEdit;
|
||||
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)
|
||||
public PlayerTab()
|
||||
{
|
||||
_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 ShowDisconnectedPressed(ButtonEventArgs args)
|
||||
{
|
||||
_showDisconnected = args.Button.Pressed;
|
||||
RefreshPlayerList(_players);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
private void OverlayDisabled()
|
||||
{
|
||||
_adminSystem.PlayerListChanged -= RefreshPlayerList;
|
||||
_adminSystem.OverlayEnabled -= OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled -= OverlayDisabled;
|
||||
|
||||
OverlayButton.OnPressed -= OverlayButtonPressed;
|
||||
|
||||
ListHeader.OnHeaderClicked -= HeaderClicked;
|
||||
OverlayButton.Pressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
if (!_showDisconnected && !info.Connected)
|
||||
return false;
|
||||
|
||||
if (IsAllLower(filter))
|
||||
private void OverlayButtonPressed(ButtonEventArgs args)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!playerString.Contains(filter))
|
||||
|
||||
if (!_showDisconnected && !info.Connected)
|
||||
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)
|
||||
{
|
||||
foreach (var c in input)
|
||||
private bool IsAllLower(string input)
|
||||
{
|
||||
if (char.IsLetter(c) && !char.IsLower(c))
|
||||
return false;
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (char.IsLetter(c) && !char.IsLower(c))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Header
|
||||
|
||||
#region Header
|
||||
|
||||
private void UpdateHeaderSymbols()
|
||||
{
|
||||
ListHeader.ResetHeaderText();
|
||||
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
|
||||
}
|
||||
|
||||
private int Compare(PlayerInfo x, PlayerInfo y)
|
||||
{
|
||||
if (!_ascending)
|
||||
private void UpdateHeaderSymbols()
|
||||
{
|
||||
(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),
|
||||
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
||||
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
||||
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
||||
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
if (!_ascending)
|
||||
{
|
||||
(x, y) = (y, x);
|
||||
}
|
||||
|
||||
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;
|
||||
return _headerClicked switch
|
||||
{
|
||||
Header.Username => Compare(x.Username, y.Username),
|
||||
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
||||
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
||||
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
||||
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Linq;
|
||||
using Content.Shared.Alert;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -25,7 +24,8 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
|
||||
SubscribeLocalEvent<AlertsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<AlertsComponent, AfterAutoHandleStateEvent>(ClientAlertsHandleState);
|
||||
}
|
||||
protected override void LoadPrototypes()
|
||||
{
|
||||
@@ -47,16 +47,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<AlertsComponent> alerts, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not AlertComponentState cast)
|
||||
return;
|
||||
|
||||
alerts.Comp.Alerts = cast.Alerts;
|
||||
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
protected override void AfterShowAlert(Entity<AlertsComponent> alerts)
|
||||
{
|
||||
UpdateHud(alerts);
|
||||
@@ -67,6 +57,11 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
private void ClientAlertsHandleState(Entity<AlertsComponent> alerts, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
private void UpdateHud(Entity<AlertsComponent> entity)
|
||||
{
|
||||
if (_playerManager.LocalEntity == entity.Owner)
|
||||
@@ -98,6 +93,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
|
||||
public void AlertClicked(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
RaisePredictiveEvent(new ClickAlertEvent(alertType));
|
||||
RaiseNetworkEvent(new ClickAlertEvent(alertType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
||||
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete);
|
||||
|
||||
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Anomaly;
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Anomaly.Ui;
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed partial class AnomalyScannerMenu : FancyWindow
|
||||
msg.PushNewline();
|
||||
var time = NextPulseTime.Value - _timing.CurTime;
|
||||
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());
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PipeColorVisualsComponent : Component;
|
||||
public sealed partial class PipeColorVisualsComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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 displayed 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>
|
||||
@@ -1,214 +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);
|
||||
var keyValuePairs = gasData.ToList();
|
||||
|
||||
if (keyValuePairs.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 keyValuePairs)
|
||||
{
|
||||
FixedPoint2 gasPercent = percent * 100f;
|
||||
|
||||
var gasShorthand = _gasShorthands.GetValueOrDefault(gas, "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;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +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;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
var castState = (AtmosAlertsComputerBoundInterfaceState) state;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Piping;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
@@ -17,7 +19,7 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PipeAppearanceComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PipeAppearanceComponent, AppearanceChangeEvent>(OnAppearanceChanged, after: [typeof(SubFloorHideSystem)]);
|
||||
SubscribeLocalEvent<PipeAppearanceComponent, AppearanceChangeEvent>(OnAppearanceChanged, after: new[] { typeof(SubFloorHideSystem) });
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PipeAppearanceComponent component, ComponentInit args)
|
||||
@@ -82,8 +84,7 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
|
||||
layer.Visible &= visible;
|
||||
|
||||
if (!visible)
|
||||
continue;
|
||||
if (!visible) continue;
|
||||
|
||||
layer.Color = color;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GasMinerSystem : SharedGasMinerSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor;
|
||||
|
||||
@@ -22,7 +27,7 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
|
||||
{
|
||||
foreach (var visLayer in component.HideOnDepowered)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(visLayer, out var powerVisibilityLayer))
|
||||
if (args.Sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer))
|
||||
args.Sprite.LayerSetVisible(powerVisibilityLayer, powered);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +36,7 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
|
||||
{
|
||||
foreach (var (setLayer, powerState) in component.SetOnDepowered)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(setLayer, out var setStateLayer))
|
||||
if (args.Sprite.LayerMapTryGet(setLayer, out int setStateLayer))
|
||||
args.Sprite.LayerSetState(setStateLayer, new RSI.StateId(powerState));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI;
|
||||
|
||||
@@ -26,6 +30,7 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
_window.AirAlarmModeChanged += OnAirAlarmModeChanged;
|
||||
_window.AutoModeChanged += OnAutoModeChanged;
|
||||
_window.ResyncAllRequested += ResyncAllDevices;
|
||||
_window.AirAlarmTabChange += OnTabChanged;
|
||||
}
|
||||
|
||||
private void ResyncAllDevices()
|
||||
@@ -58,6 +63,11 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas));
|
||||
}
|
||||
|
||||
private void OnTabChanged(AirAlarmTab tab)
|
||||
{
|
||||
SendMessage(new AirAlarmTabSetMessage(tab));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
@@ -74,7 +84,6 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_window?.Dispose();
|
||||
if (disposing) _window?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -22,6 +23,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
public event Action<AirAlarmMode>? AirAlarmModeChanged;
|
||||
public event Action<bool>? AutoModeChanged;
|
||||
public event Action? ResyncAllRequested;
|
||||
public event Action<AirAlarmTab>? AirAlarmTabChange;
|
||||
|
||||
private RichTextLabel _address => CDeviceAddress;
|
||||
private RichTextLabel _deviceTotal => CDeviceTotal;
|
||||
@@ -58,7 +60,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
AirAlarmMode.Fill => "air-alarm-ui-mode-fill",
|
||||
AirAlarmMode.Panic => "air-alarm-ui-mode-panic",
|
||||
AirAlarmMode.None => "air-alarm-ui-mode-none",
|
||||
_ => "error",
|
||||
_ => "error"
|
||||
};
|
||||
_modes.AddItem(Loc.GetString(text));
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
|
||||
};
|
||||
|
||||
_autoMode.OnToggled += _ =>
|
||||
_autoMode.OnToggled += args =>
|
||||
{
|
||||
AutoModeChanged!.Invoke(_autoMode.Pressed);
|
||||
};
|
||||
@@ -78,6 +80,11 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
|
||||
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
|
||||
|
||||
_tabContainer.OnTabChanged += idx =>
|
||||
{
|
||||
AirAlarmTabChange!((AirAlarmTab) idx);
|
||||
};
|
||||
|
||||
_resyncDevices.OnPressed += _ =>
|
||||
{
|
||||
_ventDevices.RemoveAllChildren();
|
||||
@@ -110,6 +117,8 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
{
|
||||
UpdateDeviceData(addr, dev);
|
||||
}
|
||||
|
||||
_tabContainer.CurrentTab = (int) state.Tab;
|
||||
}
|
||||
|
||||
public void UpdateModeSelector(AirAlarmMode mode)
|
||||
@@ -175,18 +184,22 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
|
||||
public static Color ColorForThreshold(float amount, AtmosAlarmThreshold threshold)
|
||||
{
|
||||
threshold.CheckThreshold(amount, out var curAlarm);
|
||||
threshold.CheckThreshold(amount, out AtmosAlarmType curAlarm);
|
||||
return ColorForAlarm(curAlarm);
|
||||
}
|
||||
|
||||
public static Color ColorForAlarm(AtmosAlarmType curAlarm)
|
||||
{
|
||||
return curAlarm switch
|
||||
if(curAlarm == AtmosAlarmType.Danger)
|
||||
{
|
||||
AtmosAlarmType.Danger => StyleNano.DangerousRedFore,
|
||||
AtmosAlarmType.Warning => StyleNano.ConcerningOrangeFore,
|
||||
_ => StyleNano.GoodGreenFore,
|
||||
};
|
||||
return StyleNano.DangerousRedFore;
|
||||
}
|
||||
else if(curAlarm == AtmosAlarmType.Warning)
|
||||
{
|
||||
return StyleNano.ConcerningOrangeFore;
|
||||
}
|
||||
|
||||
return StyleNano.GoodGreenFore;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
@@ -21,7 +25,7 @@ public sealed partial class PumpControl : BoxContainer
|
||||
private OptionButton _pressureCheck => CPressureCheck;
|
||||
private FloatSpinBox _externalBound => CExternalBound;
|
||||
private FloatSpinBox _internalBound => CInternalBound;
|
||||
private Button _copySettings => CCopySettings;
|
||||
private Button _copySettings => CCopySettings;
|
||||
|
||||
public PumpControl(GasVentPumpData data, string address)
|
||||
{
|
||||
@@ -82,7 +86,7 @@ public sealed partial class PumpControl : BoxContainer
|
||||
_data.PressureChecks = (VentPressureBound) args.Id;
|
||||
PumpDataChanged?.Invoke(_address, _data);
|
||||
};
|
||||
|
||||
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
PumpDataCopied?.Invoke(_data);
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
||||
|
||||
@@ -21,7 +27,7 @@ public sealed partial class ScrubberControl : BoxContainer
|
||||
private OptionButton _pumpDirection => CPumpDirection;
|
||||
private FloatSpinBox _volumeRate => CVolumeRate;
|
||||
private CheckBox _wideNet => CWideNet;
|
||||
private Button _copySettings => CCopySettings;
|
||||
private Button _copySettings => CCopySettings;
|
||||
|
||||
private GridContainer _gases => CGasContainer;
|
||||
private Dictionary<Gas, Button> _gasControls = new();
|
||||
@@ -71,7 +77,7 @@ public sealed partial class ScrubberControl : BoxContainer
|
||||
_data.PumpDirection = (ScrubberPumpDirection) args.Id;
|
||||
ScrubberDataChanged?.Invoke(_address, _data);
|
||||
};
|
||||
|
||||
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
ScrubberDataCopied?.Invoke(_data);
|
||||
|
||||
@@ -43,8 +43,7 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
var label = new RichTextLabel();
|
||||
|
||||
var fractionGas = amount / data.TotalMoles;
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
|
||||
("gas", $"{gas}"),
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
|
||||
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
|
||||
("amount", $"{amount:0.####}"),
|
||||
("percentage", $"{(100 * fractionGas):0.##}")));
|
||||
@@ -54,9 +53,9 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
var threshold = data.GasThresholds[gas];
|
||||
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
|
||||
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
|
||||
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
|
||||
gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
|
||||
{
|
||||
OnThresholdUpdate!(_address, type, alarmThreshold, arg3);
|
||||
OnThresholdUpdate!(_address, type, threshold, arg3);
|
||||
};
|
||||
|
||||
_gasThresholds.Add(gas, gasThresholdControl);
|
||||
@@ -65,8 +64,7 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
|
||||
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
|
||||
PressureThresholdContainer.AddChild(_pressureThreshold);
|
||||
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"),
|
||||
data.TemperatureThreshold,
|
||||
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), data.TemperatureThreshold,
|
||||
AtmosMonitorThresholdType.Temperature);
|
||||
TemperatureThresholdContainer.AddChild(_temperatureThreshold);
|
||||
|
||||
@@ -105,8 +103,7 @@ public sealed partial class SensorInfo : BoxContainer
|
||||
}
|
||||
|
||||
var fractionGas = amount / data.TotalMoles;
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
|
||||
("gas", $"{gas}"),
|
||||
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
|
||||
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
|
||||
("amount", $"{amount:0.####}"),
|
||||
("percentage", $"{(100 * fractionGas):0.##}")));
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Atmos.Monitor.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
// holy FUCK
|
||||
// this technically works because some of this you can *not* do in XAML but holy FUCK
|
||||
@@ -111,38 +115,29 @@ public sealed partial class ThresholdControl : BoxContainer
|
||||
_enabled.Pressed = !_threshold.Ignore;
|
||||
}
|
||||
|
||||
private string LabelForBound(string boundType) //<todo.eoin Replace this with enums
|
||||
private String LabelForBound(string boundType) //<todo.eoin Replace this with enums
|
||||
{
|
||||
return Loc.GetString($"air-alarm-ui-thresholds-{boundType}");
|
||||
}
|
||||
|
||||
public void UpdateThresholdData(AtmosAlarmThreshold threshold, float currentAmount)
|
||||
{
|
||||
threshold.CheckThreshold(currentAmount, out var alarm, out var bound);
|
||||
threshold.CheckThreshold(currentAmount, out AtmosAlarmType alarm, out AtmosMonitorThresholdBound which);
|
||||
|
||||
var upperDangerState = AtmosAlarmType.Normal;
|
||||
var lowerDangerState = AtmosAlarmType.Normal;
|
||||
var upperWarningState = AtmosAlarmType.Normal;
|
||||
var lowerWarningState = AtmosAlarmType.Normal;
|
||||
|
||||
switch (alarm)
|
||||
if(alarm == AtmosAlarmType.Danger)
|
||||
{
|
||||
case AtmosAlarmType.Danger:
|
||||
{
|
||||
if (bound == AtmosMonitorThresholdBound.Upper)
|
||||
upperDangerState = alarm;
|
||||
else
|
||||
lowerDangerState = alarm;
|
||||
break;
|
||||
}
|
||||
case AtmosAlarmType.Warning:
|
||||
{
|
||||
if (bound == AtmosMonitorThresholdBound.Upper)
|
||||
upperWarningState = alarm;
|
||||
else
|
||||
lowerWarningState = alarm;
|
||||
break;
|
||||
}
|
||||
if(which == AtmosMonitorThresholdBound.Upper) upperDangerState = alarm;
|
||||
else lowerDangerState = alarm;
|
||||
}
|
||||
else if(alarm == AtmosAlarmType.Warning)
|
||||
{
|
||||
if(which == AtmosMonitorThresholdBound.Upper) upperWarningState = alarm;
|
||||
else lowerWarningState = alarm;
|
||||
}
|
||||
|
||||
_upperBoundControl.SetValue(threshold.UpperBound.Value);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
@@ -102,9 +101,14 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
else
|
||||
{
|
||||
// Red-Green-Blue interpolation
|
||||
res = interp < 0.5f
|
||||
? Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2)
|
||||
: Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
|
||||
if (interp < 0.5f)
|
||||
{
|
||||
res = Color.InterpolateBetween(Color.Red, Color.LimeGreen, interp * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
res = res.WithAlpha(0.75f);
|
||||
@@ -177,10 +181,7 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
handle.DrawCircle(tileCentre, 0.05f, Color.Black);
|
||||
}
|
||||
|
||||
private void CheckAndShowBlockDir(
|
||||
AtmosDebugOverlayData data,
|
||||
DrawingHandleWorld handle,
|
||||
AtmosDirection dir,
|
||||
private void CheckAndShowBlockDir(AtmosDebugOverlayData data, DrawingHandleWorld handle, AtmosDirection dir,
|
||||
Vector2 tileCentre)
|
||||
{
|
||||
if (!data.BlockDirection.HasFlag(dir))
|
||||
@@ -242,7 +243,7 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
|
||||
var moles = data.Moles == null
|
||||
? "No Air"
|
||||
: data.Moles.Sum().ToString(CultureInfo.InvariantCulture);
|
||||
: data.Moles.Sum().ToString();
|
||||
|
||||
handle.DrawString(_font, pos, $"Moles: {moles}");
|
||||
pos += offset;
|
||||
@@ -262,12 +263,7 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
private void GetGrids(MapId mapId, Box2Rotated box)
|
||||
{
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(
|
||||
mapId,
|
||||
box,
|
||||
ref _grids,
|
||||
(EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
_mapManager.FindGridsIntersecting(mapId, box, ref _grids, (EntityUid uid, MapGridComponent grid,
|
||||
ref List<(Entity<MapGridComponent>, DebugMessage)> state) =>
|
||||
{
|
||||
if (_system.TileData.TryGetValue(uid, out var data))
|
||||
|
||||
@@ -16,8 +16,6 @@ namespace Content.Client.Atmos.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasAnalyzerWindow : DefaultWindow
|
||||
{
|
||||
private NetEntity _currentEntity = NetEntity.Invalid;
|
||||
|
||||
public GasAnalyzerWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -57,13 +55,6 @@ namespace Content.Client.Atmos.UI
|
||||
// Device Tab
|
||||
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.SetTabTitle(0, Loc.GetString("gas-analyzer-window-tab-title-capitalized", ("title", msg.DeviceName)));
|
||||
// Set up Grid
|
||||
@@ -152,7 +143,6 @@ namespace Content.Client.Atmos.UI
|
||||
CTabContainer.SetTabVisible(0, false);
|
||||
CTabContainer.CurrentTab = 1;
|
||||
minSize = new Vector2(CEnvironmentMix.DesiredSize.X + 40, MinSize.Y);
|
||||
_currentEntity = NetEntity.Invalid;
|
||||
}
|
||||
|
||||
MinSize = minSize;
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
public bool OverlayEnabled
|
||||
@@ -107,7 +107,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
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;
|
||||
|
||||
_audio.Stop(sound.Stream);
|
||||
@@ -120,13 +120,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
{
|
||||
_ambienceVolume = SharedAudioSystem.GainToVolume(value);
|
||||
|
||||
foreach (var (ent, values) in _playingSounds)
|
||||
foreach (var (comp, values) in _playingSounds)
|
||||
{
|
||||
if (values.Stream == null)
|
||||
continue;
|
||||
|
||||
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;
|
||||
@@ -165,7 +165,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
if (_gameTiming.CurTime < _targetTime)
|
||||
return;
|
||||
|
||||
_targetTime = _gameTiming.CurTime + TimeSpan.FromSeconds(_cooldown);
|
||||
_targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown);
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
|
||||
@@ -190,7 +190,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
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 TransformComponent Player;
|
||||
public readonly SharedTransformSystem TransformSystem;
|
||||
@@ -224,11 +224,11 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
if (ambientComp.Sound is SoundPathSpecifier path)
|
||||
key = path.Path.ToString();
|
||||
else
|
||||
key = ((SoundCollectionSpecifier)ambientComp.Sound).Collection ?? string.Empty;
|
||||
key = ((SoundCollectionSpecifier) ambientComp.Sound).Collection ?? string.Empty;
|
||||
|
||||
// Prioritize far away & loud sounds.
|
||||
var importance = range * (ambientComp.Volume + 32);
|
||||
state.SourceDict.GetOrNew(key).Add((importance, (value.Uid, ambientComp)));
|
||||
state.SourceDict.GetOrNew(key).Add((importance, ambientComp));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -242,18 +242,16 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
var mapPos = _xformSystem.GetMapCoordinates(playerXform);
|
||||
|
||||
// Remove out-of-range ambiences
|
||||
foreach (var (ent, sound) in _playingSounds)
|
||||
foreach (var (comp, sound) in _playingSounds)
|
||||
{
|
||||
//var entity = comp.Owner;
|
||||
var owner = ent.Owner;
|
||||
var comp = ent.Comp;
|
||||
var entity = comp.Owner;
|
||||
|
||||
if (comp.Enabled &&
|
||||
// Don't keep playing sounds that have changed since.
|
||||
sound.Sound == comp.Sound &&
|
||||
query.TryGetComponent(owner, out var xform) &&
|
||||
query.TryGetComponent(entity, out var xform) &&
|
||||
xform.MapID == playerXform.MapID &&
|
||||
!metaQuery.GetComponent(owner).EntityPaused)
|
||||
!metaQuery.GetComponent(entity).EntityPaused)
|
||||
{
|
||||
// TODO: This is just trydistance for coordinates.
|
||||
var distance = (xform.ParentUid == playerXform.ParentUid)
|
||||
@@ -265,7 +263,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
}
|
||||
|
||||
_audio.Stop(sound.Stream);
|
||||
_playingSounds.Remove(ent);
|
||||
_playingSounds.Remove(comp);
|
||||
_playingCount[sound.Path] -= 1;
|
||||
if (_playingCount[sound.Path] == 0)
|
||||
_playingCount.Remove(sound.Path);
|
||||
@@ -280,7 +278,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
_treeSys.QueryAabb(ref state, Callback, mapPos.MapId, worldAabb);
|
||||
|
||||
// Add in range ambiences
|
||||
foreach (var (key, sourceList) in state.SourceDict)
|
||||
foreach (var (key, sources) in state.SourceDict)
|
||||
{
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
break;
|
||||
@@ -288,14 +286,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
if (_playingCount.TryGetValue(key, out var playingCount) && playingCount >= MaxSingleSound)
|
||||
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 comp = sourceEntity.Comp;
|
||||
var uid = comp.Owner;
|
||||
|
||||
if (_playingSounds.ContainsKey(sourceEntity) ||
|
||||
if (_playingSounds.ContainsKey(comp) ||
|
||||
metaQuery.GetComponent(uid).EntityPaused)
|
||||
continue;
|
||||
|
||||
@@ -306,10 +303,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
.WithMaxDistance(comp.Range);
|
||||
|
||||
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
|
||||
_playingSounds[comp] = (stream.Value.Entity, comp.Sound, key);
|
||||
playingCount++;
|
||||
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if(!_adminAudioEnabled) return;
|
||||
|
||||
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)
|
||||
@@ -76,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
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)
|
||||
|
||||
@@ -214,9 +214,9 @@ public sealed partial class ContentAudioSystem
|
||||
false,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void CP14UpdateAmbientLoops()
|
||||
{
|
||||
return; //DISABLED UNTIL CLIENT ERROR SPAM FIXED
|
||||
|
||||
if (_timing.CurTime <= _nextUpdateTime)
|
||||
return;
|
||||
|
||||
@@ -82,10 +80,6 @@ public sealed partial class ContentAudioSystem
|
||||
.WithLoop(true)
|
||||
.WithVolume(proto.Sound.Params.Volume + _volumeSlider)
|
||||
.WithPlayOffset(_random.NextFloat(0f, 100f)));
|
||||
|
||||
if (newLoop is null)
|
||||
return;
|
||||
|
||||
_loopStreams.Add(proto, newLoop.Value.Entity);
|
||||
|
||||
FadeIn(newLoop.Value.Entity, newLoop.Value.Component, AmbientLoopFadeInTime);
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed partial class ContentAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
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.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
|
||||
|
||||
_state.OnStateChanged += StateManagerOnStateChanged;
|
||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerLeaveServer += OnLeave;
|
||||
|
||||
@@ -114,7 +115,7 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void LobbyMusicCVarChanged(bool musicEnabled)
|
||||
{
|
||||
if (musicEnabled && _state.CurrentState is LobbyState)
|
||||
if (musicEnabled && _stateManager.CurrentState is LobbyState)
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
@@ -184,7 +185,7 @@ public sealed partial class ContentAudioSystem
|
||||
false,
|
||||
_lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
|
||||
);
|
||||
if (playResult == null)
|
||||
if (playResult.Value.Entity == default)
|
||||
{
|
||||
_sawmill.Warning(
|
||||
$"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()
|
||||
{
|
||||
_state.OnStateChanged -= StateManagerOnStateChanged;
|
||||
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerLeaveServer -= OnLeave;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
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)
|
||||
{
|
||||
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Client.CrewManifest.UI;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
Text="{Loc 'news-read-ui-next-text'}"
|
||||
ToolTip="{Loc 'news-read-ui-next-tooltip'}"/>
|
||||
</BoxContainer>
|
||||
<controls:StripeBack Name="ArticleNameContainer">
|
||||
<controls:StripeBack Name="АrticleNameContainer">
|
||||
<PanelContainer>
|
||||
<Label Name="PageNum" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="4,0,0,0"/>
|
||||
<Label Name="PageName" Align="Center"/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -131,13 +131,13 @@ public sealed partial class ChangelogTab : Control
|
||||
Margin = new Thickness(6, 0, 0, 0),
|
||||
};
|
||||
authorLabel.SetMessage(
|
||||
FormattedMessage.FromMarkupOrThrow(Loc.GetString("changelog-author-changed", ("author", author))));
|
||||
FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
|
||||
ChangelogBody.AddChild(authorLabel);
|
||||
|
||||
foreach (var change in groupedEntry.SelectMany(c => c.Changes))
|
||||
{
|
||||
var text = new RichTextLabel();
|
||||
text.SetMessage(FormattedMessage.FromMarkupOrThrow(change.Message));
|
||||
text.SetMessage(FormattedMessage.FromMarkup(change.Message));
|
||||
ChangelogBody.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
|
||||
@@ -5,80 +5,71 @@ using Content.Shared.Chat;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Chat.Managers;
|
||||
|
||||
internal sealed class ChatManager : IChatManager
|
||||
namespace Content.Client.Chat.Managers
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public void Initialize()
|
||||
internal sealed class ChatManager : IChatManager
|
||||
{
|
||||
_sawmill = Logger.GetSawmill("chat");
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
}
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminMgr = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
public void SendAdminAlert(string message)
|
||||
{
|
||||
// See server-side manager. This just exists for shared code.
|
||||
}
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public void SendAdminAlert(EntityUid player, string message)
|
||||
{
|
||||
// See server-side manager. This just exists for shared code.
|
||||
}
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
switch (channel)
|
||||
public void Initialize()
|
||||
{
|
||||
case ChatSelectChannel.Console:
|
||||
// run locally
|
||||
_consoleHost.ExecuteCommand(text);
|
||||
break;
|
||||
_sawmill = Logger.GetSawmill("chat");
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
}
|
||||
|
||||
case ChatSelectChannel.LOOC:
|
||||
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
switch (channel)
|
||||
{
|
||||
case ChatSelectChannel.Console:
|
||||
// run locally
|
||||
_consoleHost.ExecuteCommand(text);
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.OOC:
|
||||
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.LOOC:
|
||||
_consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Admin:
|
||||
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.OOC:
|
||||
_consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Emotes:
|
||||
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
case ChatSelectChannel.Admin:
|
||||
_consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Dead:
|
||||
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
|
||||
goto case ChatSelectChannel.Local;
|
||||
case ChatSelectChannel.Emotes:
|
||||
_consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
if (_adminMgr.HasFlag(AdminFlags.Admin))
|
||||
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
|
||||
else
|
||||
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
|
||||
break;
|
||||
case ChatSelectChannel.Dead:
|
||||
if (_systems.GetEntitySystemOrNull<GhostSystem>() is {IsGhost: true})
|
||||
goto case ChatSelectChannel.Local;
|
||||
|
||||
// TODO sepearate radio and say into separate commands.
|
||||
case ChatSelectChannel.Radio:
|
||||
case ChatSelectChannel.Local:
|
||||
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
if (_adminMgr.HasFlag(AdminFlags.Admin))
|
||||
_consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\"");
|
||||
else
|
||||
_sawmill.Warning("Tried to speak on deadchat without being ghost or admin.");
|
||||
break;
|
||||
|
||||
case ChatSelectChannel.Whisper:
|
||||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
// TODO sepearate radio and say into separate commands.
|
||||
case ChatSelectChannel.Radio:
|
||||
case ChatSelectChannel.Local:
|
||||
_consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
|
||||
case ChatSelectChannel.Whisper:
|
||||
_consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\"");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ using Content.Shared.Chat;
|
||||
|
||||
namespace Content.Client.Chat.Managers
|
||||
{
|
||||
public interface IChatManager : ISharedChatManager
|
||||
public interface IChatManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly EntityWhitelistSystem _whitelistSystem;
|
||||
|
||||
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
|
||||
|
||||
public EmotesMenu()
|
||||
@@ -26,8 +29,8 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var spriteSystem = _entManager.System<SpriteSystem>();
|
||||
var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
||||
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
|
||||
@@ -37,8 +40,8 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
if (emote.Category == EmoteCategory.Invalid ||
|
||||
emote.ChatTriggers.Count == 0 ||
|
||||
!(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
|
||||
whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
|
||||
!(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
|
||||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
|
||||
continue;
|
||||
|
||||
if (!emote.Available &&
|
||||
@@ -60,7 +63,7 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Texture = spriteSystem.Frame0(emote.Icon),
|
||||
Texture = _spriteSystem.Frame0(emote.Icon),
|
||||
TextureScale = new Vector2(2f, 2f),
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Content.Client.Chat.UI
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
public enum SpeechType : byte
|
||||
{
|
||||
@@ -84,7 +83,6 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_senderEntity = senderEntity;
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
// Use text clipping so new messages don't overlap old ones being pushed up.
|
||||
RectClipContent = true;
|
||||
@@ -142,7 +140,7 @@ namespace Content.Client.Chat.UI
|
||||
}
|
||||
|
||||
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 screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);
|
||||
@@ -180,7 +178,7 @@ namespace Content.Client.Chat.UI
|
||||
var msg = new FormattedMessage();
|
||||
if (fontColor != null)
|
||||
msg.PushColor(fontColor.Value);
|
||||
msg.AddMarkupOrThrow(message);
|
||||
msg.AddMarkup(message);
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Client.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Chemistry;
|
||||
@@ -16,7 +16,7 @@ namespace Content.Client.Chemistry.EntitySystems;
|
||||
/// <inheritdoc/>
|
||||
public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
{
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
|
||||
[ValidatePrototypeId<MixingCategoryPrototype>]
|
||||
private const string DefaultMixingCategory = "DummyMix";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -10,15 +9,11 @@ namespace Content.Client.Chemistry.UI
|
||||
[UsedImplicitly]
|
||||
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private IEntityManager _entManager;
|
||||
private EntityUid _owner;
|
||||
[ViewVariables]
|
||||
private TransferAmountWindow? _window;
|
||||
|
||||
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_owner = owner;
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -26,9 +21,6 @@ namespace Content.Client.Chemistry.UI
|
||||
base.Open();
|
||||
_window = this.CreateWindow<TransferAmountWindow>();
|
||||
|
||||
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
|
||||
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
|
||||
|
||||
_window.ApplyButton.OnPressed += _ =>
|
||||
{
|
||||
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<LineEdit Name="AmountLineEdit" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc 'ui-transfer-amount-line-edit-placeholder'}"/>
|
||||
</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'}"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -8,29 +8,9 @@ namespace Content.Client.Chemistry.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class TransferAmountWindow : DefaultWindow
|
||||
{
|
||||
private int _max = Int32.MaxValue;
|
||||
private int _min = 1;
|
||||
|
||||
public TransferAmountWindow()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ using Content.Client.Items.Systems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Rounding;
|
||||
@@ -22,7 +20,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SolutionContainerVisualsComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals);
|
||||
SubscribeLocalEvent<SolutionContainerVisualsComponent, GetEquipmentVisualsEvent>(OnGetClothingVisuals);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
|
||||
@@ -177,41 +174,4 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
public sealed partial class ClickableComponent : Component
|
||||
namespace Content.Client.Clickable
|
||||
{
|
||||
[DataField] public DirBoundData? Bounds;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class DirBoundData
|
||||
[RegisterComponent]
|
||||
public sealed partial class ClickableComponent : Component
|
||||
{
|
||||
[DataField] public Box2 All;
|
||||
[DataField] public Box2 North;
|
||||
[DataField] public Box2 South;
|
||||
[DataField] public Box2 East;
|
||||
[DataField] public Box2 West;
|
||||
[Dependency] private readonly IClickMapManager _clickMapManager = default!;
|
||||
|
||||
[DataField("bounds")] public DirBoundData? Bounds;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
/// For some context, im currently refactoring inventory. Part of that is slots not being indexed by a massive enum anymore, but by strings.
|
||||
/// Problem here: Every rsi-state is using the old enum-names in their state. I already used the new inventoryslots ALOT. tldr: its this or another week of renaming files.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, string> TemporarySlotMap = new() //CP14 Public
|
||||
private static readonly Dictionary<string, string> TemporarySlotMap = new()
|
||||
{
|
||||
{"head", "HELMET"},
|
||||
{"eyes", "EYES"},
|
||||
@@ -57,6 +57,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
};
|
||||
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
|
||||
@@ -65,7 +66,6 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ClothingComponent, GetEquipmentVisualsEvent>(OnGetVisuals);
|
||||
SubscribeLocalEvent<ClothingComponent, InventoryTemplateUpdated>(OnInventoryTemplateUpdated);
|
||||
|
||||
SubscribeLocalEvent<InventoryComponent, VisualsChangedEvent>(OnVisualsChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, DidUnequipEvent>(OnDidUnequip);
|
||||
@@ -78,7 +78,11 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
UpdateAllSlots(uid, component);
|
||||
var enumerator = _inventorySystem.GetSlotEnumerator((uid, component));
|
||||
while (enumerator.NextItem(out var item, out var slot))
|
||||
{
|
||||
RenderEquipment(uid, item, slot.Name, component);
|
||||
}
|
||||
|
||||
// No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
|
||||
if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
|
||||
@@ -88,23 +92,6 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInventoryTemplateUpdated(Entity<ClothingComponent> ent, ref InventoryTemplateUpdated args)
|
||||
{
|
||||
UpdateAllSlots(ent.Owner, clothing: ent.Comp);
|
||||
}
|
||||
|
||||
private void UpdateAllSlots(
|
||||
EntityUid uid,
|
||||
InventoryComponent? inventoryComponent = null,
|
||||
ClothingComponent? clothing = null)
|
||||
{
|
||||
var enumerator = _inventorySystem.GetSlotEnumerator((uid, inventoryComponent));
|
||||
while (enumerator.NextItem(out var item, out var slot))
|
||||
{
|
||||
RenderEquipment(uid, item, slot.Name, inventoryComponent, clothingComponent: clothing);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args)
|
||||
{
|
||||
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
|
||||
@@ -341,8 +328,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
|
||||
continue;
|
||||
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers))
|
||||
index++;
|
||||
_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Clothing;
|
||||
|
||||
public sealed class FlippableClothingVisualizerSystem : VisualizerSystem<FlippableClothingVisualsComponent>
|
||||
public sealed class FlippableClothingVisualizerSystem : VisualizerSystem<FlippableClothingVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSys = default!;
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
using Content.Shared.Clothing;
|
||||
|
||||
namespace Content.Client.Clothing.Systems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class CursedMaskSystem : SharedCursedMaskSystem;
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Shared.Administration;
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class HideMechanismsCommand : LocalizedCommands
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
var tempParent = uid;
|
||||
while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
|
||||
while (containerSys.TryGetContainingContainer(tempParent, out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Markers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
@@ -24,8 +21,8 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
shell.ExecuteCommand("showsubfloorforever");
|
||||
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false);
|
||||
shell.ExecuteCommand(ShowSubFloorForever.CommandName);
|
||||
shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Overlays;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
@@ -36,7 +34,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands
|
||||
{
|
||||
var showHealthBarsComponent = new ShowHealthBarsComponent
|
||||
{
|
||||
DamageContainers = args.Select(arg => new ProtoId<DamageContainerPrototype>(arg)).ToList(),
|
||||
DamageContainers = args.ToList(),
|
||||
HealthStatusIcon = null,
|
||||
NetSyncEnabled = false
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user