Compare commits

..

4 Commits

Author SHA1 Message Date
Tornado Tech
0f6e73831a Дистанционный гибер из реал 2024-05-08 20:15:05 +10:00
Tornado Tech
a0a2e8fabf Ну оно работает 2024-05-03 20:27:12 +10:00
Tornado Tech
765d226a9d Бля 2024-04-28 01:27:41 +10:00
Tornado Tech
25c174af41 Добавил немного базы для заклинаний (Она не работает) 2024-04-28 01:24:55 +10:00
13091 changed files with 708116 additions and 1200345 deletions

View File

@@ -1,5 +1,4 @@
root = true root = true
[*] [*]
charset = utf-8 charset = utf-8
@@ -10,10 +9,9 @@ indent_style = space
tab_width = 4 tab_width = 4
# New line preferences # New line preferences
#end_of_line = crlf end_of_line = crlf:suggestion
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
max_line_length = 120
#### .NET Coding Conventions #### #### .NET Coding Conventions ####
@@ -106,6 +104,7 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs
# 'using' directive preferences # 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent csharp_using_directive_placement = outside_namespace:silent
csharp_style_namespace_declarations = file_scoped:suggestion
#### C# Formatting Rules #### #### C# Formatting Rules ####
@@ -129,7 +128,7 @@ csharp_indent_braces = false
csharp_indent_switch_labels = true csharp_indent_switch_labels = true
# Space preferences # Space preferences
csharp_space_after_cast = false csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true csharp_space_after_comma = true
csharp_space_after_dot = false csharp_space_after_dot = false
@@ -279,7 +278,7 @@ dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case
dotnet_naming_style.t_upper_camel_case_style.required_prefix = T dotnet_naming_style.t_upper_camel_case_style.required_prefix = T
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.constants_symbols.applicable_kinds = field dotnet_naming_symbols.constants_symbols.applicable_kinds = field
dotnet_naming_symbols.constants_symbols.required_modifiers = const dotnet_naming_symbols.constants_symbols.required_modifiers = const
@@ -318,32 +317,27 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
dotnet_naming_symbols.property_symbols.applicable_accessibilities = * dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
dotnet_naming_symbols.property_symbols.applicable_kinds = property dotnet_naming_symbols.property_symbols.applicable_kinds = property
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = * dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter
# ReSharper properties # ReSharper properties
resharper_braces_for_ifelse = required_for_multiline resharper_braces_for_ifelse = required_for_multiline
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true resharper_keep_existing_attribute_arrangement = true
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
resharper_csharp_trailing_comma_in_multiline_lists = true
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}] [*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
indent_size = 2 indent_size = 2

15
.github/CODEOWNERS vendored
View File

@@ -15,21 +15,19 @@
/Content.*/GameTicking/ @moonheart08 @EmoGarbage404 /Content.*/GameTicking/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/ @moonheart08 @Chief-Engineer /Resources/ServerInfo/ @moonheart08 @Chief-Engineer
/Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404 /Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/Prototypes/Maps/** @Emisse /Resources/Prototypes/Maps/ @Emisse
/Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer
/Content.*/Body/ @DrSmugleaf /Content.*/Body/ @DrSmugleaf
/Content.YAMLLinter @DrSmugleaf /Content.YAMLLinter @DrSmugleaf
/Content.Shared/Damage/ @DrSmugleaf /Content.Shared/Damage/ @DrSmugleaf
/Content.*/Anomaly/ @EmoGarbage404 @TheShuEd /Content.*/Anomaly/ @EmoGarbage404
/Content.*/Lathe/ @EmoGarbage404 /Content.*/Lathe/ @EmoGarbage404
/Content.*/Materials/ @EmoGarbage404 /Content.*/Materials/ @EmoGarbage404
/Content.*/Mech/ @EmoGarbage404 /Content.*/Mech/ @EmoGarbage404
@@ -37,7 +35,7 @@
/Content.*/Stack/ @EmoGarbage404 /Content.*/Stack/ @EmoGarbage404
/Content.*/Xenoarchaeology/ @EmoGarbage404 /Content.*/Xenoarchaeology/ @EmoGarbage404
/Content.*/Zombies/ @EmoGarbage404 /Content.*/Zombies/ @EmoGarbage404
/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404 @TheShuEd /Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404
/Resources/Prototypes/Research/ @EmoGarbage404 /Resources/Prototypes/Research/ @EmoGarbage404
/Content.*/Forensics/ @ficcialfaint /Content.*/Forensics/ @ficcialfaint
@@ -55,10 +53,3 @@
#Jezi #Jezi
/Content.*/Medical @Jezithyr /Content.*/Medical @Jezithyr
/Content.*/Body @Jezithyr /Content.*/Body @Jezithyr
# Sloth
/Content.*/Audio @metalgearsloth
/Content.*/Movement @metalgearsloth
/Content.*/NPC @metalgearsloth
/Content.*/Shuttles @metalgearsloth
/Content.*/Weapons @metalgearsloth

14
.github/FUNDING.yml vendored
View File

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

View File

@@ -1,17 +1,43 @@
<!-- Please read these guidelines before opening your PR: https://docs.spacestation14.io/en/getting-started/pr-guideline -->
<!-- The text between the arrows are comments - they will not be visible on your PR. -->
## About the PR ## About the PR
<!-- What did you change in this PR? --> <!-- What did you change in this PR? -->
<!-- Что вы изменили в своем пулл реквесте? -->
## Why / Balance ## Why / Balance
<!-- Why was it changed? Link any discussions or issues here. Please discuss how this would affect game balance. --> <!-- Why was it changed? Link any discussions or issues here. Please discuss how this would affect game balance. -->
<!-- Зачем нужно это изменение? Прикрепите любые обсуждения или проблемы здесь. Опишите, как это повлияет на текущий баланс игры. -->
## Technical details
<!-- If this is a code change, summarize at high level how your new code works. This makes it easier to review. -->
## Media ## Media
<!-- <!--
PRs which make ingame changes (adding clothing, items, new features, etc) are required to have media attached that showcase the changes. PRs which make ingame changes (adding clothing, items, new features, etc) are required to have media attached that showcase the changes.
Small fixes/refactors are exempt. Small fixes/refactors are exempt.
Any media may be used in SS14 progress reports, with clear credit given.
If you're unsure whether your PR will require media, ask a maintainer.
Check the box below to confirm that you have in fact seen this (put an X in the brackets, like [X]):
--> -->
- [ ] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase
## Breaking changes
<!-- <!--
Пулл реквесты, которые несут за собой игровые изменения (добавления одежды, предметов и так далее) требуют чтобы вы прикрепили скриншоты или видеоролики, демонстрирующие эти изменения. List any breaking changes, including namespace, public class/method/field changes, prototype renames; and provide instructions for fixing them. This will be pasted in #codebase-changes.
Небольшие исправления не считаются. -->
**Changelog**
<!--
Make players aware of new features and changes that could affect how they play the game by adding a Changelog entry. Please read the Changelog guidelines located at: https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog
-->
<!--
Make sure to take this Changelog template out of the comment block in order for it to show up.
:cl:
- add: Added fun!
- remove: Removed fun!
- tweak: Changed fun!
- fix: Fixed fun!
--> -->

20
.github/labeler.yml vendored
View File

@@ -1,22 +1,12 @@
"Changes: Sprites": "Changes: Sprites":
- changed-files: - '**/*.rsi/*.png'
- any-glob-to-any-file: '**/*.rsi/*.png'
"Changes: Map": "Changes: Map":
- changed-files: - 'Resources/Maps/*.yml'
- any-glob-to-any-file: - 'Resources/Prototypes/Maps/*.yml'
- 'Resources/Maps/**/*.yml'
- 'Resources/Prototypes/Maps/**/*.yml'
"Changes: UI": "Changes: UI":
- changed-files: - '**/*.xaml*'
- any-glob-to-any-file: '**/*.xaml*'
"Changes: Shaders":
- changed-files:
- any-glob-to-any-file: '**/*.swsl'
"No C#": "No C#":
- changed-files: - all: ["!**/*.cs"]
# 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"

View File

@@ -75,17 +75,15 @@
"license":{ "license":{
"$id":"#/properties/license", "$id":"#/properties/license",
"default":"", "default":"",
"description":"The license for the associated icon states. Restricted to CP14-compatible asset licenses.", "description":"The license for the associated icon states. Restricted to SS14-compatible asset licenses.",
"enum":[ "enum":[
"CC-BY-SA-3.0", "CC-BY-SA-3.0",
"CC-BY-SA-4.0", "CC-BY-SA-4.0",
"CC0-1.0",
"CC-BY-NC-3.0", "CC-BY-NC-3.0",
"CC-BY-NC-4.0", "CC-BY-NC-4.0",
"CC-BY-NC-SA-3.0", "CC-BY-NC-SA-3.0",
"CC-BY-NC-SA-4.0", "CC-BY-NC-SA-4.0",
"CC0-1.0", "CC0-1.0"
"CLA"
], ],
"examples":[ "examples":[
"CC-BY-SA-3.0" "CC-BY-SA-3.0"

View File

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

View File

@@ -1,20 +1,18 @@
name: Check Merge Conflicts name: Check Merge Conflicts
on: on:
push:
branches:
- master
pull_request_target: pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
jobs: jobs:
Label: Label:
if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' ) if: github.actor != 'PJBot'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check for Merge Conflicts - name: Check for Merge Conflicts
uses: eps1lon/actions-label-merge-conflict@v3.0.0 uses: ike709/actions-label-merge-conflict@9eefdd17e10566023c46d2dc6dc04fcb8ec76142
with: with:
dirtyLabel: "Merge Conflict" dirtyLabel: "Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}" repoToken: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -6,9 +6,8 @@ on:
jobs: jobs:
labeler: labeler:
if: github.actor != 'PJBot' if: github.actor != 'PJBot'
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v5 - uses: actions/labeler@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -9,6 +9,5 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions-ecosystem/action-add-labels@v1 - uses: actions-ecosystem/action-add-labels@v1
if: join(github.event.issue.labels) == ''
with: with:
labels: "Status: Untriaged" labels: "Status: Untriaged"

View File

@@ -5,8 +5,8 @@ concurrency:
on: on:
workflow_dispatch: workflow_dispatch:
# schedule: schedule:
# - cron: '0 10 * * *' - cron: '0 10 * * *'
jobs: jobs:
build: build:
@@ -41,11 +41,31 @@ jobs:
- name: Package client - name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Publish version - name: Update Build Info
run: Tools/publish_multi_request.py run: Tools/gen_build_info.py
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - name: Shuffle files around
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} run: |
mkdir "release/${{ github.sha }}"
mv release/*.zip "release/${{ github.sha }}"
- name: Upload files to centcomm
uses: appleboy/scp-action@master
with:
host: centcomm.spacestation14.io
username: wizards-build-push
key: ${{ secrets.CENTCOMM_WIZARDS_BUILDS_PUSH_KEY }}
source: "release/${{ github.sha }}"
target: "/home/wizards-build-push/builds_dir/builds/"
strip_components: 1
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
username: wizards-build-push
key: ${{ secrets.CENTCOMM_WIZARDS_BUILDS_PUSH_KEY }}
script: /home/wizards-build-push/push.ps1 ${{ github.sha }}
- name: Publish changelog (Discord) - name: Publish changelog (Discord)
run: Tools/actions_changelogs_since_last_run.py run: Tools/actions_changelogs_since_last_run.py

View File

@@ -15,12 +15,9 @@ jobs:
- name: Get changed files - name: Get changed files
id: files id: files
uses: Ana06/get-changed-files@v2.3.0 uses: Ana06/get-changed-files@v1.2
with: with:
format: 'space-delimited' format: 'space-delimited'
filter: |
**.rsi
**.png
- name: Diff changed RSIs - name: Diff changed RSIs
id: diff id: diff

View File

@@ -64,3 +64,11 @@ jobs:
- name: Package client - name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Update Build Info
run: Tools/gen_build_info.py
- name: Shuffle files around
run: |
mkdir "release/${{ github.sha }}"
mv release/*.zip "release/${{ github.sha }}"

View File

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

View File

@@ -21,5 +21,5 @@ jobs:
with: with:
schema: RobustToolbox/Schemas/rga.yml schema: RobustToolbox/Schemas/rga.yml
path_pattern: .*attributions.ya?ml$ path_pattern: .*attributions.ya?ml$
validators_path: Schemas/rga_validators.py validators_path: RobustToolbox/Schemas/rga_validators.py
validators_requirements: Schemas/rga_requirements.txt validators_requirements: RobustToolbox/Schemas/rga_requirements.txt

View File

@@ -23,4 +23,4 @@ jobs:
pip3 install --ignore-installed --user pillow jsonschema pip3 install --ignore-installed --user pillow jsonschema
- name: Validate RSIs - name: Validate RSIs
run: | run: |
python3 Schemas/validate_rsis.py Resources/ python3 RobustToolbox/Schemas/validate_rsis.py Resources/

1
.gitignore vendored
View File

@@ -306,4 +306,3 @@ Resources/MapImages
# Direnv stuff # Direnv stuff
.direnv/ .direnv/
/Tools/_CP14/LocalizationHelper/last_launch

4
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"omnisharp.analyzeOpenDocumentsOnly": true,
"dotnet.defaultSolution": "SpaceStation14.sln"
}

6
.vscode/tasks.json vendored
View File

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

View File

@@ -1,273 +0,0 @@
#nullable enable
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Doors.Components;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
namespace Content.Benchmarks;
/// <summary>
/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
/// subscriptions
/// </summary>
[Virtual]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class ComponentQueryBenchmark
{
public const string Map = "Maps/atlas.yml";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new(10);
private EntityQuery<ItemComponent> _itemQuery;
private EntityQuery<ClothingComponent> _clothingQuery;
private EntityQuery<MapComponent> _mapQuery;
private EntityUid[] _items = default!;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
_mapQuery = _entMan.GetEntityQuery<MapComponent>();
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).GetAwaiter().GetResult();
_items = new EntityUid[_entMan.Count<ItemComponent>()];
var i = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out _))
{
_items[i++] = uid;
}
}
[GlobalCleanup]
public async Task Cleanup()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
#region TryComp
/// <summary>
/// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TryComp")]
public int TryComp()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_clothingQuery.TryGetComponent(uid, out var clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that is meant to always fail to get a component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int TryCompFail()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_mapQuery.TryGetComponent(uid, out var map))
hashCode = HashCode.Combine(hashCode, map.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that is meant to always succeed getting a component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int TryCompSucceed()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_itemQuery.TryGetComponent(uid, out var item))
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that uses `Resolve()` to try get the component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int Resolve()
{
var hashCode = 0;
foreach (var uid in _items)
{
DoResolve(uid, ref hashCode);
}
return hashCode;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
{
if (_clothingQuery.Resolve(uid, ref clothing, false))
hash = HashCode.Combine(hash, clothing.GetHashCode());
}
#endregion
#region Enumeration
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int SingleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var item))
{
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int DoubleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent>();
while (enumerator.MoveNext(out _, out var item))
{
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int TripleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent, TransformComponent>();
while (enumerator.MoveNext(out _, out _, out var xform))
{
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int SingleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent>();
while (enumerator.MoveNext(out var airlock))
{
hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int DoubleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent>();
while (enumerator.MoveNext(out _, out var door))
{
hashCode = HashCode.Combine(hashCode, door.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int TripleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent, TransformComponent>();
while (enumerator.MoveNext(out _, out _, out var xform))
{
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
}
return hashCode;
}
#endregion
[Benchmark(Baseline = true)]
[BenchmarkCategory("Events")]
public int StructEvents()
{
var ev = new QueryBenchEvent();
foreach (var uid in _items)
{
_entMan.EventBus.RaiseLocalEvent(uid, ref ev);
}
return ev.HashCode;
}
}
[ByRefEvent]
public struct QueryBenchEvent
{
public int HashCode;
}
public sealed class QueryBenchSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ClothingComponent, QueryBenchEvent>(OnEvent);
}
private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
{
args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
}
}

View File

@@ -47,7 +47,6 @@ namespace Content.Benchmarks
var componentFactory = new Mock<IComponentFactory>(); var componentFactory = new Mock<IComponentFactory>();
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent()); componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
componentFactory.Setup(m => m.GetIndex(typeof(DummyComponent))).Returns(CompIdx.Index<DummyComponent>());
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg); componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg);
componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg }); componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index<DummyComponent>() }); componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index<DummyComponent>() });

View File

@@ -0,0 +1,137 @@
#nullable enable
using System;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Benchmarks;
[Virtual]
public class EntityQueryBenchmark
{
public const string Map = "Maps/atlas.yml";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new MapId(10);
private EntityQuery<ClothingComponent> _clothingQuery;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).GetAwaiter().GetResult();
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
// Apparently ~40% of entities are items, and 1 in 6 of those are clothing.
/*
var entCount = _entMan.EntityCount;
var itemCount = _entMan.Count<ItemComponent>();
var clothingCount = _entMan.Count<ClothingComponent>();
var itemRatio = (float) itemCount / entCount;
var clothingRatio = (float) clothingCount / entCount;
Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}.");
*/
}
[GlobalCleanup]
public async Task Cleanup()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
[Benchmark]
public int HasComponent()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_entMan.HasComponent<ClothingComponent>(uid))
hashCode = HashCode.Combine(hashCode, uid.Id);
}
return hashCode;
}
[Benchmark]
public int HasComponentQuery()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_clothingQuery.HasComponent(uid))
hashCode = HashCode.Combine(hashCode, uid.Id);
}
return hashCode;
}
[Benchmark]
public int TryGetComponent()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
[Benchmark]
public int TryGetComponentQuery()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_clothingQuery.TryGetComponent(uid, out var clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Enumerate all entities with both an item and clothing component.
/// </summary>
[Benchmark]
public int Enumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent, ClothingComponent>();
while (enumerator.MoveNext(out var _, out var clothing))
{
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -26,7 +26,7 @@ public class MapLoadBenchmark
public void Setup() public void Setup()
{ {
ProgramShared.PathOffset = "../../../../"; ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(); PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var server = _pair.Server; var server = _pair.Server;
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown(); PoolManager.Shutdown();
} }
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog" }; public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" };
[ParamsSource(nameof(MapsSource))] [ParamsSource(nameof(MapsSource))]
public string Map; public string Map;

View File

@@ -1,17 +1,19 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Content.IntegrationTests; using Content.IntegrationTests;
using Content.IntegrationTests.Pair; using Content.IntegrationTests.Pair;
using Content.Server.Mind;
using Content.Server.Warps; using Content.Server.Warps;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.Analyzers; using Robust.Shared.Analyzers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
@@ -47,7 +49,7 @@ public class PvsBenchmark
#if !DEBUG #if !DEBUG
ProgramShared.PathOffset = "../../../../"; ProgramShared.PathOffset = "../../../../";
#endif #endif
PoolManager.Startup(); PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>(); _entMan = _pair.Server.ResolveDependency<IEntityManager>();
@@ -56,20 +58,15 @@ public class PvsBenchmark
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false); _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
_sys = _entMan.System<SharedTransformSystem>(); _sys = _entMan.System<SharedTransformSystem>();
SetupAsync().Wait();
}
private async Task SetupAsync()
{
// Spawn the map // Spawn the map
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42); _pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
await _pair.Server.WaitPost(() => _pair.Server.WaitPost(() =>
{ {
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _); var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success) if (!success)
throw new Exception("Map load failed"); throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId); _pair.Server.MapMan.DoMapInitialize(_mapId);
}); }).Wait();
// Get list of ghost warp positions // Get list of ghost warp positions
_spawns = _entMan.AllComponentsList<WarpPointComponent>() _spawns = _entMan.AllComponentsList<WarpPointComponent>()
@@ -79,19 +76,17 @@ public class PvsBenchmark
Array.Resize(ref _players, PlayerCount); Array.Resize(ref _players, PlayerCount);
// Spawn "Players" // Spawn "Players".
_players = await _pair.Server.AddDummySessions(PlayerCount); _pair.Server.WaitPost(() =>
await _pair.Server.WaitPost(() =>
{ {
var mind = _pair.Server.System<MindSystem>();
for (var i = 0; i < PlayerCount; i++) for (var i = 0; i < PlayerCount; i++)
{ {
var pos = _spawns[i % _spawns.Length]; var pos = _spawns[i % _spawns.Length];
var uid =_entMan.SpawnEntity("MobHuman", pos); var uid =_entMan.SpawnEntity("MobHuman", pos);
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear"); _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
mind.ControlMob(_players[i].UserId, uid); _players[i] = new DummySession{AttachedEntity = uid};
} }
}); }).Wait();
// Repeatedly move players around so that they "explore" the map and see lots of entities. // Repeatedly move players around so that they "explore" the map and see lots of entities.
// This will populate their PVS data with out-of-view entities. // This will populate their PVS data with out-of-view entities.
@@ -173,4 +168,20 @@ public class PvsBenchmark
}).Wait(); }).Wait();
_pair.Server.PvsTick(_players); _pair.Server.PvsTick(_players);
} }
private sealed class DummySession : ICommonSession
{
public SessionStatus Status => SessionStatus.InGame;
public EntityUid? AttachedEntity {get; set; }
public NetUserId UserId => default;
public string Name => string.Empty;
public short Ping => default;
public INetChannel Channel { get; set; } = default!;
public LoginType AuthType => default;
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
public DateTime ConnectedTime { get; set; }
public SessionState State => default!;
public SessionData Data => default!;
public bool ClientSide { get; set; }
}
} }

