Compare commits
1 Commits
master
...
ed-2025-02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f20828b1bb |
@@ -77,8 +77,6 @@ csharp_style_expression_bodied_methods = false:suggestion
|
||||
#csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
|
||||
# Pattern matching preferences
|
||||
#csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
#csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
@@ -129,7 +127,6 @@ csharp_indent_braces = false
|
||||
#csharp_indent_case_contents_when_block = true
|
||||
#csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
xmldoc_indent_text = zeroindent
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
|
||||
47
.github/CODEOWNERS
vendored
47
.github/CODEOWNERS
vendored
@@ -1,2 +1,45 @@
|
||||
# TheShuEd
|
||||
* @TheShuEd
|
||||
# Last match in file takes precedence.
|
||||
|
||||
# Sorting by path instead of by who added it one day :(
|
||||
# this isn't how codeowners rules work pls read the first comment instead of trying to force a sorting order
|
||||
|
||||
/Resources/ConfigPresets/WizardsDen/ @nikthechampiongr @crazybrain23
|
||||
/Content.*/Administration/ @DrSmugleaf @nikthechampiongr @crazybrain23
|
||||
/Resources/ServerInfo/ @nikthechampiongr @crazybrain23
|
||||
/Resources/ServerInfo/Guidebook/ServerRules/ @nikthechampiongr @crazybrain23
|
||||
|
||||
/Resources/Prototypes/Maps/** @Emisse
|
||||
|
||||
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
|
||||
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
|
||||
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
|
||||
/Resources/Prototypes/Guidebook/rules.yml @nikthechampiongr @crazybrain23
|
||||
/Content.*/Body/ @DrSmugleaf
|
||||
/Content.YAMLLinter @DrSmugleaf
|
||||
/Content.Shared/Damage/ @DrSmugleaf
|
||||
|
||||
/Content.*/Anomaly/ @TheShuEd
|
||||
/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @TheShuEd
|
||||
|
||||
/Content.*/Forensics/ @ficcialfaint
|
||||
|
||||
# SKREEEE
|
||||
/Content.*.Database/ @PJB3005 @DrSmugleaf
|
||||
/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @nikthechampiongr @crazybrain23
|
||||
/Pow3r/ @PJB3005
|
||||
/Content.Server/Power/Pow3r/ @PJB3005
|
||||
|
||||
# notafet
|
||||
/Content.*/Atmos/ @Partmedia
|
||||
/Content.*/Botany/ @Partmedia
|
||||
|
||||
# Jezi
|
||||
/Content.*/Medical @Jezithyr
|
||||
/Content.*/Body @Jezithyr
|
||||
|
||||
# Sloth
|
||||
/Content.*/Audio @metalgearsloth
|
||||
/Content.*/Movement @metalgearsloth
|
||||
/Content.*/NPC @metalgearsloth
|
||||
/Content.*/Shuttles @metalgearsloth
|
||||
/Content.*/Weapons @metalgearsloth
|
||||
|
||||
22
.github/FUNDING.yml
vendored
22
.github/FUNDING.yml
vendored
@@ -1,14 +1,14 @@
|
||||
# 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
|
||||
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']
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
contact_links:
|
||||
- name: Report a Security Vulnerability
|
||||
url: https://github.com/space-wizards/space-station-14/blob/master/SECURITY.md
|
||||
about: Please report security vulnerabilities privately so we can fix them before they are publicly disclosed.
|
||||
- name: Request a Feature
|
||||
url: https://discord.gg/rGvu9hKffJ
|
||||
about: Submit feature requests on our Discord server (https://discord.gg/rGvu9hKffJ).
|
||||
28
.github/ISSUE_TEMPLATE/feature-request_en.yml
vendored
28
.github/ISSUE_TEMPLATE/feature-request_en.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: "Request content or suggest an idea"
|
||||
description: "Suggest content, mechanics, or other changes that you think would benefit the project."
|
||||
labels: [suggestion]
|
||||
type: Suggestion
|
||||
body:
|
||||
- type: textarea
|
||||
id: concept
|
||||
attributes:
|
||||
label: Concept
|
||||
placeholder: |
|
||||
What is the nature of your proposal?
|
||||
|
||||
Example:
|
||||
Add a new role ____ that performs tasks related to ____ and ____.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: Why is this necessary?
|
||||
placeholder: |
|
||||
Explain how it would improve the game, or what problems it would fix. Suggestions without proper reasoning and reasons for the addition may be closed.
|
||||
|
||||
Example:
|
||||
Currently, ____ often occurs in the round, which is why ____ is missing. This role could close this need by doing _____.
|
||||
validations:
|
||||
required: true
|
||||
28
.github/ISSUE_TEMPLATE/feature-request_ru.yml
vendored
28
.github/ISSUE_TEMPLATE/feature-request_ru.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: "Запросить контент или предложить идею"
|
||||
description: "Предложите контент, механику или другие изменения, которые вы считаете принесут пользу проекту."
|
||||
labels: [suggestion]
|
||||
type: Suggestion
|
||||
body:
|
||||
- type: textarea
|
||||
id: concept
|
||||
attributes:
|
||||
label: Концепция
|
||||
placeholder: |
|
||||
В чем заключается ваше предложение?
|
||||
|
||||
Пример:
|
||||
Добавить новую роль ____, которая выполняет задачи связанные с ____ и ____.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: Зачем это нужно?
|
||||
placeholder: |
|
||||
Объясните, как это улучшит игру, или какие проблемы это исправит. Предложения без должной аргументации и причины добавления могут быть закрыты.
|
||||
|
||||
Пример:
|
||||
На текущий момент, в раунде часто происходит ____, из за чего не хватает ____. Эта роль могла бы закрыть эту потребность, выполняя _____.
|
||||
validations:
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/issue_report.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/issue_report.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Report an Issue
|
||||
about: "..."
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
<!-- Explain your issue in detail. Issues without proper explanation are liable to be closed by maintainers. -->
|
||||
|
||||
**Reproduction**
|
||||
<!-- Include the steps to reproduce if applicable. -->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. Anything you think is related to the issue. -->
|
||||
49
.github/ISSUE_TEMPLATE/issue_report_en.yml
vendored
49
.github/ISSUE_TEMPLATE/issue_report_en.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: "Report an Issue"
|
||||
description: "Report a bug or problem you have found"
|
||||
type: Bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "📄 Description"
|
||||
description: |
|
||||
Explain your issue in detail. Include what you expected to happen.
|
||||
placeholder: |
|
||||
When I click the "Save" button, nothing happens.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: "🔁 Reproduction Steps"
|
||||
description: |
|
||||
What steps lead to the problem? Try to list them clearly and precisely.
|
||||
placeholder: |
|
||||
1. Open the app
|
||||
2. Go to Settings
|
||||
3. Click "Save"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: "🖼️ Screenshots"
|
||||
description: |
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
placeholder: |
|
||||
You can paste images here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: "ℹ️ Additional Context"
|
||||
description: |
|
||||
Any other context about the problem. Logs, version numbers, configs, etc.
|
||||
placeholder: |
|
||||
Occurs only on Android 11, using version 1.2.3.
|
||||
validations:
|
||||
required: false
|
||||
50
.github/ISSUE_TEMPLATE/issue_report_ru.yml
vendored
50
.github/ISSUE_TEMPLATE/issue_report_ru.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: "Сообщить об ошибке"
|
||||
description: "Сообщите о найденной ошибке или проблеме"
|
||||
type: Bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "📄 Описание"
|
||||
description: |
|
||||
Подробно опишите вашу проблему. Укажите, что должно было произойти.
|
||||
placeholder: |
|
||||
Когда я нажимаю на кнопку "Сохранить", ничего не происходит, и персонаж не сохраняется.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: "🔁 Шаги воспроизведения"
|
||||
description: |
|
||||
Какие шаги приводят к этой ошибке? Укажите их по порядку.
|
||||
placeholder: |
|
||||
1. Открыть приложение
|
||||
2. Зайти в меню создания персонажа
|
||||
3. Изменить цвет волос
|
||||
4. Нажать "Сохранить"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: "🖼️ Скриншоты"
|
||||
description: |
|
||||
При наличии, добавьте скриншоты или изображения.
|
||||
placeholder: |
|
||||
Вы можете вставить изображения прямо сюда.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: "ℹ️Дополнительная информация"
|
||||
description: |
|
||||
Дополнительные сведения: логи, версии, настройки и всё, что может быть связано.
|
||||
placeholder: |
|
||||
Возникает только на MacOS
|
||||
validations:
|
||||
required: false
|
||||
18
.github/ISSUE_TEMPLATE/toolshed-feature-request.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/toolshed-feature-request.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Toolshed feature request
|
||||
about: Suggest a feature for Toolshed (for game admins/developers)
|
||||
title: "[TOOLSHED REQUEST]"
|
||||
labels: Toolshed
|
||||
assignees: moonheart08
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem/bug? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the command you'd like**
|
||||
A clear and concise description of what you want and what it should do.
|
||||
If you're a technical user (i.e. programmer) including type signatures is helpful.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,23 +1,17 @@
|
||||
## About the PR
|
||||
<!-- What did you change in this PR? -->
|
||||
<!-- Что вы изменили в своем пулл реквесте? -->
|
||||
|
||||
## Why / Balance
|
||||
<!-- Why was it changed? Link any discussions or issues here. Please discuss how this would affect game balance. -->
|
||||
<!-- Зачем нужно это изменение? Прикрепите любые обсуждения или проблемы здесь. Опишите, как это повлияет на текущий баланс игры. -->
|
||||
|
||||
## Media
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
**Changelog**
|
||||
<!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay.
|
||||
Make sure to read the guidelines and take this Changelog template out of the comment block in order for it to show up.
|
||||
Changelog must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog.
|
||||
|
||||
:cl:
|
||||
- add: Added fun!
|
||||
- remove: Removed fun!
|
||||
- tweak: Changed fun!
|
||||
- fix: Fixed fun!
|
||||
<!--
|
||||
Пулл реквесты, которые несут за собой игровые изменения (добавления одежды, предметов и так далее) требуют чтобы вы прикрепили скриншоты или видеоролики, демонстрирующие эти изменения.
|
||||
Небольшие исправления не считаются.
|
||||
-->
|
||||
|
||||
20
.github/labeler.yml
vendored
20
.github/labeler.yml
vendored
@@ -1,34 +1,26 @@
|
||||
"Sprites":
|
||||
"Changes: Sprites":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.rsi/*.png'
|
||||
|
||||
"Mapping":
|
||||
"Changes: Map":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'Resources/Maps/**/*.yml'
|
||||
- 'Resources/Prototypes/Maps/**/*.yml'
|
||||
|
||||
"UI":
|
||||
"Changes: UI":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.xaml*'
|
||||
|
||||
"Shaders":
|
||||
"Changes: Shaders":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.swsl'
|
||||
|
||||
"Audio":
|
||||
"Changes: Audio":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.ogg'
|
||||
|
||||
"C#":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.cs'
|
||||
|
||||
"YML":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.yml'
|
||||
|
||||
"No C#":
|
||||
"Changes: No C#":
|
||||
- changed-files:
|
||||
# Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.
|
||||
- all-globs-to-all-files: "!**/*.cs"
|
||||
|
||||
18
.github/workflows/CP14Publish.yml
vendored
18
.github/workflows/CP14Publish.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Edge Publish
|
||||
name: Publish
|
||||
|
||||
concurrency:
|
||||
group: publish
|
||||
@@ -10,15 +10,6 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: sudo apt-get install -y python3-paramiko python3-lxml
|
||||
|
||||
- name: Send POST-request
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
@@ -27,10 +18,3 @@ jobs:
|
||||
password: ${{ secrets.BUILD_PASS }}
|
||||
port: 22
|
||||
script: sh update.sh &> /dev/null
|
||||
|
||||
- name: Publish changelog (Discord)
|
||||
continue-on-error: true
|
||||
run: Tools/actions_changelogs_since_last_run.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}
|
||||
2
.github/workflows/benchmarks.yml
vendored
2
.github/workflows/benchmarks.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Run Benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Get Engine version
|
||||
|
||||
6
.github/workflows/build-docfx.yml
vendored
6
.github/workflows/build-docfx.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
docfx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Setup submodule
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Project
|
||||
run: dotnet build --no-restore
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
|
||||
- name: Build DocFX
|
||||
uses: nikeee/docfx-action@v1.0.0
|
||||
|
||||
6
.github/workflows/build-map-renderer.yml
vendored
6
.github/workflows/build-map-renderer.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v3.6.0
|
||||
|
||||
- name: Setup Submodule
|
||||
run: |
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Project
|
||||
run: dotnet build Content.MapRenderer --configuration Release --no-restore /m
|
||||
run: dotnet build Content.MapRenderer --configuration Release --no-restore /p:WarningsAsErrors=nullable /m
|
||||
|
||||
- name: Run Map Renderer
|
||||
run: dotnet run --project Content.MapRenderer Dev
|
||||
|
||||
6
.github/workflows/build-test-debug.yml
vendored
6
.github/workflows/build-test-debug.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v3.6.0
|
||||
|
||||
- name: Setup Submodule
|
||||
run: |
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Project
|
||||
run: dotnet build --configuration DebugOpt --no-restore /m
|
||||
run: dotnet build --configuration DebugOpt --no-restore /p:WarningsAsErrors=nullable /m
|
||||
|
||||
- name: Run Content.Tests
|
||||
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0
|
||||
|
||||
2
.github/workflows/check-crlf.yml
vendored
2
.github/workflows/check-crlf.yml
vendored
@@ -10,6 +10,6 @@ jobs:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Check for CRLF
|
||||
run: Tools/check_crlf.py
|
||||
|
||||
38
.github/workflows/cla.yml
vendored
Normal file
38
.github/workflows/cla.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: "CLA Assistant"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened,closed,synchronize]
|
||||
paths:
|
||||
- '**CP14**'
|
||||
|
||||
# explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write # this can be 'read' if the signatures are in remote repository
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
CLAAssistant:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
if: ((github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target') && github.actor != 'TheShuEd'
|
||||
uses: contributor-assistant/github-action@v2.6.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||
# This token is required only if you have configured to store the signatures in a remote repository/organization
|
||||
# PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: 'signatures/version1/cla.json'
|
||||
path-to-document: 'https://github.com/crystallpunk-14/crystall-punk-14/blob/master/CLA.md' # e.g. a CLA or a DCO document
|
||||
# branch should not be protected
|
||||
branch: 'master'
|
||||
allowlist: TheShuEd,bot*
|
||||
|
||||
# the followings are the optional inputs - If the optional inputs are not given, then default values will be taken
|
||||
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
|
||||
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
|
||||
2
.github/workflows/close-master-pr.yml
vendored
2
.github/workflows/close-master-pr.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: "Thank you for your contribution! It appears you created a PR from your master branch, this is [something you should avoid doing](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/), and thus this PR has been automatically closed. \n \n We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html). \n \n You can move your current work from the master branch to another branch by following [these commands](https://ohshitgit.com/#accidental-commit-master). And then you may recreate your PR using the new branch."
|
||||
comment: "Thank you for contributing to the Space Station 14 repository. Unfortunately, it looks like you submitted your pull request from the master branch. We suggest you follow [our git usage documentation](https://docs.spacestation14.com/en/general-development/setup/git-for-the-ss14-developer.html) \n\n You can move your current work from the master branch to another branch by doing `git branch <branch_name` and resetting the master branch."
|
||||
|
||||
# If you prefer to just comment on the pr and not close it, uncomment the bellow and comment the above
|
||||
|
||||
|
||||
2
.github/workflows/labeler-needsreview.yml
vendored
2
.github/workflows/labeler-needsreview.yml
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [review_requested, opened]
|
||||
types: [review_requested]
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
|
||||
3
.github/workflows/publish-testing.yml
vendored
3
.github/workflows/publish-testing.yml
vendored
@@ -2,7 +2,6 @@
|
||||
|
||||
concurrency:
|
||||
group: publish-testing
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -34,7 +33,7 @@ jobs:
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
13
.github/workflows/publish.yml
vendored
13
.github/workflows/publish.yml
vendored
@@ -2,7 +2,6 @@ name: Publish
|
||||
|
||||
concurrency:
|
||||
group: publish
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -14,18 +13,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Fail if we are attempting to run on the master branch
|
||||
if: ${{GITHUB.REF_NAME == 'master' && github.repository == 'space-wizards/space-station-14'}}
|
||||
run: exit 1
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y python3-paramiko python3-lxml
|
||||
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
@@ -41,7 +36,7 @@ jobs:
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
@@ -53,14 +48,12 @@ jobs:
|
||||
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
|
||||
|
||||
- name: Publish changelog (Discord)
|
||||
continue-on-error: true
|
||||
run: Tools/actions_changelogs_since_last_run.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}
|
||||
|
||||
- name: Publish changelog (RSS)
|
||||
continue-on-error: true
|
||||
run: Tools/actions_changelog_rss.py
|
||||
env:
|
||||
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
|
||||
|
||||
4
.github/workflows/rsi-diff.yml
vendored
4
.github/workflows/rsi-diff.yml
vendored
@@ -11,14 +11,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v3.6.0
|
||||
|
||||
- name: Get changed files
|
||||
id: files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
with:
|
||||
format: 'space-delimited'
|
||||
filter: |
|
||||
filter: |
|
||||
**.rsi
|
||||
**.png
|
||||
|
||||
|
||||
6
.github/workflows/test-packaging.yml
vendored
6
.github/workflows/test-packaging.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v3.6.0
|
||||
|
||||
- name: Setup Submodule
|
||||
run: |
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
20
.github/workflows/update-credits.yml
vendored
20
.github/workflows/update-credits.yml
vendored
@@ -4,19 +4,19 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * 0
|
||||
|
||||
|
||||
jobs:
|
||||
get_credits:
|
||||
runs-on: ubuntu-latest
|
||||
# Hey there fork dev! If you like to include your own contributors in this then you can probably just change this to your own repo
|
||||
# Do this in dump_github_contributors.ps1 too into your own repo
|
||||
if: github.repository == 'space-wizards/space-station-14'
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
||||
- name: Get this week's Contributors
|
||||
shell: pwsh
|
||||
env:
|
||||
@@ -25,25 +25,25 @@ jobs:
|
||||
|
||||
# TODO
|
||||
#- name: Get this week's Patreons
|
||||
# run: Tools/script2dumppatreons > Resources/Credits/Patrons.yml
|
||||
|
||||
# run: Tools/script2dumppatreons > Resources/Credits/Patrons.yml
|
||||
|
||||
# MAKE SURE YOU ENABLED "Allow GitHub Actions to create and approve pull requests" IN YOUR ACTIONS, OTHERWISE IT WILL MOST LIKELY FAIL
|
||||
|
||||
|
||||
# For this you can use a pat token of an account with direct push access to the repo if you have protected branches.
|
||||
# For this you can use a pat token of an account with direct push access to the repo if you have protected branches.
|
||||
# Uncomment this and comment the other line if you do this.
|
||||
# https://github.com/stefanzweifel/git-auto-commit-action#push-to-protected-branches
|
||||
|
||||
|
||||
#- name: Commit new credit files
|
||||
# uses: stefanzweifel/git-auto-commit-action@v4
|
||||
# with:
|
||||
# commit_message: Update Credits
|
||||
# commit_author: PJBot <pieterjan.briers+bot@gmail.com>
|
||||
|
||||
|
||||
# This will make a PR
|
||||
- name: Set current date as env variable
|
||||
run: echo "NOW=$(date +'%Y-%m-%dT%H-%M-%S')" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
|
||||
2
.github/workflows/validate-rgas.yml
vendored
2
.github/workflows/validate-rgas.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Setup Submodule
|
||||
run: git submodule update --init
|
||||
- name: Pull engine updates
|
||||
|
||||
2
.github/workflows/validate-rsis.yml
vendored
2
.github/workflows/validate-rsis.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Validate RSIs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Setup Submodule
|
||||
run: git submodule update --init
|
||||
- name: Pull engine updates
|
||||
|
||||
2
.github/workflows/validate_mapfiles.yml
vendored
2
.github/workflows/validate_mapfiles.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Setup Submodule
|
||||
run: git submodule update --init
|
||||
- name: Pull engine updates
|
||||
|
||||
4
.github/workflows/yaml-linter.yml
vendored
4
.github/workflows/yaml-linter.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
if: github.actor != 'PJBot' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Setup submodule
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
- name: Install dependencies
|
||||
|
||||
44
.vscode/tasks.json
vendored
44
.vscode/tasks.json
vendored
@@ -32,50 +32,6 @@
|
||||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"test",
|
||||
"--no-build",
|
||||
"--configuration",
|
||||
"DebugOpt",
|
||||
"Content.Tests/Content.Tests.csproj",
|
||||
"--",
|
||||
"NUnit.ConsoleOut=0"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "integration-test",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"test",
|
||||
"--no-build",
|
||||
"--configuration",
|
||||
"DebugOpt",
|
||||
"Content.IntegrationTests/Content.IntegrationTests.csproj",
|
||||
"--",
|
||||
"NUnit.ConsoleOut=0",
|
||||
"NUnit.MapWarningTo=Failed.ConsoleOut=0",
|
||||
"NUnit.MapWarningTo=Failed"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Installs git hooks, updates them, updates submodules, that kind of thing.
|
||||
"""
|
||||
# Installs git hooks, updates them, updates submodules, that kind of thing.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
|
||||
# If this doesn't match the saved version we overwrite them all.
|
||||
CURRENT_HOOKS_VERSION = "3"
|
||||
CURRENT_HOOKS_VERSION = "2"
|
||||
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
|
||||
|
||||
|
||||
@@ -27,10 +24,12 @@ def run_command(command: List[str], capture: bool = False) -> subprocess.Complet
|
||||
|
||||
sys.stdout.flush()
|
||||
|
||||
completed = None
|
||||
|
||||
if capture:
|
||||
completed = subprocess.run(command, stdout=subprocess.PIPE, text=True)
|
||||
completed = subprocess.run(command, cwd="..", stdout=subprocess.PIPE)
|
||||
else:
|
||||
completed = subprocess.run(command)
|
||||
completed = subprocess.run(command, cwd="..")
|
||||
|
||||
if completed.returncode != 0:
|
||||
print("Error: command exited with code {}!".format(completed.returncode))
|
||||
@@ -43,7 +42,7 @@ def update_submodules():
|
||||
Updates all submodules.
|
||||
"""
|
||||
|
||||
if 'GITHUB_ACTIONS' in os.environ:
|
||||
if ('GITHUB_ACTIONS' in os.environ):
|
||||
return
|
||||
|
||||
if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
|
||||
@@ -76,21 +75,22 @@ def install_hooks():
|
||||
print("No hooks change detected.")
|
||||
return
|
||||
|
||||
with open("INSTALLED_HOOKS_VERSION", "w") as f:
|
||||
f.write(CURRENT_HOOKS_VERSION)
|
||||
|
||||
print("Hooks need updating.")
|
||||
|
||||
hooks_target_dir = Path(run_command(["git", "rev-parse", "--git-path", "hooks"], True).stdout.strip())
|
||||
hooks_target_dir = Path("..")/".git"/"hooks"
|
||||
hooks_source_dir = Path("hooks")
|
||||
|
||||
# Clear entire tree since we need to kill deleted files too.
|
||||
for filename in os.listdir(hooks_target_dir):
|
||||
os.remove(hooks_target_dir / filename)
|
||||
for filename in os.listdir(str(hooks_target_dir)):
|
||||
os.remove(str(hooks_target_dir/filename))
|
||||
|
||||
for filename in os.listdir(hooks_source_dir):
|
||||
for filename in os.listdir(str(hooks_source_dir)):
|
||||
print("Copying hook {}".format(filename))
|
||||
shutil.copy2(hooks_source_dir / filename, hooks_target_dir / filename)
|
||||
|
||||
with open("INSTALLED_HOOKS_VERSION", "w") as f:
|
||||
f.write(CURRENT_HOOKS_VERSION)
|
||||
shutil.copy2(str(hooks_source_dir/filename),
|
||||
str(hooks_target_dir/filename))
|
||||
|
||||
|
||||
def reset_solution():
|
||||
@@ -104,20 +104,7 @@ def reset_solution():
|
||||
with SOLUTION_PATH.open("w") as f:
|
||||
f.write(content)
|
||||
|
||||
def check_for_zip_download():
|
||||
# Check if .git exists,
|
||||
if run_command(["git", "rev-parse"]).returncode != 0:
|
||||
print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n"
|
||||
"When downloading straight from GitHub, it leaves out important information that git needs to function. "
|
||||
"Such as information to download the engine or even the ability to even be able to create contributions. \n"
|
||||
"Please read and follow https://docs.spacestation14.com/en/general-development/setup/setting-up-a-development-environment.html \n"
|
||||
"If you just want a Sandbox Server, you are following the wrong guide! You can download a premade server following the instructions here:"
|
||||
"https://docs.spacestation14.com/en/general-development/setup/server-hosting-tutorial.html \n"
|
||||
"Closing automatically in 30 seconds.")
|
||||
time.sleep(30)
|
||||
exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_for_zip_download()
|
||||
install_hooks()
|
||||
update_submodules()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
gitroot=$(git rev-parse --show-toplevel)
|
||||
gitroot=`git rev-parse --show-toplevel`
|
||||
|
||||
cd "$gitroot/BuildChecker" || exit
|
||||
cd "$gitroot/BuildChecker"
|
||||
|
||||
if [[ $(uname) == MINGW* || $(uname) == CYGWIN* ]]; then
|
||||
if [[ `uname` == MINGW* || `uname` == CYGWIN* ]]; then
|
||||
# Windows
|
||||
py -3 git_helper.py --quiet
|
||||
else
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Just call post-checkout since it does the same thing.
|
||||
gitroot=$(git rev-parse --git-path hooks)
|
||||
bash "$gitroot/post-checkout"
|
||||
gitroot=`git rev-parse --show-toplevel`
|
||||
bash "$gitroot/.git/hooks/post-checkout"
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
# Space Station 14 Code of Conduct
|
||||
|
||||
Space Station 14's staff and community is made up volunteers from all over the world, working on every aspect of the project - including development, teaching, and hosting integral tools.
|
||||
|
||||
Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to all levels of the project, from commenters to contributors to staff.
|
||||
|
||||
This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate.
|
||||
|
||||
This code of conduct applies specifically to the Github repositories and its spaces managed by the Space Station 14 project or Space Wizards Federation. Some spaces, such as the Space Station 14 Discord or the official Wizard's Den game servers, have their own rules but are in spirit equal to what may be found in here.
|
||||
|
||||
If you believe someone is violating the code of conduct, we ask that you report it by contacting a Maintainer, Project Manager or Wizard staff member through [Discord](https://discord.ss14.io/), [the forums](https://forum.spacestation14.com/), or emailing [telecommunications@spacestation14.com](mailto:telecommunications@spacestation14.com).
|
||||
|
||||
- **Be friendly and patient.**
|
||||
- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
|
||||
- **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and contributors, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. We have contributors of all skill levels, some even making their first foray into a new field with this project, so keep that in mind when discussing someone's work.
|
||||
- **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Space Station 14 community should be respectful when dealing with other members as well as with people outside the Space Station 14 community. Assume contributions to the project, even those that do not end up being included, are made in good faith.
|
||||
- **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
|
||||
- Violent threats or language directed against another person.
|
||||
- Discriminatory jokes and language.
|
||||
- Posting sexually explicit or violent material.
|
||||
- Posting (or threatening to post) other people's personally identifying information ("doxing").
|
||||
- Personal insults, especially those using racist or sexist terms.
|
||||
- Unwelcome sexual attention.
|
||||
- Advocating for, or encouraging, any of the above behavior.
|
||||
- Repeated harassment of others. In general, if someone asks you to stop, then stop.
|
||||
- **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and Space Station 14 is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of Space Station 14 comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to make mistakes and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
|
||||
|
||||
Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html).
|
||||
|
||||
## On Community Moderation
|
||||
|
||||
Deviating from the Code of Conduct on the Github repository may result in moderative actions taken by project Maintainers. This can involve your content being edited or deleted, and may result in a temporary or permanent block from the repository.
|
||||
|
||||
This is to ensure Space Station 14 is a healthy community in which contributors feel encouraged and empowered to contribute, and to give you as a member of this community a chance to reflect on how you are interacting with it. While outright offensive and bigoted content will *always* be unacceptable on the repository, Maintainers are at liberty to take moderative actions against more ambiguous content that fail to provide constructive criticism, or that provides constructive criticism in a non-constructive manner. Examples of this include using hyperbole, bringing up PRs/changes unrelated to the discussion at hand, hostile tone, off-topic comments, creating PRs/Issues for the sole purpose of causing discussions, skirting the line of acceptable behavior, etc. Disagreeing with content or each other is fine and appreciated, but only as long as it's done with respect and in a constructive manner.
|
||||
|
||||
Maintainers are expected to adhere to the guidelines as listed in the [Github Moderation Guidelines](https://docs.spacestation14.com/en/general-development/github-moderation-guidelines.html), though may deviate should they feel it's in the best interest of the community. If you believe you had an action incorrectly applied against you, you are encouraged to contact staff via [Discord](https://discord.ss14.io/) or [the forums](https://forum.spacestation14.com/), [appeal your Github ban](https://forum.spacestation14.com/c/ban-appeals/appeals-github/38), or make a [staff complaint](https://forum.spacestation14.com/t/staff-complaint-instructions-and-info/31).
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is an edited version of the [Django Code of Conduct](https://www.djangoproject.com/conduct/), licensed under CC BY 3.0, for the Space Station 14 Github repository.
|
||||
@@ -29,7 +29,7 @@ namespace Content.Benchmarks;
|
||||
[CategoriesColumn]
|
||||
public class ComponentQueryBenchmark
|
||||
{
|
||||
public const string Map = "Maps/saltern.yml";
|
||||
public const string Map = "Maps/atlas.yml";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns N number of entities with a <see cref="DeltaPressureComponent"/> and
|
||||
/// simulates them for a number of ticks M.
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
[GcServer(true)]
|
||||
//[MemoryDiagnoser]
|
||||
//[ThreadingDiagnoser]
|
||||
public class DeltaPressureBenchmark
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of entities (windows, really) to spawn with a <see cref="DeltaPressureComponent"/>.
|
||||
/// </summary>
|
||||
[Params(1, 10, 100, 1000, 5000, 10000, 50000, 100000)]
|
||||
public int EntityCount;
|
||||
|
||||
/// <summary>
|
||||
/// Number of entities that each parallel processing job will handle.
|
||||
/// </summary>
|
||||
// [Params(1, 10, 100, 1000, 5000, 10000)] For testing how multithreading parameters affect performance (THESE TESTS TAKE 16+ HOURS TO RUN)
|
||||
[Params(10)]
|
||||
public int BatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// Number of entities to process per iteration in the DeltaPressure
|
||||
/// processing loop.
|
||||
/// </summary>
|
||||
// [Params(100, 1000, 5000, 10000, 50000)]
|
||||
[Params(1000)]
|
||||
public int EntitiesPerIteration;
|
||||
|
||||
private readonly EntProtoId _windowProtoId = "Window";
|
||||
private readonly EntProtoId _wallProtoId = "WallPlastitaniumIndestructible";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private SharedMapSystem _map = default!;
|
||||
private IRobustRandom _random = default!;
|
||||
private IConfigurationManager _cvar = default!;
|
||||
private ITileDefinitionManager _tileDefMan = default!;
|
||||
private AtmosphereSystem _atmospereSystem = default!;
|
||||
|
||||
private Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>
|
||||
_testEnt;
|
||||
|
||||
[GlobalSetup]
|
||||
public async Task SetupAsync()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapdata = await _pair.CreateTestMap();
|
||||
|
||||
_entMan = server.ResolveDependency<IEntityManager>();
|
||||
_map = _entMan.System<SharedMapSystem>();
|
||||
_random = server.ResolveDependency<IRobustRandom>();
|
||||
_cvar = server.ResolveDependency<IConfigurationManager>();
|
||||
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
_atmospereSystem = _entMan.System<AtmosphereSystem>();
|
||||
|
||||
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
|
||||
|
||||
_cvar.SetCVar(CCVars.DeltaPressureParallelToProcessPerIteration, EntitiesPerIteration);
|
||||
_cvar.SetCVar(CCVars.DeltaPressureParallelBatchSize, BatchSize);
|
||||
|
||||
var plating = _tileDefMan["Plating"].TileId;
|
||||
|
||||
/*
|
||||
Basically, we want to have a 5-wide grid of tiles.
|
||||
Edges are walled, and the length of the grid is determined by N + 2.
|
||||
Windows should only touch the top and bottom walls, and each other.
|
||||
*/
|
||||
|
||||
var length = EntityCount + 2; // ensures we can spawn exactly N windows between side walls
|
||||
const int height = 5;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Fill required tiles (extend grid) with plating
|
||||
for (var x = 0; x < length; x++)
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn perimeter walls and windows row in the middle (y = 2)
|
||||
const int midY = height / 2;
|
||||
for (var x = 0; x < length; x++)
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
|
||||
|
||||
var isPerimeter = x == 0 || x == length - 1 || y == 0 || y == height - 1;
|
||||
if (isPerimeter)
|
||||
{
|
||||
_entMan.SpawnEntity(_wallProtoId, coords);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spawn windows only on the middle row, spanning interior (excluding side walls)
|
||||
if (y == midY)
|
||||
{
|
||||
_entMan.SpawnEntity(_windowProtoId, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Next we run the fixgridatmos command to ensure that we have some air on our grid.
|
||||
// Wait a little bit as well.
|
||||
// TODO: Unhardcode command magic string when fixgridatmos is an actual command we can ref and not just
|
||||
// a stamp-on in AtmosphereSystem.
|
||||
await _pair.WaitCommand("fixgridatmos " + mapdata.Grid.Owner, 1);
|
||||
|
||||
var uid = mapdata.Grid.Owner;
|
||||
_testEnt = new Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>(
|
||||
uid,
|
||||
_entMan.GetComponent<GridAtmosphereComponent>(uid),
|
||||
_entMan.GetComponent<GasTileOverlayComponent>(uid),
|
||||
_entMan.GetComponent<MapGridComponent>(uid),
|
||||
_entMan.GetComponent<TransformComponent>(uid));
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task PerformFullProcess()
|
||||
{
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
while (!_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure)) { }
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task PerformSingleRunProcess()
|
||||
{
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure);
|
||||
});
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public async Task CleanupAsync()
|
||||
{
|
||||
await _pair.DisposeAsync();
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Content.Benchmarks
|
||||
for (var i = 0; i < Aabbs1.Length; i++)
|
||||
{
|
||||
var aabb = Aabbs1[i];
|
||||
_b2Tree.CreateProxy(aabb, uint.MaxValue, i);
|
||||
_b2Tree.CreateProxy(aabb, i);
|
||||
_tree.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static string[] MapsSource { get; } = { "Empty", "Saltern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
|
||||
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Running;
|
||||
using Content.IntegrationTests;
|
||||
using Content.Server.Maps;
|
||||
#if DEBUG
|
||||
using BenchmarkDotNet.Configs;
|
||||
#else
|
||||
using Robust.Benchmarks.Configs;
|
||||
#endif
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Benchmarks
|
||||
{
|
||||
@@ -14,15 +22,11 @@ namespace Content.Benchmarks
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("\nWARNING: YOU ARE RUNNING A DEBUG BUILD, USE A RELEASE BUILD FOR AN ACCURATE BENCHMARK");
|
||||
Console.WriteLine("THE DEBUG BUILD IS ONLY GOOD FOR FIXING A CRASHING BENCHMARK\n");
|
||||
var baseConfig = new DebugInProcessConfig();
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig());
|
||||
#else
|
||||
var baseConfig = Environment.GetEnvironmentVariable("ROBUST_BENCHMARKS_ENABLE_SQL") != null
|
||||
? DefaultSQLConfig.Instance
|
||||
: DefaultConfig.Instance;
|
||||
#endif
|
||||
var config = ManualConfig.Create(baseConfig);
|
||||
config.BuildTimeout = TimeSpan.FromMinutes(5);
|
||||
var config = Environment.GetEnvironmentVariable("ROBUST_BENCHMARKS_ENABLE_SQL") != null ? DefaultSQLConfig.Instance : null;
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Shared.Warps;
|
||||
using Content.Server.Warps;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
[Virtual]
|
||||
public class RaiseEventBenchmark
|
||||
{
|
||||
private TestPair _pair = default!;
|
||||
private BenchSystem _sys = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(BenchSystem).Assembly);
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
var entMan = _pair.Server.EntMan;
|
||||
_sys = entMan.System<BenchSystem>();
|
||||
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
var uid = entMan.Spawn();
|
||||
_sys.Ent = new(uid, entMan.GetComponent<TransformComponent>(uid));
|
||||
_sys.Ent2 = new(_sys.Ent.Owner, _sys.Ent.Comp);
|
||||
})
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public async Task Cleanup()
|
||||
{
|
||||
await _pair.DisposeAsync();
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public int RaiseEvent()
|
||||
{
|
||||
return _sys.RaiseEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseCompEvent()
|
||||
{
|
||||
return _sys.RaiseCompEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseICompEvent()
|
||||
{
|
||||
return _sys.RaiseICompEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseCSharpEvent()
|
||||
{
|
||||
return _sys.CSharpEvent();
|
||||
}
|
||||
|
||||
public sealed class BenchSystem : EntitySystem
|
||||
{
|
||||
public Entity<TransformComponent> Ent;
|
||||
public Entity<IComponent> Ent2;
|
||||
|
||||
public delegate void EntityEventHandler(EntityUid uid, TransformComponent comp, ref BenchEv ev);
|
||||
|
||||
public event EntityEventHandler? OnCSharpEvent;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<TransformComponent, BenchEv>(OnEvent);
|
||||
OnCSharpEvent += OnEvent;
|
||||
}
|
||||
|
||||
public int RaiseEvent()
|
||||
{
|
||||
var ev = new BenchEv();
|
||||
RaiseLocalEvent(Ent.Owner, ref ev);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
public int RaiseCompEvent()
|
||||
{
|
||||
var ev = new BenchEv();
|
||||
EntityManager.EventBus.RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
public int RaiseICompEvent()
|
||||
{
|
||||
// Raise with an IComponent instead of concrete type
|
||||
var ev = new BenchEv();
|
||||
EntityManager.EventBus.RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
public int CSharpEvent()
|
||||
{
|
||||
var ev = new BenchEv();
|
||||
OnCSharpEvent?.Invoke(Ent.Owner, Ent.Comp, ref ev);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void OnEvent(EntityUid uid, TransformComponent component, ref BenchEv args)
|
||||
{
|
||||
args.N += uid.Id;
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public struct BenchEv
|
||||
{
|
||||
public int N;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -19,11 +18,9 @@ namespace Content.Benchmarks;
|
||||
[Virtual, MemoryDiagnoser]
|
||||
public class SpawnEquipDeleteBenchmark
|
||||
{
|
||||
private static readonly EntProtoId Mob = "MobHuman";
|
||||
private static readonly ProtoId<StartingGearPrototype> CaptainStartingGear = "CaptainGear";
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private StationSpawningSystem _spawnSys = default!;
|
||||
private const string Mob = "MobHuman";
|
||||
private StartingGearPrototype _gear = default!;
|
||||
private EntityUid _entity;
|
||||
private EntityCoordinates _coords;
|
||||
@@ -42,7 +39,7 @@ public class SpawnEquipDeleteBenchmark
|
||||
var mapData = await _pair.CreateTestMap();
|
||||
_coords = mapData.GridCoords;
|
||||
_spawnSys = server.System<StationSpawningSystem>();
|
||||
_gear = server.ProtoMan.Index(CaptainStartingGear);
|
||||
_gear = server.ProtoMan.Index<StartingGearPrototype>("CaptainGear");
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
|
||||
@@ -4,20 +4,39 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Access.Commands;
|
||||
|
||||
public sealed class ShowAccessReadersCommand : LocalizedEntityCommands
|
||||
public sealed class ShowAccessReadersCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
public string Command => "showaccessreaders";
|
||||
|
||||
public override string Command => "showaccessreaders";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public string Description => "Toggles showing access reader permissions on the map";
|
||||
public string Help => """
|
||||
Overlay Info:
|
||||
-Disabled | The access reader is disabled
|
||||
+Unrestricted | The access reader has no restrictions
|
||||
+Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
|
||||
+Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
|
||||
-Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
|
||||
""";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var existing = _overlay.RemoveOverlay<AccessOverlay>();
|
||||
if (!existing)
|
||||
_overlay.AddOverlay(new AccessOverlay(EntityManager, _cache, _xform));
|
||||
var collection = IoCManager.Instance;
|
||||
|
||||
shell.WriteLine(Loc.GetString($"cmd-showaccessreaders-status", ("status", !existing)));
|
||||
if (collection == null)
|
||||
return;
|
||||
|
||||
var overlay = collection.Resolve<IOverlayManager>();
|
||||
|
||||
if (overlay.RemoveOverlay<AccessOverlay>())
|
||||
{
|
||||
shell.WriteLine($"Set access reader debug overlay to false");
|
||||
return;
|
||||
}
|
||||
|
||||
var entManager = collection.Resolve<IEntityManager>();
|
||||
var cache = collection.Resolve<IResourceCache>();
|
||||
var xform = entManager.System<SharedTransformSystem>();
|
||||
|
||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
|
||||
shell.WriteLine($"Set access reader debug overlay to true");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
foreach (var access in accessLevels)
|
||||
{
|
||||
if (!protoManager.Resolve(access, out var accessLevel))
|
||||
if (!protoManager.TryIndex(access, out var accessLevel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Orientation="Horizontal"
|
||||
Margin="10 10 10 10"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinHeight="70">
|
||||
|
||||
<!-- Access groups -->
|
||||
<BoxContainer Name="AccessGroupList" Access="Public" Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.5" Margin="0 0 10 0">
|
||||
<!-- Populated with C# code -->
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" VerticalExpand="True" Margin="0 0 0 0" SetWidth="2">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#FFFFFF" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<!-- Access levels -->
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="10 0 0 0">
|
||||
<BoxContainer Name="AccessLevelChecklist" Access="Public" Orientation="Vertical" HorizontalAlignment="Left">
|
||||
<!-- Populated with C# code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
@@ -1,451 +0,0 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Access;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Access.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GroupedAccessLevelChecklist : BoxContainer
|
||||
{
|
||||
private static readonly ProtoId<AccessGroupPrototype> GeneralAccessGroup = "General";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private bool _isMonotone;
|
||||
private string? _labelStyleClass;
|
||||
|
||||
// Access data
|
||||
private HashSet<ProtoId<AccessGroupPrototype>> _accessGroups = new();
|
||||
private HashSet<ProtoId<AccessLevelPrototype>> _accessLevels = new();
|
||||
private HashSet<ProtoId<AccessLevelPrototype>> _activeAccessLevels = new();
|
||||
|
||||
// Button groups
|
||||
private readonly ButtonGroup _accessGroupsButtons = new();
|
||||
|
||||
// Temp values
|
||||
private int _accessGroupTabIndex = 0;
|
||||
private bool _canInteract = false;
|
||||
private List<AccessLevelPrototype> _accessLevelsForTab = new();
|
||||
private readonly List<AccessLevelEntry> _accessLevelEntries = new();
|
||||
private readonly Dictionary<AccessGroupPrototype, List<AccessLevelPrototype>> _groupedAccessLevels = new();
|
||||
|
||||
// Events
|
||||
public event Action<HashSet<ProtoId<AccessLevelPrototype>>, bool>? OnAccessLevelsChangedEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a UI control for changing access levels.
|
||||
/// Access levels are organized under a list of tabs by their associated access group.
|
||||
/// </summary>
|
||||
public GroupedAccessLevelChecklist()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
private void ArrangeAccessControls()
|
||||
{
|
||||
// Create a list of known access groups with which to populate the UI
|
||||
_groupedAccessLevels.Clear();
|
||||
|
||||
foreach (var accessGroup in _accessGroups)
|
||||
{
|
||||
if (!_protoManager.Resolve(accessGroup, out var accessGroupProto))
|
||||
continue;
|
||||
|
||||
_groupedAccessLevels.Add(accessGroupProto, new());
|
||||
}
|
||||
|
||||
// Ensure that the 'general' access group is added to handle
|
||||
// misc. access levels that aren't associated with any group
|
||||
if (_protoManager.Resolve(GeneralAccessGroup, out var generalAccessProto))
|
||||
_groupedAccessLevels.TryAdd(generalAccessProto, new());
|
||||
|
||||
// Assign known access levels with their associated groups
|
||||
foreach (var accessLevel in _accessLevels)
|
||||
{
|
||||
if (!_protoManager.Resolve(accessLevel, out var accessLevelProto))
|
||||
continue;
|
||||
|
||||
var assigned = false;
|
||||
|
||||
foreach (var (accessGroup, accessLevels) in _groupedAccessLevels)
|
||||
{
|
||||
if (!accessGroup.Tags.Contains(accessLevelProto.ID))
|
||||
continue;
|
||||
|
||||
assigned = true;
|
||||
_groupedAccessLevels[accessGroup].Add(accessLevelProto);
|
||||
}
|
||||
|
||||
if (!assigned && generalAccessProto != null)
|
||||
_groupedAccessLevels[generalAccessProto].Add(accessLevelProto);
|
||||
}
|
||||
|
||||
// Remove access groups that have no assigned access levels
|
||||
foreach (var (group, accessLevels) in _groupedAccessLevels)
|
||||
{
|
||||
if (accessLevels.Count == 0)
|
||||
_groupedAccessLevels.Remove(group);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryRebuildAccessGroupControls()
|
||||
{
|
||||
AccessGroupList.DisposeAllChildren();
|
||||
AccessLevelChecklist.DisposeAllChildren();
|
||||
|
||||
// No access level prototypes were assigned to any of the access level groups.
|
||||
// Either the turret controller has no assigned access levels or their names were invalid.
|
||||
if (_groupedAccessLevels.Count == 0)
|
||||
return false;
|
||||
|
||||
// Reorder the access groups alphabetically
|
||||
var orderedAccessGroups = _groupedAccessLevels.Keys.OrderBy(x => x.GetAccessGroupName()).ToList();
|
||||
|
||||
// Add group access buttons to the UI
|
||||
foreach (var accessGroup in orderedAccessGroups)
|
||||
{
|
||||
var accessGroupButton = CreateAccessGroupButton();
|
||||
|
||||
// Button styling
|
||||
if (_groupedAccessLevels.Count > 1)
|
||||
{
|
||||
if (AccessGroupList.ChildCount == 0)
|
||||
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenLeft);
|
||||
else if (_groupedAccessLevels.Count > 1 && AccessGroupList.ChildCount == (_groupedAccessLevels.Count - 1))
|
||||
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenRight);
|
||||
else
|
||||
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenBoth);
|
||||
}
|
||||
|
||||
accessGroupButton.Pressed = _accessGroupTabIndex == orderedAccessGroups.IndexOf(accessGroup);
|
||||
|
||||
// Label text and styling
|
||||
if (_labelStyleClass != null)
|
||||
accessGroupButton.Label.SetOnlyStyleClass(_labelStyleClass);
|
||||
|
||||
var accessLevelPrototypes = _groupedAccessLevels[accessGroup];
|
||||
var prefix = accessLevelPrototypes.All(x => _activeAccessLevels.Contains(x))
|
||||
? "»"
|
||||
: accessLevelPrototypes.Any(x => _activeAccessLevels.Contains(x))
|
||||
? "›"
|
||||
: " ";
|
||||
|
||||
var text = Loc.GetString(
|
||||
"turret-controls-window-access-group-label",
|
||||
("prefix", prefix),
|
||||
("label", accessGroup.GetAccessGroupName())
|
||||
);
|
||||
|
||||
accessGroupButton.Text = text;
|
||||
|
||||
// Button events
|
||||
accessGroupButton.OnPressed += _ => OnAccessGroupChanged(accessGroupButton.GetPositionInParent());
|
||||
|
||||
AccessGroupList.AddChild(accessGroupButton);
|
||||
}
|
||||
|
||||
// Adjust the current tab index so it remains in range
|
||||
if (_accessGroupTabIndex >= _groupedAccessLevels.Count)
|
||||
_accessGroupTabIndex = _groupedAccessLevels.Count - 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the checkbox list for the access level controls.
|
||||
/// </summary>
|
||||
public void RebuildAccessLevelsControls()
|
||||
{
|
||||
AccessLevelChecklist.DisposeAllChildren();
|
||||
_accessLevelEntries.Clear();
|
||||
|
||||
// No access level prototypes were assigned to any of the access level groups
|
||||
// Either turret controller has no assigned access levels, or their names were invalid
|
||||
if (_groupedAccessLevels.Count == 0)
|
||||
return;
|
||||
|
||||
// Reorder the access groups alphabetically
|
||||
var orderedAccessGroups = _groupedAccessLevels.Keys.OrderBy(x => x.GetAccessGroupName()).ToList();
|
||||
|
||||
// Get the access levels associated with the current tab
|
||||
var selectedAccessGroupTabProto = orderedAccessGroups[_accessGroupTabIndex];
|
||||
_accessLevelsForTab = _groupedAccessLevels[selectedAccessGroupTabProto];
|
||||
_accessLevelsForTab = _accessLevelsForTab.OrderBy(x => x.GetAccessLevelName()).ToList();
|
||||
|
||||
// Add an 'all' checkbox as the first child of the list if it has more than one access level
|
||||
// Toggling this checkbox on will mark all other boxes below it on/off
|
||||
var allCheckBox = CreateAccessLevelCheckbox();
|
||||
allCheckBox.Text = Loc.GetString("turret-controls-window-all-checkbox");
|
||||
|
||||
if (_labelStyleClass != null)
|
||||
allCheckBox.Label.SetOnlyStyleClass(_labelStyleClass);
|
||||
|
||||
// Add the 'all' checkbox events
|
||||
allCheckBox.OnPressed += args =>
|
||||
{
|
||||
SetCheckBoxPressedState(_accessLevelEntries, allCheckBox.Pressed);
|
||||
|
||||
var accessLevels = new HashSet<ProtoId<AccessLevelPrototype>>();
|
||||
|
||||
foreach (var accessLevel in _accessLevelsForTab)
|
||||
{
|
||||
accessLevels.Add(accessLevel);
|
||||
}
|
||||
|
||||
OnAccessLevelsChangedEvent?.Invoke(accessLevels, allCheckBox.Pressed);
|
||||
};
|
||||
|
||||
AccessLevelChecklist.AddChild(allCheckBox);
|
||||
|
||||
// Hide the 'all' checkbox if the tab has only one access level
|
||||
var allCheckBoxVisible = _accessLevelsForTab.Count > 1;
|
||||
|
||||
allCheckBox.Visible = allCheckBoxVisible;
|
||||
allCheckBox.Disabled = !_canInteract;
|
||||
|
||||
// Add any remaining missing access level buttons to the UI
|
||||
foreach (var accessLevel in _accessLevelsForTab)
|
||||
{
|
||||
// Create the entry
|
||||
var accessLevelEntry = new AccessLevelEntry(_isMonotone);
|
||||
|
||||
accessLevelEntry.AccessLevel = accessLevel;
|
||||
accessLevelEntry.CheckBox.Text = accessLevel.GetAccessLevelName();
|
||||
accessLevelEntry.CheckBox.Pressed = _activeAccessLevels.Contains(accessLevel);
|
||||
accessLevelEntry.CheckBox.Disabled = !_canInteract;
|
||||
|
||||
if (_labelStyleClass != null)
|
||||
accessLevelEntry.CheckBox.Label.SetOnlyStyleClass(_labelStyleClass);
|
||||
|
||||
// Set the checkbox linkage lines
|
||||
var isEndOfList = _accessLevelsForTab.IndexOf(accessLevel) == (_accessLevelsForTab.Count - 1);
|
||||
|
||||
var lines = new List<(Vector2, Vector2)>
|
||||
{
|
||||
(new Vector2(0.5f, 0f), new Vector2(0.5f, isEndOfList ? 0.5f : 1f)),
|
||||
(new Vector2(0.5f, 0.5f), new Vector2(1f, 0.5f)),
|
||||
};
|
||||
|
||||
accessLevelEntry.UpdateCheckBoxLink(lines);
|
||||
accessLevelEntry.CheckBoxLink.Visible = allCheckBoxVisible;
|
||||
accessLevelEntry.CheckBoxLink.Modulate = !_canInteract ? Color.Gray : Color.White;
|
||||
|
||||
// Add checkbox events
|
||||
accessLevelEntry.CheckBox.OnPressed += args =>
|
||||
{
|
||||
// If the checkbox and its siblings are checked, check the 'all' checkbox too
|
||||
allCheckBox.Pressed = AreAllCheckBoxesPressed(_accessLevelEntries.Select(x => x.CheckBox));
|
||||
|
||||
OnAccessLevelsChangedEvent?.Invoke([accessLevelEntry.AccessLevel], accessLevelEntry.CheckBox.Pressed);
|
||||
};
|
||||
|
||||
AccessLevelChecklist.AddChild(accessLevelEntry);
|
||||
_accessLevelEntries.Add(accessLevelEntry);
|
||||
}
|
||||
|
||||
// Press the 'all' checkbox if all others are pressed
|
||||
allCheckBox.Pressed = AreAllCheckBoxesPressed(_accessLevelEntries.Select(x => x.CheckBox));
|
||||
}
|
||||
|
||||
private bool AreAllCheckBoxesPressed(IEnumerable<CheckBox> checkBoxes)
|
||||
{
|
||||
foreach (var checkBox in checkBoxes)
|
||||
{
|
||||
if (!checkBox.Pressed)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetCheckBoxPressedState(List<AccessLevelEntry> accessLevelEntries, bool pressed)
|
||||
{
|
||||
foreach (var accessLevelEntry in accessLevelEntries)
|
||||
{
|
||||
accessLevelEntry.CheckBox.Pressed = pressed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Provides the UI with a list of access groups using which list of tabs should be populated.
|
||||
/// </summary>
|
||||
public void SetAccessGroups(HashSet<ProtoId<AccessGroupPrototype>> accessGroups)
|
||||
{
|
||||
_accessGroups = accessGroups;
|
||||
|
||||
ArrangeAccessControls();
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the UI with a list of access levels with which it can populate the currently selected tab.
|
||||
/// </summary>
|
||||
public void SetAccessLevels(HashSet<ProtoId<AccessLevelPrototype>> accessLevels)
|
||||
{
|
||||
_accessLevels = accessLevels;
|
||||
|
||||
ArrangeAccessControls();
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets which access level checkboxes should be marked on the UI.
|
||||
/// </summary>
|
||||
public void SetActiveAccessLevels(HashSet<ProtoId<AccessLevelPrototype>> activeAccessLevels)
|
||||
{
|
||||
_activeAccessLevels = activeAccessLevels;
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the local player can interact with the checkboxes.
|
||||
/// </summary>
|
||||
public void SetLocalPlayerAccessibility(bool canInteract)
|
||||
{
|
||||
_canInteract = canInteract;
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the UI should use monotone buttons and checkboxes.
|
||||
/// </summary>
|
||||
public void SetMonotone(bool monotone)
|
||||
{
|
||||
_isMonotone = monotone;
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the specified style to the labels on the UI buttons and checkboxes.
|
||||
/// </summary>
|
||||
public void SetLabelStyleClass(string? styleClass)
|
||||
{
|
||||
_labelStyleClass = styleClass;
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
private void OnAccessGroupChanged(int newTabIndex)
|
||||
{
|
||||
if (newTabIndex == _accessGroupTabIndex)
|
||||
return;
|
||||
|
||||
_accessGroupTabIndex = newTabIndex;
|
||||
|
||||
if (TryRebuildAccessGroupControls())
|
||||
RebuildAccessLevelsControls();
|
||||
}
|
||||
|
||||
private Button CreateAccessGroupButton()
|
||||
{
|
||||
var button = _isMonotone ? new MonotoneButton() : new Button();
|
||||
|
||||
button.ToggleMode = true;
|
||||
button.Group = _accessGroupsButtons;
|
||||
button.Label.HorizontalAlignment = HAlignment.Left;
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private CheckBox CreateAccessLevelCheckbox()
|
||||
{
|
||||
var checkbox = _isMonotone ? new MonotoneCheckBox() : new CheckBox();
|
||||
|
||||
checkbox.Margin = new Thickness(0, 0, 0, 3);
|
||||
checkbox.ToggleMode = true;
|
||||
checkbox.ReservesSpace = false;
|
||||
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
private sealed class AccessLevelEntry : BoxContainer
|
||||
{
|
||||
public ProtoId<AccessLevelPrototype> AccessLevel;
|
||||
public readonly CheckBox CheckBox;
|
||||
public readonly LineRenderer CheckBoxLink;
|
||||
|
||||
public AccessLevelEntry(bool monotone)
|
||||
{
|
||||
HorizontalExpand = true;
|
||||
|
||||
CheckBoxLink = new LineRenderer
|
||||
{
|
||||
SetWidth = 22,
|
||||
VerticalExpand = true,
|
||||
Margin = new Thickness(0, -1),
|
||||
ReservesSpace = false,
|
||||
};
|
||||
|
||||
AddChild(CheckBoxLink);
|
||||
|
||||
CheckBox = monotone ? new MonotoneCheckBox() : new CheckBox();
|
||||
CheckBox.ToggleMode = true;
|
||||
CheckBox.Margin = new Thickness(0f, 0f, 0f, 3f);
|
||||
|
||||
AddChild(CheckBox);
|
||||
}
|
||||
|
||||
public void UpdateCheckBoxLink(List<(Vector2, Vector2)> lines)
|
||||
{
|
||||
CheckBoxLink.Lines = lines;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LineRenderer : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// List of lines to render (their start and end x-y coordinates).
|
||||
/// Position (0,0) is the top left corner of the control and
|
||||
/// position (1,1) is the bottom right corner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The color of the lines is inherited from the control.
|
||||
/// </remarks>
|
||||
public List<(Vector2, Vector2)> Lines;
|
||||
|
||||
public LineRenderer()
|
||||
{
|
||||
Lines = new List<(Vector2, Vector2)>();
|
||||
}
|
||||
|
||||
public LineRenderer(List<(Vector2, Vector2)> lines)
|
||||
{
|
||||
Lines = lines;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
foreach (var line in Lines)
|
||||
{
|
||||
var start = PixelPosition +
|
||||
new Vector2(PixelWidth * line.Item1.X, PixelHeight * line.Item1.Y);
|
||||
|
||||
var end = PixelPosition +
|
||||
new Vector2(PixelWidth * line.Item2.X, PixelHeight * line.Item2.Y);
|
||||
|
||||
handle.DrawLine(start, end, ActualModulateSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
|
||||
@@ -14,21 +11,13 @@ namespace Content.Client.Access.UI
|
||||
public sealed class IdCardConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
private readonly SharedIdCardConsoleSystem _idCardConsoleSystem = default!;
|
||||
|
||||
private IdCardConsoleWindow? _window;
|
||||
|
||||
// CCVar.
|
||||
private int _maxNameLength;
|
||||
private int _maxIdJobLength;
|
||||
|
||||
public IdCardConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_idCardConsoleSystem = EntMan.System<SharedIdCardConsoleSystem>();
|
||||
|
||||
_maxNameLength =_cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -75,13 +64,13 @@ namespace Content.Client.Access.UI
|
||||
_window?.UpdateState(castState);
|
||||
}
|
||||
|
||||
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
|
||||
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, string newJobPrototype)
|
||||
{
|
||||
if (newFullName.Length > _maxNameLength)
|
||||
newFullName = newFullName[.._maxNameLength];
|
||||
if (newFullName.Length > MaxFullNameLength)
|
||||
newFullName = newFullName[..MaxFullNameLength];
|
||||
|
||||
if (newJobTitle.Length > _maxIdJobLength)
|
||||
newJobTitle = newJobTitle[.._maxIdJobLength];
|
||||
if (newJobTitle.Length > MaxJobTitleLength)
|
||||
newJobTitle = newJobTitle[..MaxJobTitleLength];
|
||||
|
||||
SendMessage(new WriteToTargetIdMessage(
|
||||
newFullName,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
|
||||
@@ -16,17 +14,12 @@ namespace Content.Client.Access.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class IdCardConsoleWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
private readonly ISawmill _logMill = default!;
|
||||
|
||||
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||
|
||||
// CCVar.
|
||||
private int _maxNameLength;
|
||||
private int _maxIdJobLength;
|
||||
|
||||
private AccessLevelControl _accessButtons = new();
|
||||
private readonly List<string> _jobPrototypeIds = new();
|
||||
|
||||
@@ -46,11 +39,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
_owner = owner;
|
||||
|
||||
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
|
||||
FullNameLineEdit.OnTextEntered += _ => SubmitData();
|
||||
FullNameLineEdit.IsValid = s => s.Length <= _maxNameLength;
|
||||
FullNameLineEdit.OnTextChanged += _ =>
|
||||
{
|
||||
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
|
||||
@@ -58,7 +47,6 @@ namespace Content.Client.Access.UI
|
||||
FullNameSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
|
||||
JobTitleLineEdit.IsValid = s => s.Length <= _maxIdJobLength;
|
||||
JobTitleLineEdit.OnTextChanged += _ =>
|
||||
{
|
||||
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
|
||||
@@ -123,7 +111,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
foreach (var group in job.AccessGroups)
|
||||
{
|
||||
if (!_prototypeManager.Resolve(group, out AccessGroupPrototype? groupPrototype))
|
||||
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -186,8 +174,7 @@ namespace Content.Client.Access.UI
|
||||
new List<ProtoId<AccessLevelPrototype>>());
|
||||
|
||||
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
||||
// If the job index is < 0 that means they don't have a job registered in the station records
|
||||
// or the IdCardComponent's JobPrototype field.
|
||||
// If the job index is < 0 that means they don't have a job registered in the station records.
|
||||
// For example, a new ID from a box would have no job index.
|
||||
if (jobIndex < 0)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using Content.Shared.Actions.Components;
|
||||
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
||||
|
||||
namespace Content.Client.Actions;
|
||||
|
||||
/// <summary>
|
||||
@@ -10,17 +7,3 @@ public sealed class FillActionSlotEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid? Action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client-side event used to attempt to trigger a targeted action.
|
||||
/// This only gets raised if the has <see cref="TargetActionComponent">.
|
||||
/// Handlers must set <c>Handled</c> to true, then if the action has been performed,
|
||||
/// i.e. a target is found, then FoundTarget must be set to true.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct ActionTargetAttemptEvent(
|
||||
PointerInputCmdArgs Input,
|
||||
Entity<ActionsComponent> User,
|
||||
ActionComponent Action,
|
||||
bool Handled = false,
|
||||
bool FoundTarget = false);
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Mapping;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -28,10 +22,9 @@ namespace Content.Client.Actions
|
||||
{
|
||||
public delegate void OnActionReplaced(EntityUid actionId);
|
||||
|
||||
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
public event Action<EntityUid>? OnActionAdded;
|
||||
@@ -43,67 +36,156 @@ namespace Content.Client.Actions
|
||||
public event Action<List<SlotAssignment>>? AssignSlot;
|
||||
|
||||
private readonly List<EntityUid> _removed = new();
|
||||
private readonly List<Entity<ActionComponent>> _added = new();
|
||||
|
||||
public static readonly EntProtoId MappingEntityAction = "BaseMappingEntityAction";
|
||||
private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActionsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<ActionsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
|
||||
|
||||
SubscribeLocalEvent<ActionComponent, AfterAutoHandleStateEvent>(OnActionAutoHandleState);
|
||||
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ActionTargetAttemptEvent>(OnEntityTargetAttempt);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ActionTargetAttemptEvent>(OnWorldTargetAttempt);
|
||||
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
|
||||
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
|
||||
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
|
||||
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
|
||||
}
|
||||
|
||||
|
||||
private void OnActionAutoHandleState(Entity<ActionComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
UpdateAction(ent);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateAction(Entity<ActionComponent> ent)
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
// TODO: Decouple this.
|
||||
ent.Comp.IconColor = _sharedCharges.GetCurrentCharges(ent.Owner) == 0 ? ent.Comp.DisabledIconColor : ent.Comp.OriginalIconColor;
|
||||
base.UpdateAction(ent);
|
||||
if (_playerManager.LocalEntity != ent.Comp.AttachedEntity)
|
||||
if (args.Current is not InstantActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState<InstantActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EntityTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.Blacklist = state.Blacklist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not WorldTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
BaseHandleState<WorldTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void OnEntityWorldTargetHandleState(EntityUid uid,
|
||||
EntityWorldTargetActionComponent component,
|
||||
ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not EntityWorldTargetActionComponentState state)
|
||||
return;
|
||||
|
||||
component.Whitelist = state.Whitelist;
|
||||
component.CanTargetSelf = state.CanTargetSelf;
|
||||
BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
|
||||
}
|
||||
|
||||
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
|
||||
{
|
||||
// TODO ACTIONS use auto comp states
|
||||
component.Icon = state.Icon;
|
||||
component.IconOn = state.IconOn;
|
||||
component.IconColor = state.IconColor;
|
||||
component.OriginalIconColor = state.OriginalIconColor;
|
||||
component.DisabledIconColor = state.DisabledIconColor;
|
||||
component.Keywords.Clear();
|
||||
component.Keywords.UnionWith(state.Keywords);
|
||||
component.Enabled = state.Enabled;
|
||||
component.Toggled = state.Toggled;
|
||||
component.Cooldown = state.Cooldown;
|
||||
component.UseDelay = state.UseDelay;
|
||||
component.Charges = state.Charges;
|
||||
component.MaxCharges = state.MaxCharges;
|
||||
component.RenewCharges = state.RenewCharges;
|
||||
component.Container = EnsureEntity<T>(state.Container, uid);
|
||||
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
component.CheckConsciousness = state.CheckConsciousness;
|
||||
component.ClientExclusive = state.ClientExclusive;
|
||||
component.Priority = state.Priority;
|
||||
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
|
||||
component.RaiseOnUser = state.RaiseOnUser;
|
||||
component.RaiseOnAction = state.RaiseOnAction;
|
||||
component.AutoPopulate = state.AutoPopulate;
|
||||
component.Temporary = state.Temporary;
|
||||
component.ItemIconStyle = state.ItemIconStyle;
|
||||
component.Sound = state.Sound;
|
||||
|
||||
UpdateAction(uid, component);
|
||||
}
|
||||
|
||||
public override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
|
||||
{
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return;
|
||||
|
||||
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
|
||||
base.UpdateAction(actionId, action);
|
||||
if (_playerManager.LocalEntity != action.AttachedEntity)
|
||||
return;
|
||||
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<ActionsComponent> ent, ref ComponentHandleState args)
|
||||
private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ActionsComponentState state)
|
||||
return;
|
||||
|
||||
var (uid, comp) = ent;
|
||||
_added.Clear();
|
||||
_removed.Clear();
|
||||
var stateEnts = EnsureEntitySet<ActionsComponent>(state.Actions, uid);
|
||||
foreach (var act in comp.Actions)
|
||||
foreach (var act in component.Actions)
|
||||
{
|
||||
if (!stateEnts.Contains(act) && !IsClientSide(act))
|
||||
_removed.Add(act);
|
||||
}
|
||||
comp.Actions.ExceptWith(_removed);
|
||||
component.Actions.ExceptWith(_removed);
|
||||
|
||||
foreach (var actionId in stateEnts)
|
||||
{
|
||||
if (!actionId.IsValid())
|
||||
continue;
|
||||
|
||||
if (!comp.Actions.Add(actionId))
|
||||
if (!component.Actions.Add(actionId))
|
||||
continue;
|
||||
|
||||
if (GetAction(actionId) is {} action)
|
||||
_added.Add(action);
|
||||
TryGetActionData(actionId, out var action);
|
||||
_added.Add((actionId, action));
|
||||
}
|
||||
|
||||
if (_playerManager.LocalEntity != uid)
|
||||
@@ -118,46 +200,45 @@ namespace Content.Client.Actions
|
||||
|
||||
foreach (var action in _added)
|
||||
{
|
||||
OnActionAdded?.Invoke(action);
|
||||
OnActionAdded?.Invoke(action.Item1);
|
||||
}
|
||||
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
public static int ActionComparer(Entity<ActionComponent> a, Entity<ActionComponent> b)
|
||||
public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b)
|
||||
{
|
||||
var priorityA = a.Comp?.Priority ?? 0;
|
||||
var priorityB = b.Comp?.Priority ?? 0;
|
||||
var priorityA = a.Item2?.Priority ?? 0;
|
||||
var priorityB = b.Item2?.Priority ?? 0;
|
||||
if (priorityA != priorityB)
|
||||
return priorityA - priorityB;
|
||||
|
||||
priorityA = a.Comp?.Container?.Id ?? 0;
|
||||
priorityB = b.Comp?.Container?.Id ?? 0;
|
||||
priorityA = a.Item2?.Container?.Id ?? 0;
|
||||
priorityB = b.Item2?.Container?.Id ?? 0;
|
||||
return priorityA - priorityB;
|
||||
}
|
||||
|
||||
protected override void ActionAdded(Entity<ActionsComponent> performer, Entity<ActionComponent> action)
|
||||
protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp,
|
||||
BaseActionComponent action)
|
||||
{
|
||||
if (_playerManager.LocalEntity != performer.Owner)
|
||||
if (_playerManager.LocalEntity != performer)
|
||||
return;
|
||||
|
||||
OnActionAdded?.Invoke(action);
|
||||
ActionsUpdated?.Invoke();
|
||||
OnActionAdded?.Invoke(actionId);
|
||||
}
|
||||
|
||||
protected override void ActionRemoved(Entity<ActionsComponent> performer, Entity<ActionComponent> action)
|
||||
protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
|
||||
{
|
||||
if (_playerManager.LocalEntity != performer.Owner)
|
||||
if (_playerManager.LocalEntity != performer)
|
||||
return;
|
||||
|
||||
OnActionRemoved?.Invoke(action);
|
||||
ActionsUpdated?.Invoke();
|
||||
OnActionRemoved?.Invoke(actionId);
|
||||
}
|
||||
|
||||
public IEnumerable<Entity<ActionComponent>> GetClientActions()
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } user)
|
||||
return Enumerable.Empty<Entity<ActionComponent>>();
|
||||
return Enumerable.Empty<(EntityUid, BaseActionComponent)>();
|
||||
|
||||
return GetActions(user);
|
||||
}
|
||||
@@ -194,24 +275,25 @@ namespace Content.Client.Actions
|
||||
CommandBinds.Unregister<ActionsSystem>();
|
||||
}
|
||||
|
||||
public void TriggerAction(Entity<ActionComponent> action)
|
||||
public void TriggerAction(EntityUid actionId, BaseActionComponent action)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } user)
|
||||
return;
|
||||
|
||||
// TODO: unhardcode this somehow
|
||||
|
||||
if (!HasComp<InstantActionComponent>(action))
|
||||
return;
|
||||
|
||||
if (action.Comp.ClientExclusive)
|
||||
if (_playerManager.LocalEntity is not { } user ||
|
||||
!TryComp(user, out ActionsComponent? actions))
|
||||
{
|
||||
PerformAction(user, action);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action is not InstantActionComponent instantAction)
|
||||
return;
|
||||
|
||||
if (action.ClientExclusive)
|
||||
{
|
||||
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = new RequestPerformActionEvent(GetNetEntity(action));
|
||||
RaisePredictiveEvent(request);
|
||||
var request = new RequestPerformActionEvent(GetNetEntity(actionId));
|
||||
EntityManager.RaisePredictiveEvent(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,140 +316,39 @@ namespace Content.Client.Actions
|
||||
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
|
||||
return;
|
||||
|
||||
var actions = EnsureComp<ActionsComponent>(user);
|
||||
|
||||
ClearAssignments?.Invoke();
|
||||
|
||||
var assignments = new List<SlotAssignment>();
|
||||
|
||||
foreach (var entry in sequence.Sequence)
|
||||
{
|
||||
if (entry is not MappingDataNode map)
|
||||
continue;
|
||||
|
||||
if (!map.TryGet("action", out var actionNode))
|
||||
continue;
|
||||
|
||||
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||
var actionId = Spawn();
|
||||
AddComp(actionId, action);
|
||||
AddActionDirect(user, actionId);
|
||||
|
||||
if (map.TryGet<ValueDataNode>("name", out var nameNode))
|
||||
_metaData.SetEntityName(actionId, nameNode.Value);
|
||||
|
||||
if (!map.TryGet("assignments", out var assignmentNode))
|
||||
continue;
|
||||
|
||||
var actionId = EntityUid.Invalid;
|
||||
if (map.TryGet<ValueDataNode>("action", out var actionNode))
|
||||
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(assignmentNode, notNullableOverride: true);
|
||||
|
||||
foreach (var index in nodeAssignments)
|
||||
{
|
||||
var id = new EntProtoId(actionNode.Value);
|
||||
actionId = Spawn(id);
|
||||
var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
|
||||
assignments.Add(assignment);
|
||||
}
|
||||
else if (map.TryGet<ValueDataNode>("entity", out var entityNode))
|
||||
{
|
||||
var id = new EntProtoId(entityNode.Value);
|
||||
var proto = _proto.Index(id);
|
||||
actionId = Spawn(MappingEntityAction);
|
||||
SetIcon(actionId, new SpriteSpecifier.EntityPrototype(id));
|
||||
SetEvent(actionId, new StartPlacementActionEvent()
|
||||
{
|
||||
PlacementOption = "SnapgridCenter",
|
||||
EntityType = id
|
||||
});
|
||||
_metaData.SetEntityName(actionId, proto.Name);
|
||||
}
|
||||
else if (map.TryGet<ValueDataNode>("tileId", out var tileNode))
|
||||
{
|
||||
var id = new ProtoId<ContentTileDefinition>(tileNode.Value);
|
||||
var proto = _proto.Index(id);
|
||||
actionId = Spawn(MappingEntityAction);
|
||||
if (proto.Sprite is {} sprite)
|
||||
SetIcon(actionId, new SpriteSpecifier.Texture(sprite));
|
||||
SetEvent(actionId, new StartPlacementActionEvent()
|
||||
{
|
||||
PlacementOption = "AlignTileAny",
|
||||
TileId = id
|
||||
});
|
||||
_metaData.SetEntityName(actionId, Loc.GetString(proto.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Mapping actions from {path} had unknown action data!");
|
||||
continue;
|
||||
}
|
||||
|
||||
AddActionDirect((user, actions), actionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWorldTargetAttempt(Entity<WorldTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
var (uid, comp) = ent;
|
||||
var action = args.Action;
|
||||
var coords = args.Input.Coordinates;
|
||||
var user = args.User;
|
||||
|
||||
if (!ValidateWorldTarget(user, coords, ent))
|
||||
return;
|
||||
|
||||
// optionally send the clicked entity too, if it matches its whitelist etc
|
||||
// this is the actual entity-world targeting magic
|
||||
EntityUid? targetEnt = null;
|
||||
if (TryComp<EntityTargetActionComponent>(ent, out var entity) &&
|
||||
args.Input.EntityUid != null &&
|
||||
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
|
||||
{
|
||||
targetEnt = args.Input.EntityUid;
|
||||
}
|
||||
|
||||
if (action.ClientExclusive)
|
||||
{
|
||||
// TODO: abstract away from single event or maybe just RaiseLocalEvent?
|
||||
if (comp.Event is {} ev)
|
||||
{
|
||||
ev.Target = coords;
|
||||
ev.Entity = targetEnt;
|
||||
}
|
||||
|
||||
PerformAction((user, user.Comp), (uid, action));
|
||||
}
|
||||
else
|
||||
RaisePredictiveEvent(new RequestPerformActionEvent(GetNetEntity(uid), GetNetEntity(targetEnt), GetNetCoordinates(coords)));
|
||||
|
||||
args.FoundTarget = true;
|
||||
}
|
||||
|
||||
private void OnEntityTargetAttempt(Entity<EntityTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
if (args.Input.EntityUid is not { Valid: true } entity)
|
||||
return;
|
||||
|
||||
// let world target component handle it
|
||||
var (uid, comp) = ent;
|
||||
if (comp.Event is not {} ev)
|
||||
{
|
||||
DebugTools.Assert(HasComp<WorldTargetActionComponent>(ent), $"Action {ToPrettyString(ent)} requir45es WorldTargetActionComponent for entity-world targeting");
|
||||
return;
|
||||
}
|
||||
|
||||
var action = args.Action;
|
||||
var user = args.User;
|
||||
|
||||
if (!ValidateEntityTarget(user, entity, ent))
|
||||
return;
|
||||
|
||||
if (action.ClientExclusive)
|
||||
{
|
||||
ev.Target = entity;
|
||||
|
||||
PerformAction((user, user.Comp), (uid, action));
|
||||
}
|
||||
else
|
||||
{
|
||||
RaisePredictiveEvent(new RequestPerformActionEvent(GetNetEntity(uid), GetNetEntity(entity)));
|
||||
}
|
||||
|
||||
args.FoundTarget = true;
|
||||
AssignSlot?.Invoke(assignments);
|
||||
}
|
||||
|
||||
public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Content.Client.Actions.UI
|
||||
/// </summary>
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; }
|
||||
|
||||
public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null)
|
||||
public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null, FormattedMessage? charges = null)
|
||||
{
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
|
||||
@@ -52,6 +52,17 @@ namespace Content.Client.Actions.UI
|
||||
vbox.AddChild(description);
|
||||
}
|
||||
|
||||
if (charges != null && !string.IsNullOrWhiteSpace(charges.ToString()))
|
||||
{
|
||||
var chargesLabel = new RichTextLabel
|
||||
{
|
||||
MaxWidth = TooltipTextMaxWidth,
|
||||
StyleClasses = { StyleNano.StyleClassTooltipActionCharges }
|
||||
};
|
||||
chargesLabel.SetMessage(charges);
|
||||
vbox.AddChild(chargesLabel);
|
||||
}
|
||||
|
||||
vbox.AddChild(_cooldownLabel = new RichTextLabel
|
||||
{
|
||||
MaxWidth = TooltipTextMaxWidth,
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -19,78 +14,32 @@ namespace Content.Client.Administration;
|
||||
|
||||
internal sealed class AdminNameOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
|
||||
private readonly AdminSystem _system;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly SharedRoleSystem _roles;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly Font _font;
|
||||
private readonly Font _fontBold;
|
||||
private AdminOverlayAntagFormat _overlayFormat;
|
||||
private AdminOverlayAntagSymbolStyle _overlaySymbolStyle;
|
||||
private bool _overlayPlaytime;
|
||||
private bool _overlayStartingJob;
|
||||
private float _ghostFadeDistance;
|
||||
private float _ghostHideDistance;
|
||||
private int _overlayStackMax;
|
||||
private float _overlayMergeDistance;
|
||||
|
||||
//TODO make this adjustable via GUI?
|
||||
private static readonly FrozenSet<ProtoId<RoleTypePrototype>> Filter =
|
||||
new ProtoId<RoleTypePrototype>[] {"SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"}
|
||||
.ToFrozenSet();
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
private readonly ProtoId<RoleTypePrototype>[] _filter =
|
||||
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
|
||||
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
|
||||
private readonly Color _antagColorClassic = Color.OrangeRed;
|
||||
|
||||
public AdminNameOverlay(
|
||||
AdminSystem system,
|
||||
IEntityManager entityManager,
|
||||
IEyeManager eyeManager,
|
||||
IResourceCache resourceCache,
|
||||
EntityLookupSystem entityLookup,
|
||||
IUserInterfaceManager userInterfaceManager,
|
||||
IConfigurationManager config,
|
||||
SharedRoleSystem roles,
|
||||
IPrototypeManager prototypeManager)
|
||||
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
_roles = roles;
|
||||
_prototypeManager = prototypeManager;
|
||||
ZIndex = 200;
|
||||
// Setting these to a specific ttf would break the antag symbols
|
||||
_font = resourceCache.NotoStack();
|
||||
_fontBold = resourceCache.NotoStack(variation: "Bold");
|
||||
|
||||
config.OnValueChanged(CCVars.AdminOverlayAntagFormat, (show) => { _overlayFormat = UpdateOverlayFormat(show); }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlaySymbolStyle, (show) => { _overlaySymbolStyle = UpdateOverlaySymbolStyle(show); }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayGhostFadeDistance, (f) => { _ghostFadeDistance = f; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayStackMax, (i) => { _overlayStackMax = i; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true);
|
||||
}
|
||||
|
||||
private AdminOverlayAntagFormat UpdateOverlayFormat(string formatString)
|
||||
{
|
||||
if (!Enum.TryParse<AdminOverlayAntagFormat>(formatString, out var format))
|
||||
format = AdminOverlayAntagFormat.Binary;
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
private AdminOverlayAntagSymbolStyle UpdateOverlaySymbolStyle(string symbolString)
|
||||
{
|
||||
if (!Enum.TryParse<AdminOverlayAntagSymbolStyle>(symbolString, out var symbolStyle))
|
||||
symbolStyle = AdminOverlayAntagSymbolStyle.Off;
|
||||
|
||||
return symbolStyle;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
@@ -98,185 +47,75 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.WorldAABB;
|
||||
var colorDisconnected = Color.White;
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 14f) * uiScale;
|
||||
var drawnOverlays = new List<(Vector2,Vector2)>() ; // A saved list of the overlays already drawn
|
||||
|
||||
// Get all player positions before drawing overlays, so they can be sorted before iteration
|
||||
var sortable = new List<(PlayerInfo, Box2, EntityUid, Vector2)>();
|
||||
foreach (var info in _system.PlayerList)
|
||||
//TODO make this adjustable via GUI
|
||||
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
|
||||
var playTime = _config.GetCVar(CCVars.AdminOverlayPlaytime);
|
||||
var startingJob = _config.GetCVar(CCVars.AdminOverlayStartingJob);
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(info.NetEntity);
|
||||
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
|
||||
|
||||
// If entity does not exist or is on a different map, skip
|
||||
if (entity == null
|
||||
|| !_entityManager.EntityExists(entity)
|
||||
|| _entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
|
||||
// Otherwise the entity can not exist yet
|
||||
if (entity == null || !_entityManager.EntityExists(entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = _entityLookup.GetWorldAABB(entity.Value);
|
||||
// if not on screen, skip
|
||||
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get on-screen coordinates of player
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center).Rounded();
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 14f) * uiScale;
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
|
||||
sortable.Add((info, aabb, entity.Value, screenCoordinates));
|
||||
}
|
||||
|
||||
// Draw overlays for visible players, starting from the top of the screen
|
||||
foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList())
|
||||
{
|
||||
var playerInfo = info.Item1;
|
||||
var rolePrototype = playerInfo.RoleProto == null
|
||||
? null
|
||||
: _prototypeManager.Index(playerInfo.RoleProto.Value);
|
||||
|
||||
var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
|
||||
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
|
||||
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
|
||||
|
||||
var aabb = info.Item2;
|
||||
var entity = info.Item3;
|
||||
var screenCoordinatesCenter = info.Item4;
|
||||
//the center position is kept separately, for simpler position comparison later
|
||||
var centerOffset = new Vector2(28f, -18f) * uiScale;
|
||||
var screenCoordinates = screenCoordinatesCenter + centerOffset;
|
||||
var alpha = 1f;
|
||||
|
||||
//TODO make a smarter system where the starting offset can be modified by the predicted position and size of already-drawn overlays/stacks?
|
||||
var currentOffset = Vector2.Zero;
|
||||
|
||||
// Ghosts near the cursor are made transparent/invisible
|
||||
// TODO would be "cheaper" if playerinfo already contained a ghost bool, this gets called every frame for every onscreen player!
|
||||
if (_entityManager.HasComponent<GhostComponent>(entity))
|
||||
{
|
||||
// We want the map positions here, so we don't have to worry about resolution and such shenanigans
|
||||
var mobPosition = aabb.Center;
|
||||
var mousePosition = _eyeManager
|
||||
.ScreenToMap(_userInterfaceManager.MousePositionScaled.Position * uiScale)
|
||||
.Position;
|
||||
var dist = Vector2.Distance(mobPosition, mousePosition);
|
||||
if (dist < _ghostHideDistance)
|
||||
continue;
|
||||
|
||||
alpha = Math.Clamp((dist - _ghostHideDistance) / (_ghostFadeDistance - _ghostHideDistance), 0f, 1f);
|
||||
colorDisconnected.A = alpha;
|
||||
}
|
||||
|
||||
// If the new overlay text block is within merge distance of any previous ones
|
||||
// merge them into a stack so they don't hide each other
|
||||
var stack = drawnOverlays.FindAll(x =>
|
||||
Vector2.Distance(_eyeManager.ScreenToMap(x.Item1).Position, aabb.Center) <= _overlayMergeDistance);
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
screenCoordinates = stack.First().Item1 + centerOffset;
|
||||
// Replacing this overlay's coordinates for the later save with the stack root's coordinates
|
||||
// so that other overlays don't try to stack to these coordinates
|
||||
screenCoordinatesCenter = stack.First().Item1;
|
||||
|
||||
var i = 1;
|
||||
foreach (var s in stack)
|
||||
{
|
||||
// additional entries after maximum stack size is reached will be drawn over the last entry
|
||||
if (i <= _overlayStackMax - 1)
|
||||
currentOffset = lineoffset + s.Item2 ;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Character name
|
||||
var color = Color.Aquamarine;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
// Username
|
||||
color = Color.Yellow;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
// Playtime
|
||||
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && _overlayPlaytime)
|
||||
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && playTime)
|
||||
{
|
||||
color = Color.Orange;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? Color.Orange : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// Job
|
||||
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && _overlayStartingJob)
|
||||
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && startingJob)
|
||||
{
|
||||
color = Color.GreenYellow;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? Color.GreenYellow : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// Determine antag symbol
|
||||
string? symbol;
|
||||
switch (_overlaySymbolStyle)
|
||||
if (classic && playerInfo.Antag)
|
||||
{
|
||||
case AdminOverlayAntagSymbolStyle.Specific:
|
||||
symbol = roleSymbol;
|
||||
break;
|
||||
case AdminOverlayAntagSymbolStyle.Basic:
|
||||
symbol = Loc.GetString("player-tab-antag-prefix");
|
||||
break;
|
||||
default:
|
||||
case AdminOverlayAntagSymbolStyle.Off:
|
||||
symbol = string.Empty;
|
||||
break;
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, _antagLabelClassic, uiScale, Color.OrangeRed);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// Determine antag/role type name
|
||||
string? text;
|
||||
switch (_overlayFormat)
|
||||
else if (!classic && _filter.Contains(playerInfo.RoleProto))
|
||||
{
|
||||
case AdminOverlayAntagFormat.Roletype:
|
||||
color = roleColor;
|
||||
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||
text = IsFiltered(playerInfo.RoleProto)
|
||||
? roleName.ToUpper()
|
||||
: string.Empty;
|
||||
break;
|
||||
case AdminOverlayAntagFormat.Subtype:
|
||||
color = roleColor;
|
||||
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||
text = IsFiltered(playerInfo.RoleProto)
|
||||
? _roles.GetRoleSubtypeLabel(roleName, playerInfo.Subtype).ToUpper()
|
||||
: string.Empty;
|
||||
break;
|
||||
default:
|
||||
case AdminOverlayAntagFormat.Binary:
|
||||
color = Color.OrangeRed;
|
||||
symbol = playerInfo.Antag ? symbol : string.Empty;
|
||||
text = playerInfo.Antag ? _antagLabelClassic : string.Empty;
|
||||
break;
|
||||
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var color = playerInfo.RoleProto.Color;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// Draw antag label
|
||||
color.A = alpha;
|
||||
var label = !string.IsNullOrEmpty(symbol)
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", text))
|
||||
: text;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
//Save the coordinates and size of the text block, for stack merge check
|
||||
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFiltered(ProtoId<RoleTypePrototype>? roleProtoId)
|
||||
{
|
||||
if (roleProtoId == null)
|
||||
return false;
|
||||
|
||||
return Filter.Contains(roleProtoId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Administration.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Administration.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class HeadstandComponent : SharedHeadstandComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@ namespace Content.Client.Administration.Managers
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IClientNetManager _netMgr = default!;
|
||||
[Dependency] private readonly IClientConGroupController _conGroup = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _host = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
|
||||
@@ -87,12 +86,12 @@ namespace Content.Client.Administration.Managers
|
||||
private void UpdateMessageRx(MsgUpdateAdminStatus message)
|
||||
{
|
||||
_availableCommands.Clear();
|
||||
var host = IoCManager.Resolve<IClientConsoleHost>();
|
||||
|
||||
// Anything marked as Any we'll just add even if the server doesn't know about it.
|
||||
foreach (var (command, instance) in _host.AvailableCommands)
|
||||
foreach (var (command, instance) in host.AvailableCommands)
|
||||
{
|
||||
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null)
|
||||
continue;
|
||||
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null) continue;
|
||||
_availableCommands.Add(command);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
public enum AdminOverlayAntagFormat
|
||||
{
|
||||
Binary,
|
||||
Roletype,
|
||||
Subtype
|
||||
}
|
||||
|
||||
public enum AdminOverlayAntagSymbolStyle
|
||||
{
|
||||
Off,
|
||||
Basic,
|
||||
Specific
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Administration;
|
||||
|
||||
namespace Content.Client.Administration.Systems;
|
||||
|
||||
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
|
||||
{
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration.Systems
|
||||
{
|
||||
@@ -16,9 +14,6 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -27,16 +22,7 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
private void InitializeOverlay()
|
||||
{
|
||||
_adminNameOverlay = new AdminNameOverlay(
|
||||
this,
|
||||
EntityManager,
|
||||
_eyeManager,
|
||||
_resourceCache,
|
||||
_entityLookup,
|
||||
_userInterfaceManager,
|
||||
_configurationManager,
|
||||
_roles,
|
||||
_proto);
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
@@ -52,8 +38,7 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
public void AdminOverlayOn()
|
||||
{
|
||||
if (_overlayManager.HasOverlay<AdminNameOverlay>())
|
||||
return;
|
||||
if (_overlayManager.HasOverlay<AdminNameOverlay>()) return;
|
||||
_overlayManager.AddOverlay(_adminNameOverlay);
|
||||
OverlayEnabled?.Invoke();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Content.Client.Administration.Systems
|
||||
var verb = new VvVerb()
|
||||
{
|
||||
Text = Loc.GetString("view-variables"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Act = () => _clientConsoleHost.ExecuteCommand($"vv {GetNetEntity(args.Target)}"),
|
||||
ClientExclusive = true // opening VV window is client-side. Don't ask server to run this verb.
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Client.Administration.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Administration.Systems;
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace Content.Client.Administration.Systems;
|
||||
|
||||
public sealed class KillSignSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<KillSignComponent, ComponentStartup>(KillSignAdded);
|
||||
@@ -20,10 +18,10 @@ public sealed class KillSignSystem : EntitySystem
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
if (!_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var layer, false))
|
||||
if (!sprite.LayerMapTryGet(KillSignKey.Key, out var layer))
|
||||
return;
|
||||
|
||||
_sprite.RemoveLayer((uid, sprite), layer);
|
||||
sprite.RemoveLayer(layer);
|
||||
}
|
||||
|
||||
private void KillSignAdded(EntityUid uid, KillSignComponent component, ComponentStartup args)
|
||||
@@ -31,15 +29,15 @@ public sealed class KillSignSystem : EntitySystem
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
if (_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var _, false))
|
||||
if (sprite.LayerMapTryGet(KillSignKey.Key, out var _))
|
||||
return;
|
||||
|
||||
var adj = _sprite.GetLocalBounds((uid, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
|
||||
var adj = sprite.Bounds.Height / 2 + ((1.0f/32) * 6.0f);
|
||||
|
||||
var layer = _sprite.AddLayer((uid, sprite), new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
|
||||
_sprite.LayerMapSet((uid, sprite), KillSignKey.Key, layer);
|
||||
var layer = sprite.AddLayer(new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
|
||||
sprite.LayerMapSet(KillSignKey.Key, layer);
|
||||
|
||||
_sprite.LayerSetOffset((uid, sprite), layer, new Vector2(0.0f, adj));
|
||||
sprite.LayerSetOffset(layer, new Vector2(0.0f, adj));
|
||||
sprite.LayerSetShader(layer, "unshaded");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:viewport="clr-namespace:Content.Client.Viewport"
|
||||
MouseFilter="Stop">
|
||||
<PanelContainer StyleClasses="BackgroundDark" Name="AdminCameraWindowRoot" Access="Public">
|
||||
<BoxContainer Orientation="Vertical" Access="Public">
|
||||
<!-- Camera -->
|
||||
<Control VerticalExpand="True" Name="CameraViewBox">
|
||||
<viewport:ScalingViewport Name="CameraView"
|
||||
MinSize="100 100"
|
||||
MouseFilter="Ignore" />
|
||||
</Control>
|
||||
<!-- Controller buttons -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="5 5 5 5">
|
||||
<Button StyleClasses="OpenRight" Name="FollowButton" HorizontalExpand="True" Access="Public" Text="{Loc 'admin-camera-window-follow'}" />
|
||||
<Button StyleClasses="OpenLeft" Name="PopControl" HorizontalExpand="True" Access="Public" Text="{Loc 'admin-camera-window-pop-out'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Eye;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminCamera;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminCameraControl : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
|
||||
public event Action? OnFollow;
|
||||
public event Action? OnPopoutControl;
|
||||
|
||||
private readonly EyeLerpingSystem _eyeLerpingSystem;
|
||||
private readonly FixedEye _defaultEye = new();
|
||||
private AdminCameraEuiState? _nextState;
|
||||
|
||||
private const float MinimumZoom = 0.1f;
|
||||
private const float MaximumZoom = 2.0f;
|
||||
|
||||
public EntityUid? CurrentCamera;
|
||||
public float Zoom = 1.0f;
|
||||
|
||||
public bool IsPoppedOut;
|
||||
|
||||
public AdminCameraControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_eyeLerpingSystem = _entManager.System<EyeLerpingSystem>();
|
||||
|
||||
CameraView.Eye = _defaultEye;
|
||||
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
PopControl.OnPressed += _ => OnPopoutControl?.Invoke();
|
||||
CameraView.OnResized += OnResized;
|
||||
}
|
||||
|
||||
private new void OnResized()
|
||||
{
|
||||
var width = Math.Max(CameraView.PixelWidth, (int)Math.Floor(CameraView.MinWidth));
|
||||
var height = Math.Max(CameraView.PixelHeight, (int)Math.Floor(CameraView.MinHeight));
|
||||
|
||||
CameraView.ViewportSize = new Vector2i(width, height);
|
||||
}
|
||||
|
||||
protected override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (CameraView.Eye == null)
|
||||
return;
|
||||
|
||||
Zoom = Math.Clamp(Zoom - args.Delta.Y * 0.15f * Zoom, MinimumZoom, MaximumZoom);
|
||||
CameraView.Eye.Zoom = new Vector2(Zoom, Zoom);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
public void SetState(AdminCameraEuiState state)
|
||||
{
|
||||
_nextState = state;
|
||||
}
|
||||
|
||||
// I know that this is awful, but I copied this from the solution editor anyways.
|
||||
// This is needed because EUIs update before the gamestate is applied, which means it will fail to get the uid from the net entity.
|
||||
// The suggestion from the comment in the solution editor saying to use a BUI is not ideal either:
|
||||
// - We would need to bind the UI to an entity, but with how BUIs currently work we cannot open it in the same tick as we spawn that entity on the server.
|
||||
// - We want the UI opened by the user session, not by their currently attached entity. Otherwise it would close in cases where admins move from one entity to another, for example when ghosting.
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (_nextState == null || _timing.LastRealTick < _nextState.Tick) // make sure the last gamestate has been applied
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetEntity(_nextState.Camera, out var cameraUid))
|
||||
return;
|
||||
|
||||
if (CurrentCamera == null)
|
||||
{
|
||||
_eyeLerpingSystem.AddEye(cameraUid.Value);
|
||||
CurrentCamera = cameraUid;
|
||||
}
|
||||
else if (CurrentCamera != cameraUid)
|
||||
{
|
||||
_eyeLerpingSystem.RemoveEye(CurrentCamera.Value);
|
||||
_eyeLerpingSystem.AddEye(cameraUid.Value);
|
||||
CurrentCamera = cameraUid;
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<EyeComponent>(CurrentCamera, out var eye))
|
||||
CameraView.Eye = eye.Eye ?? _defaultEye;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminCamera;
|
||||
|
||||
/// <summary>
|
||||
/// Admin Eui for opening a viewport window to observe entities.
|
||||
/// Use the "Open Camera" admin verb or the "camera" command to open.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class AdminCameraEui : BaseEui
|
||||
{
|
||||
private readonly AdminCameraWindow _window;
|
||||
private readonly AdminCameraControl _control;
|
||||
|
||||
// If not null the camera is in "popped out" mode and is in an external window.
|
||||
private OSWindow? _OSWindow;
|
||||
|
||||
// The last location the window was located at in game.
|
||||
// Is used for getting knowing where to "pop in" external windows.
|
||||
private Vector2 _lastLocation;
|
||||
|
||||
public AdminCameraEui()
|
||||
{
|
||||
_window = new AdminCameraWindow();
|
||||
_control = new AdminCameraControl();
|
||||
|
||||
_window.Contents.AddChild(_control);
|
||||
|
||||
_control.OnFollow += () => SendMessage(new AdminCameraFollowMessage());
|
||||
_window.OnClose += () =>
|
||||
{
|
||||
if (!_control.IsPoppedOut)
|
||||
SendMessage(new CloseEuiMessage());
|
||||
};
|
||||
|
||||
_control.OnPopoutControl += () =>
|
||||
{
|
||||
if (_control.IsPoppedOut)
|
||||
PopIn();
|
||||
else
|
||||
PopOut();
|
||||
};
|
||||
}
|
||||
|
||||
// Pop the window out into an external OS window
|
||||
private void PopOut()
|
||||
{
|
||||
_lastLocation = _window.Position;
|
||||
|
||||
// TODO: When there is a way to have a minimum window size, enforce something!
|
||||
_OSWindow = new OSWindow
|
||||
{
|
||||
SetSize = _window.Size,
|
||||
Title = _window.Title ?? Loc.GetString("admin-camera-window-title-placeholder"),
|
||||
};
|
||||
|
||||
_OSWindow.Show();
|
||||
|
||||
if (_OSWindow.Root == null)
|
||||
return;
|
||||
|
||||
_control.Orphan();
|
||||
_OSWindow.Root.AddChild(_control);
|
||||
|
||||
_OSWindow.Closed += () =>
|
||||
{
|
||||
if (_control.IsPoppedOut)
|
||||
SendMessage(new CloseEuiMessage());
|
||||
};
|
||||
|
||||
_control.IsPoppedOut = true;
|
||||
_control.PopControl.Text = Loc.GetString("admin-camera-window-pop-in");
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
// Pop the window back into the in game window.
|
||||
private void PopIn()
|
||||
{
|
||||
_control.Orphan();
|
||||
_window.Contents.AddChild(_control);
|
||||
|
||||
_window.Open(_lastLocation);
|
||||
|
||||
_control.IsPoppedOut = false;
|
||||
_control.PopControl.Text = Loc.GetString("admin-camera-window-pop-out");
|
||||
|
||||
_OSWindow?.Close();
|
||||
_OSWindow = null;
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase baseState)
|
||||
{
|
||||
if (baseState is not AdminCameraEuiState state)
|
||||
return;
|
||||
|
||||
_window.SetState(state);
|
||||
_control.SetState(state);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc admin-camera-window-title-placeholder}"
|
||||
SetSize="425 550"
|
||||
MinSize="200 225"
|
||||
Name="Window">
|
||||
</DefaultWindow>
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminCamera;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminCameraWindow : DefaultWindow
|
||||
{
|
||||
public AdminCameraWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
ContentsContainer.Margin = new Thickness(5, 0, 5, 0);
|
||||
}
|
||||
|
||||
public void SetState(AdminCameraEuiState state)
|
||||
{
|
||||
Title = Loc.GetString("admin-camera-window-title", ("name", state.Name));
|
||||
}
|
||||
}
|
||||
59
Content.Client/Administration/UI/AdminUIHelpers.cs
Normal file
59
Content.Client/Administration/UI/AdminUIHelpers.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Threading;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Administration.UI;
|
||||
|
||||
public static class AdminUIHelpers
|
||||
{
|
||||
private static void ResetButton(Button button, ConfirmationData data)
|
||||
{
|
||||
data.Cancellation.Cancel();
|
||||
button.ModulateSelfOverride = null;
|
||||
button.Text = data.OriginalText;
|
||||
}
|
||||
|
||||
public static bool RemoveConfirm(Button button, Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
if (confirmations.Remove(button, out var data))
|
||||
{
|
||||
ResetButton(button, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RemoveAllConfirms(Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
foreach (var (button, confirmation) in confirmations)
|
||||
{
|
||||
ResetButton(button, confirmation);
|
||||
}
|
||||
|
||||
confirmations.Clear();
|
||||
}
|
||||
|
||||
public static bool TryConfirm(Button button, Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
if (RemoveConfirm(button, confirmations))
|
||||
return true;
|
||||
|
||||
var data = new ConfirmationData(new CancellationTokenSource(), button.Text);
|
||||
confirmations[button] = data;
|
||||
|
||||
Timer.Spawn(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
confirmations.Remove(button);
|
||||
button.ModulateSelfOverride = null;
|
||||
button.Text = data.OriginalText;
|
||||
}, data.Cancellation.Token);
|
||||
|
||||
button.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault;
|
||||
button.Text = Loc.GetString("admin-player-actions-confirm");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ConfirmationData(CancellationTokenSource Cancellation, string? OriginalText);
|
||||
@@ -1,7 +1,7 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc ban-panel-title}" MinSize="410 500">
|
||||
Title="{Loc ban-panel-title}" MinSize="350 500">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<TabContainer Name="Tabs" VerticalExpand="True">
|
||||
<!-- Basic info -->
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.UI.CustomControls;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -33,21 +31,14 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
private uint Multiplier { get; set; }
|
||||
private bool HasBanFlag { get; set; }
|
||||
private TimeSpan? ButtonResetOn { get; set; }
|
||||
|
||||
// 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.
|
||||
// Role group name -> the role buttons themselves.
|
||||
private readonly Dictionary<string, List<Button>> _roleCheckboxes = new();
|
||||
private readonly List<CheckBox> _roleCheckboxes = new();
|
||||
private readonly ISawmill _banpanelSawmill;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
private const string ExpandedArrow = "▼";
|
||||
private const string ContractedArrow = "▶";
|
||||
|
||||
private enum TabNumbers
|
||||
{
|
||||
@@ -153,90 +144,47 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
|
||||
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
|
||||
|
||||
var departmentJobs = _protoMan.EnumeratePrototypes<DepartmentPrototype>()
|
||||
.OrderBy(x => x.Weight);
|
||||
foreach (var proto in departmentJobs)
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
||||
{
|
||||
var roles = proto.Roles.Select(x => _protoMan.Index(x))
|
||||
.OrderBy(x => x.ID);
|
||||
CreateRoleGroup(proto.ID, proto.Color, roles);
|
||||
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
|
||||
}
|
||||
|
||||
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
|
||||
.OrderBy(x => x.ID);
|
||||
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
|
||||
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a "Role group" which stores information and logic for one "group" of roll bans.
|
||||
/// For example, all antags are one group, logi is a group, medical is a group, etc...
|
||||
/// </summary>
|
||||
private void CreateRoleGroup<T>(string groupName, Color color, IEnumerable<T> roles) where T : class, IPrototype
|
||||
private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
|
||||
{
|
||||
var outerContainer = new BoxContainer
|
||||
{
|
||||
Name = $"{groupName}GroupOuterBox",
|
||||
Name = $"{roleName}GroupOuterBox",
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Margin = new Thickness(4),
|
||||
Margin = new Thickness(4)
|
||||
};
|
||||
|
||||
// Stores stuff like ban all and expand buttons.
|
||||
var roleGroupHeader = new BoxContainer
|
||||
var departmentCheckbox = new CheckBox
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Name = $"{roleName}GroupCheckbox",
|
||||
Text = roleName,
|
||||
Modulate = color,
|
||||
HorizontalAlignment = HAlignment.Left
|
||||
};
|
||||
|
||||
// Stores the role checkboxes themselves.
|
||||
var innerContainer = new GridContainer
|
||||
outerContainer.AddChild(departmentCheckbox);
|
||||
var innerContainer = new BoxContainer
|
||||
{
|
||||
Name = $"{groupName}GroupInnerBox",
|
||||
Name = $"{roleName}GroupInnerBox",
|
||||
HorizontalExpand = true,
|
||||
Columns = 2,
|
||||
Visible = false,
|
||||
Margin = new Thickness(15, 5, 0, 5),
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal
|
||||
};
|
||||
|
||||
var roleGroupCheckbox = CreateRoleGroupHeader(groupName, roleGroupHeader, color, innerContainer);
|
||||
|
||||
outerContainer.AddChild(roleGroupHeader);
|
||||
|
||||
// Add the roles themselves
|
||||
foreach (var role in roles)
|
||||
departmentCheckbox.OnToggled += args =>
|
||||
{
|
||||
AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox);
|
||||
}
|
||||
|
||||
outerContainer.AddChild(innerContainer);
|
||||
|
||||
RolesContainer.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
foreach (var child in innerContainer.Children)
|
||||
{
|
||||
BackgroundColor = color
|
||||
}
|
||||
});
|
||||
RolesContainer.AddChild(outerContainer);
|
||||
RolesContainer.AddChild(new HSeparator());
|
||||
}
|
||||
|
||||
private Button CreateRoleGroupHeader(string groupName, BoxContainer header, Color color, GridContainer innerContainer)
|
||||
{
|
||||
var roleGroupCheckbox = new Button
|
||||
{
|
||||
Name = $"{groupName}GroupCheckbox",
|
||||
Text = "Ban all",
|
||||
Margin = new Thickness(0, 0, 5, 0),
|
||||
ToggleMode = true,
|
||||
};
|
||||
|
||||
// When this is toggled, toggle all buttons in this group so they match.
|
||||
roleGroupCheckbox.OnToggled += args =>
|
||||
{
|
||||
foreach (var role in _roleCheckboxes[groupName])
|
||||
{
|
||||
role.Pressed = args.Pressed;
|
||||
if (child is CheckBox c)
|
||||
{
|
||||
c.Pressed = args.Pressed;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Pressed)
|
||||
@@ -251,12 +199,15 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var roleButtons in _roleCheckboxes.Values)
|
||||
foreach (var childContainer in RolesContainer.Children)
|
||||
{
|
||||
foreach (var button in roleButtons)
|
||||
if (childContainer is Container)
|
||||
{
|
||||
if (button.Pressed)
|
||||
return;
|
||||
foreach (var child in childContainer.Children)
|
||||
{
|
||||
if (child is CheckBox { Pressed: true })
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,73 +220,38 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
SeverityOption.SelectId((int) newSeverity);
|
||||
}
|
||||
};
|
||||
|
||||
var hideButton = new Button
|
||||
outerContainer.AddChild(innerContainer);
|
||||
foreach (var role in roleList)
|
||||
{
|
||||
Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow,
|
||||
ToggleMode = true,
|
||||
};
|
||||
hideButton.OnPressed += args =>
|
||||
AddRoleCheckbox(role, innerContainer, departmentCheckbox);
|
||||
}
|
||||
RolesContainer.AddChild(new PanelContainer
|
||||
{
|
||||
innerContainer.Visible = args.Button.Pressed;
|
||||
((Button)args.Button).Text = args.Button.Pressed
|
||||
? Loc.GetString("role-bans-contract-roles") + " " + ExpandedArrow
|
||||
: Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow;
|
||||
};
|
||||
header.AddChild(new Label
|
||||
{
|
||||
Text = groupName,
|
||||
Modulate = color,
|
||||
Margin = new Thickness(0, 0, 5, 0),
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = color
|
||||
}
|
||||
});
|
||||
header.AddChild(roleGroupCheckbox);
|
||||
header.AddChild(hideButton);
|
||||
return roleGroupCheckbox;
|
||||
RolesContainer.AddChild(outerContainer);
|
||||
RolesContainer.AddChild(new HSeparator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a checkbutton specifically for one "role" in a "group"
|
||||
/// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
|
||||
/// </summary>
|
||||
private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
|
||||
private void AddRoleCheckbox(string role, Control container, CheckBox header)
|
||||
{
|
||||
var roleCheckboxContainer = new BoxContainer();
|
||||
var roleCheckButton = new Button
|
||||
var roleCheckbox = new CheckBox
|
||||
{
|
||||
Name = $"{role}RoleCheckbox",
|
||||
Text = role,
|
||||
ToggleMode = true,
|
||||
Text = role
|
||||
};
|
||||
roleCheckButton.OnToggled += args =>
|
||||
roleCheckbox.OnToggled += args =>
|
||||
{
|
||||
// Checks the role group checkbox if all the children are pressed
|
||||
if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
|
||||
roleGroupCheckbox.Pressed = args.Pressed;
|
||||
if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed))
|
||||
header.Pressed = args.Pressed;
|
||||
else
|
||||
roleGroupCheckbox.Pressed = false;
|
||||
header.Pressed = false;
|
||||
};
|
||||
|
||||
// This is adding the icon before the role name
|
||||
// TODO: This should not be using raw strings for prototypes as it means it won't be validated at all.
|
||||
// I know the ban manager is doing the same thing, but that should not leak into UI code.
|
||||
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.Resolve(jobPrototype.Icon, out var iconProto))
|
||||
{
|
||||
var jobIconTexture = new TextureRect
|
||||
{
|
||||
Texture = _entMan.System<SpriteSystem>().Frame0(iconProto.Icon),
|
||||
TextureScale = new Vector2(2.5f, 2.5f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
Margin = new Thickness(5, 0, 0, 0),
|
||||
};
|
||||
roleCheckboxContainer.AddChild(jobIconTexture);
|
||||
}
|
||||
|
||||
roleCheckboxContainer.AddChild(roleCheckButton);
|
||||
|
||||
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
|
||||
|
||||
_roleCheckboxes.TryAdd(group, []);
|
||||
_roleCheckboxes[group].Add(roleCheckButton);
|
||||
container.AddChild(roleCheckbox);
|
||||
_roleCheckboxes.Add(roleCheckbox);
|
||||
}
|
||||
|
||||
public void UpdateBanFlag(bool newFlag)
|
||||
@@ -553,13 +469,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
if (_roleCheckboxes.Count == 0)
|
||||
throw new DebugAssertException("RoleCheckboxes was empty");
|
||||
|
||||
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
|
||||
{
|
||||
if (button is { Pressed: true, Text: not null })
|
||||
{
|
||||
rolesList.Add(button.Text);
|
||||
}
|
||||
}
|
||||
rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
|
||||
|
||||
if (rolesList.Count == 0)
|
||||
{
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<SplitContainer Orientation="Vertical" ResizeMode="NotResizable">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="1" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
<BoxContainer Orientation="Horizontal" SetHeight="30" >
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -29,15 +29,13 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
public AdminAHelpUIHandler AHelpHelper = default!;
|
||||
|
||||
private PlayerInfo? _currentPlayer;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public BwoinkControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var newPlayerThreshold = 0;
|
||||
_cfg.OnValueChanged(CCVars.NewPlayerThreshold, (val) => { newPlayerThreshold = val; }, true);
|
||||
|
||||
var uiController = _ui.GetUIController<AHelpUIController>();
|
||||
if (uiController.UIHelper is not AdminAHelpUIHandler helper)
|
||||
return;
|
||||
@@ -61,9 +59,9 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (info.Connected)
|
||||
sb.Append(info.ActiveThisRound ? '⚫' : '◐');
|
||||
sb.Append('●');
|
||||
else
|
||||
sb.Append(info.ActiveThisRound ? '⭘' : '·');
|
||||
sb.Append(info.ActiveThisRound ? '○' : '·');
|
||||
|
||||
sb.Append(' ');
|
||||
if (AHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
|
||||
@@ -75,12 +73,10 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
// Mark antagonists with symbol
|
||||
if (info.Antag && info.ActiveThisRound)
|
||||
sb.Append(new Rune(0x1F5E1)); // 🗡
|
||||
|
||||
// Mark new players with symbol
|
||||
if (IsNewPlayer(info))
|
||||
if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
sb.Append(new Rune(0x23F2)); // ⏲
|
||||
|
||||
sb.AppendFormat("\"{0}\"", text);
|
||||
@@ -88,19 +84,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
return sb.ToString();
|
||||
};
|
||||
|
||||
// <summary>
|
||||
// Returns true if the player's overall playtime is under the set threshold
|
||||
// </summary>
|
||||
bool IsNewPlayer(PlayerInfo info)
|
||||
{
|
||||
// Don't show every disconnected player as new, don't show 0-minute players as new if threshold is
|
||||
if (newPlayerThreshold <= 0 || info.OverallPlaytime is null && !info.Connected)
|
||||
return false;
|
||||
|
||||
return (info.OverallPlaytime is null
|
||||
|| info.OverallPlaytime < TimeSpan.FromMinutes(newPlayerThreshold));
|
||||
}
|
||||
|
||||
ChannelSelector.Comparison = (a, b) =>
|
||||
{
|
||||
var ach = AHelpHelper.EnsurePanel(a.SessionId);
|
||||
@@ -110,37 +93,31 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
if (a.IsPinned != b.IsPinned)
|
||||
return a.IsPinned ? -1 : 1;
|
||||
|
||||
// Then, any chat with unread messages.
|
||||
// First, sort by unread. Any chat with unread messages appears first.
|
||||
var aUnread = ach.Unread > 0;
|
||||
var bUnread = bch.Unread > 0;
|
||||
if (aUnread != bUnread)
|
||||
return aUnread ? -1 : 1;
|
||||
|
||||
// Then, any chat with recent messages from the current round
|
||||
// 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;
|
||||
|
||||
// Sort by connection status. Disconnected players will be last.
|
||||
// Next, sort by connection status. Any disconnected players are grouped towards the end.
|
||||
if (a.Connected != b.Connected)
|
||||
return a.Connected ? -1 : 1;
|
||||
|
||||
// Sort connected players by whether they have joined the round, then by New Player status, then by Antag status
|
||||
// Sort connected players by New Player status, then by Antag status
|
||||
if (a.Connected && b.Connected)
|
||||
{
|
||||
var aNewPlayer = IsNewPlayer(a);
|
||||
var bNewPlayer = IsNewPlayer(b);
|
||||
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
|
||||
// Players who have joined the round will be listed before players in the lobby
|
||||
if (a.ActiveThisRound != b.ActiveThisRound)
|
||||
return a.ActiveThisRound ? -1 : 1;
|
||||
|
||||
// Within both the joined group and lobby group, new players will be grouped and listed first
|
||||
if (aNewPlayer != bNewPlayer)
|
||||
return aNewPlayer ? -1 : 1;
|
||||
|
||||
// Within all four previous groups, antagonists will be listed first.
|
||||
if (a.Antag != b.Antag)
|
||||
return a.Antag ? -1 : 1;
|
||||
}
|
||||
@@ -177,6 +154,11 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Kick.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(Kick, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Reason field
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"kick \"{_currentPlayer.Username}\"");
|
||||
@@ -190,6 +172,11 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Respawn.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(Respawn, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"respawn \"{_currentPlayer.Username}\"");
|
||||
};
|
||||
|
||||
@@ -22,9 +22,12 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
return;
|
||||
}
|
||||
|
||||
Title = $"{sel.CharacterName} / {sel.Username} | {Loc.GetString("generic-playtime-title")}: ";
|
||||
Title = $"{sel.CharacterName} / {sel.Username}";
|
||||
|
||||
Title += sel.OverallPlaytime != null ? sel.PlaytimeString : Loc.GetString("generic-unknown-title");
|
||||
if (sel.OverallPlaytime != null)
|
||||
{
|
||||
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
|
||||
}
|
||||
};
|
||||
|
||||
OnOpen += () =>
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Label Name="Count" Access="Public"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Button Name="ExportLogs" Access="Public" Text="{Loc admin-logs-export}"/>
|
||||
<Button Name="PopOutButton" Access="Public" Text="{Loc admin-logs-pop-out}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Client.Administration.UI.CustomControls;
|
||||
using System.Linq;
|
||||
using Content.Client.Eui;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Eui;
|
||||
@@ -17,16 +15,6 @@ public sealed class AdminLogsEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly IFileDialogManager _dialogManager = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private const char CsvSeparator = ',';
|
||||
private const string CsvQuote = "\"";
|
||||
private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message";
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
private bool _currentlyExportingLogs = false;
|
||||
|
||||
public AdminLogsEui()
|
||||
{
|
||||
@@ -38,9 +26,6 @@ public sealed class AdminLogsEui : BaseEui
|
||||
LogsControl.RefreshButton.OnPressed += _ => RequestLogs();
|
||||
LogsControl.NextButton.OnPressed += _ => NextLogs();
|
||||
LogsControl.PopOutButton.OnPressed += _ => PopOut();
|
||||
LogsControl.ExportLogs.OnPressed += _ => ExportLogs();
|
||||
|
||||
_sawmill = _log.GetSawmill("admin.logs.ui");
|
||||
}
|
||||
|
||||
private WindowRoot? Root { get; set; }
|
||||
@@ -89,71 +74,6 @@ public sealed class AdminLogsEui : BaseEui
|
||||
SendMessage(request);
|
||||
}
|
||||
|
||||
private async void ExportLogs()
|
||||
{
|
||||
if (_currentlyExportingLogs)
|
||||
return;
|
||||
|
||||
_currentlyExportingLogs = true;
|
||||
LogsControl.ExportLogs.Disabled = true;
|
||||
|
||||
var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("csv")));
|
||||
|
||||
if (file == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Buffer is set to 4KB for performance reasons. As the average export of 1000 logs is ~200KB
|
||||
await using var writer = new StreamWriter(file.Value.fileStream, bufferSize: 4096);
|
||||
await writer.WriteLineAsync(CsvHeader);
|
||||
foreach (var child in LogsControl.LogsContainer.Children)
|
||||
{
|
||||
if (child is not AdminLogLabel logLabel || !child.Visible)
|
||||
continue;
|
||||
|
||||
var log = logLabel.Log;
|
||||
|
||||
// Date
|
||||
// I swear to god if someone adds ,s or "s to the other fields...
|
||||
await writer.WriteAsync(log.Date.ToString("s", System.Globalization.CultureInfo.InvariantCulture));
|
||||
await writer.WriteAsync(CsvSeparator);
|
||||
// ID
|
||||
await writer.WriteAsync(log.Id.ToString());
|
||||
await writer.WriteAsync(CsvSeparator);
|
||||
// PlayerID
|
||||
var players = log.Players;
|
||||
for (var i = 0; i < players.Length; i++)
|
||||
{
|
||||
await writer.WriteAsync(players[i] + (i == players.Length - 1 ? "" : " "));
|
||||
}
|
||||
await writer.WriteAsync(CsvSeparator);
|
||||
// Severity
|
||||
await writer.WriteAsync(log.Impact.ToString());
|
||||
await writer.WriteAsync(CsvSeparator);
|
||||
// Type
|
||||
await writer.WriteAsync(log.Type.ToString());
|
||||
await writer.WriteAsync(CsvSeparator);
|
||||
// Message
|
||||
await writer.WriteAsync(CsvQuote);
|
||||
await writer.WriteAsync(log.Message.Replace(CsvQuote, CsvQuote + CsvQuote));
|
||||
await writer.WriteAsync(CsvQuote);
|
||||
|
||||
await writer.WriteLineAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
_sawmill.Error($"Error when exporting admin log:\n{exc.StackTrace}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await file.Value.fileStream.DisposeAsync();
|
||||
_currentlyExportingLogs = false;
|
||||
LogsControl.ExportLogs.Disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopOut()
|
||||
{
|
||||
if (LogsWindow == null)
|
||||
|
||||
@@ -8,21 +8,6 @@
|
||||
<OptionButton Name="SolutionOption" HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 4">
|
||||
<Button Name="VVButton"
|
||||
Text="{Loc 'admin-solutions-window-vv-button'}"
|
||||
ToolTip="{Loc 'admin-solutions-window-vv-button-tooltip'}"
|
||||
Disabled="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="SolutionButton"
|
||||
Text="{Loc 'admin-solutions-window-solution-button'}"
|
||||
ToolTip="{Loc 'admin-solutions-window-solution-button-tooltip'}"
|
||||
Disabled="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- The total volume / capacity of the solution -->
|
||||
<BoxContainer Name="VolumeBox" Orientation="Vertical" HorizontalExpand="True" Margin="0 4"/>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
@@ -21,7 +20,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
|
||||
private NetEntity _target = NetEntity.Invalid;
|
||||
private string? _selectedSolution;
|
||||
@@ -36,11 +34,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
|
||||
SolutionOption.OnItemSelected += SolutionSelected;
|
||||
AddButton.OnPressed += OpenAddReagentWindow;
|
||||
VVButton.OnPressed += OpenVVWindow;
|
||||
SolutionButton.OnPressed += OpenSolutionWindow;
|
||||
|
||||
VVButton.Disabled = !_admin.CanViewVar();
|
||||
SolutionButton.Disabled = !_admin.CanViewVar();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
@@ -278,32 +271,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
|
||||
_addReagentWindow.OpenCentered();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the corresponding solution entity in a ViewVariables window.
|
||||
/// </summary>
|
||||
private void OpenVVWindow(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (_solutions == null
|
||||
|| _selectedSolution == null
|
||||
|| !_solutions.TryGetValue(_selectedSolution, out var uid)
|
||||
|| !_entityManager.TryGetNetEntity(uid, out var netEntity))
|
||||
return;
|
||||
_consoleHost.ExecuteCommand($"vv {netEntity}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open the corresponding Solution instance in a ViewVariables window.
|
||||
/// </summary>
|
||||
private void OpenSolutionWindow(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
if (_solutions == null
|
||||
|| _selectedSolution == null
|
||||
|| !_solutions.TryGetValue(_selectedSolution, out var uid)
|
||||
|| !_entityManager.TryGetNetEntity(uid, out var netEntity))
|
||||
return;
|
||||
_consoleHost.ExecuteCommand($"vv /entity/{netEntity}/Solution/Solution");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a new solution is selected, set _selectedSolution and update the reagent list.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<Popup xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#18181B" BackgroundColor="#25252a"/>
|
||||
@@ -20,7 +19,7 @@
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EditButton" Text="{Loc admin-notes-edit}"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc admin-notes-delete}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" HorizontalAlignment="Right"/>
|
||||
<Button Name="DeleteButton" Text="{Loc admin-notes-delete}" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -19,19 +19,17 @@
|
||||
<Label Name="SharedConnections"/>
|
||||
|
||||
<BoxContainer Align="Center">
|
||||
<GridContainer Rows="6">
|
||||
<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="FollowButton" Text="{Loc player-panel-follow}"/>
|
||||
<Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
|
||||
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
|
||||
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
|
||||
<Button Name="CameraButton" Text="{Loc player-panel-camera}" Disabled="True"/>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -18,10 +19,8 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
public event Action<NetUserId?>? OnOpenBans;
|
||||
public event Action<NetUserId?>? OnAhelp;
|
||||
public event Action<string?>? OnKick;
|
||||
public event Action<string?>? OnCamera;
|
||||
public event Action<NetUserId?>? OnOpenBanPanel;
|
||||
public event Action<NetUserId?, bool>? OnWhitelistToggle;
|
||||
public event Action? OnFollow;
|
||||
public event Action? OnFreezeAndMuteToggle;
|
||||
public event Action? OnFreeze;
|
||||
public event Action? OnLogs;
|
||||
@@ -34,27 +33,25 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
|
||||
public PlayerPanel(IClientAdminManager adminManager)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
|
||||
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
|
||||
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
|
||||
CameraButton.OnPressed += _ => OnCamera?.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);
|
||||
};
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
|
||||
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
|
||||
LogsButton.OnPressed += _ => OnLogs?.Invoke();
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
|
||||
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
|
||||
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)
|
||||
@@ -124,7 +121,6 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
{
|
||||
BanButton.Disabled = !_adminManager.CanCommand("banpanel");
|
||||
KickButton.Disabled = !_adminManager.CanCommand("kick");
|
||||
CameraButton.Disabled = !_adminManager.CanCommand("camera");
|
||||
NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
|
||||
ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
|
||||
WhitelistToggle.Disabled =
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
|
||||
private PlayerPanel PlayerPanel { get; }
|
||||
private PlayerPanel PlayerPanel { get; }
|
||||
|
||||
public PlayerPanelEui()
|
||||
{
|
||||
@@ -25,7 +25,6 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
|
||||
// Kick command does not support GUIDs
|
||||
PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
|
||||
PlayerPanel.OnCamera += username => _console.ExecuteCommand($"camera \"{username}\"");
|
||||
PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
|
||||
PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
|
||||
PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
|
||||
@@ -38,8 +37,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
|
||||
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
|
||||
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
|
||||
PlayerPanel.OnDelete += () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
PlayerPanel.OnFollow += () => SendMessage(new PlayerPanelFollowMessage());
|
||||
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
|
||||
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ namespace Content.Client.Administration.UI.SpawnExplosion;
|
||||
public sealed partial class SpawnExplosionWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private readonly SpawnExplosionEui _eui;
|
||||
@@ -38,7 +38,6 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_mapSystem = _entMan.System<SharedMapSystem>();
|
||||
_transform = _entMan.System<TransformSystem>();
|
||||
_eui = eui;
|
||||
|
||||
@@ -88,7 +87,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
|
||||
{
|
||||
_mapData.Clear();
|
||||
MapOptions.Clear();
|
||||
foreach (var map in _mapSystem.GetAllMapIds())
|
||||
foreach (var map in _mapManager.GetAllMapIds())
|
||||
{
|
||||
_mapData.Add(map);
|
||||
MapOptions.AddItem(map.ToString());
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<cc:UICommandButton Command="callshuttle" Text="{Loc admin-player-actions-window-shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
|
||||
<cc:CommandButton Command="adminlogs" Text="{Loc admin-player-actions-window-admin-logs}"/>
|
||||
<cc:CommandButton Command="faxui" Text="{Loc admin-player-actions-window-admin-fax}"/>
|
||||
<cc:CommandButton Command="achatwindow" Text="{Loc admin-player-actions-window-admin-chat}"/>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
@@ -11,9 +10,9 @@
|
||||
</BoxContainer>
|
||||
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<controls:ConfirmButton Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
|
||||
<Button Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" Disabled="True"/>
|
||||
<Button Name="SubmitAHelpButton" Text="{Loc admin-player-actions-ahelp}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
|
||||
<Button Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" Disabled="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
public sealed partial class PlayerActionsWindow : DefaultWindow
|
||||
{
|
||||
private PlayerInfo? _selectedPlayer;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public PlayerActionsWindow()
|
||||
{
|
||||
@@ -27,6 +28,9 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
|
||||
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
|
||||
{
|
||||
if (_selectedPlayer != obj)
|
||||
AdminUIHelpers.RemoveAllConfirms(_confirmations);
|
||||
|
||||
_selectedPlayer = obj;
|
||||
var disableButtons = _selectedPlayer == null;
|
||||
SubmitKickButton.Disabled = disableButtons;
|
||||
@@ -39,6 +43,9 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
if (_selectedPlayer == null)
|
||||
return;
|
||||
|
||||
if (!AdminUIHelpers.TryConfirm(SubmitKickButton, _confirmations))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"kick \"{_selectedPlayer.Username}\" \"{CommandParsing.Escape(ReasonLine.Text)}\"");
|
||||
}
|
||||
@@ -57,6 +64,9 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
if (_selectedPlayer == null)
|
||||
return;
|
||||
|
||||
if (!AdminUIHelpers.TryConfirm(SubmitRespawnButton, _confirmations))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"respawn \"{_selectedPlayer.Username}\"");
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminbusTab
|
||||
[UsedImplicitly]
|
||||
public sealed partial class LoadBlueprintsWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public LoadBlueprintsWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -24,9 +21,9 @@ namespace Content.Client.Administration.UI.Tabs.AdminbusTab
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
var mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
foreach (var mapId in mapSystem.GetAllMapIds())
|
||||
foreach (var mapId in mapManager.GetAllMapIds())
|
||||
{
|
||||
MapOptions.AddItem(mapId.ToString(), (int) mapId);
|
||||
}
|
||||
@@ -42,19 +39,21 @@ namespace Content.Client.Administration.UI.Tabs.AdminbusTab
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
var player = _playerManager.LocalEntity;
|
||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||
var xformSystem = entManager.System<SharedTransformSystem>();
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
var player = playerManager.LocalEntity;
|
||||
|
||||
var currentMap = MapId.Nullspace;
|
||||
var position = Vector2.Zero;
|
||||
var rotation = Angle.Zero;
|
||||
|
||||
if (_entityManager.TryGetComponent<TransformComponent>(player, out var xform))
|
||||
if (entManager.TryGetComponent<TransformComponent>(player, out var xform))
|
||||
{
|
||||
currentMap = xform.MapID;
|
||||
position = xformSystem.GetWorldPosition(xform);
|
||||
|
||||
if (_entityManager.TryGetComponent<TransformComponent>(xform.GridUid, out var gridXform))
|
||||
if (entManager.TryGetComponent<TransformComponent>(xform.GridUid, out var gridXform))
|
||||
{
|
||||
rotation = xformSystem.GetWorldRotation(gridXform);
|
||||
}
|
||||
|
||||
@@ -57,43 +57,12 @@ public sealed partial class ObjectsTab : Control
|
||||
|
||||
private void TeleportTo(NetEntity nent)
|
||||
{
|
||||
var selection = _selections[ObjectTypeOptions.SelectedId];
|
||||
switch (selection)
|
||||
{
|
||||
case ObjectsTabSelection.Grids:
|
||||
{
|
||||
// directly teleport to the entity
|
||||
_console.ExecuteCommand($"tpto {nent}");
|
||||
}
|
||||
break;
|
||||
case ObjectsTabSelection.Maps:
|
||||
{
|
||||
// teleport to the map, not to the map entity (which is in nullspace)
|
||||
if (!_entityManager.TryGetEntity(nent, out var map) || !_entityManager.TryGetComponent<MapComponent>(map, out var mapComp))
|
||||
break;
|
||||
_console.ExecuteCommand($"tp 0 0 {mapComp.MapId}");
|
||||
break;
|
||||
}
|
||||
case ObjectsTabSelection.Stations:
|
||||
{
|
||||
// teleport to the station's largest grid, not to the station entity (which is in nullspace)
|
||||
if (!_entityManager.TryGetEntity(nent, out var station))
|
||||
break;
|
||||
var largestGrid = _entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetLargestGrid(station.Value);
|
||||
if (largestGrid == null)
|
||||
break;
|
||||
_console.ExecuteCommand($"tpto {largestGrid.Value}");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
_console.ExecuteCommand($"tpto {nent}");
|
||||
}
|
||||
|
||||
private void Delete(NetEntity nent)
|
||||
{
|
||||
_console.ExecuteCommand($"delete {nent}");
|
||||
RefreshObjectList();
|
||||
}
|
||||
|
||||
public void RefreshObjectList()
|
||||
@@ -107,24 +76,28 @@ public sealed partial class ObjectsTab : Control
|
||||
switch (selection)
|
||||
{
|
||||
case ObjectsTabSelection.Stations:
|
||||
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetStationNames());
|
||||
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().Stations);
|
||||
break;
|
||||
case ObjectsTabSelection.Grids:
|
||||
{
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var metadata))
|
||||
{
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var metadata))
|
||||
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
|
||||
|
||||
break;
|
||||
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectsTabSelection.Maps:
|
||||
{
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var metadata))
|
||||
{
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var metadata))
|
||||
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
|
||||
|
||||
break;
|
||||
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
|
||||
}
|
||||
@@ -159,8 +132,8 @@ public sealed partial class ObjectsTab : Control
|
||||
entry.OnTeleport += TeleportTo;
|
||||
entry.OnDelete += Delete;
|
||||
button.ToolTip = $"{info.Name}, {info.Entity}";
|
||||
|
||||
button.AddChild(entry);
|
||||
button.StyleClasses.Clear();
|
||||
}
|
||||
|
||||
private bool DataFilterCondition(string filter, ListData listData)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Name="BackgroundColorPanel">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
@@ -21,7 +20,7 @@
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<controls:ConfirmButton Name="DeleteButton"
|
||||
<Button Name="DeleteButton"
|
||||
Text="{Loc object-tab-entity-delete}"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -13,6 +13,7 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
|
||||
public Action<NetEntity>? OnTeleport;
|
||||
public Action<NetEntity>? OnDelete;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
@@ -27,6 +28,13 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
DeleteButton.Disabled = !manager.CanCommand("delete");
|
||||
|
||||
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke(nent);
|
||||
DeleteButton.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
OnDelete?.Invoke(nent);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@ using System.Linq;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
@@ -18,7 +16,6 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
public sealed partial class PlayerTab : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
@@ -32,10 +29,6 @@ public sealed partial class PlayerTab : Control
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
|
||||
private AdminPlayerTabColorOption _playerTabColorSetting;
|
||||
private AdminPlayerTabRoleTypeOption _playerTabRoleSetting;
|
||||
private AdminPlayerTabSymbolOption _playerTabSymbolSetting;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
public PlayerTab()
|
||||
@@ -48,11 +41,6 @@ public sealed partial class PlayerTab : Control
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabRoleSetting, RoleSettingChanged, true);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true);
|
||||
|
||||
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
|
||||
@@ -118,30 +106,6 @@ public sealed partial class PlayerTab : Control
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void RoleSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabRoleTypeOption>(s, out var format))
|
||||
format = AdminPlayerTabRoleTypeOption.Subtype;
|
||||
_playerTabRoleSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void ColorSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabColorOption>(s, out var format))
|
||||
format = AdminPlayerTabColorOption.Both;
|
||||
_playerTabColorSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void SymbolSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabSymbolOption>(s, out var format))
|
||||
format = AdminPlayerTabSymbolOption.Specific;
|
||||
_playerTabSymbolSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
|
||||
{
|
||||
_players = players;
|
||||
@@ -164,15 +128,9 @@ public sealed partial class PlayerTab : Control
|
||||
if (data is not PlayerListData { Info: var player})
|
||||
return;
|
||||
|
||||
var entry = new PlayerTabEntry(
|
||||
player,
|
||||
new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor),
|
||||
_playerTabColorSetting,
|
||||
_playerTabRoleSetting,
|
||||
_playerTabSymbolSetting);
|
||||
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}";
|
||||
button.StyleClasses.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -238,7 +196,8 @@ public sealed partial class PlayerTab : Control
|
||||
Header.Username => Compare(x.Username, y.Username),
|
||||
Header.Character => Compare(x.CharacterName, y.CharacterName),
|
||||
Header.Job => Compare(x.StartingJob, y.StartingJob),
|
||||
Header.RoleType => y.SortWeight - x.SortWeight,
|
||||
Header.Antagonist => x.Antag.CompareTo(y.Antag),
|
||||
Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
|
||||
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
|
||||
_ => 1
|
||||
};
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="AntagonistLabel"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
<customControls:VSeparator/>
|
||||
<Label Name="RoleTypeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -1,104 +1,32 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTabEntry : PanelContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
public NetEntity? PlayerEntity;
|
||||
|
||||
public PlayerTabEntry(
|
||||
PlayerInfo player,
|
||||
StyleBoxFlat styleBoxFlat,
|
||||
AdminPlayerTabColorOption colorOption,
|
||||
AdminPlayerTabRoleTypeOption roleSetting,
|
||||
AdminPlayerTabSymbolOption symbolSetting)
|
||||
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
var roles = _entMan.System<SharedRoleSystem>();
|
||||
|
||||
var rolePrototype = player.RoleProto == null ? null : _prototype.Index(player.RoleProto.Value);
|
||||
|
||||
UsernameLabel.Text = player.Username;
|
||||
if (!player.Connected)
|
||||
UsernameLabel.StyleClasses.Add("Disabled");
|
||||
JobLabel.Text = player.StartingJob;
|
||||
|
||||
var colorAntags = false;
|
||||
var colorRoles = false;
|
||||
switch (colorOption)
|
||||
{
|
||||
case AdminPlayerTabColorOption.Off:
|
||||
break;
|
||||
case AdminPlayerTabColorOption.Character:
|
||||
colorAntags = true;
|
||||
break;
|
||||
case AdminPlayerTabColorOption.Roletype:
|
||||
colorRoles = true;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabColorOption.Both:
|
||||
colorAntags = true;
|
||||
colorRoles = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var symbol = string.Empty;
|
||||
switch (symbolSetting)
|
||||
{
|
||||
case AdminPlayerTabSymbolOption.Off:
|
||||
break;
|
||||
case AdminPlayerTabSymbolOption.Basic:
|
||||
symbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabSymbolOption.Specific:
|
||||
symbol = player.Antag ? rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
|
||||
|
||||
if (player.Antag && colorAntags)
|
||||
CharacterLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
|
||||
CharacterLabel.Text = player.CharacterName;
|
||||
if (player.IdentityName != player.CharacterName)
|
||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||
|
||||
var roletype = RoleTypeLabel.Text = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
|
||||
var subtype = roles.GetRoleSubtypeLabel(rolePrototype?.Name ?? RoleTypePrototype.FallbackName, player.Subtype);
|
||||
switch (roleSetting)
|
||||
{
|
||||
case AdminPlayerTabRoleTypeOption.RoleTypeSubtype:
|
||||
RoleTypeLabel.Text = roletype != subtype
|
||||
? roletype + " - " +subtype
|
||||
: roletype;
|
||||
break;
|
||||
case AdminPlayerTabRoleTypeOption.SubtypeRoleType:
|
||||
RoleTypeLabel.Text = roletype != subtype
|
||||
? subtype + " - " + roletype
|
||||
: roletype;
|
||||
break;
|
||||
case AdminPlayerTabRoleTypeOption.RoleType:
|
||||
RoleTypeLabel.Text = roletype;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabRoleTypeOption.Subtype:
|
||||
RoleTypeLabel.Text = subtype;
|
||||
break;
|
||||
}
|
||||
|
||||
if (colorRoles)
|
||||
RoleTypeLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
|
||||
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
|
||||
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||
PlayerEntity = player.NetEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
Text="{Loc player-tab-job}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="AntagonistLabel"
|
||||
SizeFlagsStretchRatio="1"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc player-tab-antagonist}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="RoleTypeLabel"
|
||||
SizeFlagsStretchRatio="2"
|
||||
HorizontalExpand="True"
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
UsernameLabel.OnKeyBindDown += UsernameClicked;
|
||||
CharacterLabel.OnKeyBindDown += CharacterClicked;
|
||||
JobLabel.OnKeyBindDown += JobClicked;
|
||||
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
|
||||
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
|
||||
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
|
||||
}
|
||||
@@ -29,6 +30,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
Header.Username => UsernameLabel,
|
||||
Header.Character => CharacterLabel,
|
||||
Header.Job => JobLabel,
|
||||
Header.Antagonist => AntagonistLabel,
|
||||
Header.RoleType => RoleTypeLabel,
|
||||
Header.Playtime => PlaytimeLabel,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
||||
@@ -40,6 +42,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
UsernameLabel.Text = Loc.GetString("player-tab-username");
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character");
|
||||
JobLabel.Text = Loc.GetString("player-tab-job");
|
||||
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
|
||||
RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
|
||||
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
|
||||
}
|
||||
@@ -70,6 +73,11 @@ public sealed partial class PlayerTabHeader : Control
|
||||
HeaderClicked(args, Header.Job);
|
||||
}
|
||||
|
||||
private void AntagonistClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.Antagonist);
|
||||
}
|
||||
|
||||
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.RoleType);
|
||||
@@ -89,6 +97,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
UsernameLabel.OnKeyBindDown -= UsernameClicked;
|
||||
CharacterLabel.OnKeyBindDown -= CharacterClicked;
|
||||
JobLabel.OnKeyBindDown -= JobClicked;
|
||||
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
|
||||
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
|
||||
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
|
||||
}
|
||||
@@ -99,6 +108,7 @@ public sealed partial class PlayerTabHeader : Control
|
||||
Username,
|
||||
Character,
|
||||
Job,
|
||||
Antagonist,
|
||||
RoleType,
|
||||
Playtime
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
public enum AdminPlayerTabColorOption
|
||||
{
|
||||
Off,
|
||||
Character,
|
||||
Roletype,
|
||||
Both
|
||||
}
|
||||
|
||||
public enum AdminPlayerTabRoleTypeOption
|
||||
{
|
||||
RoleType,
|
||||
Subtype,
|
||||
RoleTypeSubtype,
|
||||
SubtypeRoleType
|
||||
}
|
||||
|
||||
public enum AdminPlayerTabSymbolOption
|
||||
{
|
||||
Off,
|
||||
Basic,
|
||||
Specific
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Margin="4"
|
||||
MinSize="50 50">
|
||||
<GridContainer
|
||||
Columns="3">
|
||||
<controls:ConfirmButton Name="StartRound" Text="{Loc administration-ui-round-tab-start-round}" />
|
||||
<controls:ConfirmButton Name="EndRound" Text="{Loc administration-ui-round-tab-end-round}" />
|
||||
<controls:ConfirmButton Name="RestartRound" Text="{Loc administration-ui-round-tab-restart-round}" />
|
||||
<controls:ConfirmButton Name="RestartRoundNow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
|
||||
<cc:CommandButton Command="startround" Text="{Loc administration-ui-round-tab-start-round}" />
|
||||
<cc:CommandButton Command="endround" Text="{Loc administration-ui-round-tab-end-round}" />
|
||||
<cc:CommandButton Command="restartround" Text="{Loc administration-ui-round-tab-restart-round}" />
|
||||
<cc:CommandButton Command="restartroundnow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
|
||||
</GridContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RoundTab : Control
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
public RoundTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
StartRound.OnPressed += _ => _console.ExecuteCommand("startround");
|
||||
EndRound.OnPressed += _ => _console.ExecuteCommand("endround");
|
||||
RestartRound.OnPressed += _ => _console.ExecuteCommand("restartround");
|
||||
RestartRoundNow.OnPressed += _ => _console.ExecuteCommand("restartroundnow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Margin="4"
|
||||
MinSize="50 50">
|
||||
<GridContainer
|
||||
Columns="4" >
|
||||
<controls:ConfirmButton Name="ServerShutdownButton" Text="{Loc server-shutdown}" />
|
||||
<cc:CommandButton Command="shutdown" Text="{Loc server-shutdown}" />
|
||||
<cc:CommandButton Name="SetOocButton" Command="setooc" Text="{Loc server-ooc-toggle}" ToggleMode="True" />
|
||||
<cc:CommandButton Name="SetLoocButton" Command="setlooc" Text="{Loc server-looc-toggle}" ToggleMode="True" />
|
||||
</GridContainer>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -11,7 +10,6 @@ namespace Content.Client.Administration.UI.Tabs
|
||||
public sealed partial class ServerTab : Control
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
public ServerTab()
|
||||
{
|
||||
@@ -20,8 +18,6 @@ namespace Content.Client.Administration.UI.Tabs
|
||||
|
||||
_config.OnValueChanged(CCVars.OocEnabled, OocEnabledChanged, true);
|
||||
_config.OnValueChanged(CCVars.LoocEnabled, LoocEnabledChanged, true);
|
||||
|
||||
ServerShutdownButton.OnPressed += _ => _console.ExecuteCommand("shutdown");
|
||||
}
|
||||
|
||||
private void OocEnabledChanged(bool value)
|
||||
|
||||
@@ -8,8 +8,6 @@ namespace Content.Client.AlertLevel;
|
||||
|
||||
public sealed class AlertLevelDisplaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -23,26 +21,26 @@ public sealed class AlertLevelDisplaySystem : EntitySystem
|
||||
{
|
||||
return;
|
||||
}
|
||||
var layer = _sprite.LayerMapReserve((uid, args.Sprite), AlertLevelDisplay.Layer);
|
||||
var layer = args.Sprite.LayerMapReserveBlank(AlertLevelDisplay.Layer);
|
||||
|
||||
if (args.AppearanceData.TryGetValue(AlertLevelDisplay.Powered, out var poweredObject))
|
||||
{
|
||||
_sprite.LayerSetVisible((uid, args.Sprite), layer, poweredObject is true);
|
||||
args.Sprite.LayerSetVisible(layer, poweredObject is true);
|
||||
}
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(AlertLevelDisplay.CurrentLevel, out var level))
|
||||
{
|
||||
_sprite.LayerSetRsiState((uid, args.Sprite), layer, alertLevelDisplay.AlertVisuals.Values.First());
|
||||
args.Sprite.LayerSetState(layer, alertLevelDisplay.AlertVisuals.Values.First());
|
||||
return;
|
||||
}
|
||||
|
||||
if (alertLevelDisplay.AlertVisuals.TryGetValue((string)level, out var visual))
|
||||
if (alertLevelDisplay.AlertVisuals.TryGetValue((string) level, out var visual))
|
||||
{
|
||||
_sprite.LayerSetRsiState((uid, args.Sprite), layer, visual);
|
||||
args.Sprite.LayerSetState(layer, visual);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sprite.LayerSetRsiState((uid, args.Sprite), layer, alertLevelDisplay.AlertVisuals.Values.First());
|
||||
args.Sprite.LayerSetState(layer, alertLevelDisplay.AlertVisuals.Values.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Linq;
|
||||
using Content.Shared.Alert;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -16,7 +15,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
public event EventHandler? ClearAlerts;
|
||||
public event EventHandler<IReadOnlyDictionary<AlertKey, AlertState>>? SyncAlerts;
|
||||
@@ -29,12 +27,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
protected override void HandledAlert()
|
||||
{
|
||||
_ui.ClickSound();
|
||||
}
|
||||
|
||||
protected override void LoadPrototypes()
|
||||
{
|
||||
base.LoadPrototypes();
|
||||
@@ -60,23 +52,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
if (args.Current is not AlertComponentState cast)
|
||||
return;
|
||||
|
||||
// Save all client-sided alerts to later put back in
|
||||
var clientAlerts = new Dictionary<AlertKey, AlertState>();
|
||||
foreach (var alert in alerts.Comp.Alerts)
|
||||
{
|
||||
if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto))
|
||||
{
|
||||
if (alertProto.ClientHandled)
|
||||
clientAlerts[alert.Key] = alert.Value;
|
||||
}
|
||||
}
|
||||
|
||||
alerts.Comp.Alerts = new(cast.Alerts);
|
||||
|
||||
foreach (var alert in clientAlerts)
|
||||
{
|
||||
alerts.Comp.Alerts[alert.Key] = alert.Value;
|
||||
}
|
||||
alerts.Comp.Alerts = cast.Alerts;
|
||||
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user