View File

@@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark
public async Task SetupAsync() public async Task SetupAsync()
{ {
ProgramShared.PathOffset = "../../../../"; ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(); PoolManager.Startup(null);
_pair = await PoolManager.GetServerClient(); _pair = await PoolManager.GetServerClient();
var server = _pair.Server; var server = _pair.Server;

View File

@@ -2,4 +2,6 @@
namespace Content.Client.Access; namespace Content.Client.Access;
public sealed class IdCardSystem : SharedIdCardSystem; public sealed class IdCardSystem : SharedIdCardSystem
{
}

View File

@@ -12,18 +12,11 @@ namespace Content.Client.Access.UI;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class AccessLevelControl : GridContainer public sealed partial class AccessLevelControl : GridContainer
{ {
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
public readonly Dictionary<ProtoId<AccessLevelPrototype>, Button> ButtonsList = new(); public readonly Dictionary<ProtoId<AccessLevelPrototype>, Button> ButtonsList = new();
public AccessLevelControl() public AccessLevelControl()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("accesslevelcontrol");
} }
public void Populate(List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager) public void Populate(List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
@@ -32,7 +25,7 @@ public sealed partial class AccessLevelControl : GridContainer
{ {
if (!prototypeManager.TryIndex(access, out var accessLevel)) if (!prototypeManager.TryIndex(access, out var accessLevel))
{ {
_sawmill.Error($"Unable to find accesslevel for {access}"); Logger.Error($"Unable to find accesslevel for {access}");
continue; continue;
} }

View File

@@ -2,7 +2,6 @@ using Content.Shared.Access;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.AccessOverriderComponent; using static Content.Shared.Access.Components.AccessOverriderComponent;
@@ -24,28 +23,6 @@ namespace Content.Client.Access.UI
{ {
base.Open(); base.Open();
_window = this.CreateWindow<AccessOverriderWindow>();
RefreshAccess();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
_window.OnSubmit += SubmitData;
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
}
public override void OnProtoReload(PrototypesReloadedEventArgs args)
{
base.OnProtoReload(args);
if (!args.WasModified<AccessLevelPrototype>())
return;
RefreshAccess();
if (State != null)
_window?.UpdateState(_prototypeManager, (AccessOverriderBoundUserInterfaceState) State);
}
private void RefreshAccess()
{
List<ProtoId<AccessLevelPrototype>> accessLevels; List<ProtoId<AccessLevelPrototype>> accessLevels;
if (EntMan.TryGetComponent<AccessOverriderComponent>(Owner, out var accessOverrider)) if (EntMan.TryGetComponent<AccessOverriderComponent>(Owner, out var accessOverrider))
@@ -53,20 +30,38 @@ namespace Content.Client.Access.UI
accessLevels = accessOverrider.AccessLevels; accessLevels = accessOverrider.AccessLevels;
accessLevels.Sort(); accessLevels.Sort();
} }
else else
{ {
accessLevels = new List<ProtoId<AccessLevelPrototype>>(); accessLevels = new List<ProtoId<AccessLevelPrototype>>();
_accessOverriderSystem.Log.Error($"No AccessOverrider component found for {EntMan.ToPrettyString(Owner)}!"); _accessOverriderSystem.Log.Error($"No AccessOverrider component found for {EntMan.ToPrettyString(Owner)}!");
} }
_window?.SetAccessLevels(_prototypeManager, accessLevels); _window = new AccessOverriderWindow(this, _prototypeManager, accessLevels)
{
Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName
};
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
_window.OnClose += Close;
_window.OpenCentered();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window?.Dispose();
} }
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
{ {
base.UpdateState(state); base.UpdateState(state);
var castState = (AccessOverriderBoundUserInterfaceState) state; var castState = (AccessOverriderBoundUserInterfaceState) state;
_window?.UpdateState(_prototypeManager, castState); _window?.UpdateState(castState);
} }
public void SubmitData(List<ProtoId<AccessLevelPrototype>> newAccessList) public void SubmitData(List<ProtoId<AccessLevelPrototype>> newAccessList)

View File

@@ -13,24 +13,26 @@ namespace Content.Client.Access.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class AccessOverriderWindow : DefaultWindow public sealed partial class AccessOverriderWindow : DefaultWindow
{ {
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly AccessOverriderBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new(); private readonly Dictionary<string, Button> _accessButtons = new();
public event Action<List<ProtoId<AccessLevelPrototype>>>? OnSubmit; public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
public AccessOverriderWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
} IoCManager.InjectDependencies(this);
var logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels) _owner = owner;
{
_accessButtons.Clear();
AccessLevelGrid.DisposeAllChildren();
foreach (var access in accessLevels) foreach (var access in accessLevels)
{ {
if (!protoManager.TryIndex(access, out var accessLevel)) if (!prototypeManager.TryIndex(access, out var accessLevel))
{ {
logMill.Error($"Unable to find accesslevel for {access}");
continue; continue;
} }
@@ -42,16 +44,11 @@ namespace Content.Client.Access.UI
AccessLevelGrid.AddChild(newButton); AccessLevelGrid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton); _accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ => newButton.OnPressed += _ => SubmitData();
{
OnSubmit?.Invoke(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId<AccessLevelPrototype>(x.Key)).ToList());
};
} }
} }
public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUserInterfaceState state) public void UpdateState(AccessOverriderBoundUserInterfaceState state)
{ {
PrivilegedIdLabel.Text = state.PrivilegedIdName; PrivilegedIdLabel.Text = state.PrivilegedIdName;
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
@@ -69,11 +66,11 @@ namespace Content.Client.Access.UI
if (state.MissingPrivilegesList != null && state.MissingPrivilegesList.Any()) if (state.MissingPrivilegesList != null && state.MissingPrivilegesList.Any())
{ {
var missingPrivileges = new List<string>(); List<string> missingPrivileges = new List<string>();
foreach (string tag in state.MissingPrivilegesList) foreach (string tag in state.MissingPrivilegesList)
{ {
var privilege = Loc.GetString(protoManager.Index<AccessLevelPrototype>(tag)?.Name ?? "generic-unknown"); string privilege = Loc.GetString(_prototypeManager.Index<AccessLevelPrototype>(tag)?.Name ?? "generic-unknown");
missingPrivileges.Add(privilege); missingPrivileges.Add(privilege);
} }
@@ -93,5 +90,13 @@ namespace Content.Client.Access.UI
} }
} }
} }
private void SubmitData()
{
_owner.SubmitData(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId<AccessLevelPrototype>(x.Key)).ToList());
}
} }
} }

View File

@@ -1,8 +1,5 @@
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Access.UI namespace Content.Client.Access.UI
{ {
@@ -21,11 +18,16 @@ namespace Content.Client.Access.UI
{ {
base.Open(); base.Open();
_window = this.CreateWindow<AgentIDCardWindow>(); _window?.Dispose();
_window = new AgentIDCardWindow(this);
if (State != null)
UpdateState(State);
_window.OpenCentered();
_window.OnClose += Close;
_window.OnNameChanged += OnNameChanged; _window.OnNameChanged += OnNameChanged;
_window.OnJobChanged += OnJobChanged; _window.OnJobChanged += OnJobChanged;
_window.OnJobIconChanged += OnJobIconChanged;
} }
private void OnNameChanged(string newName) private void OnNameChanged(string newName)
@@ -38,9 +40,9 @@ namespace Content.Client.Access.UI
SendMessage(new AgentIDCardJobChangedMessage(newJob)); SendMessage(new AgentIDCardJobChangedMessage(newJob));
} }
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId) public void OnJobIconChanged(string newJobIcon)
{ {
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId)); SendMessage(new AgentIDCardJobIconChangedMessage(newJobIcon));
} }
/// <summary> /// <summary>
@@ -55,7 +57,16 @@ namespace Content.Client.Access.UI
_window.SetCurrentName(cast.CurrentName); _window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob); _window.SetCurrentJob(cast.CurrentJob);
_window.SetAllowedIcons(cast.CurrentJobIconId); _window.SetAllowedIcons(cast.Icons);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window?.Dispose();
} }
} }
} }

View File

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

View File

@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Numerics; using System.Numerics;
using System.Linq;
namespace Content.Client.Access.UI namespace Content.Client.Access.UI
{ {
@@ -18,19 +17,19 @@ namespace Content.Client.Access.UI
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!; [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _spriteSystem; private readonly SpriteSystem _spriteSystem;
private readonly AgentIDCardBoundUserInterface _bui;
private const int JobIconColumnCount = 10; private const int JobIconColumnCount = 10;
public event Action<string>? OnNameChanged; public event Action<string>? OnNameChanged;
public event Action<string>? OnJobChanged; public event Action<string>? OnJobChanged;
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged; public AgentIDCardWindow(AgentIDCardBoundUserInterface bui)
public AgentIDCardWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>(); _spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
_bui = bui;
NameLineEdit.OnTextEntered += e => OnNameChanged?.Invoke(e.Text); NameLineEdit.OnTextEntered += e => OnNameChanged?.Invoke(e.Text);
NameLineEdit.OnFocusExit += e => OnNameChanged?.Invoke(e.Text); NameLineEdit.OnFocusExit += e => OnNameChanged?.Invoke(e.Text);
@@ -39,16 +38,19 @@ namespace Content.Client.Access.UI
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text); JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
} }
public void SetAllowedIcons(string currentJobIconId) public void SetAllowedIcons(HashSet<string> icons)
{ {
IconGrid.DisposeAllChildren(); IconGrid.DisposeAllChildren();
var jobIconButtonGroup = new ButtonGroup(); var jobIconGroup = new ButtonGroup();
var i = 0; var i = 0;
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList(); foreach (var jobIconId in icons)
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
foreach (var jobIcon in icons)
{ {
if (!_prototypeManager.TryIndex<StatusIconPrototype>(jobIconId, out var jobIcon))
{
continue;
}
String styleBase = StyleBase.ButtonOpenBoth; String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount; var modulo = i % JobIconColumnCount;
if (modulo == 0) if (modulo == 0)
@@ -62,13 +64,12 @@ namespace Content.Client.Access.UI
Access = AccessLevel.Public, Access = AccessLevel.Public,
StyleClasses = { styleBase }, StyleClasses = { styleBase },
MaxSize = new Vector2(42, 28), MaxSize = new Vector2(42, 28),
Group = jobIconButtonGroup, Group = jobIconGroup,
Pressed = currentJobIconId == jobIcon.ID, Pressed = i == 0,
ToolTip = jobIcon.LocalizedJobName
}; };
// Generate buttons textures // Generate buttons textures
var jobIconTexture = new TextureRect TextureRect jobIconTexture = new TextureRect
{ {
Texture = _spriteSystem.Frame0(jobIcon.Icon), Texture = _spriteSystem.Frame0(jobIcon.Icon),
TextureScale = new Vector2(2.5f, 2.5f), TextureScale = new Vector2(2.5f, 2.5f),
@@ -76,9 +77,8 @@ namespace Content.Client.Access.UI
}; };
jobIconButton.AddChild(jobIconTexture); jobIconButton.AddChild(jobIconTexture);
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID); jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID);
IconGrid.AddChild(jobIconButton); IconGrid.AddChild(jobIconButton);
i++; i++;
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Shared.Access; using Content.Shared.Access;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Access;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest; using Content.Shared.CrewManifest;

View File

@@ -27,9 +27,6 @@ namespace Content.Client.Access.UI
private string? _lastJobTitle; private string? _lastJobTitle;
private string? _lastJobProto; private string? _lastJobProto;
// The job that will be picked if the ID doesn't have a job on the station.
private static ProtoId<JobPrototype> _defaultJob = "Passenger";
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels) List<ProtoId<AccessLevelPrototype>> accessLevels)
{ {
@@ -68,6 +65,7 @@ namespace Content.Client.Access.UI
} }
JobPresetOptionButton.OnItemSelected += SelectJobPreset; JobPresetOptionButton.OnItemSelected += SelectJobPreset;
_accessButtons.Populate(accessLevels, prototypeManager); _accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons); AccessLevelControlContainer.AddChild(_accessButtons);
@@ -174,15 +172,11 @@ namespace Content.Client.Access.UI
new List<ProtoId<AccessLevelPrototype>>()); new List<ProtoId<AccessLevelPrototype>>());
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype); var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
// If the job index is < 0 that means they don't have a job registered in the station records. if (jobIndex >= 0)
// For example, a new ID from a box would have no job index.
if (jobIndex < 0)
{ {
jobIndex = _jobPrototypeIds.IndexOf(_defaultJob); JobPresetOptionButton.SelectId(jobIndex);
} }
JobPresetOptionButton.SelectId(jobIndex);
_lastFullName = state.TargetIdFullName; _lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle; _lastJobTitle = state.TargetIdJobTitle;
_lastJobProto = state.TargetIdJobPrototype; _lastJobProto = state.TargetIdJobPrototype;

View File

@@ -48,30 +48,6 @@ namespace Content.Client.Actions
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState); SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState); SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState); SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var worldActionQuery = EntityQueryEnumerator<WorldTargetActionComponent>();
while (worldActionQuery.MoveNext(out var uid, out var action))
{
UpdateAction(uid, action);
}
var instantActionQuery = EntityQueryEnumerator<InstantActionComponent>();
while (instantActionQuery.MoveNext(out var uid, out var action))
{
UpdateAction(uid, action);
}
var entityActionQuery = EntityQueryEnumerator<EntityTargetActionComponent>();
while (entityActionQuery.MoveNext(out var uid, out var action))
{
UpdateAction(uid, action);
}
} }
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args) private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
@@ -100,26 +76,12 @@ namespace Content.Client.Actions
BaseHandleState<WorldTargetActionComponent>(uid, component, state); BaseHandleState<WorldTargetActionComponent>(uid, component, state);
} }
private void OnEntityWorldTargetHandleState(EntityUid uid,
EntityWorldTargetActionComponent component,
ref ComponentHandleState args)
{
if (args.Current is not EntityWorldTargetActionComponentState state)
return;
component.Whitelist = state.Whitelist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
}
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{ {
// TODO ACTIONS use auto comp states // TODO ACTIONS use auto comp states
component.Icon = state.Icon; component.Icon = state.Icon;
component.IconOn = state.IconOn; component.IconOn = state.IconOn;
component.IconColor = state.IconColor; component.IconColor = state.IconColor;
component.OriginalIconColor = state.OriginalIconColor;
component.DisabledIconColor = state.DisabledIconColor;
component.Keywords.Clear(); component.Keywords.Clear();
component.Keywords.UnionWith(state.Keywords); component.Keywords.UnionWith(state.Keywords);
component.Enabled = state.Enabled; component.Enabled = state.Enabled;
@@ -145,13 +107,11 @@ namespace Content.Client.Actions
UpdateAction(uid, component); UpdateAction(uid, component);
} }
public override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null) protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{ {
if (!ResolveActionData(actionId, ref action)) if (!ResolveActionData(actionId, ref action))
return; return;
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
base.UpdateAction(actionId, action); base.UpdateAction(actionId, action);
if (_playerManager.LocalEntity != action.AttachedEntity) if (_playerManager.LocalEntity != action.AttachedEntity)
return; return;
@@ -286,6 +246,9 @@ namespace Content.Client.Actions
if (action.ClientExclusive) if (action.ClientExclusive)
{ {
if (instantAction.Event != null)
instantAction.Event.Performer = user;
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
} }
else else
@@ -327,7 +290,7 @@ namespace Content.Client.Actions
continue; continue;
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true); var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
var actionId = Spawn(); var actionId = Spawn(null);
AddComp(actionId, action); AddComp(actionId, action);
AddActionDirect(user, actionId); AddActionDirect(user, actionId);

View File

@@ -1,4 +1,4 @@
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -77,12 +77,9 @@ namespace Content.Client.Actions.UI
MaxWidth = TooltipTextMaxWidth, MaxWidth = TooltipTextMaxWidth,
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements} StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
}; };
requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" +
if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup)) requires +
return; "[/color]"));
requiresLabel.SetMessage(markup);
vbox.AddChild(requiresLabel); vbox.AddChild(requiresLabel);
} }
} }
@@ -100,11 +97,8 @@ namespace Content.Client.Actions.UI
if (timeLeft > TimeSpan.Zero) if (timeLeft > TimeSpan.Zero)
{ {
var duration = Cooldown.Value.End - Cooldown.Value.Start; var duration = Cooldown.Value.End - Cooldown.Value.Start;
_cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup)) $"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
return;
_cooldownLabel.SetMessage(markup);
_cooldownLabel.Visible = true; _cooldownLabel.Visible = true;
} }
else else

View File

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

View File

@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components; namespace Content.Client.Administration.Components;
[RegisterComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class HeadstandComponent : SharedHeadstandComponent public sealed partial class HeadstandComponent : SharedHeadstandComponent
{ {

View File

@@ -3,5 +3,6 @@ using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components; namespace Content.Client.Administration.Components;
[RegisterComponent] [NetworkedComponent, RegisterComponent]
public sealed partial class KillSignComponent : SharedKillSignComponent; public sealed partial class KillSignComponent : SharedKillSignComponent
{ }

View File

@@ -126,15 +126,12 @@ namespace Content.Client.Administration.Managers
public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false) public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
{ {
if (uid == _player.LocalEntity && (_adminData?.Active ?? includeDeAdmin)) return uid == _player.LocalEntity ? _adminData : null;
return _adminData;
return null;
} }
public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false) public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false)
{ {
if (_player.LocalUser == session.UserId && (_adminData?.Active ?? includeDeAdmin)) if (_player.LocalUser == session.UserId)
return _adminData; return _adminData;
return null; return null;

View File

@@ -1,7 +0,0 @@
using Content.Shared.Administration;
namespace Content.Client.Administration.Systems;
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
{
}

View File

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

View File

@@ -1,6 +1,3 @@
using Content.Shared.Administration;
using Content.Shared.Administration.Managers;
using Content.Shared.Mind.Components;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -14,12 +11,10 @@ namespace Content.Client.Administration.Systems
{ {
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!; [Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
[Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!; [Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
[Dependency] private readonly ISharedAdminManager _admin = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs); SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs);
} }
private void AddAdminVerbs(GetVerbsEvent<Verb> args) private void AddAdminVerbs(GetVerbsEvent<Verb> args)
@@ -38,24 +33,6 @@ namespace Content.Client.Administration.Systems
}; };
args.Verbs.Add(verb); args.Verbs.Add(verb);
} }
if (!_admin.IsAdmin(args.User))
return;
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
args.ExtraCategories.Add(VerbCategory.Admin);
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun) && HasComp<MindContainerComponent>(args.Target))
args.ExtraCategories.Add(VerbCategory.Antag);
if (_admin.HasAdminFlag(args.User, AdminFlags.Debug))
args.ExtraCategories.Add(VerbCategory.Debug);
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun))
args.ExtraCategories.Add(VerbCategory.Smite);
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
args.ExtraCategories.Add(VerbCategory.Tricks);
} }
} }
} }

View File

@@ -6,8 +6,7 @@
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs" xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab" xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab" xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab" xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
<TabContainer Name="MasterTabContainer"> <TabContainer Name="MasterTabContainer">
<adminTab:AdminTab /> <adminTab:AdminTab />
<adminbusTab:AdminbusTab /> <adminbusTab:AdminbusTab />
@@ -15,7 +14,6 @@
<tabs:RoundTab /> <tabs:RoundTab />
<tabs:ServerTab /> <tabs:ServerTab />
<panic:PanicBunkerTab Name="PanicBunkerControl" Access="Public" /> <panic:PanicBunkerTab Name="PanicBunkerControl" Access="Public" />
<baby:BabyJailTab Name="BabyJailControl" Access="Public" />
<playerTab:PlayerTab Name="PlayerTabControl" Access="Public" /> <playerTab:PlayerTab Name="PlayerTabControl" Access="Public" />
<objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" /> <objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" />
</TabContainer> </TabContainer>

View File

@@ -3,57 +3,34 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI; namespace Content.Client.Administration.UI
[GenerateTypedNameReferences]
public sealed partial class AdminMenuWindow : DefaultWindow
{ {
public event Action? OnDisposed; [GenerateTypedNameReferences]
public sealed partial class AdminMenuWindow : DefaultWindow
public AdminMenuWindow()
{ {
MinSize = new Vector2(650, 250); public event Action? OnDisposed;
Title = Loc.GetString("admin-menu-title");
RobustXamlLoader.Load(this);
MasterTabContainer.SetTabTitle((int) TabIndex.Admin, Loc.GetString("admin-menu-admin-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Adminbus, Loc.GetString("admin-menu-adminbus-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Atmos, Loc.GetString("admin-menu-atmos-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Round, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Server, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.PanicBunker, Loc.GetString("admin-menu-panic-bunker-tab"));
/*
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
MasterTabContainer.SetTabTitle((int) TabIndex.BabyJail, Loc.GetString("admin-menu-baby-jail-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Players, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Objects, Loc.GetString("admin-menu-objects-tab"));
MasterTabContainer.OnTabChanged += OnTabChanged;
}
private void OnTabChanged(int tabIndex) public AdminMenuWindow()
{ {
var tabEnum = (TabIndex)tabIndex; MinSize = new Vector2(650, 250);
if (tabEnum == TabIndex.Objects) Title = Loc.GetString("admin-menu-title");
ObjectsTabControl.RefreshObjectList(); RobustXamlLoader.Load(this);
} MasterTabContainer.SetTabTitle(0, Loc.GetString("admin-menu-admin-tab"));
MasterTabContainer.SetTabTitle(1, Loc.GetString("admin-menu-adminbus-tab"));
MasterTabContainer.SetTabTitle(2, Loc.GetString("admin-menu-atmos-tab"));
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
OnDisposed?.Invoke(); OnDisposed?.Invoke();
base.Dispose(disposing); base.Dispose(disposing);
OnDisposed = null; OnDisposed = null;
} }
private enum TabIndex
{
Admin = 0,
Adminbus,
Atmos,
Round,
Server,
PanicBunker,
BabyJail,
Players,
Objects,
} }
} }

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Content.Client.Administration.UI.CustomControls; using Content.Client.Administration.UI.CustomControls;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
@@ -12,7 +11,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -34,11 +32,8 @@ public sealed partial class BanPanel : DefaultWindow
// This is less efficient than just holding a reference to the root control and enumerating children, but you // This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated. // have to know how the controls are nested, which makes the code more complicated.
private readonly List<CheckBox> _roleCheckboxes = new(); private readonly List<CheckBox> _roleCheckboxes = new();
private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private enum TabNumbers private enum TabNumbers
{ {
@@ -70,7 +65,6 @@ public sealed partial class BanPanel : DefaultWindow
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged; PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged(); PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
PlayerCheckbox.OnPressed += _ => PlayerCheckbox.OnPressed += _ =>
@@ -110,11 +104,6 @@ public sealed partial class BanPanel : DefaultWindow
}; };
SubmitButton.OnPressed += SubmitButtonOnOnPressed; SubmitButton.OnPressed += SubmitButtonOnOnPressed;
IpCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanIpBanDefault);
HwidCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanHwidBanDefault);
LastConnCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanUseLastDetails);
EraseCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanErasePlayer);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None); SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor); SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium); SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium);
@@ -147,7 +136,7 @@ public sealed partial class BanPanel : DefaultWindow
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>()) foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{ {
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color); CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
} }
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red); CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
@@ -186,39 +175,6 @@ public sealed partial class BanPanel : DefaultWindow
c.Pressed = args.Pressed; c.Pressed = args.Pressed;
} }
} }
if (args.Pressed)
{
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banpanelSawmill
.Warning("Departmental role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
else
{
foreach (var childContainer in RolesContainer.Children)
{
if (childContainer is Container)
{
foreach (var child in childContainer.Children)
{
if (child is CheckBox { Pressed: true })
return;
}
}
}
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
}; };
outerContainer.AddChild(innerContainer); outerContainer.AddChild(innerContainer);
foreach (var role in roleList) foreach (var role in roleList)
@@ -397,35 +353,6 @@ public sealed partial class BanPanel : DefaultWindow
{ {
TypeOption.ModulateSelfOverride = null; TypeOption.ModulateSelfOverride = null;
Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role); Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role);
NoteSeverity? newSeverity = null;
switch (TypeOption.SelectedId)
{
case (int)Types.Server:
if (Enum.TryParse(_cfg.GetCVar(CCVars.ServerBanDefaultSeverity), true, out NoteSeverity serverSeverity))
newSeverity = serverSeverity;
else
{
_banpanelSawmill
.Warning("Server ban severity could not be parsed from config!");
}
break;
case (int) Types.Role:
if (Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity roleSeverity))
{
newSeverity = roleSeverity;
}
else
{
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
}
break;
}
if (newSeverity != null)
SeverityOption.SelectId((int) newSeverity.Value);
} }
private void UpdateSubmitEnabled() private void UpdateSubmitEnabled()

View File

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

View File

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

View File

@@ -16,25 +16,18 @@ namespace Content.Client.Administration.UI.Bwoink
Bwoink.ChannelSelector.OnSelectionChanged += sel => Bwoink.ChannelSelector.OnSelectionChanged += sel =>
{ {
if (sel is null) if (sel is not null)
{ {
Title = Loc.GetString("bwoink-title-none-selected"); Title = $"{sel.CharacterName} / {sel.Username}";
return;
}
Title = $"{sel.CharacterName} / {sel.Username}"; if (sel.OverallPlaytime != null)
{
if (sel.OverallPlaytime != null) Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
{ }
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
} }
}; };
OnOpen += () => OnOpen += () => Bwoink.PopulateList();
{
Bwoink.ChannelSelector.StopFiltering();
Bwoink.PopulateList();
};
} }
} }
} }

View File

@@ -5,7 +5,7 @@
<LineEdit Name="FilterLineEdit" <LineEdit Name="FilterLineEdit"
MinSize="100 0" MinSize="100 0"
HorizontalExpand="True" HorizontalExpand="True"
PlaceHolder="{Loc player-list-filter}"/> PlaceHolder="{Loc Filter}"/>
<PanelContainer Name="BackgroundPanel" <PanelContainer Name="BackgroundPanel"
VerticalExpand="True" VerticalExpand="True"
HorizontalExpand="True"> HorizontalExpand="True">

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
<Popup xmlns="https://spacestation14.io" <Popup xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"> xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer> <PanelContainer StyleClasses="BackgroundDark">
<PanelContainer.PanelOverride> <PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#18181B" BackgroundColor="#25252a"/> <gfx:StyleBoxFlat BorderThickness="1" BorderColor="#18181B"/>
</PanelContainer.PanelOverride> </PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" Margin="4 4 4 4"> <BoxContainer Orientation="Vertical">
<Label Name="PlayerNameLabel"/> <Label Name="PlayerNameLabel"/>
<Label Name="IdLabel"/> <Label Name="IdLabel"/>
<Label Name="TypeLabel"/> <Label Name="TypeLabel"/>

View File

@@ -1,36 +0,0 @@
<ui:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc ban-panel-title}" MinSize="300 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Name="PlayerName"/>
<Button Name="UsernameCopyButton" Text="{Loc player-panel-copy-username}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Name="Whitelisted"/>
<controls:ConfirmButton Name="WhitelistToggle" Text="{Loc 'player-panel-false'}" Visible="False"></controls:ConfirmButton>
</BoxContainer>
<Label Name="Playtime"/>
<Label Name="Notes"/>
<Label Name="Bans"/>
<Label Name="RoleBans"/>
<Label Name="SharedConnections"/>
<BoxContainer Align="Center">
<GridContainer Rows="5">
<Button Name="NotesButton" Text="{Loc player-panel-show-notes}" SetWidth="136" Disabled="True"/>
<Button Name="AhelpButton" Text="{Loc player-panel-help}" Disabled="True"/>
<Button Name="FreezeButton" Text = "{Loc player-panel-freeze}" Disabled="True"/>
<controls:ConfirmButton Name="KickButton" Text="{Loc player-panel-kick}" Disabled="True"/>
<controls:ConfirmButton Name="DeleteButton" Text="{Loc player-panel-delete}" Disabled="True"/>
<Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
<Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
</GridContainer>
</BoxContainer>
</BoxContainer>
</ui:FancyWindow>

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
public Matrix3x2 SpaceMatrix; public Matrix3 SpaceMatrix;
public MapId Map; public MapId Map;
private readonly Font _font; private readonly Font _font;
@@ -78,8 +78,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (SpaceTiles == null) if (SpaceTiles == null)
return; return;
Matrix3x2.Invert(SpaceMatrix, out var invSpace); gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds);
gridBounds = invSpace.TransformBox(args.WorldBounds);
DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize); DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize);
} }
@@ -87,7 +86,7 @@ public sealed class ExplosionDebugOverlay : Overlay
private void DrawText( private void DrawText(
DrawingHandleScreen handle, DrawingHandleScreen handle,
Box2 gridBounds, Box2 gridBounds,
Matrix3x2 transform, Matrix3 transform,
Dictionary<int, List<Vector2i>> tileSets, Dictionary<int, List<Vector2i>> tileSets,
ushort tileSize) ushort tileSize)
{ {
@@ -104,7 +103,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (!gridBounds.Contains(centre)) if (!gridBounds.Contains(centre))
continue; continue;
var worldCenter = Vector2.Transform(centre, transform); var worldCenter = transform.Transform(centre);
var screenCenter = _eyeManager.WorldToScreen(worldCenter); var screenCenter = _eyeManager.WorldToScreen(worldCenter);
@@ -120,7 +119,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (tileSets.TryGetValue(0, out var set)) if (tileSets.TryGetValue(0, out var set))
{ {
var epicenter = set.First(); var epicenter = set.First();
var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform); var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize);
var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24); var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24);
var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}"; var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}";
handle.DrawString(_font, screenCenter, text); handle.DrawString(_font, screenCenter, text);
@@ -149,12 +148,11 @@ public sealed class ExplosionDebugOverlay : Overlay
if (SpaceTiles == null) if (SpaceTiles == null)
return; return;
Matrix3x2.Invert(SpaceMatrix, out var invSpace); gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2);
gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2);
handle.SetTransform(SpaceMatrix); handle.SetTransform(SpaceMatrix);
DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize); DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize);
handle.SetTransform(Matrix3x2.Identity); handle.SetTransform(Matrix3.Identity);
} }
private void DrawTiles( private void DrawTiles(

View File

@@ -3,7 +3,6 @@ using Content.Shared.Explosion;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -23,7 +22,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IEntityManager _entMan = default!;
private readonly SharedTransformSystem _transform = default!;
private readonly SpawnExplosionEui _eui; private readonly SpawnExplosionEui _eui;
private List<MapId> _mapData = new(); private List<MapId> _mapData = new();
@@ -38,7 +37,6 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_transform = _entMan.System<TransformSystem>();
_eui = eui; _eui = eui;
ExplosionOption.OnItemSelected += ExplosionSelected; ExplosionOption.OnItemSelected += ExplosionSelected;
@@ -106,7 +104,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
_pausePreview = true; _pausePreview = true;
MapOptions.Select(_mapData.IndexOf(transform.MapID)); MapOptions.Select(_mapData.IndexOf(transform.MapID));
(MapX.Value, MapY.Value) = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: transform).Position; (MapX.Value, MapY.Value) = transform.MapPosition.Position;
_pausePreview = false; _pausePreview = false;
UpdatePreview(); UpdatePreview();

View File

@@ -4,7 +4,7 @@
Title="{Loc admin-player-actions-window-title}" MinSize="425 272"> Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-player-actions-reason}" MinWidth="100" /> <Label Text="{Loc Reason}" MinWidth="100" />
<Control MinWidth="50" /> <Control MinWidth="50" />
<LineEdit Name="ReasonLine" MinWidth="100" HorizontalExpand="True" /> <LineEdit Name="ReasonLine" MinWidth="100" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>

View File

@@ -1,9 +1,9 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc admin-ui-teleport}" MinSize="425 230"> Title="{Loc Teleport}" MinSize="425 230">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<cc:PlayerListControl Name="PlayerList" /> <cc:PlayerListControl Name="PlayerList" />
<Button Name="SubmitButton" Text="{Loc admin-ui-teleport}" /> <Button Name="SubmitButton" Text="{Loc Teleport}" />
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -1,33 +1,33 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" Title="{Loc admin-ui-blueprint-load}"> xmlns="https://spacestation14.io" Title="{Loc Load Blueprint}">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-blueprint-map}" MinSize="100 0" /> <Label Text="{Loc Map}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="MapOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="MapOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-blueprint-path}" MinSize="100 0" /> <Label Text="{Loc Path}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<LineEdit Name="MapPath" MinSize="200 0" HorizontalExpand="True" Text="/Maps/" /> <LineEdit Name="MapPath" MinSize="200 0" HorizontalExpand="True" Text="/Maps/" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-blueprint-x}" MinSize="100 0" /> <Label Text="{Loc X}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="XCoordinate" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="XCoordinate" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-blueprint-y}" MinSize="100 0" /> <Label Text="{Loc Y}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="YCoordinate" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="YCoordinate" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-blueprint-rotation}" MinSize="100 0" /> <Label Text="{Loc Rotation}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="RotationSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="RotationSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Button Name="SubmitButton" Text="{Loc admin-ui-blueprint-load}" /> <Button Name="SubmitButton" Text="{Loc Load Blueprint}" />
<Button Name="TeleportButton" Text="{Loc admin-ui-blueprint-teleport}" /> <Button Name="TeleportButton" Text="{Loc Teleport to}" />
<Button Name="ResetButton" Text="{Loc admin-ui-blueprint-reset}"></Button> <Button Name="ResetButton" Text="{Loc Reset to default}"></Button>
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -1,11 +1,11 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-add}"> xmlns="https://spacestation14.io" Title="{Loc Add Atmos}">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" /> <Label Text="{Loc Grid}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-add}" /> <Button Name="SubmitButton" Text="{Loc Add Atmos}" />
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -35,7 +35,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
while (query.MoveNext(out var uid, out var grid)) while (query.MoveNext(out var uid, out var grid))
{ {
_data.Add((uid, grid)); _data.Add((uid, grid));
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}"); GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
} }
GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id); GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id);

View File

@@ -1,31 +1,31 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-add-gas}"> xmlns="https://spacestation14.io" Title="{Loc Add Gas}">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" /> <Label Text="{Loc Grid}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-tile-x}" MinSize="100 0" /> <Label Text="{Loc TileX}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="TileXSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="TileXSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-tile-y}" MinSize="100 0" /> <Label Text="{Loc TileY}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="TileYSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="TileYSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-gas}" MinSize="100 0" /> <Label Text="{Loc Gas}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="GasOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="GasOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-gas-amount}" MinSize="100 0" /> <Label Text="{Loc Amount}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="AmountSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="AmountSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-add-gas}" /> <Button Name="SubmitButton" Text="{Loc Add Gas}" />
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -33,7 +33,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
_gridData.Add(entManager.GetNetEntity(uid)); _gridData.Add(entManager.GetNetEntity(uid));
var player = playerManager.LocalEntity; var player = playerManager.LocalEntity;
var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid; var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid;
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString("admin-ui-atmos-grid-current") : "")}"); GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
} }
GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id); GridOptions.OnItemSelected += eventArgs => GridOptions.SelectId(eventArgs.Id);

View File

@@ -6,10 +6,10 @@
Margin="4" Margin="4"
MinSize="50 50"> MinSize="50 50">
<GridContainer Columns="4"> <GridContainer Columns="4">
<cc:UICommandButton Text="{Loc admin-ui-atmos-add}" Command="addatmos" WindowType="{x:Type at:AddAtmosWindow}" /> <cc:UICommandButton Text="{Loc Add Atmos}" Command="addatmos" WindowType="{x:Type at:AddAtmosWindow}" />
<cc:UICommandButton Text="{Loc admin-ui-atmos-add-gas}" Command="addgas" WindowType="{x:Type at:AddGasWindow}" /> <cc:UICommandButton Text="{Loc Add Gas}" Command="addgas" WindowType="{x:Type at:AddGasWindow}" />
<cc:UICommandButton Text="{Loc admin-ui-atmos-fill-gas}" Command="fillgas" WindowType="{x:Type at:FillGasWindow}" /> <cc:UICommandButton Text="{Loc Fill Gas}" Command="fillgas" WindowType="{x:Type at:FillGasWindow}" />
<cc:UICommandButton Text="{Loc admin-ui-atmos-set-temperature}" Command="settemp" <cc:UICommandButton Text="{Loc Set Temperature}" Command="settemp"
WindowType="{x:Type at:SetTemperatureWindow}" /> WindowType="{x:Type at:SetTemperatureWindow}" />
</GridContainer> </GridContainer>
</Control> </Control>

View File

@@ -1,21 +1,21 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-fill-gas}"> xmlns="https://spacestation14.io" Title="{Loc Fill Gas}">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" /> <Label Text="{Loc Grid}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-gas}" MinSize="100 0" /> <Label Text="{Loc Gas}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="GasOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="GasOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-gas-amount}" MinSize="100 0" /> <Label Text="{Loc Amount}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="AmountSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="AmountSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-fill-gas}" /> <Button Name="SubmitButton" Text="{Loc Fill Gas}" />
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -36,7 +36,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
{ {
var player = playerManager.LocalEntity; var player = playerManager.LocalEntity;
var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid; var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid;
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}"); GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
_gridData.Add(entManager.GetNetEntity(uid)); _gridData.Add(entManager.GetNetEntity(uid));
} }

View File

@@ -1,26 +1,26 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" Title="{Loc admin-ui-atmos-set-temperature}"> xmlns="https://spacestation14.io" Title="{Loc Set Temperature}">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-grid}" MinSize="100 0" /> <Label Text="{Loc Grid}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" /> <OptionButton Name="GridOptions" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-tile-x}" MinSize="100 0" /> <Label Text="{Loc TileX}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="TileXSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="TileXSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-tile-y}" MinSize="100 0" /> <Label Text="{Loc TileY}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="TileYSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="TileYSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-ui-atmos-temperature}" MinSize="100 0" /> <Label Text="{Loc Temperature}" MinSize="100 0" />
<Control MinSize="50 0" /> <Control MinSize="50 0" />
<SpinBox Name="TemperatureSpin" MinSize="100 0" HorizontalExpand="True" /> <SpinBox Name="TemperatureSpin" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer> </BoxContainer>
<Button Name="SubmitButton" Text="{Loc admin-ui-atmos-set-temperature}" /> <Button Name="SubmitButton" Text="{Loc Set Temperature}" />
</BoxContainer> </BoxContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -32,7 +32,7 @@ namespace Content.Client.Administration.UI.Tabs.AtmosTab
{ {
var player = playerManager.LocalEntity; var player = playerManager.LocalEntity;
var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid; var playerGrid = entManager.GetComponentOrNull<TransformComponent>(player)?.GridUid;
GridOptions.AddItem($"{uid} {(playerGrid == uid ? Loc.GetString($"admin-ui-atmos-grid-current") : "")}"); GridOptions.AddItem($"{uid} {(playerGrid == uid ? " (Current)" : "")}");
_data.Add(entManager.GetNetEntity(uid)); _data.Add(entManager.GetNetEntity(uid));
} }

View File

@@ -1,6 +0,0 @@
<controls:BabyJailStatusWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
Title="{Loc admin-ui-baby-jail-window-title}">
<RichTextLabel Name="MessageLabel" Access="Public" />
</controls:BabyJailStatusWindow>

View File

@@ -1,21 +0,0 @@
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
/*
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
[GenerateTypedNameReferences]
public sealed partial class BabyJailStatusWindow : FancyWindow
{
public BabyJailStatusWindow()
{
RobustXamlLoader.Load(this);
MessageLabel.SetMarkup(Loc.GetString("admin-ui-baby-jail-is-enabled"));
}
}

View File

@@ -1,26 +0,0 @@
<controls:BabyJailTab
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Margin="4">
<BoxContainer Orientation="Vertical">
<cc:CommandButton Name="EnabledButton" Command="babyjail" ToggleMode="True"
Text="{Loc admin-ui-baby-jail-disabled}"
ToolTip="{Loc admin-ui-baby-jail-tooltip}" />
<cc:CommandButton Name="ShowReasonButton" Command="babyjail_show_reason"
ToggleMode="True" Text="{Loc admin-ui-baby-jail-show-reason}"
ToolTip="{Loc admin-ui-baby-jail-show-reason-tooltip}" />
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-baby-jail-max-account-age}" MinWidth="175" />
<LineEdit Name="MaxAccountAge" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-baby-jail-max-overall-minutes}" MinWidth="175" />
<LineEdit Name="MaxOverallMinutes" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:BabyJailTab>

View File

@@ -1,75 +0,0 @@
using Content.Shared.Administration.Events;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
/*
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
[GenerateTypedNameReferences]
public sealed partial class BabyJailTab : Control
{
[Dependency] private readonly IConsoleHost _console = default!;
private string _maxAccountAge;
private string _maxOverallMinutes;
public BabyJailTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
_maxAccountAge = MaxAccountAge.Text;
MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
_maxOverallMinutes = MaxOverallMinutes.Text;
}
private void SendMaxAccountAge(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _maxAccountAge ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"babyjail_max_account_age {minutes}");
}
private void SendMaxOverallMinutes(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _maxOverallMinutes ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
}
public void UpdateStatus(BabyJailStatus status)
{
EnabledButton.Pressed = status.Enabled;
EnabledButton.Text = Loc.GetString(status.Enabled
? "admin-ui-baby-jail-enabled"
: "admin-ui-baby-jail-disabled"
);
EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
ShowReasonButton.Pressed = status.ShowReason;
MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
_maxAccountAge = MaxAccountAge.Text;
MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
_maxOverallMinutes = MaxOverallMinutes.Text;
}
}

View File

@@ -1,20 +1,15 @@
<Control xmlns="https://spacestation14.io" <Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc object-tab-object-type}" /> <Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
<OptionButton Name="ObjectTypeOptions" HorizontalAlignment="Left" /> Text="{Loc Object type:}" />
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc object-tab-object-search}" HorizontalExpand="True" <OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
SizeFlagsStretchRatio="1" />
<Button Name="RefreshListButton" Text="{Loc object-tab-refresh-button}" ToggleMode="False" />
</BoxContainer>
<cc:HSeparator />
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<ot:ObjectsTabHeader Name="ListHeader" />
<cc:HSeparator />
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True" />
</BoxContainer> </BoxContainer>
<cc:HSeparator/>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ObjectList">
</BoxContainer>
</ScrollContainer>
</BoxContainer> </BoxContainer>
</Control> </Control>

View File

@@ -1,30 +1,29 @@
using Content.Client.Administration.Managers;
using Content.Client.Station; using Content.Client.Station;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab; namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control public sealed partial class ObjectsTab : Control
{ {
[Dependency] private readonly IClientAdminManager _admin = default!; [Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
private readonly Color _altColor = Color.FromHex("#292B38"); private readonly List<ObjectsTabEntry> _objects = new();
private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); private List<ObjectsTabSelection> _selections = new();
private bool _ascending; public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
private readonly List<ObjectsTabSelection> _selections = []; // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown; // OR
// I can do this.
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
public ObjectsTab() public ObjectsTab()
{ {
@@ -37,35 +36,16 @@ public sealed partial class ObjectsTab : Control
RefreshObjectList(_selections[ev.Id]); RefreshObjectList(_selections[ev.Id]);
}; };
foreach (var type in Enum.GetValues<ObjectsTabSelection>()) foreach (var type in Enum.GetValues(typeof(ObjectsTabSelection)))
{ {
_selections.Add(type); _selections.Add((ObjectsTabSelection)type!);
ObjectTypeOptions.AddItem(GetLocalizedEnumValue(type)); ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
} }
ListHeader.OnHeaderClicked += HeaderClicked; RefreshObjectList();
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshListButton.OnPressed += _ => RefreshObjectList();
var defaultSelection = ObjectsTabSelection.Grids;
ObjectTypeOptions.SelectId((int)defaultSelection);
RefreshObjectList(defaultSelection);
} }
private void TeleportTo(NetEntity nent) private void RefreshObjectList()
{
_console.ExecuteCommand($"tpto {nent}");
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
}
public void RefreshObjectList()
{ {
RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]); RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
} }
@@ -95,96 +75,41 @@ public sealed partial class ObjectsTab : Control
{ {
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid))); entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
} }
break; break;
} }
default: default:
throw new ArgumentOutOfRangeException(nameof(selection), selection, null); throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
} }
entities.Sort((a, b) => foreach (var control in _objects)
{ {
var valueA = GetComparableValue(a, _headerClicked); ObjectList.RemoveChild(control);
var valueB = GetComparableValue(b, _headerClicked);
return _ascending
? Comparer<object>.Default.Compare(valueA, valueB)
: Comparer<object>.Default.Compare(valueB, valueA);
});
var listData = new List<ObjectsListData>();
for (var index = 0; index < entities.Count; index++)
{
var info = entities[index];
listData.Add(new ObjectsListData(info,
$"{info.Name} {info.Entity}",
index % 2 == 0 ? _altColor : _defaultColor));
} }
SearchList.PopulateList(listData); _objects.Clear();
foreach (var (name, nent) in entities)
{
var ctrl = new ObjectsTabEntry(name, nent);
_objects.Add(ctrl);
ObjectList.AddChild(ctrl);
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
}
} }
private void GenerateButton(ListData data, ListContainerButton button) protected override void FrameUpdate(FrameEventArgs args)
{ {
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor }) base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
return; return;
var entry = new ObjectsTabEntry(_admin, info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor }); // I do not care for precision.
entry.OnTeleport += TeleportTo; _nextUpdate = _timing.CurTime + _updateFrequency;
entry.OnDelete += Delete;
button.ToolTip = $"{info.Name}, {info.Entity}";
button.AddChild(entry);
}
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not ObjectsListData { FilteringString: var filteringString })
return false;
// If the filter is empty, do not filter out any entries
if (string.IsNullOrEmpty(filter))
return true;
return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
}
private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
{
return header switch
{
ObjectsTabHeader.Header.ObjectName => entity.Name,
ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
_ => entity.Name,
};
}
private void HeaderClicked(ObjectsTabHeader.Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
RefreshObjectList(); RefreshObjectList();
} }
private string GetLocalizedEnumValue(ObjectsTabSelection selection)
{
return selection switch
{
ObjectsTabSelection.Grids => Loc.GetString("object-tab-object-type-grids"),
ObjectsTabSelection.Maps => Loc.GetString("object-tab-object-type-maps"),
ObjectsTabSelection.Stations => Loc.GetString("object-tab-object-type-stations"),
_ => throw new ArgumentOutOfRangeException(nameof(selection), selection, null),
};
}
private enum ObjectsTabSelection private enum ObjectsTabSelection
{ {
Grids, Grids,
@@ -193,5 +118,3 @@ public sealed partial class ObjectsTab : Control
} }
} }
public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor)
: ListData;

View File

@@ -1,29 +1,17 @@
<PanelContainer xmlns="https://spacestation14.io" <ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
Name="BackgroundColorPanel"> <PanelContainer Name="BackgroundColorPanel"/>
<BoxContainer Orientation="Horizontal" <BoxContainer Orientation="Horizontal"
HorizontalExpand="True" HorizontalExpand="True"
SeparationOverride="4"> SeparationOverride="4">
<Label Name="NameLabel" <Label Name="NameLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="EIDLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="TeleportButton"
Text="{Loc object-tab-entity-teleport}"
SizeFlagsStretchRatio="3" SizeFlagsStretchRatio="3"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True"/> ClipText="True"/>
<customControls:VSeparator/> <customControls:VSeparator/>
<Button Name="DeleteButton" <Label Name="EIDLabel"
Text="{Loc object-tab-entity-delete}" SizeFlagsStretchRatio="3"
SizeFlagsStretchRatio="3" HorizontalExpand="True"
HorizontalExpand="True" ClipText="True"/>
ClipText="True"/>
</BoxContainer> </BoxContainer>
</PanelContainer> </ContainerButton>

View File

@@ -1,40 +1,19 @@
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.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab; namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ObjectsTabEntry : PanelContainer public sealed partial class ObjectsTabEntry : ContainerButton
{ {
public NetEntity AssocEntity; public NetEntity AssocEntity;
public Action<NetEntity>? OnTeleport; public ObjectsTabEntry(string name, NetEntity nent)
public Action<NetEntity>? OnDelete;
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
AssocEntity = nent; AssocEntity = nent;
EIDLabel.Text = nent.ToString(); EIDLabel.Text = nent.ToString();
NameLabel.Text = name; NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox;
TeleportButton.Disabled = !manager.CanCommand("tpto");
DeleteButton.Disabled = !manager.CanCommand("delete");
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
DeleteButton.OnPressed += _ =>
{
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
{
return;
}
OnDelete?.Invoke(nent);
};
} }
} }

View File

@@ -1,27 +0,0 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel" Access="Public"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="ObjectNameLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-object-name}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="EntityIDLabel"
SizeFlagsStretchRatio="5"
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>

View File

@@ -1,86 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab
{
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabHeader : Control
{
public event Action<Header>? OnHeaderClicked;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
public ObjectsTabHeader()
{
RobustXamlLoader.Load(this);
ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
EntityIDLabel.OnKeyBindDown += EntityIDClicked;
}
public Label GetHeader(Header header)
{
return header switch
{
Header.ObjectName => ObjectNameLabel,
Header.EntityID => EntityIDLabel,
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
};
}
public void ResetHeaderText()
{
ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
}
public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
{
ResetHeaderText();
var arrow = ascending ? ArrowUp : ArrowDown;
GetHeader(headerClicked).Text += $" {arrow}";
}
private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
{
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
OnHeaderClicked?.Invoke(header);
args.Handle();
}
private void ObjectNameClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.ObjectName);
}
private void EntityIDClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.EntityID);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
}
}
public enum Header
{
ObjectName,
EntityID
}
}
}

View File

@@ -31,12 +31,12 @@
<BoxContainer Orientation="Horizontal" Margin="2"> <BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-account-age}" MinWidth="175" /> <Label Text="{Loc admin-ui-panic-bunker-min-account-age}" MinWidth="175" />
<LineEdit Name="MinAccountAge" MinWidth="50" Margin="0 0 5 0" /> <LineEdit Name="MinAccountAge" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" /> <Label Text="{Loc generic-hours}" />
</BoxContainer> </BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2"> <BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-overall-minutes}" MinWidth="175" /> <Label Text="{Loc admin-ui-panic-bunker-min-overall-hours}" MinWidth="175" />
<LineEdit Name="MinOverallMinutes" MinWidth="50" Margin="0 0 5 0" /> <LineEdit Name="MinOverallHours" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" /> <Label Text="{Loc generic-hours}" />
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>

View File

@@ -12,7 +12,7 @@ public sealed partial class PanicBunkerTab : Control
[Dependency] private readonly IConsoleHost _console = default!; [Dependency] private readonly IConsoleHost _console = default!;
private string _minAccountAge; private string _minAccountAge;
private string _minOverallMinutes; private string _minOverallHours;
public PanicBunkerTab() public PanicBunkerTab()
{ {
@@ -25,9 +25,9 @@ public sealed partial class PanicBunkerTab : Control
MinAccountAge.OnFocusExit += args => SendMinAccountAge(args.Text); MinAccountAge.OnFocusExit += args => SendMinAccountAge(args.Text);
_minAccountAge = MinAccountAge.Text; _minAccountAge = MinAccountAge.Text;
MinOverallMinutes.OnTextEntered += args => SendMinOverallMinutes(args.Text); MinOverallHours.OnTextEntered += args => SendMinOverallHours(args.Text);
MinOverallMinutes.OnFocusExit += args => SendMinOverallMinutes(args.Text); MinOverallHours.OnFocusExit += args => SendMinOverallHours(args.Text);
_minOverallMinutes = MinOverallMinutes.Text; _minOverallHours = MinOverallHours.Text;
} }
private void SendMinAccountAge(string text) private void SendMinAccountAge(string text)
@@ -42,16 +42,16 @@ public sealed partial class PanicBunkerTab : Control
_console.ExecuteCommand($"panicbunker_min_account_age {minutes}"); _console.ExecuteCommand($"panicbunker_min_account_age {minutes}");
} }
private void SendMinOverallMinutes(string text) private void SendMinOverallHours(string text)
{ {
if (string.IsNullOrWhiteSpace(text) || if (string.IsNullOrWhiteSpace(text) ||
text == _minOverallMinutes || text == _minOverallHours ||
!int.TryParse(text, out var minutes)) !int.TryParse(text, out var hours))
{ {
return; return;
} }
_console.ExecuteCommand($"panicbunker_min_overall_minutes {minutes}"); _console.ExecuteCommand($"panicbunker_min_overall_hours {hours}");
} }
public void UpdateStatus(PanicBunkerStatus status) public void UpdateStatus(PanicBunkerStatus status)
@@ -68,10 +68,10 @@ public sealed partial class PanicBunkerTab : Control
CountDeadminnedButton.Pressed = status.CountDeadminnedAdmins; CountDeadminnedButton.Pressed = status.CountDeadminnedAdmins;
ShowReasonButton.Pressed = status.ShowReason; ShowReasonButton.Pressed = status.ShowReason;
MinAccountAge.Text = status.MinAccountAgeMinutes.ToString(); MinAccountAge.Text = status.MinAccountAgeHours.ToString();
_minAccountAge = MinAccountAge.Text; _minAccountAge = MinAccountAge.Text;
MinOverallMinutes.Text = status.MinOverallMinutes.ToString(); MinOverallHours.Text = status.MinOverallHours.ToString();
_minOverallMinutes = MinOverallMinutes.Text; _minOverallHours = MinOverallHours.Text;
} }
} }

View File

@@ -1,19 +1,21 @@
<Control xmlns="https://spacestation14.io" <Control xmlns="https://spacestation14.io"
xmlns:pt="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab" xmlns:pt="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Name="PlayerCount" HorizontalExpand="True" Text="{Loc player-tab-player-count}" /> <Label Name="PlayerCount" HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
<LineEdit Name="SearchLineEdit" HorizontalExpand="True" Text="{Loc Player Count}" />
PlaceHolder="{Loc player-tab-filter-line-edit-placeholder}" /> <Button Name="ShowDisconnectedButton" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"
<Button Name="ShowDisconnectedButton" HorizontalExpand="True" Text="{Loc player-tab-show-disconnected}" ToggleMode="True"/>
Text="{Loc player-tab-show-disconnected}" ToggleMode="True" /> <Button Name="OverlayButton" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"
<Button Name="OverlayButton" HorizontalExpand="True" Text="{Loc player-tab-overlay}" ToggleMode="True" /> Text="{Loc player-tab-overlay}" ToggleMode="True"/>
</BoxContainer> </BoxContainer>
<Control MinSize="0 5"/> <Control MinSize="0 5" />
<pt:PlayerTabHeader Name="ListHeader"/> <ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<cc:HSeparator/> <BoxContainer Orientation="Vertical" Name="PlayerList">
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/> <pt:PlayerTabHeader Name="ListHeader" />
<cc:HSeparator />
</BoxContainer>
</ScrollContainer>
</BoxContainer> </BoxContainer>
</Control> </Control>

View File

@@ -1,6 +1,5 @@
using System.Linq; using System.Linq;
using Content.Client.Administration.Systems; using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -10,219 +9,170 @@ using Robust.Client.UserInterface.XAML;
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader; using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
using static Robust.Client.UserInterface.Controls.BaseButton; using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.Tabs.PlayerTab; namespace Content.Client.Administration.UI.Tabs.PlayerTab
[GenerateTypedNameReferences]
public sealed partial class PlayerTab : Control
{ {
[Dependency] private readonly IEntityManager _entManager = default!; [GenerateTypedNameReferences]
[Dependency] private readonly IPlayerManager _playerMan = default!; public sealed partial class PlayerTab : Control
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
private Header _headerClicked = Header.Username;
private bool _ascending = true;
private bool _showDisconnected;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
public PlayerTab()
{ {
IoCManager.InjectDependencies(this); [Dependency] private readonly IEntityManager _entManager = default!;
RobustXamlLoader.Load(this); [Dependency] private readonly IPlayerManager _playerMan = default!;
_adminSystem = _entManager.System<AdminSystem>(); private const string ArrowUp = "↑";
_adminSystem.PlayerListChanged += RefreshPlayerList; private const string ArrowDown = "↓";
_adminSystem.OverlayEnabled += OverlayEnabled; private readonly Color _altColor = Color.FromHex("#292B38");
_adminSystem.OverlayDisabled += OverlayDisabled; private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
OverlayButton.OnPressed += OverlayButtonPressed; private Header _headerClicked = Header.Username;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed; private bool _ascending = true;
private bool _showDisconnected;
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor); public event Action<PlayerTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit; public PlayerTab()
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
private void OverlayEnabled()
{
OverlayButton.Pressed = true;
}
private void OverlayDisabled()
{
OverlayButton.Pressed = false;
}
private void OverlayButtonPressed(ButtonEventArgs args)
{
if (args.Button.Pressed)
{ {
_adminSystem.AdminOverlayOn(); IoCManager.InjectDependencies(this);
} _adminSystem = _entManager.System<AdminSystem>();
else RobustXamlLoader.Load(this);
{ RefreshPlayerList(_adminSystem.PlayerList);
_adminSystem.AdminOverlayOff();
}
}
#endregion _adminSystem.PlayerListChanged += RefreshPlayerList;
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
private void ShowDisconnectedPressed(ButtonEventArgs args) OverlayButton.OnPressed += OverlayButtonPressed;
{ ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
protected override void Dispose(bool disposing) ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
{ ListHeader.OnHeaderClicked += HeaderClicked;
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;
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; private void OverlayEnabled()
}
private bool IsAllLower(string input)
{
foreach (var c in input)
{ {
if (char.IsLetter(c) && !char.IsLower(c)) OverlayButton.Pressed = true;
return false;
} }
return true; private void OverlayDisabled()
}
#endregion
#region Header
private void UpdateHeaderSymbols()
{
ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
}
private int Compare(PlayerInfo x, PlayerInfo y)
{
if (!_ascending)
{ {
(x, y) = (y, x); OverlayButton.Pressed = false;
} }
return _headerClicked switch private void OverlayButtonPressed(ButtonEventArgs args)
{ {
Header.Username => Compare(x.Username, y.Username), if (args.Button.Pressed)
Header.Character => Compare(x.CharacterName, y.CharacterName), {
Header.Job => Compare(x.StartingJob, y.StartingJob), _adminSystem.AdminOverlayOn();
Header.Antagonist => x.Antag.CompareTo(y.Antag), }
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default), else
_ => 1 {
}; _adminSystem.AdminOverlayOff();
} }
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); private void ShowDisconnectedPressed(ButtonEventArgs args)
} {
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
#endregion 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;
}
}
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
foreach (var child in PlayerList.Children.ToArray())
{
if (child is PlayerTabEntry)
child.Dispose();
}
_players = players;
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
var sortedPlayers = new List<PlayerInfo>(players);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
var useAltColor = false;
foreach (var player in sortedPlayers)
{
if (!_showDisconnected && !player.Connected)
continue;
var entry = new PlayerTabEntry(player.Username,
player.CharacterName,
player.IdentityName,
player.StartingJob,
player.Antag ? "YES" : "NO",
new StyleBoxFlat(useAltColor ? _altColor : _defaultColor),
player.Connected,
player.PlaytimeString);
entry.PlayerEntity = player.NetEntity;
entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(entry, args);
entry.ToolTip = Loc.GetString("player-tab-entry-tooltip");
PlayerList.AddChild(entry);
useAltColor ^= true;
}
}
private void UpdateHeaderSymbols()
{
ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
}
private int Compare(PlayerInfo x, PlayerInfo y)
{
if (!_ascending)
{
(x, y) = (y, x);
}
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
};
}
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);
}
}
} }
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;

View File

@@ -1,6 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io" <ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
Name="BackgroundColorPanel"> <PanelContainer Name="BackgroundColorPanel"/>
<BoxContainer Orientation="Horizontal" <BoxContainer Orientation="Horizontal"
HorizontalExpand="True" HorizontalExpand="True"
SeparationOverride="4"> SeparationOverride="4">
@@ -15,18 +15,17 @@
ClipText="True"/> ClipText="True"/>
<customControls:VSeparator/> <customControls:VSeparator/>
<Label Name="JobLabel" <Label Name="JobLabel"
SizeFlagsStretchRatio="2" SizeFlagsStretchRatio="3"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True"/> ClipText="True"/>
<customControls:VSeparator/> <customControls:VSeparator/>
<Label Name="AntagonistLabel" <Label Name="AntagonistLabel"
SizeFlagsStretchRatio="1" SizeFlagsStretchRatio="2"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True"/> ClipText="True"/>
<customControls:VSeparator/>
<Label Name="OverallPlaytimeLabel" <Label Name="OverallPlaytimeLabel"
SizeFlagsStretchRatio="1" SizeFlagsStretchRatio="2"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True"/> ClipText="True"/>
</BoxContainer> </BoxContainer>
</PanelContainer> </ContainerButton>

View File

@@ -1,5 +1,4 @@
using Content.Shared.Administration; using Robust.Client.AutoGenerated;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -7,24 +6,23 @@ using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.PlayerTab; namespace Content.Client.Administration.UI.Tabs.PlayerTab;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class PlayerTabEntry : PanelContainer public sealed partial class PlayerTabEntry : ContainerButton
{ {
public NetEntity? PlayerEntity; public NetEntity? PlayerEntity;
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat) public PlayerTabEntry(string username, string character, string identity, string job, string antagonist, StyleBox styleBox, bool connected, string overallPlaytime)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
UsernameLabel.Text = player.Username; UsernameLabel.Text = username;
if (!player.Connected) if (!connected)
UsernameLabel.StyleClasses.Add("Disabled"); UsernameLabel.StyleClasses.Add("Disabled");
JobLabel.Text = player.StartingJob; JobLabel.Text = job;
CharacterLabel.Text = player.CharacterName; CharacterLabel.Text = character;
if (player.IdentityName != player.CharacterName) if (identity != character)
CharacterLabel.Text += $" [{player.IdentityName}]"; CharacterLabel.Text += $" [{identity}]";
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no"); AntagonistLabel.Text = antagonist;
BackgroundColorPanel.PanelOverride = styleBoxFlat; BackgroundColorPanel.PanelOverride = styleBox;
OverallPlaytimeLabel.Text = player.PlaytimeString; OverallPlaytimeLabel.Text = overallPlaytime;
PlayerEntity = player.NetEntity;
} }
} }

View File

@@ -19,25 +19,23 @@
MouseFilter="Pass"/> MouseFilter="Pass"/>
<cc:VSeparator/> <cc:VSeparator/>
<Label Name="JobLabel" <Label Name="JobLabel"
SizeFlagsStretchRatio="2" SizeFlagsStretchRatio="3"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True" ClipText="True"
Text="{Loc player-tab-job}" Text="{Loc player-tab-job}"
MouseFilter="Pass"/> MouseFilter="Pass"/>
<cc:VSeparator/> <cc:VSeparator/>
<Label Name="AntagonistLabel" <Label Name="AntagonistLabel"
SizeFlagsStretchRatio="1" SizeFlagsStretchRatio="2"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True" ClipText="True"
Text="{Loc player-tab-antagonist}" Text="{Loc player-tab-antagonist}"
MouseFilter="Pass"/> MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="PlaytimeLabel" <Label Name="PlaytimeLabel"
SizeFlagsStretchRatio="1" SizeFlagsStretchRatio="2"
HorizontalExpand="True" HorizontalExpand="True"
ClipText="True" ClipText="True"
Text="{Loc player-tab-playtime}" Text="{Loc player-tab-playtime}"
MouseFilter="Pass" MouseFilter="Pass"/>
ToolTip="{Loc player-tab-entry-tooltip}"/>
</BoxContainer> </BoxContainer>
</Control> </Control>

View File

@@ -5,9 +5,9 @@
MinSize="50 50"> MinSize="50 50">
<GridContainer <GridContainer
Columns="3"> Columns="3">
<cc:CommandButton Command="startround" Text="{Loc administration-ui-round-tab-start-round}" /> <cc:CommandButton Command="startround" Text="{Loc Start Round}" />
<cc:CommandButton Command="endround" Text="{Loc administration-ui-round-tab-end-round}" /> <cc:CommandButton Command="endround" Text="{Loc End Round}" />
<cc:CommandButton Command="restartround" Text="{Loc administration-ui-round-tab-restart-round}" /> <cc:CommandButton Command="restartround" Text="{Loc Restart Round}" />
<cc:CommandButton Command="restartroundnow" Text="{Loc administration-ui-round-tab-restart-round-now}" /> <cc:CommandButton Command="restartroundnow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
</GridContainer> </GridContainer>
</Control> </Control>

View File

@@ -91,8 +91,8 @@ public sealed class ClientAlertsSystem : AlertsSystem
ClearAlerts?.Invoke(this, EventArgs.Empty); ClearAlerts?.Invoke(this, EventArgs.Empty);
} }
public void AlertClicked(ProtoId<AlertPrototype> alertType) public void AlertClicked(AlertType alertType)
{ {
RaisePredictiveEvent(new ClickAlertEvent(alertType)); RaiseNetworkEvent(new ClickAlertEvent(alertType));
} }
} }

View File

@@ -1,6 +1,5 @@
using Content.Shared.Ame.Components; using Content.Shared.Ame.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Ame.UI namespace Content.Client.Ame.UI
{ {
@@ -17,8 +16,9 @@ namespace Content.Client.Ame.UI
{ {
base.Open(); base.Open();
_window = this.CreateWindow<AmeWindow>(); _window = new AmeWindow(this);
_window.OnAmeButton += ButtonPressed; _window.OnClose += Close;
_window.OpenCentered();
} }
/// <summary> /// <summary>
@@ -40,5 +40,15 @@ namespace Content.Client.Ame.UI
{ {
SendMessage(new UiButtonPressedMessage(button)); SendMessage(new UiButtonPressedMessage(button));
} }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
} }
} }

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Content.Client.UserInterface; using Content.Client.UserInterface;
using Content.Shared.Ame.Components; using Content.Shared.Ame.Components;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
@@ -10,17 +9,15 @@ namespace Content.Client.Ame.UI
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class AmeWindow : DefaultWindow public sealed partial class AmeWindow : DefaultWindow
{ {
public event Action<UiButton>? OnAmeButton; public AmeWindow(AmeControllerBoundUserInterface ui)
public AmeWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
EjectButton.OnPressed += _ => OnAmeButton?.Invoke(UiButton.Eject); EjectButton.OnPressed += _ => ui.ButtonPressed(UiButton.Eject);
ToggleInjection.OnPressed += _ => OnAmeButton?.Invoke(UiButton.ToggleInjection); ToggleInjection.OnPressed += _ => ui.ButtonPressed(UiButton.ToggleInjection);
IncreaseFuelButton.OnPressed += _ => OnAmeButton?.Invoke(UiButton.IncreaseFuel); IncreaseFuelButton.OnPressed += _ => ui.ButtonPressed(UiButton.IncreaseFuel);
DecreaseFuelButton.OnPressed += _ => OnAmeButton?.Invoke(UiButton.DecreaseFuel); DecreaseFuelButton.OnPressed += _ => ui.ButtonPressed(UiButton.DecreaseFuel);
} }
/// <summary> /// <summary>
@@ -32,7 +29,7 @@ namespace Content.Client.Ame.UI
var castState = (AmeControllerBoundUserInterfaceState) state; var castState = (AmeControllerBoundUserInterfaceState) state;
// Disable all buttons if not powered // Disable all buttons if not powered
if (Contents.Children.Any()) if (Contents.Children != null)
{ {
ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower); ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false; EjectButton.Disabled = false;
@@ -68,8 +65,8 @@ namespace Content.Client.Ame.UI
CoreCount.Text = $"{castState.CoreCount}"; CoreCount.Text = $"{castState.CoreCount}";
InjectionAmount.Text = $"{castState.InjectionAmount}"; InjectionAmount.Text = $"{castState.InjectionAmount}";
// format power statistics to pretty numbers // format power statistics to pretty numbers
CurrentPowerSupply.Text = $"{castState.CurrentPowerSupply:N1}"; CurrentPowerSupply.Text = $"{castState.CurrentPowerSupply.ToString("N1")}";
TargetedPowerSupply.Text = $"{castState.TargetedPowerSupply:N1}"; TargetedPowerSupply.Text = $"{castState.TargetedPowerSupply.ToString("N1")}";
} }
} }
} }

View File

@@ -1,17 +0,0 @@
using System.Numerics;
namespace Content.Client.Animations;
/// <summary>
/// Entities with this component tracks the user's world position every frame.
/// </summary>
[RegisterComponent]
public sealed partial class TrackUserComponent : Component
{
public EntityUid? User;
/// <summary>
/// Offset in the direction of the entity's rotation.
/// </summary>
public Vector2 Offset = Vector2.Zero;
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
using Content.Shared.Anomaly; using Content.Shared.Anomaly;
using Content.Shared.Gravity;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.UserInterface; using Robust.Client.GameObjects;
namespace Content.Client.Anomaly.Ui; namespace Content.Client.Anomaly.Ui;
@@ -17,8 +18,10 @@ public sealed class AnomalyGeneratorBoundUserInterface : BoundUserInterface
{ {
base.Open(); base.Open();
_window = this.CreateWindow<AnomalyGeneratorWindow>(); _window = new(Owner);
_window.SetEntity(Owner);
_window.OpenCentered();
_window.OnClose += Close;
_window.OnGenerateButtonPressed += () => _window.OnGenerateButtonPressed += () =>
{ {
@@ -34,5 +37,18 @@ public sealed class AnomalyGeneratorBoundUserInterface : BoundUserInterface
return; return;
_window?.UpdateState(msg); _window?.UpdateState(msg);
} }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_window?.Dispose();
}
public void SetPowerSwitch(bool on)
{
SendMessage(new SharedGravityGeneratorComponent.SwitchGeneratorMessage(on));
}
} }

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