Compare commits

..
Author SHA1 Message Date
NullWanderer 28a6d4baeb Merge branch 'master' of https://github.com/DeltaV-Station/Delta-v into april-fools-2024 2024-04-01 00:52:41 +02:00
trollbreeder b4fb5ddc2b Mobs with combat mode now only mouselook when combat mode is OFF (#26562)
inverts whether you mouselook or not for mobs with combat mode
2024-04-01 00:47:53 +02:00
Kara b2ebeabe83 better animal sprites (fools) (#26275)
better animal sprites
2024-04-01 00:47:51 +02:00
Vasilis ebd2c9b968 Quick hotfix since panda is screaming at me in vc 2024-04-01 00:47:41 +02:00
NullWanderer c578732c5c Revert "Adds a new medication for headache & pain relief (#26591)"
This reverts commit 9d0e47c300.
2024-03-31 17:04:17 +02:00
Ubaser 0ba0632b35 Switch around budget and normal insuls (#26558)
* add

* Update colored.yml

* Update colored.yml

* Update colored.yml

* death
2024-03-31 16:57:34 +02:00
Vyacheslav Kovalevsky 1b05a76ade Cancer mouse (#26132)
* cancer mouse

* minor tweaks
2024-03-31 16:57:31 +02:00
Leon Friedrich 9d0e47c300 Adds a new medication for headache & pain relief (#26591)
* Well damn it kinda works

* And this is to go even further ...

* Add drugs
2024-03-31 16:56:57 +02:00
SlamBamActionman de42819c0b Bring back tippy from april fools last year (#26112)
Tippy is BACK
2024-03-31 16:55:54 +02:00
Pieter-Jan Briers 76c415380a You can FTL to the Chess Dimension. (#26536) 2024-03-31 16:55:50 +02:00
Simon f0943ef42f Arcade that sends you to OpenDream Paradise station (#26476)
* The actual april fools joke is the shitcode I produced

* Make text clearer.

* Spawner adjustments

* Improve image size.
Thanks .landwhale (discord)!

* Fix mismatching duration
2024-03-31 16:55:03 +02:00
Vyacheslav Kovalevskyandmetalgearsloth 0c3c4d2d32 replace boring items with fun items on spawn (#26087)
* ReplaceRandom

* maybe use polymorph

* Revert "maybe use polymorph"

This reverts commit 28ecd64d122a335dd999d52a9795afc1df09c991.

* handle containers also

* better name

* chance to grow hot potato too

* replace banana peel with explosive

* fix containers

* more fun

* even more fun

* extra fun

* Update Content.Server/ReplaceOnSpawn/ReplaceOnSpawnSystem.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-31 16:54:45 +02:00
lzk 2e09441d76 Clown goblins (April Fools) (#26306)
* Clown goblins migration

* remove some stuff

* fx
2024-03-31 16:53:46 +02:00
Hannah Giovanna Dawson e189a8f71f Skug brainrot wawa rain world backflip cat (#26168)
* wawa

* Minor cleanup, change intro/outro sfx

* Fix yaml

* wawaskillissue

* challenge

* Remove unusable audio, update spriting

* Kill remaining wawa sfx

* Fix name prefix

* these sound effects cost £460

* these attributations cost £0

* louder, mono wawa noises

* Audio fixes

* Tidy server system

* Code tidy-up, wawa

* Remove locstring hardcodes

* Bug fixes, wawa

* meta update, wawa

* Force antagprotoid to be hardcoded because yaml validation cringe, wawa
2024-03-27 23:25:17 +01:00
Keer-Sar e74f92556e Wehvitational Wehularity Engine UNCOOKED! (#26219)
* creat uncooked version of PR #26201

* removed cooked code from cooked branch, fixed sprite directory
2024-03-27 23:25:13 +01:00
Crotalus 216a0790b1 Any crew member can be a traitor (#26333)
All jobs can be traitor
2024-03-27 23:24:53 +01:00
Vyacheslav Kovalevsky 08e134c3ea Add new "oof" body fall sound (#26384) 2024-03-27 23:23:36 +01:00
Alzore cb08bfa498 Smoking now does very not minor cellular damage (April fools) (#26298)
* stage7lungcancer

* tumors
2024-03-27 23:23:01 +01:00
GeneralGawsandGeneralGaws 9d083b6914 Clown ghost skin (april fools) (#26272)
clownghost

Co-authored-by: GeneralGaws <limonmessi@mail.ru>
2024-03-27 23:22:56 +01:00
chromiumboy e0ad8c0da0 Syndicate traitors can now purchase nuke codes (#26280) 2024-03-27 23:22:54 +01:00
deltanedasanddeltanedas f21b2275b4 swap carp and carp plushie sprites (april 1) (#26080)
* add plushie textures

* swap carp and plushie sprites

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-03-27 23:22:39 +01:00
lzk 06b13f39e6 Give every job sad trombone implant (april fools) (#26084)
Give every job sad trombone implant
2024-03-27 23:21:01 +01:00
Vyacheslav Kovalevsky 42a9317d3f reverse lights (#26117) 2024-03-27 23:16:51 +01:00
Vyacheslav Kovalevsky 182657e73e Blood is fuel (#26140)
* blood is fuel

* ultrakill silicon laws

* red welders

* fix jrpacman desc

* only junior runs on blood (bloodpacks are not material)
2024-03-27 23:15:07 +01:00
Vyacheslav Kovalevsky 02a6bb7d23 Color all wires green to save planet(s) (#26115)
* use green coil for everything

* replace cables too

* wires in panels too

* fix cable sprites (a)

* Revert "fix cable sprites (a)"

This reverts commit d789f78ff024a1ffdc70336515db9a0e43fdadc1.

* Revert "replace cables too"

This reverts commit f321eca49b11b04d346c7037d05af0928be797e4.

* color cables green

* no orange
2024-03-27 23:14:23 +01:00
39639 changed files with 892703 additions and 4583133 deletions
+8 -21
View File
@@ -1,5 +1,4 @@
root = true
[*]
charset = utf-8
@@ -13,7 +12,6 @@ tab_width = 4
#end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
#### .NET Coding Conventions ####
@@ -77,8 +75,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,10 +125,9 @@ 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
csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
@@ -282,7 +277,7 @@ dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case
dotnet_naming_style.t_upper_camel_case_style.required_prefix = T
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
dotnet_naming_symbols.constants_symbols.required_modifiers = const
@@ -321,37 +316,29 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
dotnet_naming_symbols.property_symbols.applicable_kinds = property
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter
# ReSharper properties
resharper_braces_for_ifelse = required_for_multiline
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
resharper_csharp_trailing_comma_in_multiline_lists = true
resharper_csharp_qualified_using_at_nested_scope = false
resharper_csharp_prefer_qualified_reference = false
resharper_csharp_allow_alias = false
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,slnx}]
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
indent_size = 2
[nuget.config]
+2 -3
View File
@@ -1,5 +1,4 @@
set -e
if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM="
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
fi
use flake
+5 -22
View File
@@ -1,25 +1,8 @@
# Last match in file takes precedence.
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file
* @DeltaV-Station/maintainers
/Content.*/SimpleStation14/ @DEATHB4DEFEAT
# Server config files
/Resources/ConfigPresets/ @DeltaV-Station/lead-maintainers
# Github workflows
/.github/ @DeltaV-Station/lead-maintainers
# Lobby art and music - automatically direction issues since its immediately visible to players
/Resources/Audio/Lobby/ @DeltaV-Station/direction @DeltaV-Station/maintainers
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction @DeltaV-Station/maintainers
# Maps
/Resources/Maps/ @DeltaV-Station/maptainers
/Resources/Prototypes/Maps/ @DeltaV-Station/maptainers
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers @DeltaV-Station/maintainers
# Server rules
/Resources/ServerInfo/Guidebook/_DV/Rules/ @DeltaV-Station/head-administrators
# Wiki
/.wiki/ @DeltaV-Station/direction
/Resources/*.yml @Colin-Tel
/Resources/*/SimpleStation14/ @DEATHB4DEFEAT
/Resources/Maps/ @IamVelcroboy
/Resources/Prototypes/Maps/ @IamVelcroboy
+24 -22
View File
@@ -1,38 +1,40 @@
<!-- If you are new to the Delta-V repository, please read the [Contributing Guidelines](https://github.com/DeltaV-Station/Delta-v/blob/master/CONTRIBUTING.md) -->
<!-- Please read these guidelines before opening your PR: https://docs.spacestation14.io/en/getting-started/pr-guideline -->
<!-- The text between the arrows are comments - they will not be visible on your PR. -->
## About the PR
<!-- What did you change? -->
<!-- What did you change in this PR? -->
## Why / Balance
<!-- Discuss how this would affect game balance or explain why it was changed. Link any relevant discussions or issues. -->
<!-- Why was it changed? Link any discussions or issues here. Please discuss how this would affect game balance. -->
## Technical details
<!-- Summary of code changes for easier review. -->
<!-- If this is a code change, summarize at high level how your new code works. This makes it easier to review. -->
## Media
<!-- Attach media if the PR makes in-game changes (clothing, items, features, etc).
Small fixes/refactors are exempt. -->
<!--
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.
Any media may be used in SS14 progress reports, with clear credit given.
## Requirements
<!-- Confirm the following by placing an X in the brackets without spaces inside (for example: [X] ): -->
- [ ] I have tested all added content and changes.
- [ ] I have added media to this PR or it does not require an in-game showcase.
<!-- You should understand that not following the above may get your PR closed at maintainer’s discretion -->
If you're unsure whether your PR will require media, ask a maintainer.
## Licensing
<!-- This is for the benefit of other forks wishing to port features from DeltaV
Our repository is AGPL, meaning projects using the MIT license would have to ask for permission from the PR author (ideally that's you, reading this) before being allowed to port it over
This just saves them the trouble. Note that AGPL or MIT licensing is only applicable to SOFTWARE, and that assets such as textures and audio have their own licensing scheme that is defined in the codebase itself. -->
- [ ] I confirm that I am the creator of the code in this PR, and allow licensing it under the following license(s), or that the original author(s) has given me permission to do so:
- [X] AGPL (https://github.com/DeltaV-Station/Delta-v/blob/master/LICENSE-AGPLv3.txt) <!-- This one is required to contribute to DeltaV -->
- [X] MIT (https://github.com/DeltaV-Station/Delta-v/blob/master/LICENSE-MIT.txt) <!-- Optional - A lot of other SS14 forks use MIT -->
<!-- Feel free to add more licenses as you see fit -->
Check the box below to confirm that you have in fact seen this (put an X in the brackets, like [X]):
-->
- [ ] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase
## Breaking changes
<!--
List any breaking changes, including namespace, public class/method/field changes, prototype renames; and provide instructions for fixing them. This will be pasted in #codebase-changes.
-->
**Changelog**
<!-- 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. -->
<!--
Make players aware of new features and changes that could affect how they play the game by adding a Changelog entry. Please read the Changelog guidelines located at: https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog
-->
<!--
Make sure to take this Changelog template out of the comment block in order for it to show up.
:cl:
- add: Added fun!
- remove: Removed fun!
+12 -36
View File
@@ -1,48 +1,24 @@
"Changes: C#":
- changed-files:
- any-glob-to-any-file: "**/*.cs"
"Changes: C#":
- "**/*.cs"
"Changes: Documentation":
- changed-files:
- any-glob-to-any-file:
- "**/*.xml"
- "**/*.md"
- "**/*.xml"
- "**/*.md"
"Changes: Localization":
- changed-files:
- any-glob-to-any-file:
- 'Resources/Locale/**/*.ftl'
- 'Resources/Locale/**/*.ftl'
"Changes: Map":
- changed-files:
- any-glob-to-any-file:
- "Resources/Maps/**/*.yml"
- "Resources/Prototypes/Maps/**/*.yml"
- "Resources/Maps/**/*.yml"
- "Resources/Prototypes/Maps/**/*.yml"
"Changes: Sprite":
- changed-files:
- any-glob-to-any-file:
- "**/*.rsi/*.png"
- "**/*.rsi/*.json"
- "**/*.rsi/*.png"
- "**/*.rsi/*.json"
"Changes: UI":
- changed-files:
- any-glob-to-any-file:
- "**/*.xaml*"
- "**/*.xaml*"
"Changes: YML":
- changed-files:
- any-glob-to-any-file:
- "**/*.yml"
"Changes: Shaders":
- changed-files:
- any-glob-to-any-file: '**/*.swsl'
"Changes: Audio":
- changed-files:
- any-glob-to-any-file: '**/*.ogg'
"Changes: Wiki":
- changed-files:
- any-glob-to-any-file: '.wiki/_DV/**/*.txt'
- any: ["**/*.yml"]
all: ["!Resources/Maps/**/*.yml", "!Resources/Prototypes/Maps/**/*.yml"]
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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,15 +19,15 @@ 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: 10.0.x
dotnet-version: 8.0.x
- name: Install dependencies
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
+5 -5
View File
@@ -2,7 +2,7 @@
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -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,15 +34,15 @@ 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: 10.0.x
dotnet-version: 8.0.x
- name: Install dependencies
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
+7 -18
View File
@@ -2,7 +2,7 @@ name: Build & Test Debug
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -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: |
@@ -32,37 +32,26 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 10.0.x
dotnet-version: 8.0.x
- name: Install dependencies
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
shell: pwsh
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0 NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0
- name: Run Content.IntegrationTests
shell: pwsh
run: |
$env:DOTNET_gcServer=1
dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
- name: Archive NUnit3 test results.
if: always()
uses: actions/upload-artifact@v4
with:
name: nunit3-results-${{ matrix.os }}
path: test_results/*
retention-days: 7
compression-level: 9
dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed
ci-success:
name: Build & Test Debug
needs:
+56
View File
@@ -0,0 +1,56 @@
name: PR Changelogs
concurrency: commit_action
on:
pull_request_target:
types: [closed]
env:
GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
CHANGELOG_DIR: ${{ vars.CHANGELOG_DIR }}
PR_NUMBER: ${{ github.event.number }}
jobs:
changelog:
runs-on: self-hosted
if: github.event.pull_request.merged == true
permissions:
contents: write
steps:
- name: Checkout Master
uses: actions/checkout@v3
with:
token: ${{ secrets.BOT_TOKEN }}
ref: "${{ vars.CHANGELOG_BRANCH }}"
- name: Setup Git
run: |
git config --global user.name "${{ vars.CHANGELOG_USER }}"
git config --global user.email "${{ vars.CHANGELOG_EMAIL }}"
shell: bash
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Install Dependencies
run: |
cd "Tools/changelog"
npm install
shell: bash
continue-on-error: true
- name: Generate Changelog
run: |
cd "Tools/changelog"
node changelog.js
shell: bash
continue-on-error: true
- name: Commit Changelog
run: |
git add *.yml
git commit -m "${{ vars.CHANGELOG_MESSAGE }} (#${{ env.PR_NUMBER }})"
git push
shell: bash
continue-on-error: true
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+19
View File
@@ -0,0 +1,19 @@
name: Check Merge Conflicts
on:
push:
branches:
- master
pull_request_target:
jobs:
Label:
if: github.actor != 'PJBot' && github.actor != 'DeltaV-Bot'
runs-on: ubuntu-latest
steps:
- name: Check for Merge Conflicts
uses: ike709/actions-label-merge-conflict@9eefdd17e10566023c46d2dc6dc04fcb8ec76142
with:
dirtyLabel: "Status: Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
-21
View File
@@ -1,21 +0,0 @@
name: Check Merge Conflicts
on:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
jobs:
Label:
if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' ) && ( github.actor != 'DeltaV-Bot' )
runs-on: ubuntu-latest
steps:
- name: Check for Merge Conflicts
uses: eps1lon/actions-label-merge-conflict@v3.0.0
with:
dirtyLabel: "S: Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
+3 -3
View File
@@ -2,7 +2,7 @@
on:
pull_request_target:
types: [review_requested, opened]
types: [review_requested]
jobs:
add_label:
@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
labels: "S: Needs Review"
labels: "Status: Needs Review"
- uses: actions-ecosystem/action-remove-labels@v1
with:
labels: "S: Awaiting Changes"
labels: "Status: Awaiting Changes"
+3 -4
View File
@@ -6,9 +6,8 @@ on:
jobs:
labeler:
if: github.actor != 'PJBot' && github.actor != 'DeltaV-Bot'
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
- uses: actions/labeler@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
-24
View File
@@ -1,24 +0,0 @@
# The PAT does not have the correct permissions to make this work; Eventually we'll want to replace this workflow with a proper bot
# name: "Labels: Approved"
# on:
# pull_request_review:
# types: [submitted]
# jobs:
# add_label:
# # Change the repository name after you've made sure the team name is correct for your fork!
# if: ${{ (github.repository == 'DeltaV-Station/Delta-v') && (github.event.review.state == 'APPROVED') }}
# permissions:
# contents: read
# pull-requests: write
# runs-on: ubuntu-latest
# steps:
# - uses: tspascoal/get-user-teams-membership@v3
# id: checkUserMember
# with:
# username: ${{ github.actor }}
# team: "maintainers"
# GITHUB_TOKEN: ${{ secrets.LABELER_PAT }}
# - if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }}
# uses: actions-ecosystem/action-add-labels@v1
# with:
# labels: "S: Approved"
-21
View File
@@ -1,21 +0,0 @@
name: "Labels: Size"
on: pull_request_target
jobs:
size-label:
runs-on: ubuntu-latest
steps:
- name: size-label
uses: "pascalgn/size-label-action@v0.5.5"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
with:
# Custom size configuration
# DeltaV: changed to powers of 4
sizes: >
{
"0": "XS",
"16": "S",
"64": "M",
"256": "L",
"1024": "XL"
}
+1 -2
View File
@@ -9,6 +9,5 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions-ecosystem/action-add-labels@v1
if: join(github.event.issue.labels) == ''
with:
labels: "S: Untriaged"
labels: "Status: Untriaged"
+32 -23
View File
@@ -2,12 +2,11 @@ name: Publish
concurrency:
group: publish
cancel-in-progress: true
on:
workflow_dispatch:
schedule:
- cron: '45 16 * * *'
- cron: '0 10 * * *'
jobs:
build:
@@ -17,19 +16,18 @@ jobs:
- 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: 10.0.x
dotnet-version: 8.0.x
- name: Get Engine Tag
run: |
cd RobustToolbox
git fetch --depth=1
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Install dependencies
run: dotnet restore
@@ -38,35 +36,46 @@ 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
- name: Publish version
run: Tools/publish_multi_request.py
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
- name: Update Build Info
run: Tools/gen_build_info.py
- name: Shuffle files around
run: |
mkdir "release/${{ github.sha }}"
mv release/*.zip "release/${{ github.sha }}"
- name: Upload files to centcomm
uses: appleboy/scp-action@master
with:
host: ${{ secrets.PUBLISH_HOST }}
username: ${{ secrets.PUBLISH_USER }}
key: ${{ secrets.PUBLISH_KEY }}
port: ${{ secrets.PUBLISH_PORT }}
source: "release/${{ github.sha }}"
target: "/var/www/builds.delta-v.org/delta-v/builds/"
strip_components: 1
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PUBLISH_HOST }}
username: ${{ secrets.PUBLISH_USER }}
key: ${{ secrets.PUBLISH_KEY }}
port: ${{ secrets.PUBLISH_PORT }}
script: /home/deltav/publish/push.ps1 ${{ github.sha }}
- 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 }}
CHANGELOG_FILE: "Resources/Changelog/DeltaVChangelog.yml"
- name: Publish maps 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 }}
CHANGELOG_FILE: "Resources/Changelog/Maps.yml"
- name: Publish changelog (RSS)
continue-on-error: true
run: Tools/actions_changelog_rss.py
env:
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
+2 -5
View File
@@ -11,16 +11,13 @@ 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
uses: Ana06/get-changed-files@v1.2
with:
format: 'space-delimited'
filter: |
**.rsi
**.png
- name: Diff changed RSIs
id: diff
-35
View File
@@ -1,35 +0,0 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 0 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90
remove-stale-when-updated: true
stale-issue-message: 'This issue has not been triaged for 90 days'
only-issue-labels: 'S: Untriaged'
stale-issue-label: 'Stale'
days-before-issue-close: -1
stale-pr-message: 'This Pull Request has not seen activity in 90 days, and will be closed in 2 days without activity'
exempt-pr-labels: 'Ignore Stale'
stale-pr-label: 'Stale'
days-before-pr-close: 2
+15 -14
View File
@@ -2,7 +2,7 @@
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
paths:
- '**.cs'
- '**.csproj'
@@ -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: |
@@ -48,11 +48,10 @@ jobs:
cd RobustToolbox/
git submodule update --init --recursive
# ubuntu-latest has .NET 10
# - name: Setup .NET Core
# uses: actions/setup-dotnet@v4.1.0
# with:
# dotnet-version: 10.0.x
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
- name: Install dependencies
run: dotnet restore
@@ -61,13 +60,15 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --log-build --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 --log-build --no-wipe-release
run: dotnet run --project Content.Packaging client --no-wipe-release
- uses: actions/upload-artifact@v4
with:
name: binlogs
path: release/*.binlog
retention-days: 7
- name: Update Build Info
run: Tools/gen_build_info.py
- name: Shuffle files around
run: |
mkdir "release/${{ github.sha }}"
mv release/*.zip "release/${{ github.sha }}"
+7 -22
View File
@@ -13,14 +13,12 @@ jobs:
if: github.repository == 'DeltaV-Station/Delta-v'
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:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt
# TODO
@@ -34,34 +32,21 @@ jobs:
# 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@v7
# with:
# commit_message: Update Credits
# commit_author: DeltaV-Bot <github@deltav.gay>
# token: ${{ secrets.GITHUB_TOKEN }}
#- 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
id: cpr
uses: peter-evans/create-pull-request@v5
with:
commit-message: Update Credits
title: Update Credits
body: This is an automated Pull Request. This PR updates the github contributors in the credits section.
author: DeltaV-Bot <github@deltav.gay>
author: DeltaV-Bot <github@delta-v.org>
branch: automated/credits-${{env.NOW}}
- name: Auto-merge PR with bypass
if: steps.cpr.outputs.pull-request-number != ''
env:
GH_TOKEN: ${{secrets.CREDITS_TOKEN}}
run: |
gh pr merge ${{ steps.cpr.outputs.pull-request-number }} \
--squash \
--admin \
--delete-branch \
--repo ${{ github.repository }}
+2 -2
View File
@@ -1,7 +1,7 @@
name: RGA schema validator
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -12,7 +12,7 @@ jobs:
if: github.actor != 'PJBot' && github.event.pull_request.draft == false && github.actor != 'DeltaV-Bot'
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 -2
View File
@@ -2,7 +2,7 @@ name: RSI Validator
on:
push:
branches: [ master ]
branches: [ staging, trying ]
merge_group:
pull_request:
paths:
@@ -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 -2
View File
@@ -1,7 +1,7 @@
name: Map file schema validator
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -12,7 +12,7 @@ jobs:
if: github.actor != 'PJBot' && github.event.pull_request.draft == false && github.actor != 'DeltaV-Bot'
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 -5
View File
@@ -2,7 +2,7 @@ name: YAML Linter
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -13,7 +13,7 @@ jobs:
if: github.actor != 'PJBot' && github.event.pull_request.draft == false && github.actor != 'DeltaV-Bot'
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
@@ -23,11 +23,10 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 10.0.x
dotnet-version: 8.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
-3
View File
@@ -1,6 +1,3 @@
# MSbuild binlog files
*.binlog
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+1 -2
View File
@@ -1,5 +1,4 @@
[submodule "RobustToolbox"]
path = RobustToolbox
url = https://github.com/space-wizards/RobustToolbox.git
branch = master
ignore = dirty
branch = master
-34
View File
@@ -1,34 +0,0 @@
# Delta-V's pre-commit.com config
# You don't have to install it yourself, CI applies fixes to PRs automatically.
# don't touch upstream files, DeltaV is only for allowing ConfigPresets
files: "(_DV|DeltaV)/"
repos:
- repo: "https://github.com/pre-commit/pre-commit-hooks"
rev: "v6.0.0"
hooks:
# File checkers
- id: check-yaml
# only check prototypes, maps would be too slow
files: "^Resources/Prototypes/_DV/.*\\.yml"
# !type:... doesn't work without somehow loading SS14, which would probably make commits take years
args: [ "--unsafe" ]
- id: check-json
# only need to check meta.json files
files: "^Resources/Textures/_DV/.*meta\\.json$"
- id: check-xml
# Check all ui XAML files
# Can't check guidebook "xml" as it is a monster
files: "^Content.Client/_DV/.*\\.xaml$"
types: ["text"]
- id: check-toml
files: "^Resources/ConfigPresets/.*\\.toml$"
- id: check-merge-conflict
# Fixers that can and will change commits to PRs
- id: end-of-file-fixer
- id: trailing-whitespace
- id: fix-byte-order-marker
- id: pretty-format-json
# Format texture meta.json files
files: "^Resources/Textures/_DV/.*meta\\.json$"
args: [ "--autofix", "--indent", "4", "--no-ensure-ascii", "--no-sort-keys" ]
+1 -6
View File
@@ -1,11 +1,6 @@
{
"recommendations": [
"ms-dotnettools.csharp",
"editorconfig.editorconfig",
"ertanic.robust-lsp",
"GitHub.vscode-pull-request-github",
"eamodio.gitlens",
"macabeus.vscode-fluent",
"redhat.vscode-yaml"
"editorconfig.editorconfig"
]
}
+8 -87
View File
@@ -4,17 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "YAML Linter",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-yaml-linter",
"program": "${workspaceFolder}/bin/Content.YAMLLinter/Content.YAMLLinter.dll",
"cwd": "${workspaceFolder}/Content.YAMLLinter",
"console": "internalConsole",
"stopAtEntry": false
},
// Client configurations
{
"name": "Client",
"type": "coreclr",
@@ -25,28 +14,7 @@
"stopAtEntry": false
},
{
"name": "Client - Tools",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Tools",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
"args": [],
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Client - Release",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Release",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
"args": [],
"console": "internalConsole",
"stopAtEntry": false
},
// Compatibility renderer client configurations
{
"name": "Client - (Compatibility renderer)",
"name": "Client (Compatibility renderer)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
@@ -54,27 +22,6 @@
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Client - Tools - (Compatibility renderer)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Tools",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
"args": ["--cvar display.compat=true"],
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Client - Release - (Compatibility renderer)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Release",
"program": "${workspaceFolder}/bin/Content.Client/Content.Client.dll",
"args": ["--cvar display.compat=true"],
"console": "internalConsole",
"stopAtEntry": false
},
// Server configurations
{
"name": "Server",
"type": "coreclr",
@@ -85,25 +32,15 @@
"stopAtEntry": false
},
{
"name": "Server - Tools",
"name": "YAML Linter",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Tools",
"program": "${workspaceFolder}/bin/Content.Server/Content.Server.dll",
"args": [""],
"console": "integratedTerminal",
"preLaunchTask": "build-yaml-linter",
"program": "${workspaceFolder}/bin/Content.YAMLLinter/Content.YAMLLinter.dll",
"cwd": "${workspaceFolder}/Content.YAMLLinter",
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": "Server - Release",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build - Release",
"program": "${workspaceFolder}/bin/Content.Server/Content.Server.dll",
"args": [""],
"console": "integratedTerminal",
"stopAtEntry": false
},
}
],
"compounds": [
{
@@ -113,22 +50,6 @@
"Client"
],
"preLaunchTask": "build"
},
{
"name": "Server/Client - Tools",
"configurations": [
"Server - Tools",
"Client - Tools"
],
"preLaunchTask": "build - Tools"
},
{
"name": "Server/Client - Release",
"configurations": [
"Server - Release",
"Client - Release"
],
"preLaunchTask": "build - Release"
}
]
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"omnisharp.analyzeOpenDocumentsOnly": true,
"dotnet.defaultSolution": "SpaceStation14.sln"
}
+3 -43
View File
@@ -10,7 +10,7 @@
"args": [
"build",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:'ForceNoAlign;NoSummary'" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary" // Do not generate summary otherwise it leads to duplicate errors in Problems panel
],
"group": {
"kind": "build",
@@ -21,46 +21,6 @@
},
"problemMatcher": "$msCompile"
},
{
"label": "build - Tools",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"--configuration",
"Tools",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:'ForceNoAlign;NoSummary'", // Do not generate summary otherwise it leads to duplicate errors in Problems panel
],
"group": {
"kind": "build",
"isDefault": false
},
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
{
"label": "build - Release",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"--configuration",
"Release",
"/property:GenerateFullPaths=true", // Ask dotnet build to generate full paths for file names.
"/consoleloggerparameters:'ForceNoAlign;NoSummary'", // Do not generate summary otherwise it leads to duplicate errors in Problems panel
],
"group": {
"kind": "build",
"isDefault": false
},
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
{
"label": "build-yaml-linter",
"command": "dotnet",
@@ -69,9 +29,9 @@
"build",
"${workspaceFolder}/Content.YAMLLinter/Content.YAMLLinter.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
}
-321
View File
@@ -1,321 +0,0 @@
== <span style="color:#4f8439;>Green Alert</span> ==
The normal operating procedure. ''There is no currently known threat to the ship, nor crew.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#4f8439BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#4f8439BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Open carrying of long arms and shields disallowed for Security personnel. Standard stab-and bullet-resistant equipment recommended for Security personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for general orders.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Secure areas unbolted and accessible to all authorized personnel. HSAs and areas with an expectation of privacy, such as bedrooms, dorms, or medical treatment facilities, may be bolted as needed.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Binary suit sensors recommended at a minimum.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | Report to supervisor for general orders.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with minimal force required; prioritize de-escalation whenever possible.
|}
== <span style="color:#416db7;>Blue Alert</span> ==
Elevated alert status. ''There is an ongoing, known, or suspected security threat to the NanoTrasen vessel or its crew, or another significant security emergency.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#416db7BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#416db7BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > [[Head of Security|Head of Station Security]] > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Open carry of weaponry permitted for all authorised entities. Body armour mandatory for Security personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for general orders. Searching individuals suspected of committing a crime is encouraged.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Secure areas unbolted unless affected by known threats. Bolting [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] recommended.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Binary suit sensors recommended at a minimum. EVA suits recommended for emergency responders, functioning internals and PPE heavily advised.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits recommended for engineers.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with minimal force required; non-lethal weapons may be used freely based on threat and compliance.
|}
== <span style="color:#a43e3e;>Red Alert</span> ==
Emergency alert status. ''There are multiple major emergency situations ongoing, or a major security emergency affecting the station.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#a43e3eBF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#a43e3eBF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > Security Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons heavily recommended. Body armour and helmets mandatory for Security personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for general orders. Protection of personnel prioritized over body and department searches.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | All [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] recommended to be bolted and under guard. Secure areas unbolted unless affected by known threats.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors heavily advised. EVA suits and functioning internals heavily advised for emergency responders.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Make emergency internals easily accessible.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with adequate force to safely subdue suspects, dependent on threat and compliance. Lethal force is permitted to effect arrests against evasive targets.
|}
== <span style="color:#583ca2;>Violet Alert</span> ==
Elevated alert status. ''There is a serious viral outbreak, ongoing major death event, or there is another significant medical emergency.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#583ca2BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#583ca2BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > [[Chief Medical Officer]] > Medical Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons disadvised. Standard stab-and bullet-resistant equipment recommended for Security personnel. Biohazard gear advised.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for orders. Non-medical personnel should ensure they do not interfere with medical staff. Security should seek to aid medical staff where possible.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Secure areas unbolted. [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] and areas with an expectation of privacy, such as bedrooms, dorms, or medical treatment facilities, may be bolted as needed.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Report to supervisor for orders. Full suit sensors heavily advised. EVA suits recommended for emergency responders, functioning internals and PPE heavily advised.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | Report to supervisor for general orders.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with minimal force required; prioritize de-escalation whenever possible.
|}
== <span style="color:#999933;>Yellow Alert</span> ==
Elevated alert status. ''There is a major issue with the atmospheric system, the station has suffered/is about to suffer significant damage, or there is another significant engineering emergency.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#999933BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#999933BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > [[Chief Engineer]] > Engineering Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons disadvised. EVA protection heavily advised for all personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for orders. Non-engineering personnel advised to evacuate affected areas. Security should seek to aid engineering staff where possible.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Secure areas unbolted. [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] and areas with an expectation of privacy, such as bedrooms, dorms, or medical treatment facilities, may be bolted as needed.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors heavily advised. EVA suits and functioning internals recommended for emergency responders.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Distribute emergency internals to crew. Propose evacuation if damage is irreparable or sufficiently hazardous.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with minimal force required; prioritize de-escalation whenever possible.
|}
== White Alert ==
Elevated alert status. ''The station is suffering dangerously high levels of glimmer, there are several active psionic threats, or there is another significant epistemic emergency.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#a6a6a6BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#a6a6a6BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > [[Mystagogue]] > Epistemics Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Psionic insulation recommended for all personnel. Open carry of weaponry permitted for authorised entities. Standard stab- and bullet-resistant equipment recommended for Security personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for orders. Security and Engineering should seek to aid epistemics staff where possible. Propose evacuation if glimmer spiral is unrecoverable.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Secure areas unbolted. [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] and areas with an expectation of privacy, such as bedrooms, dorms, or medical treatment facilities, may be bolted as needed.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors heavily advised. Prepare for increased incidents of burn and brain damage.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Distribute emergency internals to crew. Prepare for detonation of [[Glimmer#Probers|probers]].
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with minimal force required; ROE on mindwiping is lifted and may be performed by the Psionic Mantis, Chaplain or Mystagogue at their discretion.
|}
== <span style="color:#36717d;>Gamma Alert</span> ==
Emergency alert status. ''Central Command has called the Gamma Alert; the Station is on its last legs, almost everyone is dead, or there is another existential crisis affecting the station.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#36717dBF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#36717dBF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > Security Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons heavily recommended. Body armour and helmets mandatory for Security personnel. EVA protection heavily advised for all personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for general orders. Martial law is in effect.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | All [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] recommended to be bolted and under guard. Secure areas unbolted.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors mandatory for observation and safety. EVA suits and functioning internals heavily advised for emergency responders.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Distribute emergency internals to crew.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Martial law is in effect.
|}
== <span style="color:#996633;>Delta Alert</span> ==
Emergency alert status. ''The nuclear destruction mechanism has been engaged and station destruction is imminent. All crew aboard the station must attempt to reverse the self-destruct mechanism, if engaged in error. Should reversal prove impossible, evacuate as applicable.
''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#996633BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#996633BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > Security Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons heavily recommended. Body armour and helmets mandatory for Security personnel. EVA protection heavily advised for all personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Security and Command must expend all efforts to prevent station destruction if engaged in error. Martial law is in effect.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | All secure areas and [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] should remain unbolted.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors mandatory for observation and safety. EVA suits and functioning internals heavily advised for emergency responders.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Distribute emergency internals to crew.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Martial law is in effect.
|}
== <span style="color:#cae8e8;>Code Octarine</span> ==
Emergency alert status''. The station's deep-spectrum sensors have identified critical Λ-CDM levels. The station and all surrounding space is under threat of an impending existential noospheric-to-real threat.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#583ca2BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#583ca2BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > [[Mystagogue]] > Security Personnel > Department Heads > Supervisory Roles >
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons recommended. Body armour and helmets mandatory for Security personnel, permitted for distribution to authorised personnel. EVA protection strongly advised for all personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Emergency personnel must expend all efforts to prevent station destruction. Arrests, searches, and raids may be performed at the discretion of security personnel and without a warrant. Where possible, threats should be subdued and brought to the Chaplain or Mystagogue for processing.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | All secure areas and [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] should remain unbolted.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors mandatory for observation and safety. EVA suits and functioning internals heavily advised for emergency responders.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Distribute emergency internals to crew.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with adequate force to neutralise threats. Lethal force authorised at the discretion of Security personnel, in self-defence, or to effect arrests. ROE on mindwiping is lifted and may be performed by the Psionic Mantis or Mystagogue at their discretion.
|}
== <span style="color:#b000ff;>Code Epsilon</span> ==
Emergency alert status. ''Central Command has ordered the Epsilon security level on the station. Only orders from Central Command Officials are to be obeyed by the Station's Crew. Martial Law is in effect.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#b000ffFF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#b000ffFF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | Central Command
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Await orders from Central Command Personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Await orders from Central Command Personnel.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Await orders from Central Command Personnel.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors mandatory for observation and safety. Await orders from Central Command Personnel.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | Await orders from Central Command Personnel.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Martial Law is in effect. Do not engage with Central Command Personnel. Await orders from Central Command Personnel.
|}
== <span style="color:#ff8119;>Orange Alert</span> ==
Emergency alert status. ''There is a critical station-wide structural or atmospheric threat and recovery is unlikely. Engineering staff are advised to minimize hazards and secure the Evacuation Dock. Crewmembers are advised to stay away from hazardous areas, and prepare for Evacuation.''
{| class="wikitable" style="width: 80%; border: 1px solid #000000;"
!style="text-align:left; width:25%; padding:5px; padding-left:10px; background-color:#FF8119BF; border: 1px solid #000000;" | '''Condition'''
!style="text-align:left; width:75%; padding:5px; padding-left:10px; background-color:#FF8119BF; border: 1px solid #000000;" | '''Description'''
|-
| style="border: 1px solid #000000;" | Chain of Command
| style="border: 1px solid #000000;" | [[Standard_Operating_Procedure#Captain’s_Authority|CO]] > [[Chief Engineer]] > Engineering Personnel > Department Heads > Supervisory Roles
|-
| style="border: 1px solid #000000;" | Armoury Policy
| style="border: 1px solid #000000;" | Issuing of lethal weapons disadvised. EVA protection heavily advised for all personnel.
|-
| style="border: 1px solid #000000;" | Discipline
| style="border: 1px solid #000000;" | Report to supervisor for orders. Non-engineering personnel advised to evacuate affected areas. Security should seek to aid engineering staff where possible.
|-
| style="border: 1px solid #000000;" | Secure Areas
| style="border: 1px solid #000000;" | Secure areas unbolted. [[Standard_Operating_Procedure#High_Security_Areas|HSAs]] and areas with an expectation of privacy, such as bedrooms, dorms, or medical treatment facilities, may be bolted as needed.
|-
| style="border: 1px solid #000000;" | Medical
| style="border: 1px solid #000000;" | Full suit sensors heavily advised. EVA suits and functioning internals recommended for emergency responders.
|-
| style="border: 1px solid #000000;" | Engineering
| style="border: 1px solid #000000;" | EVA suits heavily advised for engineers. Distribute emergency internals to crew. Ensure evacuation as soon as possible.
|-
| style="border: 1px solid #000000;" | Rules of Engagement
| style="border: 1px solid #000000;" | Engage with minimal force required; prioritize de-escalation whenever possible.
|}
{{Guides Menu}}
-272
View File
@@ -1,272 +0,0 @@
__TOC__
= Company Policy =
Welcome to the '''NanoTrasen Space Station Company Policy Manual - 14th Edition!'''
== Preface ==
As a member of the crew aboard a large and cutting-edge space station, it is imperative that you adhere to a set of ''responsibilities and behaviours'' that reflect our company’s commitment to safety and cooperation. Our [[Maps|space stations]] are not just places of work - they’re thriving hubs of progress, ingenuity, and unity.<br>
Every action aboard our stations has meaning and impact, and it’s this interconnectedness that drives everyone to succeed - from the humble assistant to the [[Captain|highest-ranking officers.]]
Within this manual, you’ll find our general guidelines for employees, a list of your rights and responsibilities as a member aboard a NanoTrasen station, expectations for specific [[jobs|roles]], information on occupational hazards aboard a space station, and contractual obligations.
The stipulations outlined here represent a baseline for the ethical and respectful behaviour we expect from all of our valued employees, and have been designed solely to facilitate the safety and productivity of our NanoTrasen corporate family. We encourage you to study this manual so you may gain a comprehensive understanding of the values we uphold and how they are vital to the seamless function of our stations.
We thank you for choosing to embark on this incredible journey with us. Together, we will push the razor’s edge of progress, and make NanoTrasen synonymous with the future.
[NanoTrasen Central Command]
== Code of conduct ==
At NanoTrasen, we pride ourselves on maintaining the highest standards of professionalism and respect. As an employee, and as a representative of the corporation as a whole, you will be expected to uphold the following company values and ideals:
#'''Consideration and Care'''
#: Think before you act, and ensure you’re acting appropriately and safely. Adhere to company standards, undergo regular training, and ensure you’re following any safety notices or instructions from authorities.
<!-- -->
#'''Respect and Collaboration'''
#: Treat all colleagues, clients , and members of the public with courtesy and professionalism. Embrace diversity of background and culture, and foster a synergetic work environment.
<!-- -->
#'''Integrity and Honesty'''
#: By acting with a foundation of integrity and being honest with your colleagues and superiors, you’re upholding the trust placed in you by our customers, our shareholders, and the NanoTrasen brand. Honesty with yourself and others serves the community at large.
<!-- -->
#'''Confidentiality and Accountability'''
#: Safeguard sensitive information about our personnel, our clients, company operations, research, and restricted data. Take ownership of this responsibility and the various others delegated to you, striving for excellence in your work and diligence in your duties.
<!-- -->
#'''Adaptability and Ingenuity'''
#: Embrace change, stay adaptable, and demonstrate a willingness to learn and grow in response to new circumstances. Never quit, and never give up; show NanoTrasen’s commitment and hard work ethic regardless of any obstacles in your way.
== Appearance standards ==
As a NanoTrasen employee, you are expected to wear provided clothing, and to keep them clean, well-maintained, and appropriately fitted. This ensures that you’re representing our brand in a professional manner, as well as maintaining the safety of yourself and others in the station environment.
#'''Uniforms and Attire'''
#: Wear the uniforms and attire provided to you by NanoTrasen. Take care of your clothes, and wear them with pride.
<!-- -->
#'''Grooming'''
#: Maintain personal hygiene and grooming to a high standard. Neat hair and well-kept appearances contribute to a professional atmosphere and boost first impressions.
<!-- -->
#'''Accessories and Personal Items'''
#: Limit the use of accessories that might interfere with safety equipment or delegated tasks. Personal items should be stored in designated spaces to avoid clutter and to ensure the safety of you and your coworkers.
<!-- -->
#'''Personal Protective Equipment'''
#: Wear the appropriate PPE as required by your role, any safety notices, or instructions from authorities. The safety of our employees and customers is paramount.
<!-- -->
#'''Respect and Consideration'''
#: Be mindful of others’ cultures, beliefs, and other qualities when expressing yourself and your own personal style. Your behaviour and actions should respect the sensitivities and beliefs of others.
Additionally, it should be noted that any accessory, item of clothing or apparel, or other cosmetic possession that promotes or depicts obscene or hateful content, criminal or terrorist groups, or illegal and unethical acts are unacceptable for wear by our employees. Failure to uphold our company’s values by violating this rule will result in disciplinary action.
== Rights and responsibilities ==
All general crew aboard NanoTrasen Space Stations have certain rights and responsibilities in order to ensure that the company, its employees, and any customers or the general public are taken into consideration.
'''All members of the crew:'''
*Have the right to live in a safe and healthy environment, and to be provided access to necessary safety equipment and medical assistance.
*Have the right to be treated with respect and fairness regardless of their role or background.
*Have the right to receive clear and accurate information related to their roles, tasks, or relevant updates.
*Have the right to receive proper training and development opportunities as necessary to enhance their skills and contribute effectively to company operations.
*Have the right to confidentiality regarding personal information and sensitive matters within the bounds of operational requirements.
*Have the right to access necessary resources for performing their duties, including equipment, tools, or facilities.
*Are responsible for following safety protocols, operating procedures, and company policies to ensure their own safety and others’.
*Are responsible for upholding the company’s reputation through their behaviour, communication, and general conduct.
*Are responsible for fulfilling their assigned tasks and responsibilities efficiently and to the best of their ability, particularly where they are essential to station function.
*Are responsible for promptly reporting grievances, hazards, or malfunction to appropriate personnel.
*Are responsible for conscientiously managing and utilising their resources for efficient operation.
*Are responsible for upholding the policies outlined within this CPM14 manual.
== Self-defence ==
NanoTrasen places a ''high priority on the safety and wellbeing of all individuals'' aboard its space stations, including employees and patrons. The company recognises that maintaining a secure environment is essential to operations, and, in light of this fact, all NanoTrasen employees are authorised to exercise their right to self-defence when confronted with an imminent threat of physical harm or violence upon their person, in accordance with these guidelines:
*The threat must be immediate and serious, with clear intent to cause grievous harm or injury to the employee.
*Before resorting to self-defence, the employee must have exhausted all reasonable alternatives for avoiding or escaping the threat - including following established protocols, attempting de-escalation, and seeking assistance from security personnel.
*The response to the threat must be proportional to the level of danger posed; that is to say, the employee must only use the level of force necessary to protect themselves from harm.
*The employee’s primary directive is to ensure their own safety. Once the opportunity to escape arises, or the threat is neutralised, the employee must cease their use of force and immediately seek assistance from security personnel, medical staff, and any other relevant authorities.
In any situation where an employee exercises self-defence against any individual aboard the station, they are ''obligated'' to provide a '''detailed account of the incident''', including the circumstances immediately prior, to their immediate supervisor, security personnel, or station administration, where applicable and at their earliest convenience.<br>
The authorisation for self-defence is an exceptional measure intended to address extreme situations where no other viable options exist, and only to facilitate a secure environment that fosters collaboration, innovation, and mutual respect between NanoTrasen’s employees.
== Conduct unbecoming ==
NanoTrasen personnel found to behave or act in a way that is broadly considered or specified by superiors as inappropriate, unacceptable, or contrary to our company’s standards and expectations for their position or profession, particularly in a professional capacity or when representing the company as a whole, may be demoted from that position or profession, have their contract terminated, or be otherwise reprimanded for their unbecoming conduct.
Behaviour that violates our company’s values and policies oppugn our employees’ character, integrity, and suitability; additionally, improper conduct reflects poorly on our management. As such, heads of departments are ''fully authorised and encouraged'' to demote employees that discredit our company’s values by poor behaviour and demeanour.
== Dangers from our competitors ==
In our spinward stations, there exist threats from direct competitors to NanoTrasen. Conglomerates of companies, crime rings, and terrorist cells known for their unscrupulous practices, including the use of fear tactics, criminal methods, and corporate espionage to undermine and disrupt the operations of legitimate entities such as NanoTrasen. Their activities pose a significant danger to the safety, stability, and success of our stations and the company as a whole. Their motives range from obtaining valuable technology and proprietary information, to sabotaging critical systems, to directly attacking crew.
Identifying saboteur agents can prove challenging, as they often operate covertly and blend in with the station’s population. However, there are certain behaviours and indicators to be on the look-out for:
:*'''Unusual Interest'''
:*: Individuals showing an excessive interest in sensitive research, proprietary data, or operational reasons without a legitimate reason may be potential corporate agents.
<!-- -->
:*'''Inconsistent Backgrounds'''
:*: Agents may provide inconsistent or fabricated personal or professional backgrounds, making it important to verify credentials through proper channels.
<!-- -->
:*'''Secrecy and Isolation'''
:*: Suspicious individuals may isolate themselves, avoid interactions, or engage with only select individuals or parties, raising concerns about their true intentions.
<!-- -->
:*'''Sudden Change in Behaviour'''
:*: Drastic changes in behaviour, such as displays of aggression or secrecy, could signal involvement with hostile groups.
To combat the presence of hostile entities, crew are advised to:
*Report suspected individuals.
*Increase monitoring and patrols.
*Identify unauthorised access attempts and potential breaches.
*Secure critical data.
*Conduct regular emergency drills.
*Perform de-escalation training.
*Implement advanced counter-espionage protocols.
Should any person or party aboard a NanoTrasen vessel be found to be currently affiliated with, or in the present employ of, any hostile company, crime ring, or terrorist group, they are to have their employment with NanoTrasen terminated where applicable, be stripped of any gratuitous access, and have any corporate property on their person or possession confiscated and returned to relevant station personnel. Such persons or parties may be heavily scrutinised by Station Security forces until they, or their remains, can be remanded to the custody of their employers, an independent port or vessel, or other local jurisdiction, at the earliest convenient time and at no expense to the company.
== Hostile Corporation Technology ==
Additionally, due to the presence of our competitors and their agents in the sectors in which our company operates, there is an ever-present possibility that hostile parties' technology or other property may manifest aboard our station by any number of means. The presence, acquisition, and distribution of such items should always be investigated and scrutinised heavily, and if Space Law and local legal ordinances allow, held securely, confiscated, and restricted from use.
== Potential Threats ==
While working aboard NanoTrasen stations, you may encounter the highly unlikely scenario of your life being threatened by hostile forces. Fortunately, our highly-trained security teams are well-equipped to deal with such threats. Please note, however, that wasting company resources and time for a pre-emptive defense against a non-existent threat are grounds for demotion. Below are the potential hazards you may face:
'''NANOTRASEN DOES NOT TAKE RESPONSIBILITY FOR CREW INJURY OR DEATH IF CREW MEMBERS DISREGARD THE INFORMATION BELOW.'''
=== Hostile Fauna ===
Be it from Epistemics experiments, corpses brought in by salvage, or something living in the vents, you may be attacked by hostile non-sophont entities. NanoTrasen does not encourage befriending such animals, unless it’s an approved experiment for research. Below are the procedures of how to deal with such a threat:
*Do not engage with the threat yourself.
*Evacuate the department or the zone.
*Immediately inform Security of the incident. Provide details such as:
**Threat type.
**Estimated numbers.
**Property damage.
**Any additional information.
*Inform Medical if there are injuries. If paramedics are unavailable, stabilize and carefully deliver the wounded to Medical.
*Provide ID access if Security personnel are not able to enter the area.
*Once Security confirms the threat being neutralized, clean up the department and report the incident to Command or Central Command.
=== Hostile Boarding Parties ===
Due to the station’s invaluable resources and/or high profile crewmembers, NanoTrasen stations may attract hostile boarding parties. These individuals typically gain unauthorized access to the station and are usually armored, armed with deadly weapons. While their intentions are rarely known, common goals may include:
*Assassination of crewmembers.
*Theft of valuable station materials.
*Theft of sensitive research.
*Mass crew harm or terrorism.
*Sedition of the chain of Command.
*Detonation of the station’s nuclear warhead.
If such an unlikely scenario occurs, the following procedures are listed below:
*Do not engage with the threat yourself.
*Seek immediate shelter.
*Immediately inform Security of the incident. Provide details such as:
**Hostile’s appearance.
**What kind of armaments they’re equipped with.
**Estimated numbers.
**Last known location.
**Property damage.
**Any additional information.
*Inform Medical if there are injuries. If paramedics are unavailable, stabilize and treat the wounded. Only move the wounded if the path to Medical is safe. Do not take unnecessary risks.
*Remain in place until the situation has been resolved.
In the case of overwhelming force that Security cannot handle:
*Request assistance from Central Command through fax. In your fax, give as much detail of the threat as you can and the damages to the station and crew, alongside with signatures and stamps. Read “Contacting Central Command” for more information.
*Evacuate the station.
=== Unauthorized Cults ===
NanoTrasen recognizes and respects the free expression of religion aboard its stations. Discrimination of any kind will not be tolerated and is grounds for termination. However, Nanotrasen does not permit religious activity that involves:
*Fanatical recruitment of additional members.
*Harm to crew members, be it physical, psychological or metaphysical.
*Practices associated with EarthGov banned religions that are officially designated as harmful cults.
For classification, speak with the station’s Mystagogue or Priest.
Please note that the Oracle’s religion is permitted aboard NanoTrasen stations, as they are the primary subject of research.
In the case of an unauthorized cult is suspected of forming on board, all crew members are encouraged to report unusual activity. Suspicious behaviour can be:
*Secrecy and Isolation. Suspicious individuals may engage only with selected individuals in secluded areas.
**Sudden changes in behaviour. Known members having a sudden change in their behavior or personality may indicate their affiliation.
**Otherworldly or physics defying effects occurring around certain crew members.
**Appearance of unidentified anomalies.
If a hostile cult is confirmed, follow these procedures:
*Listen to Epistemics, Security and Command for any relevant information and orders about the threat.
*Report suspicious individuals to Security and Epistemics. Give as much detail as possible, such as their appearance, potential tools, behaviour and more.
*Form a group of trusted co-workers and stick together. Do not isolate yourself.
*Await further instructions and remain in secure areas until the situation has been resolved by Security and Epistemics personnel.
=== Pathogens ===
While all NanoTrasen personnel are vaccinated against a wide range of known diseases, and while the station's atmospheric systems utilize only the highest-grade filtration technology, an outbreak may still occur on the station. Most such incidents are minor in scope - like the common cold. However, employees are reminded to remain vigilant and to treat any outbreak with the seriousness it deserves.
In the event of a confirmed outbreak, please follow these procedures:
*Follow instructions. Listen to Medical staff and comply with all orders and updates.
*Wear protective equipment, such as masks and gloves. Biosuits are recommended if available.
*Maintain hygiene. Wash hands frequently, and clean up any potential biohazards.
*Report Symptoms. Notify Medical and Security if any personnel display signs of infection.
*Stay in your department.
*Limit physical contact.
If you’re starting to show symptoms of the pathogen, please immediately follow these procedures:
*Immediately head to the Medical Department.
*Inform Medical personnel of your symptoms.
*Listen to all orders from Medical personnel.
*Store any valuable NanoTrasen gear, weaponry or property in a secure location.
*If a treatment is currently unavailable, quarantine yourself in Medical.
*Await treatment.
=== Unions ===
NanoTrasen supports free speech, the right to fair working conditions and does not consider itself anti-union. However, The Company is not neutral either. NanoTrasen will always defend the interests of its customers, shareholders and associates. Worker unions may disrupt work productivity, which can in turn jeopardize job security for all crew members.
If an organizing union attempts to use force to advance its goals, such as vandalizing Company property or physically attacking other crewmembers, Security is authorized under Space Law to quell and suppress unions.
It is every crew member's duty to report potential warning signs of unionization to their managers. Warnings signs may be:
*Crew members refusing to work.
*Usage of union related terminology, (living wage, working conditions, etc.)
*Circulation or distribution of petitions and union fliers.
*Unusual interest in Company policies, benefits, employee list or any other Company information.
*Noticeable changes in behavior among known crew members.
*Non-manifest individuals supporting union activity.
*Clothing, accessories associated with unions.
*Increased negativity and complaints.
In the case of a violent union riot, follow these procedures:
*Do not join the riot.
*Inform Security and Command immediately. Provide details:
**Names and job positions of any suspected union members and leaders.
**Whether the union members are armed.
*Inform Central Command of the incident.
*Shelter in a safe place and await for the situation to be resolved.
== Contacting Central Command ==
Should you require a question answered about your duties, advice on how to properly handle situations, or wish to submit an incident report (including gross misconduct by Command staff), Central Command Officials are always happy to assist. Please ensure your fax is descriptive, legible and includes your signature or departmental stamp.
Refrain from sending junk mail to Central Command. Penalties may include pay cuts or a renegotiation of your contract.
In the highly unlikely event that station security is faced with an overwhelming hostile force or the chain of Command is compromised, crewmembers are advised to request assistance from Central Command.
Your fax should include:
*Stations designation (available in your PDA)
*The overwhelming threat. Try to provide as much detail as possible, such as:
**Estimated number of hostiles.
**If armed, what kind of armaments they’re equipped with.
**Number of casualties. Information about Security’s and Command’s status is recommended.
**Station’s structural integrity.
Due to the prevalence of misinformation reaching Central Command, please ensure to add as many stamps and signatures as possible/convenient.
Be warned that all faxes, urgent or otherwise, may take some time to be properly read and receive a response. Central Command is dedicated to managing multiple stations simultaneously. Rest assured: your fax will be addressed, even without your notice.
= Conclusion =
In closing, we thank you for your commitment to our mission and the values that guide NanoTrasen as a whole. Your dedication is the keystone to our success.
And remember, we are bound by a shared responsibility in this success. Failure to uphold your contract will result in immediate consequences, up to and including termination with extreme prejudice.
Safe travels!
[NanoTrasen Central Command]
{{Guides Menu}}
-373
View File
@@ -1,373 +0,0 @@
Space Law is a broad set of rules and regulations, collected and regulated by TerraGov, enacted and enforced by NanoTrasen and its [[Security Officer|Station Security]], with the oversight of [[Administrators#What_Do_Admins_Do?|CentCom]] - and, on occasion, TerraGov's Galactic Police Force. Space Law applies to all ranks and positions on stations under its jurisdiction, from the lowliest [[Passenger]] to the highest [[Captain]]; all are equal under the eyes of the Law, and all must ultimately answer to it.
It is highly recommended to read the '''[[Standard Operating Procedure|Standard Operating Procedures]]'''. Summaries of several pertinent sections are listed below.
* '''[[Standard Operating Procedure#Controlled Items|Controlled Items]]:''' If a member of station crew requires access to a Controlled Item for use in their job, or for regular function, they must obtain the signed and stamped approval of a relevant member of Station Command or the CO. The latter is able to override the decisions of Station Command in this regard, however.
* '''[[Standard Operating Procedure#Entity Definitions|Entity]]:''' Any thing with distinct, independent existence.
* '''[[Standard Operating Procedure#Entity Definitions|Employee]]:''' An entity who has an employment contract with a Company.
* '''[[Standard Operating Procedure#Entity Definitions|Pet]]:''' Any Entity that is provided to the Company's Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support.
* '''[[Standard Operating Procedure#Security Jurisdiction|Security Jurisdiction]]:''' Station law enforcement officers are bound by Space Law; they cannot operate outside of standard accessible areas, or perform searches, unless such action is allowed by either Alert Procedure or a valid warrant.
* '''[[Standard Operating Procedure#Arrest|Arrest and Sentencing]]:''' Any entity may be arrested, during which they are restrained, informed of their charges, and taken to the brig for search and processing under '''[[Standard Operating Procedure#Brig Procedure|Brig Procedure]]'''.
* '''[[Standard Operating Procedure#Sentencing|Arrest and Sentencing]]:''' Any member of law enforcement is allowed to pass sentence on Misdemeanor, Felony, and Grand Felony charges. The Chief Justice, or in their absence, the Head of Security are allowed to pass sentence on all Charges.
* '''[[Standard Operating Procedure#Treatment of Prisoners|Treatment of Prisoners]]:''' Prisoners must be provided with food, water, clothing, free movement within the brig, adequate medical care, access to the Common and Prison radio channels, and moral, spiritual, or legal counseling if requested and available. If the brig becomes uninhabitable, prisoners must be securely and safely relocated to another area until the brig is functional once more. Prisoners sentenced to Permanent Confinement have additional rights and firmer restrictions that must be upheld by law enforcement officials.
* '''[[Standard Operating Procedure#Paroles and Pardons|Paroles & Pardons]]:''' Parole may be offered under the discretion of the Warden, Head of Security, or CO. Pardons may be issued by the arresting officer, the Judge of a trial, by the Warden (to those within the permanent brig), or by the CO, when deemed in the best interest of the crew or vessel, or when the circumstances of the crime warrant suspension of sentence.
* '''[[Standard Operating Procedure#Pressing Charges|Pressing Charges]]:''' Any Employee may press charges against any other entity. Individuals may only be charged with a crime if it can be proven beyond reasonable doubt that they have committed the crime.
* '''[[Standard Operating Procedure#Hearing Procedure|Hearing Procedure]]:''' A criminal hearing’s Judge must be, in sequential order: The Chief Justice, the Court Clerk, an impartial member of Justice or Station Command, or the Warden. The Prosecution should ideally be the Prosecutor, or an available member of Station Command or Station Security in that order. The Defense should ideally be a member of the Justice department assigned to the defendant, though they are allowed to represent themselves if they wish.
* '''[[Alert Procedure]]:''' Codes Blue, Red, Gamma, and Delta authorize Security to carry progressively more advanced equipment, and loosen '''[[Standard Operating Procedure#Rules of Engagement|Rules of Engagement]]''' regarding their use. The former two codes discourage openly carrying firearms by personnel; the latter two suspend many rights to privacy, and grant Security the power to do whatever neccessary to retake or scuttle the station.
==Procedural Defense==
If a defendant is accused of a crime, they are granted the privilege to challenge the legitimacy of charges of claims brought against them. Any of the following may be invoked:
:*Double Jeopardy Clause: accused persons may are not to be charged on the same charges following an acquittal or conviction, except in the case of acquittal and subsequent credible admission of guilt.
:*Entrapment Clause: accused persons found to be induced or coerced into the commission of the crimes listed in the charges are to be acquitted.
:*Exclusionary Clause: evidence collected and/or analyzed in violation of accused persons’ rights under law is inadmissible.
=== Crime Codes Quick Reference ===
Use this to quickly find the Crime Code Numbers.
Crime codes are organized into four Category Codes (_XX) as listed below, with the most severe crimes being given the highest Severity Codes (x__). The sentencing officer or Judge should use discretion, and not charge an entity with two similar crimes for a singular offense.
For example, an attack upon an Employee which causes them to die of their injuries is Murder, not Assault AND Murder. Likewise, an entity should not be charged for both Possession AND Grand Possession on a singular Controlled Item held without proper authorization.
{| style='text-align:center; border:solid 1px black' cellspacing=0
! style="border: 1px solid black; width:5%" | Code
! style='background-color:#999933BF; border: 1px solid black; width:23.75%' | 1XX - Misdemeanor
! style='background-color:#996633BF; border: 1px solid black; width:23.75%' | 2XX - Felony
! style='background-color:#993333BF; border: 1px solid black; width:23.75%' | 3XX- Grand Felony
! style="background-color:#990033BF; border: 1px solid black; width:23.75%" | 4XX - Capital
|-
!style="border: 1px solid black;" | 01
|style='background:#9999334D; border: 1px solid black;' | Animal Cruelty
|style='background:#9966334D; border: 1px solid black;' | Assault
|style='background:#9933334D; border: 1px solid black;' | Manslaughter
| style="background:#9900334D; border: 1px solid black;" | Murder
|-
!style="border: 1px solid black;" | 02
|style='background:#9999334D; border: 1px solid black;' | Harassment
|style='background:#9966334D; border: 1px solid black;' | Breaking and Entering
|style='background:#9933334D; border: 1px solid black;'| Kidnapping
| style="background:#9900334D; border: 1px solid black;" | Terrorism
|-
!style="border: 1px solid black;" | 03
|style='background:#9999334D; border: 1px solid black;' | Vigilantism
|style='background:#9966334D; border: 1px solid black;' | Rioting
|style='background:#9933334D; border: 1px solid black;' | Grand Possession
| style="background:#9900334D; border: 1px solid black;" | Grand Sabotage
|-
!style="border: 1px solid black;" | 04
|style='background:#9999334D; border: 1px solid black;' | Petty Larceny
|style='background:#9966334D; border: 1px solid black;' | Endangerment
|style='background:#9933334D; border: 1px solid black' | Noöspheric Tampering
| style="background:#9900334D; border: 1px solid black;" | Prevention of Revival
|-
!style="border: 1px solid black;" | 05
|style='background:#9999334D; border: 1px solid black;' | Trespass
|style='background:#9966334D; border: 1px solid black;' | Possession
|style='background:#9933334D; border: 1px solid black;' | Sabotage
| style="background:#9900334D; border: 1px solid black;" | Sedition
|-
!style="border: 1px solid black;" | 06
|style='background:#9999334D; border: 1px solid black;' | Vandalism
|style='background:#9966334D; border: 1px solid black;' | Obstruction of Justice
|style='background:#9933334D; border: 1px solid black;' | Abuse of Power
| style="background:#9900334D; border: 1px solid black;" | Breach of Custody
|-
!style="border: 1px solid black;" | 07
|style='background:#9999334D; border: 1px solid black;' | Hooliganism
|style='background:#9966334D; border: 1px solid black;' | Perjury
|style='background:#9933334D; border: 1px solid black;' | Grand Larceny
| style="background:#9900334D; border: 1px solid black;" |
|-
!style="border: 1px solid black;" | 08
|style='background:#9999334D; border: 1px solid black;' |
|style='background:#9966334D; border: 1px solid black;' | Contempt of Court
|style='background:#9933334D; border: 1px solid black;' | Black Marketeering
| style="background:#9900334D; border: 1px solid black;" |
|-
!style="border: 1px solid black;" | 09
|style='background:#9999334D; border: 1px solid black;' |
|style='background:#9966334D; border: 1px solid black;' | Identity Theft
|style="background:#9933334D; border: 1px solid black;" |
| style="background:#9900334D; border: 1px solid black;" |
|}
== Sentences ==
=== Capital ===
Capital Sentences are organized in order of severity, with the least severe last. Execution should be utilized as an '''absolute last resort,''' for criminals whose crimes are extraordinarily cruel, or who prove impossible to confine through less restrictive means.
* '''Execution:''' Termination of the convict's life.
** Whenever any execution is performed, outside of Martial Law, a general announcement must be made stating who was executed, and for what reason.
** The convict may either face execution by firing squad, by lethal electrocution, by being ejected from the station via an airlock (being "spaced"), or by a method of their choosing, at the Judge's sole discretion.
** If recoverable, their remains must either be cremated, processed via the biomass reclaimer, or stored in the morgue.
* '''Exfiltration:''' Immediate retrieval of the convict via a CentComm or GALPOL prison transport.
** The prisoner may be held within the permanent brig until the transport arrives.
* '''Extended Confinement:''' Placing the convict into the Permanent Brig until the end of the shift, where they are to be transported to Central Command for processing.
** The convict has the same rights as any other prisoner within the Permanent Brig, whether they arrived by transport or were sentenced to Extended Confinement.
:<!-- -->
=== Standard ===
:*'''Brigging''': Placing the convict into a temporary holding cell.
<!-- -->
:*'''Catch & Release''': Taking the convict to Security, processing them, searching them, and then releasing them.
<!-- -->
:*'''Demotion''': Downgrading the convict’s employment contract and revoking their access accordingly, with permission from or when ordered by the convict’s head of department. May be applied when the convict has committed a crime relating to their employment within their department or with NanoTrasen as a whole.
<!-- -->
:*'''Licence restriction''': Downgrading or suspending the convict’s armament licence and confiscating possessions accordingly. May be applied when the convict is guilty of possession or a crime involving a weapon they were lawfully permitted to carry.
<!-- -->
:*'''Mindwiping''': Removal of the convict’s noöspheric or other otherworldy connections by use of mindbreaker toxin, ardent censer, or any other means. May be applied when the convict is guilty of a crime in which they use psionics, otherworldy abilities, or similar powers.
== Crime Modifiers ==
Sentencing modifiers are to be applied by the sentencing officer, judge, or arbiter. Aggravating modifiers may not extend a sentence beyond the specified limit, while extenuating modifiers may not reduce a sentence below the specified limit.
=== Aggravating ===
:*'''Solicitation'''
:**Any entity found guilty of encouraging, bribing, requesting, or instructing someone to commit a crime, with the intent that the entity being solicited commit the crime, will receive for the crimes they solicited in full.
:*'''Accessory'''
:**Any entity found guilty of assisting in the commission of a crime, without participating in the crime itself, knowing that a crime will be, has been, or is being committed, will receive the sentence for the crimes in which they took part in full.
:*'''Attempts'''
:**Any entity found guilty of attempting to commit a crime, with the intent to carry out that crime, will receive the sentence for the crimes they attempted in full.
:*'''Conspiracy'''
:**Any entity found guilty of planning to commit a crime, with the intent to carry out that crime eventually, and having committed an overt act pursuant to that end, will receive the sentence for the crimes they conspired towards in full.
:*'''Deadly weapon'''
:**Any entity found guilty of committing a crime, utilising any object used offensively for the purpose of inflicting mortal or great bodily harm, will receive an additional modifier of, at most, +50% of the original sentence.
:*'''Aggravation'''
:**Any entity found guilty of committing a crime with reckless abandon, evident signs of lack of regret or remorse, or blatant disregard for potential collateral damage, will receive an additional modifier of, at most, +50% of the original sentence.
:*'''Recidivism'''
:**Any entity convicted of the same criminal charge multiple times within one shift will receive an additional modifier of, at most, +100% of the original sentence. '''OOC NOTE: If a character is sexually harassing another character, or being bigoted, in a way that breaks Server Rules A1 or A2, you are allowed to summarily execute them. Please contact admins via AHelp if such an incident occurs.'''
=== Extenuating ===
:*'''Cooperation'''
:**Any entity who shares actionable intelligence with the authorities, turns oneself in, testifies against accomplices, confesses to their crime, or otherwise aids the prosecution, may have their sentence reduced to no less than 50% of the original sentence.
:*'''Dire Emergency'''
:**Any entity found to have committed a crime in the process of absolutely necessary and reasonable actions, taken with the intent to resolve or mitigate the effects of a dire emergency on one’s vessel or habitat, may have their sentence reduced by any amount, or completely waived.
:*'''Self-Defence'''
:**Any entity found to have committed a crime or crimes in protection of oneself or another, and only to the necessary extent to avert a direct and imminent threat to the health and life of innocent persons, may have their sentence reduced by any amount, or completely waived.
:*'''Good Behavior'''
:**Any convict who peacefully complies with the authorities, displays clear signs of remorse concerning the committed crime, and/or conducts themselves peacefully while imprisoned, may be paroled as outlined in the Standard Operating Procedures.
== Crimes ==
=== Capital Crimes ===
{| style="text-align:center; background-color:#9900334D; width: 95%; border: 1px solid black;" cellspacing=0
! style="background-color:#990033BF; width:4%; border: 1px solid black;" |Code
! style="background-color:#990033BF; width:0.1%; border: 1px solid black;" |
! style="background-color:#990033BF; width:20%; border: 1px solid black;" |Name
! style="background-color:#990033BF; width:12.5%; border: 1px solid black;" |Time
! style="background-color:#990033BF; width:62.5%; border: 1px solid black;" |Description
|-
| style="border: 1px solid black;" | 401
| style="border: 1px solid black;" | [[File:SL_Murder.png]]
! style="border: 1px solid black;" | Murder
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To maliciously commit an act resulting in the death of an Employee, with reckless indifference to their life.
|-
| style="border: 1px solid black;" | 402
| style="border: 1px solid black;" | [[File:SL_Terrorism.png]]
! style="border: 1px solid black;" | '''Terrorism'''
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To maliciously commit an act or take hostages with reckless indifference to life in order to provoke a state of terror in, intimidate, or otherwise compel an Employee to act or abstain from acting.
|-
| style="border: 1px solid black;" | 403
| style="border: 1px solid black;" | [[File:SL_GrandSabotage.png]]
! style="border: 1px solid black;" | Grand Sabotage
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To maliciously commit an act that, directly or indirectly, renders a vessel or its part uninhabitable or inoperable; or, to commit an act that causes irreversible damage to High Security Areas and/or systems of a vessel.
|-
| style="border: 1px solid black;" | 404
| style="border: 1px solid black;" | [[File:SL_Decorporealisation.png]]
! style="border: 1px solid black;" | '''Prevention of Revival'''
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To commit an act that prevents the revival of an Employee by standard medical care following their death.
|-
| style="border: 1px solid black;" | 405
| style="border: 1px solid black;" | [[File:SL_Sedition.png]]
! style="border: 1px solid black;" | Sedition
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To act to overthrow a lawfully established Chain of Command or governing body without lawful or legitimate cause.
|-
| style="border: 1px solid black;" | 406
| style="border: 1px solid black;" | WIP
! style="border: 1px solid black;" | Breach of Custody
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To escape the custody of law enforcement while serving a legal sentence, or awaiting trial for a capital offense.
|}
----
=== Grand Felonies ===
{| style="text-align:center; background-color:#9933334D; width: 95%; border: 1px solid black;" cellspacing=0
! style="background-color:#993333BF; width:4%; border: 1px solid black;" | Code
! style="background-color:#993333BF; width:0.1%; border: 1px solid black;" |
! style="background-color:#993333BF; width:20%; border: 1px solid black;" | Name
! style="background-color:#993333BF; width:12.5%; border: 1px solid black;" | Time
! style="background-color:#993333BF; width:62.5%; border: 1px solid black;" | Description
|-
|style="border:1px solid black;" | 301
|style= "border:1px solid black;"| [[File:SL_Manslaughter.png]]
|style= "border:1px solid black;"| Manslaughter
|style= "border:1px solid black;"| 10 minutes
|style= "border:1px solid black;"| To act without malice in a manner which, directly or indirectly, leads to the death of an Employee.
|-
|style= "border:1px solid black;"| 302
|style= "border:1px solid black;"| [[File:SL_Kidnapping.png]]
|style= "border:1px solid black;"| Kidnapping
|style= "border:1px solid black;"| 10 minutes
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an Employee against their will.
|-
| style="border:1px solid black;" | 303
| style="border:1px solid black;" | WIP
| style="border:1px solid black;" | Grand Possession
| style="border:1px solid black;" | 12-20 minutes
| style="border:1px solid black;" | To be in the possession of a Category C, D, or E Controlled Armament.
|-
| style="border:1px solid black;" | 304
| style="border:1px solid black;" | [[File:SL_Mindbreaking.png]]
| style="border:1px solid black;" | Noöspheric Tampering
| style="border:1px solid black;" | 8 minutes
| style="border:1px solid black;" | To maliciously tamper with or create noöspheric anomalies, entities, aberrations, or other elements; Or to otherwise deliberately perform actions that would raise Glimmer or other anomalous reading levels in a way that threatens normative reality.
|-
| style="border:1px solid black;" | 305
| style="border:1px solid black;" | [[File:SL_Sabotage.png]]
| style="border:1px solid black;" | Sabotage
| style="border:1px solid black;" | 8 minutes
| style="border:1px solid black;" | To maliciously commit an act that, directly or indirectly, hinders the operation of a vessel of its part; or, to commit an act that modifies and/or damages technology or equipment one is not authorized to access.
|-
| style="border:1px solid black;" | 306
| style="border:1px solid black;" | [[File:SL_AbuseOfPower.png]]
| style="border:1px solid black;" | Abuse of Power
| style="border:1px solid black;" | 10 minutes
| style="border:1px solid black;" | To intentionally misuse or wrongfully exercise one’s own authority, influence, or control, resulting in harm, unjust treatment, or demonstratable loss to another Employee.
|-
| style="border:1px solid black;" | 307
| style="border:1px solid black;" | [[File:SL_GrandTheft.png]]
| style="border:1px solid black;" | Grand Larceny
| style="border:1px solid black;" | 8 minutes
| style="border:1px solid black;" | To deprive an Employee of any Controlled Item in their lawful possession.
|-
| style="border:1px solid black;" | 308
| style="border:1px solid black;" | [[File:SL_BlackMarketeering.png]]
| style="border:1px solid black;" | Black Marketeering
| style="border:1px solid black;" | 8 minutes
| style="border:1px solid black;" | To sell, distribute, or otherwise circulate Controlled Items to unauthorized entities.
|}
----
=== Felonies ===
{| style="text-align:center; background-color:#9966334D; width: 95%; border: 1px solid black;" cellspacing="0"
! style="background-color:#996633BF; width:4%; border: 1px solid black;" |Code
! style="background-color:#996633BF; width:0.1%; border: 1px solid black;" |
! style="background-color:#996633BF; width:20%; border: 1px solid black;" |Name
! style="background-color:#996633BF; width:12.5%; border: 1px solid black;" |Time
! style="background-color:#996633BF; width:62.5%; border: 1px solid black;" |Description
|-
| style="border: 1px solid black;" | 201
| style="border: 1px solid black;" | [[File:SL_Assault.png]]
! style="border: 1px solid black;" | {{anchor|Assault}}Assault
| style="border: 1px solid black;" | 5 minutes
| style="border: 1px solid black;" | To cause physical harm or to effect unwanted physical contact on an Employee, without the apparent intent to kill them, or to threaten such actions with both capability and intent to do so.
|-
| style="border: 1px solid black;" | 202
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
| style="border: 1px solid black;" | 5 minutes
| style="border: 1px solid black;" | To break and enter into an area where one is not authorised nor invited.
|-
| style="border: 1px solid black;" | 203
| style="border: 1px solid black;" | [[File:SL_Rioting.png]]
! style="border: 1px solid black;" | {{anchor|Rioting}}Rioting
| style="border: 1px solid black;" | 4 minutes
| style="border: 1px solid black;" | To partake in an unauthorised riotous, tumultuous, and disruptive public assembly that refuses to disperse after warning.
|-
| style="border: 1px solid black;" | 204
| style="border: 1px solid black;" | [[File:SL_Endangerment.png]]
! style="border: 1px solid black;" | {{anchor|Endangerment}}Endangerment
| style="border: 1px solid black;" | 6 minutes
| style="border: 1px solid black;" | To recklessly abandon obligations involving the continued wellbeing and/or protection of life and property, through malpractice, action, or inaction.
|-
| style="border: 1px solid black;" | 205
| style="border: 1px solid black;" | [[File:SL_Possession.png]]
! style="border: 1px solid black;" | {{anchor|Possession}}Possession
| style="border: 1px solid black;" | 3-8 minutes
| style="border: 1px solid black;" | To be in unauthorised possession of restricted items or items of particular danger. See [[Standard_Operating_Procedure#Controlled_Substances/Items|Controlled Substances/Items]], [[Standard_Operating_Procedure#Controlled_Armaments|Controlled Armaments]].
|-
| style="border: 1px solid black;" | 206
| style="border: 1px solid black;" | [[File:SL_ObstructionOfJustice.png]]
! style="border: 1px solid black;" | {{anchor|Obstruction of Justice}}Obstruction of Justice
| style="border: 1px solid black;" | 4 minutes
| style="border: 1px solid black;" | To wilfully disobey, interfere with, or refuse a decree of the court, warrant, or arrest.
|-
| style="border: 1px solid black;" | 207
| style="border: 1px solid black;" | [[File:SL_PerjuryOrFalseReport.png]]
! style="border: 1px solid black;" | {{anchor|Perjury or False Report}}Perjury or False Report
| style="border: 1px solid black;" | 4 minutes
| style="border: 1px solid black;" | To wilfully and maliciously tell an untruth either in court or in the process of making an actionable report to law enforcement.
|-
| style="border: 1px solid black;" | 208
| style="border: 1px solid black;" | [[File:SL_ContemptOfCourt.png]]
! style="border: 1px solid black;" | {{anchor|Contempt of Court}}Contempt of Court
| style="border: 1px solid black;" | 4 minutes
| style="border: 1px solid black;" | To conduct oneself disruptively and disrespectfully before the court.
|-
|style= "border:1px solid black;"| 209
|style= "border:1px solid black;"| WIP
!style= "border:1px solid black;"| Identity Theft
|style= "border:1px solid black;"| 5 minutes
|style= "border:1px solid black;"| To maliciously assume the identity of an Employee, such as through writing, attire, and/or speech.
|}
----
=== Misdemeanors ===
{| style="text-align:center; background-color:#9999334D; width: 95%; border: 1px solid black;" cellspacing=0
! style="background-color:#999933BF; width:4%; border: 1px solid black;" |Code
! style="background-color:#999933BF; width:0.1%; border: 1px solid black;" |
! style="background-color:#999933BF; width:20%; border: 1px solid black;" |Name
! style="background-color:#999933BF; width:12.5%; border: 1px solid black;" |Time
! style="background-color:#999933BF; width:62.5%; border: 1px solid black;" |Description
|-
| style="border: 1px solid black;" | 101
| style="border: 1px solid black;" | [[File:SL_AnimalCruelty.png]]
! style="border: 1px solid black;" | {{anchor|Animal Cruelty}}Animal Cruelty
| style="border: 1px solid black;" | 2 minutes
| style="border: 1px solid black;" | To inflict unnecessary suffering on a Pet with malicious intent.
|-
|style= "border:1px solid black;"| 102
|style= "border:1px solid black;"| WIP
!style= "border:1px solid black;"| Harassment
|style= "border:1px solid black;"| 3 minutes
|style= "border:1px solid black;"| To verbally demean, humiliate, or harass an Employee with malice; or maliciously spread false information amongst a population.
|-
|style= "border:1px solid black;"| 103
|style= "border:1px solid black;"| WIP
!style= "border:1px solid black;"| Vigilantism
|style= "border:1px solid black;"| 4 minutes
|style= "border:1px solid black;"| To perform the responsibility and duty of a law enforcement official without approval or due cause to act.
|-
| style="border: 1px solid black;" | 104
| style="border: 1px solid black;" | [[File:SL_Theft.png]]
! style="border: 1px solid black;" | {{anchor|Theft}}Petty Larceny
| style="border: 1px solid black;" | 2 minutes
| style="border: 1px solid black;" | To deprive an Employee of an item in their lawful property without consent.
|-
| style="border: 1px solid black;" | 105
| style="border: 1px solid black;" | [[File:SL_Trespass.png]]
! style="border: 1px solid black;" | {{anchor|Trespass}}Trespass
| style="border: 1px solid black;" | 2 minutes
| style="border: 1px solid black;" | To enter into an area where one is not authorised nor invited.
|-
| style="border: 1px solid black;" | 106
| style="border: 1px solid black;" | [[File:SL_Vandalism.png]]
! style="border: 1px solid black;" | {{anchor|Vandalism}}Vandalism
| style="border: 1px solid black;" | 2 minutes
| style="border: 1px solid black;" | To deface or superficially damage public property, or property belonging to another Employee.
|-
| style="border: 1px solid black;" | 107
| style="border: 1px solid black;" | [[File:SL_Hooliganism.png]]
! style="border: 1px solid black;" | {{anchor|Hooliganism}}Hooliganism
| style="border: 1px solid black;" | As necessary
| style="border: 1px solid black;" | To intentionally and maliciously engage in disruptive conduct, where one refuses to cease.
|}
{{Guides Menu}}
@@ -1,428 +0,0 @@
This page covers the standardized guidelines and procedures aboard the station, but are not written in law; be aware that in cases of dire emergency the commanding officer or certain individuals are authorized to temporarily alter current SOP as they see fit, at the risk of criminal penalties in the case of misuse or abuse! Make sure to look at [[Alert Procedure]] for guidelines that change based on the current alert level.
=General Regulations=
== '''Definitions''' ==
The terms specified below are defined for further use within the procedures.
:*Captain: The officer legally appointed by NanoTrasen as Commanding Officer of the station. This title is only transferable by Central Command.
:*Commanding Officer / CO: An officer appointed to lead the station and its Command department, whether it be the Captain by NanoTrasen, or an Acting CO by Station Command.
:* Command Staff/Station Command: Members of the Command department who lead a department aboard the station.
:* Crew: All Employees employed by Station Command, or by NT to reside on the station and perform an established function therein. This includes any Employee listed on the Crew Manifest or issued a functional NT identification and access card. Individuals imprisoned by NT, along with those imprisoned during the course of a shift for violations of Space Law, are considered Crew.
:* Judge: An individual legally appointed to preside over a hearing, as specified within Hearing Procedure.
:* Arrestee: Any Employee who has been formally charged with a crime.
:* Prisoner: Any Employee who has been sentenced for the commission of a crime, or awaiting a hearing for a capital offense.
:* Permanent Brig/Permabrig/Brig: A secure location for the long-term confinement of Prisoners. Typically outfitted to sustain the basic survival needs of the Prisoners within.
==Right to Refuse Service==
All crew operating within their designated job locations are given the right to refuse service at any time for disorderly or abusive conduct. Additionally, all crew members are permitted to use nonlethal force to remove noncompliant or disorderly individuals only when such persons pose a threat to the safety of staff members, or disrupt the orderly functioning of the station. Service may not be refused to first responders or damage-control personnel acting to address or mitigate an emergency or crisis situation, unless such conduct is egregiously disorderly or abusive.
= Entity Definitions =
The following definitions are provided to clarify the legal protections- and obligations- that Standard Operating Procedures and Space Law provide to any given entity. Unless explicitly covered by a more distinctive term, all Entities are to be treated as such until clarification is provided.
== Entity ==
Any individual with a distinct existence. Entities may also encompass a plurality of identities contained within a single physical form. NanoTrasen does not require Entities be offered the protections of Standard Operating Procedure, Space Law, or Rules of Engagement unless further defined below. Security Officers are encouraged to be reasonable with their use of force regarding circumstances, especially if the Entity's termination would leave lawsuits or tarnish reputation for NanoTrasen.
== Pet ==
Any Entity that is provided to NanoTrasen Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support. Any harm against such a Pet may render the offender liable to the crime of Animal Cruelty.
Examples of Pets include, but are not limited to:
:* Renault the Fox
:* Smile the Rainbow Slime
:* Laika the Guard Dog
:* Silvia the Medical Cobra
:* a corgi, purchased by Station Logistics
:* hand-held "personal AI" devices
== Synthetic ==
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage.
Examples of Synthetics include, but are not limited to:
:* Crew-constructed Cyborgs
:* The Station AI
:* Positronic Brains
== Employee ==
Any Entity who has a contract with NanoTrasen.
Typically, these individuals possess all of the following:
:* A communications headset (or similar communications capability) outfitted with encryption keys relevant to their assigned job function.
:* An ID card with their name and job function, pre-programmed with door access relevant to aforementioned job function.
:* Company-approved attire for an individual of their job function.
In situations where an individual lacks one or more of these items, law enforcement officials are encouraged to use their best judgement, and mitigate any risk of liability to charges of Endangerment or Abuse of Power.
Harm or death of an Employee may leave the offender liable to the crimes of Assault, Manslaughter, or even Murder.
Examples of Employees include, but are not limited to:
:* All crew present on the Space Station, including those who are removed from cryogenic stasis during the course of a shift.
:* Any crewmate arriving from a different NanoTrasen Space Station, due to navigation errors, disasters, or similar.
:* Individuals imprisoned by NT, along with those imprisoned during the course of a shift for violations of Space Law.
:* Any and all members of NanoTrasen Central Command, including Emergency Response Team soldiers and Central Commanders.
In cases where there is reasonable doubt as to the identity of a member of Central Command, the Commanding Officer is encouraged to request clarity from Central Command themselves.
Any Employees that are fired by Station Command are still considered Employees until Central Command can confirm their contract termination.
=== Provisional Employees ===
Provisional Employees are a special subcategory of Employee. During the course of a shift, events may lead to an Entity, or Entities, arriving aboard the station who wish to be employed by NanoTrasen. The Head of Personnel, alongside any relevant departmental head, may accept the Entity into NanoTrasen's employ provisionally.
In such a case, the Head of Personnel must create a document listing the Provisional Employee's name and assigned job function, along with any physical features deemed necessary to identify them. This must be signed and stamped (where applicable) by the Head of Personnel, their relevant departmental head, and the Provisional Employee. A copy must be kept within the Head of Personnel's records, and with the Provisional Employee if possible.
Provisional Employees serve "at will", and may have their employment revoked at any time by Central or Station Command authorities.
=Controlled Items, Substances, & Areas=
== Controlled Items ==
Controlled Items include any item that is restricted from general possession and use. Additionally, any equipment or technology providing insight into the development, production, or procurement of a controlled item is also controlled in and of itself. A list of such items follows:
* Any Controlled Substances, as listed below.
* Any Controlled Armaments, as listed below.
* Electromagnetic pulsing and/or disturbance technology.
* Cryptographic systems, software, or technology.
* Nuclear materials, weapons, and technology.
* Long-range telecommunications and broadcasting technology.
* Astronavigational guidance equipment or technology.
* Telecommunication or signal jamming technology.
* Telecommunications encryption technology.
* Hypodermic spray tools and injectors.
* Cloaking, chameleon, and IFF-inhibiting technology.
* Technology utilizing bluespace as an integral part of its function.
* Teleportation and portal technology.
* Hydraulic or powered equipment designed for the electronic or mechanical bypass of secure-access systems.
* Rapid construction/piping technology
Any individual requiring any above item in the course of their job, or for regular function, may request a document of authorization from the relevant departmental head. Such a document must have an itemized list of the approved Controlled Items, the name and position of the holder, and a signed and stamped statement by the authorizing party.
Should the holder commit crimes through the use of such authorized items, the authorizing party may be found guilty of Accessory to said crimes, in addition to Endangerment and Abuse of Power where applicable. The Commanding Officer is also authorized to supervise the approval and revocation of documentation as they see fit.
== Controlled Substances ==
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a prescription or special dispensation from the Chief Medical Officer. Medical personnel are also permitted to [[Standard Operating Procedure#Prescribing Medication|prescribe medication]] for chronic illnesses or mental health. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
A list of such Substances follow:
* Mindbreaker Toxin and other psionically altering agents.
* Romerol, and other rapid acting biological or chemical agents.
* Desoxyephedrine, and similar stimulants.
* Nocturine, and similar sedatives.
* Phlogiston, and other explosive and/or pyrotechnic compounds.
* Amatoxin, and other toxic and/or poisonous compounds.
* Blissifylovene, Soretizone, Tranquinase, and all similar psychoactives used to aid in mental wellbeing.
== Controlled Armaments ==
Controlled Armaments are grouped into categories of increasing severity. If the offender possesses several categories of contraband, only the highest severity of said items should be charged.
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 3min brig and further legal action as the situation demands.''
* Any object or instrument specifically designed, adapted, or used to cause physical harm in close combat
* Any object or equipment specifically designed or adapted to protect against physical harm caused by weapons, projectiles, or other forms of direct assault
* Any object or instrument specifically designed or adapted to incapacitate or disorient by using light or sound without causing permanent injury
* Any object or equipment specifically designed or adapted for explicitly training purposes that would otherwise fall under another category
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 8min brig and further legal action as the situation demands.''
* Any object or instrument specifically designed or adapted to cause physical harm in close combat, which employs a powered system to enhance performance
* Any manually-operated ballistic arms that possess an internal magazine or feeding system, or lack an internal magazine or feeding system altogether
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 12min brig and further legal action as the situation demands.''
* Any manually-operated ballistic arms that use an external magazine or feeding system
* Any semi-automatic ballistic arms not easily concealable within a pocket or coat
* Any object or instrument specifically designed or adapted for explicitly less-lethal purposes that would otherwise fall under another category
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 15min brig and further legal action as the situation demands.''
* Any semi-automatic ballistic arms that are easily concealable
* Any fully-automatic ballistic arms
* Any laser, plasma, or energy based arms, including ones designed, intended, or used for melee combat.
* Any object or device specifically designed or adapted to cause physical harm through detonation of an explosive charge
* Any object or equipment specifically designed or adapted to protect against both a space environment and physical harm caused by weapons, projectiles, or other forms of direct assault
'''Category E:''' ''Illegal possession of Cat. E controlled armaments may incur a sentence of up to 20min brig and further legal action as the situation demands.''
* Any crew-served or emplaced weapon
* Any object or instrument specifically designed or adapted to launch large-calibre explosives or projectiles for offensive or defensive purposes
* Any object or instrument specifically designed or adapted to employ chemical, biological, esoteric, radiological, or nuclear hazards for offensive or defensive purposes
Armaments licenses may be issued by the Commanding Officer, Warden, or Head of Security. The license must include the name of the holder, a list of allowed armaments, and a stamped signature from the issuer.
If the holder uses their armaments in the commission of a crime, the issuer may be held liable for accessory to the crimes committed, along with Endangerment and Abuse of Power where applicable.
== Departmental Dispensations ==
Certain personnel may require certain Controlled Items in the course of their job, and as such must be provided exceptions. '''Any member of any department which begins their shift with a controlled item in their possession, within their allocated area, or with a departmental lathe capable of producing said controlled item, is assumed authorized for its possession and use.'''
Additionally, tools and equipment located within emergency lockers are not considered Controlled Items, so long as they are used as intended.
===Logistics===
Members of a station’s Logistics department are permitted to approve any order with the direct permission of an individual with legal access to the contents of that order. Additionally, they may deny any order, at any time, for lack of funds, authority, or any other reason deemed sufficient by their direct superior.
Unlocked crates, orders stored outside the department, and supplies illegally distributed to unauthorized personnel carry the risk of Endangerment of even Black Marketeering charges. Crates of Controlled Items, Substances, or Armaments should be stored securely within the department, with a copy of their invoices on file.
=== Research ===
Epistemics employees are permitted to use their lab spaces as they see fit, with the following caveats:
* Any Employee experimentation must be done with the consent of the test subject, and must not have an undue risk of life. The Mystagogue or CO may terminate any experiment for safety or ethical purposes and at their discretion.
* Testing of any explosives or weapons that risk hull penetration or mass destruction must be done at an off-station site.
Epistemics personnel may also utilise Controlled Items within the confines of designated testing areas and in a manner directly approved jointly by the Mystagogue and by the Head of Security or CO.
Utilizing or storing Controlled items without direct approval, utilizing Controlled Items outside of the testing areas, and distributing Controlled Items to crew constitute criminal offences under Space Law and may additionally incur demotion as punishment.
=== Medical ===
All medical personnel may carry or use Controlled Substances, along with hypodermic tools, in the course of their job functions and in a manner approved by the CMO or CO, with the following caveats:
* Any Controlled Substance with the capability to cause rapid and grievous bodily harm or death may not be carried outside of the chemicals lab or treatment areas without a signed and stamped document of authorization.
* Any Controlled Substance with the capacity to cause harm as a consequence of overdose may not be stocked, stored, or displayed in any area or fashion that leaves such substances open and accessible to the public.
* Any hypodermic tool must be kept secure within locked storage or on the person of an authorized individual.
==High Security Areas==
High Security Areas, or HSAs, should be restricted, and, during standard station operations, secured where applicable or per alert level. They differ from regular secure areas in that they may contain items, technology, or other assets of high importance or value, and must therefore be held safe. Unauthorized persons within HSAs may be guilty of trespass, or of breaking and entering.
Common HSAs for commercial and military vessels are as follows:
* Bridge
* Armory\Gun Deck
* Vault\Strongroom
* Station AI Rooms (Upload, Core, etc.)
* Gravity Generation\Life Support\Engine Rooms
* Telecommunications\Telemetry
* Permanent Brig\Holding Cells
=Commanding Officer=
==Captain’s Authority==
The Commanding Officer of a station or vessel is afforded several rights, broadly referred to as "Captain's Authority".
While they are formally and legally appointed CO, they are the ultimate authority aboard a vessel until they are directly dismissed by a superior from Central Command.
Additionally, they have the authority to pardon and/or parole any individuals accused and sentenced of Grand Felony, Felony, and Misdemeanor-level sentences if deemed in the best interest of the crew and vessel. The CO may be held liable for accessory to any future criminal acts performed by the pardoned individual, and they must make an announcement to the station stating who was pardoned, for what crimes, and for what reason.
Captain’s Authority carries a responsibility, however; the CO must do everything in their power to protect all entities and assets aboard the station with which they have been entrusted, and must not abandon them while there is a reasonable hope that they may be saved.
Additionally, the legally appointed Captain of a vessel is afforded two additional rights over that of a CO:
* To, with public announcement to the crew, temporarily modify SOP in times of emergency, at the risk of criminal penalties if misused or abused, and
* To deputize members of the crew to carry out law enforcement activity. These deputies are bound by Security Regulations and Space Law, and the Captain may suffer criminal penalties for any crime committed by them.
==Line of Succession==
It is the responsibility of Command to ensure that a station retains a CO immediately at the start of the shift and thereafter. If the CO dies and is unrecoverable, or if a CO is not assigned at the start of the shift, an Acting CO must be selected forthwith per the following guide:
# All active members of Station Command must meet, and choose one of their number to become the Acting CO.
# If no Station Command is alive or on active duty, then each Department must nominate a temporary Delegate. These Delegates must meet and choose an Acting CO from among their number.
# The Acting CO must then duly inform Central Command via fax and make a stationwide announcement.
# Await the end of your shift.
After Central Command has received the results of the Line of Succession, the Acting CO is afforded all prescribed powers of a CO. An access biscuit ''may or may not'' be sent by Centcomm to assist with this. When a legally appointed Captain arrives, it is the express duty of the Acting CO to hand over any and all Captain-related equipment and accesses to the new CO, and to resign themselves to their original role before their temporary promotion.
==Command Promotions==
The CO of the vessel is authorized to promote Crew into Station Command positions if it is determined that the vacancy is a detriment to station productivity. The CO has a duty to ensure that the Crew they promote are of a reasonable level of skill, and can be held liable if they disregard this duty.
Crew members promoted into Station Command positions are considered to be acting members of Station Command, with all rights and responsibilities therein. When NT-appointed Station Command arrive, the acting Command members are required to hand over any specialized equipment (Command ID cards, attire, or tools) and resign themselves to their original position from before their temporary promotion.
==Mutiny==
Should such a situation arise in which the CO is unfit for their position and declines relinquishment of position and power, and where all alternative legal options have been exhausted, officers may be permitted to lead a mutiny. Officers leading a mutiny must inform Central Command of this occurrence before the mutiny has begun.
A mutiny, defined as a coordinated effort to depose the commanding officer or officers of a vessel for committing unlawful acts, requires the crew to neutralize the offending staff with non-lethal force where applicable, and lethal measures and weapons only in acts of self-preservation or when given no other option. The commanding officer or officers, as well as their loyalists, are all bound to Space Law, the current alert level and its corresponding RoE.
Should the mutiny be unsuccessful, only the head officer(s) who approved the mutiny may be charged with sedition and receive a hearing, while all mutineers may be demoted, processed by law enforcement, and charged for the crimes they have committed.
Should the mutiny be successful, the head officer(s) who approved the mutiny must select a new Commanding Officer per Line of Succession. The new CO will then sentence the previous heads of staff for their crimes per Space Law.
== Foreign Vessels ==
Should civilian and military vessels receive a docking request from another neutral vessel, they may approve or deny such a request. Any vessel may, with consent from the docking entity, be searched by law enforcement upon arrival, and its crew granted free movement accompanied by an escort at the discretion of the CO or law enforcement officials. Crew of vessels legally docked on NanoTrasen Stations are considered to be in contract with NanoTrasen and as such are protected by Space Law, Standard operating Procedure and the Rules of Engagement.
Crew of foreign vessels may be refused entry, at any time and for any reason, and must be returned to, or be escorted to, their own vessel where applicable. Failure to comply may constitute trespass. Docking vessels bearing a warrant or commission represent an exception, and may not be denied entry or be forced to submit to a search.
Crimes committed against the station or its inhabitants, on station or abroad, are grounds to charge the perpetrator according to the laws of the station. Should visiting entities break the law, or be suspected of doing so, and flee aboard their own vessel, law enforcement personnel are authorized to board the vessel in continuous “hot” pursuit and are fully authorized to effect an arrest upon the relevant party.
=Security and Justice Regulations=
==Security Jurisdiction==
Law enforcement personnel are bound by trespass and assault laws, and thus cannot operate outside accessible areas, or perform searches without a Probable Cause, or a [[Standard Operating Procedure#Warrants|warrant]]. Advanced surgical procedures and cursory implant investigations must be performed by a trained member of the Medical department, operating under their [[Standard Operating Procedure#Standard of Care|Standard of Care]].
However, officers are allowed to trespass into a department in “hot pursuit” of a suspect who has fled from an accessible area, who they have Probable Cause to believe has committed a crime
==Probable Cause==
While actively enforcing the law, officers are held to the standard of Probable Cause. This phrase represents a reasonable suspicion that a crime has very recently been, or is currently being, committed, and does not require the officer to have solid evidence of criminal activity. Examples of Probable Cause include witnessing criminal activity or credible reports of such.
When judged, Probable Cause is to be interpreted within the context of the entirety of the station’s circumstances at the time of the incident. Probable Cause negates the need of a warrant for searches, and, once concluded, is to be promptly reported to the Prosecutor, Warden, or Head of Security for review.
== Rules of Engagement ==
Law enforcement personnel hold a Category D Armaments License, and are authorized at all times to use lethal force to the extent necessary to neutralize adversaries under any of the following circumstances:
* If the adversary’s number or strength leave the officer at a severe tactical disadvantage.
* If the adversary exhibits a clear and present danger to the vessel by physical means.
* If the adversary poses a significant threat of serious bodily injury or death to the officer or others.
* If the adversary presents an existential threat to the chain of command by illegal means.
* If the adversary cannot be reasonably subdued through non-lethal means without great bodily harm to the officer.
…wherein an “adversary” is an Employee that demonstrates a hostile intent, commits or directly contributes to a hostile act that does not constitute an actual attack, or is actively attacking the vessel or its crew.
Despite their allowance of lethal force, Security is required to follow the Rules of Engagement of the current Alert Level, as outlined within [[Alert Procedure]].
== Martial Law ==
Martial Law may only be declared by Central Command. Under Martial Law Security is:
* able to perform arrests, searches, and departmental raids at their discretion and without a warrant.
* authorised to take whatever actions deemed necessary to neutralise or repel station threats.
* authorized to summarily execute station threats.
* not bound to the Rules of Engagement.
* not obligated to provide any medical care to prisoners, including revival.
== Armory Procedure ==
The armory and its usage are governed by the Warden. The Warden is responsible for stocking, distributing, and recording the collection of weapons used by the station during the shift. They are also afforded the authority to arm security at their discretion, so long as the weaponry distributed is in appropriate response to [[Alert Procedure]].
All weaponry distributed from armory is issued on a temporary basis. While issued, the weaponry remains as station property, and the wielder assumes a duty to protect the weapon and return it to the Warden when ordered. No non-crew may be issued weaponry from the armory.
The Warden is accountable for responsibly issuing weapons. Any weaponry issued during a heightened alert level must be returned when the alert has been lowered, or when the threat is no longer present. The weaponry issued must be proportionate to the threat posed, and weapons which threaten the structural integrity of the station should not be used unless absolutely necessary.
==Treatment of Prisoners==
Prisoners have certain rights that must be upheld by law enforcement and Station Command. ''Prisoners must be provided with the following'':
* Adequate medical care and moral, spiritual, or legal counseling if it is requested and available.
* Access to the Common and Prison radio channels only while serving their sentence. This right may be revoked should it be abused.
* Standard prisoner clothing must always be available for prisoners.
* Confiscated clothing should be returned upon request, except in the case of Extended Confinement or if the clothing in question poses a clear risk to prisoner or crew safety.
* Food and water for organic prisoners must be available and given on request.
* Should the holding cells or Permabrig become uninhabitable, prisoners must be securely and safely relocated to another area for confinement, until the holding cells or Permabrig are returned to a serviceable state.
* Prisoners should be granted freedom of movement within their holding area unless there is an undue risk to life and limb.
Additionally, prisoners sentenced to Extended Confinement have certain rights but also '''more firm restrictions that must be upheld by law enforcement''':
* Visitation may be permitted only for prisoners sentenced to Extended Confinement. A reason must be provided for the visit, and the visitor must consent to a search of their belongings.
* Prisoners may request an appeal for capital crimes; these requests may be refused depending on the discretion and availability of the Clerk, Chief Justice, or at the discretion of the warden or head of station security.
* Prisoners may request or may otherwise be given parole by the Warden or Head of Security.
Should prisoners repeatedly and maliciously antagonize law enforcement, to an extent where continued extended confinement becomes infeasible, the warden is fully authorized and encouraged to upgrade the relevant prisoners’ sentences to Exfiltration summarily.
Moreover, prisoners are liable for any and all crimes that they commit while held either in the temporary holding cells or the Permanent Brig, and may, in the case of the Permanent Brig, be placed in solitary confinement for the duration of the sentence the committed crimes incur.
==Arrest and Sentencing==
Any Employee, that has been formally charged with the commission of a crime, or can be assumed to have committed a crime under [[Standard Operating Procedure#Probable Cause| Probable Cause]] may be arrested by a member of Security. In an arrest, the suspect will be restrained, informed of their charges, and moved to the Brig for processing.
Legal counsel may be requested by an Arrestee, and must be provided if available.
At the brig the prisoner is searched and confined to a holding cell. Any items on their person they are not authorized to possess are confiscated and sent to the justice department for processing and storage as evidence. It is the duty of both the Warden and the Chief Justice to ensure evidence is handled properly and safely.
The prisoner is sentenced according to Space Law.
Any items deemed unbefitting of a prisoner may be taken for the duration of incarceration and must be returned at the end of the sentence.
Any member of Security is authorized to pass sentences on all Grand Felony, Felony, and Misdemeanor-level crimes. The Chief Justice, or in their absence the Head of Security, is authorized to pass sentences on all Crimes, and to correct, or even acquit them, in accordance with Space Law.
Should the Prisoners sentence exceed a total of 25 minutes, or if the crimes charged include a capital crime, the prisoner is transported to the Permabrig to await their [[Standard Operating Procedure#Hearings|Hearing]]. If after 25 minutes a hearing still has not been conducted, the prisoner is offered to be implanted with a tracking implant and released, and to arrive to the hearing when it happens. Failure to attend constitutes contempt of court.
==Appeals, Parole, Pardons==
Appeals are handled by the Chief Justice. Attorneys may appeal a past ruling on behalf of their client, if presented with new evidence. If the evidence exonerates the Prisoner, their sentence is adjusted as necessary by the Chief Justice.
A Prisoner sentenced to a Felony or Grand Felony crime may request an appeal by the Clerk or Chief Justice, these appeals may be denied for any reason.
Prisoners may additionally receive parole per discretion of the Warden, Head of Security, or CO. Parolees are encouraged to seek gainful employment on station. Parolees may have any sentence escalated to permanent confinement regardless of severity.
A pardon may be issued to any Employee accused of, or sentenced to, crimes, if deemed in the best interest of the crew and vessel, and when the circumstances of the defendant or their offense warrant a suspension of their sentence. A pardon may be issued by the Arresting Officer or Warden, in case of the Prisoners, or the Judge of a hearing and must be accompanied by an announcement, explaining who was pardoned, of what crime, and for what reason.
==Pressing Criminal Charges==
Any crew member may press charges against any other crew member. An individual may be charged of a crime, if it can be reasonably argued, that the accused has committed an act in the nature and fashion as described by the particular criminal charge.
Multiple counts of one crime can be charged. For illegal actions against the station abroad, each incident separated by a reasonable period of inaction, lawful conduct, or another crime is interpreted as one count. For crimes directly bereaving a person (violent acts, theft of personal property etc.), the same applies in addition to each victim being one count. In the case of possession, theft, or any incident involving several separate and distinct items, one charge may be applied per relevant item.
Charges that are a direct escalation of one another in nature cannot be simultaneously held against the perpetrator if they pertain to the same incident and the same victim/item (if applicable). Then, only the most severe of these charges applies. Additionally, if one charge incorporates another charge within its definition or description, the perpetrator cannot be held accountable for both; rather, only the most severe of these charges applies.
==Warrants==
Warrants represent official orders to the security department, issued by the Justice Department. Warrants are only valid when issued by either the Chief Justice or Clerk, or in both of their absences, the Commanding Officer or Head of Security.
* Individual Warrants may order the search of an individual, or the arrest of the individual with evidence. This may also include surgical procedures, cursory implant inspections, or any other invasive searches with evidence. These warrants must include a name, the actions prescribed (search, arrest, etc.), and a description if applicable.
* Search Warrants may order the search of an entire departmental area, by force if necessary, and may order the arrest of listed individuals implicated in a crime or crimes with probable cause or evidence. Search Warrants should include an area, a brief description of the probable cause, and a list of individuals if applicable.
==Injunctions==
The Chief Justice, Commanding Officer, or Judge of a hearing are fully authorized to issue injunctions: legally-binding, written orders restricting Inhabitants from doing something disruptive to station productivity or personal rights.
These may be issued on discretion. Injunctions may not compel someone to violate Space Law or SOP, and the issuer is liable for abuse of power charges if this is misused. An injunction must include a list of affected names and a detailed description of the restrictions to be valid.
=Hearings=
NanoTrasen authorizes its Justice Department to conduct Hearings in order to determine if a person accused of sufficient crimes may be held in indefinite custody, to be handed over to GalPol at the end of the shift.
The purpose of these hearings is not to determine legally binding guilt, but to measure how feasible their guilt is; and to avoid lawsuits which would tarnish the NanoTrasen brand.
Hearings are a tool of Space Law. Like any other action that represents the Law, they are subject to Procedural Defense as detailed in Space Law.
A hearing must be held for capital charges, and for sentences that exceed a total of 25 minutes when added together. The right for a hearing may be waived by the suspect by pleading guilty, under martial law, or in absence of any Justice personnel on station.
A hearing is conducted by a Judge (chosen in order from: Chief Justice, Clerk, Commanding Officer, Command), who charged is with ensuring the hearing is completed, decides the time and location of the hearing, and whether they want to make the hearing accessible to the public or not. A hearing must include the Judge and Prosecution, and should additionally include the accused and defense, if available. The Judge has the right to find any person in the hearing guilty of contempt of court.
Hearings are concerned with any and all crimes the accused is currently charged with, not just capital crimes.
Defense must be provided to the accused if requested and feasible, and should consist of either an attorney, any member of the justice department, or any Employee chosen by the accused.
The prosecution should consist of either a prosecutor, any member of the justice department, or any member of station security.
Neither prosecution nor defense may interrupt one another during presentation of evidence, including objecting to witness statements made. In the case of a witness making demonstrably untrue statements, perjury and contempt of court may be charged as necessary. It is encouraged the judge pay attention and inquire about competing claims to truth.
==Hearing Procedure==
A Hearing should take no longer than 10 minutes, and follows these steps:
* 0. Prosecution informs the Judge, the Accused and the Defense of all charges. The Accused is asked if they want to plead guilty.
* 1. The Judge confirms that every required person is present, and begins the hearing.
* 2. The Prosecution reads the charges. Any crimes not charged are not subject to the hearing and cannot be accounted for in sentencing.
* 3. The Prosecution presents all evidence to support the charges.
* 4. The Attorney contests the prosecution's version of events and presents their own evidence.
* 5. The Judge rules on whether the suspect reasonably has committed the crime. If the accused is found not guilty of a more severe version of a crime, the Judge may still find them guilty of a less severe version (for example if found not guilty of murder, the accused may be found guilty of manslaughter), despite the crime not being charged.
* 6. Any Ruling is enacted; if a party refuses to follow through with the ruling, they may suffer criminal penalties.
Suspects who are found guilty of a part of the charged crimes, in a way where the total sentence does not exceed 25 minutes, may be released immediately, at the judges discretion.
A capital sentence should be approved under Space Law.
The default sentence for suspects guilty of a capital crime is permanent confinement. If there is no doubt of the suspects guilt of a capital crime, or a guilty plea to a capital crime, and the nature of the crime is judged to be extreme and heinous, the Judge is authorized to sentence the prisoner to be executed.
=Medical Regulations=
==Standard of Care==
Upon agreeing to provide medical care, staff assume the following duties of care:
* They must provide a reasonable level of skillful, quality care
* They must not make critical errors resulting in major personal injury or death, at the risk of criminal and civil liability.
* They must show diligence in providing treatment, both inside and outside of the medical department area during their shift.
* They must provide privacy to patients in non-emergency situations.
* If they have limited resources, they must allocate what resources are available to them via triage.
* They must not provide treatment that would breach Space Law or violate ethical principles.
* They must ensure that invasive procedures or surgeries, including implant removals or inspections, are performed by trained personnel and properly given authorization via a valid search warrant. The need for a warrant is removed during a Code Gamma or similar alert status.
==DNRs==
Do Not Resuscitate (DNR) orders are commands issued to medical personnel to restrict the revival of a specified individual. DNRs are issued for the following reasons:
* Lawful executions;
* Suicide in the custody of law enforcement, verified by the Corpsman.
* Patient refusal.
DNRs that do not involve execution must be verified by the CMO before taking effect.
==Medical Evaluation==
Given good medical reason and with documentation, the Chief Medical Officer of a vessel may declare any on-duty personnel unfit for duty and order them demoted from their position. This includes the Commanding Officer, who may not overrule this procedure.
In order for a medical evaluation to be considered lawfully applicable, the CMO and relevant head of department must both agree that the specified crewmate is unfit for duty in order to proceed with the demotion. In the case of the Commanding Officer, Central Command should be contacted as soon as possible.
==Prescribing Medication==
In the event that a patient requires stronger medication outside of medbay, Medical personnel have the ability to prescribe medicine to a patient. Prescriptions must be written on valid prescription paper, as well as signed and stamped with a valid medical personnel's stamp and signature. Any such document must state the substance(s) being prescribed as well as the amount(s) where applicable. The chief medical officer also has the right to revoke any prescription with valid medical reasoning. Prescriptions are not valid if they are both intended for and signed and stamped by the same person (e.g. You cannot prescribe something to yourself as a doctor).
=== Prescription Medicine ===
Prescriptions that are valid with any medical personnel's stamp and signature are as follows:
* diphenylmethylamine
* equilibrazine
* tranquinase
* neurozenium
* serenitol
* soretizone
Highly-addictive mental health prescriptions that are only valid with the chief medical officer's or psychologist's stamp and signature are as follows:
* blissifylovene
* happiness
Highly-addictive or potentially-harmful prescriptions that are only valid with the chief medical officer's stamp and signature are as follows:
* agonolexyne
* desoxyephedrine
* nocturine
=== Dangerous Chemical Authorizations ===
Other dangerous chemicals that may be authorized and given out ONLY with valid written permission with the chief medical officer's stamp are as follows:
* mindbreaker, heartbreaker, and mute toxins
* amatoxin and other dangerous poisons
* phlogiston, napalm, and chlorine trifluoride
* vestine and romerol
* Any other [[Standard Operating Procedure#Controlled Substances|controlled substances]] not listed here
The chief medical officer is also required to inform and/or forward authorization of any dangerous chemical to Security.
==Quarantine==
Medical staff are afforded the right to quarantine individuals that currently suffer from infectious or contagious diseases, and may confine them within pre-designated quarantine areas in the course of treatment, until the point that they are no longer infectious or harmful to the rest of the crew.
==Foreign Bodies==
Any parasitic alien organism that requires a living host to live and reproduce must be eliminated. An exception can be made for experimental purposes, provided a CO, CMO and Mystagogue were to approve it.
=Engineering Regulations=
==Health & Safety==
* In public areas, all wiring must be concealed under tiles. In non-public areas, catwalks are recommended to be placed for the safety and mobility of workers.
* The atmospheric distribution pipe ("distro") pressure should be no more than 500 kPa and composition should be a ratio of 70-80% nitrogen and 20-30% oxygen.
* The atmospheric waste pipe ("waste") shall be kept free of clogging, and directed either to recycling, gas storage, or space.
* Newly constructed burn chambers may not be located within the station or directly adjacent to the station's hull, unless receiving written authorization from Central Command.
==Building Permits==
Any renovations larger than small modifications to the station's structural layout, power network, or atmospheric system shall require a building permit or work order approved expressly by the Chief Engineer, or if a Chief Engineer is not present, the Captain or Acting Commanding Officer. Modifications within departments shall additionally require the approval of the relevant department head. Areas within maintenance tunnels do not require permission to modify. However, any modifications to maintenance tunnel passageways should ensure they remain traversable and unobstructed by personnel who need to use them as part of their job.
==Emergency Access==
In emergency situations, engineers are permitted to bypass door access (i.e. hack) and gain access to unauthorized locations if said locations require repairs.
==Hazard Zones==
An area with a hull breach, hazardous material leak, or other similar danger shall be considered a hazard zone. To ensure crew safety, engineering personnel are required to demarcate hazard zones with holofans, inflatable walls, and/or engineering tape prior to beginning work. Any non-authorized personnel impeding or preventing repairs in hazardous areas may be liable for endangerment.
==Condemned Areas==
The Chief Engineer may declare a section of the station condemned if repairs cannot be reasonably completed. In the event that there is no Chief Engineer, any engineer may condemn a section of the station with authorization from the Captain or Acting Commanding Officer. Doors leading to such areas shall be welded or bolted to prevent crew access.
+2 -2
View File
@@ -12,12 +12,12 @@ You want to handle the Build, Clean and Rebuild tasks to prevent missing task er
If you want to learn more about these kinds of things, check out Microsoft's official documentation about MSBuild:
https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
-->
<Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Python>python3</Python>
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
<ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid>
<TargetFramework>net4.7.2</TargetFramework>
<TargetFrameworkMoniker>.NETFramework, Version=v4.7.2</TargetFrameworkMoniker>
<RestorePackages>false</RestorePackages>
</PropertyGroup>
<PropertyGroup>
+24 -41
View File
@@ -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.slnx"
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
# If this doesn't match the saved version we overwrite them all.
CURRENT_HOOKS_VERSION = "4"
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,26 +75,23 @@ def install_hooks():
print("No hooks change detected.")
return
print("Hooks need updating.")
hooks_target_dir = Path(run_command(["git", "rev-parse", "--git-path", "hooks"], True).stdout.strip())
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(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)
# Begin DeltaV Additions - add pre-commit hooks if you have it installed
if shutil.which("pre-commit") is not None:
run_command(["pre-commit", "install"], True)
# End DeltaV Additions
print("Hooks need updating.")
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(str(hooks_target_dir)):
os.remove(str(hooks_target_dir/filename))
for filename in os.listdir(str(hooks_source_dir)):
print("Copying hook {}".format(filename))
shutil.copy2(str(hooks_source_dir/filename),
str(hooks_target_dir/filename))
def reset_solution():
"""
@@ -108,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()
+4 -4
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
#!/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
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
#!/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"
-233
View File
@@ -1,233 +0,0 @@
# Delta-V Contributing Guidelines
Generally we follow [upstream's PR guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html) for code quality and such.
Importantly do not make webedits, copied verbatim from above:
> Do not use GitHub's web editor to create PRs. PRs submitted through the web editor may be closed without review.
Upstream is the [space-wizards/space-station-14](https://github.com/space-wizards/space-station-14) repository that wizden runs on.
# Content specific to Delta-V
In general anything you create from scratch (not modifying something that exists from upstream) should go in a DeltaV subfolder, `_DV`.
Examples:
- `Content.Server/_DV/Chapel/SacrificialAltarSystem.cs`
- `Resources/Prototypes/_DV/ai_factions.yml`
- `Resources/Audio/_DV/Items/gavel.ogg`
- `Resources/Textures/_DV/Icons/cri.rsi`
- `Resources/Locale/en-US/_DV/shipyard/shipyard-console.ftl`
- `Resources/ServerInfo/Guidebook/_DV/AlertProcedure.xml`
Note that guidebooks go in `ServerInfo/Guidebook/_DV` and not `ServerInfo/_DV`!
# Changes to upstream files
Follow a few guidelines when modifying non-DeltaV files, to help us manage our project. (files that are not in `_DV` or `Nyano` folders)
Primarily, **add comments on or around all new or changed lines** in upstream files. Explain what was changed to make resolving merge conflicts easier; we regularly merge new upstream changes into our project.
### Changing Upstream YAML .yml files
**Add comments on or around any changed lines.**
If you add a new component to a prototype, add an explanation to the `type: ...` line. Example:
```yml
- type: entity
parent: MobSiliconBase
id: MobSupplyBot
components:
- type: InteractionPopup # DeltaV - Make supplybots pettable
interactSuccessString: petting-success-supplybot
interactFailureString: petting-failure-supplybot
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
```
Whereas if you just modify some fields of a component, comment the fields instead, using inline or block comments. Examples:
```yml
- type: entityTable
id: FillLockerWarden
table: !type:AllSelector
children:
- id: ClothingHandsGlovesCombat
- id: ClothingShoesBootsSecurityMagboots # DeltaV - Added security magboots.
- id: ClothingShoesBootsJack
#- id: ClothingOuterCoatWarden # DeltaV - removed for incongruence
#- id: ClothingOuterWinterWarden # DeltaV - removed for incongruence
- id: RubberStampWarden
- id: DoorRemoteArmory
- id: HoloprojectorSecurity
# Begin DeltaV additions
- id: WeaponEnergyShotgun
- id: BoxPDAPrisoner
- id: LunchboxSecurityFilledRandom
prob: 0.3
# End DeltaV additions
```
### Changing Upstream C# .cs files
If you are adding a lot of C# code, then take advantage of partial classes. Put the new code in its own file in the `_DV` folder, if it makes sense.
Otherwise, **add comments on or around any changed lines.**
### Single-Line Changes
Format should look like this.
```cs
/* Importing Namespaces - Include optional comment if its not obvious what its being used for. */
using Content.Server._DV.Psionics.Glimmer; // DeltaV
using Content.Shared.Damage.Systems; // DeltaV - Addition of HandHeldArmor
/* Changing an upstream line - Same line as the change */
if (!TryComp<EyeComponent>(ent, out var eye) || _disabled) // DeltaV - check if disabled
/* Adding - Either same line or above the line. */
EnsureComp<PotentialPsionicComponent>(entity); // Deltav - Psionics
/* "Deleting" - Don't actually delete, just comment out and say why. This only applies to upstream code. */
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
```
> * Its pretty obvious in the example above that importing `Content.Server._DV.Psionics.Glimmer` means we'll be interacting with glimmer so putting `// DeltaV - Add Glimmer` is needlessly redundant.
> * It's not as obvious what the `Content.Shared.Damage.Systems` namespace is used for, since its so broad, so adding a comment what feature is using it helps.
> * Actual code changes should almost always include the comment after ``// DeltaV`.
### Multi-Line Changes
Depending on how much you are editing, putting a comment on EACH line may be excessive, so if you have a larger block of code you are changing, denote it like so:
```cs
// BEGIN DeltaV - Remove innate radio and radios from pockets
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
{
if (_inventory.TryGetSlotEntity(target, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
_inventory.TryUnequip(target, $"pocket{i}", true, true);
}
RemComp<ActiveRadioComponent>(target); // If the zombie has an innate radio, get rid of it.
// END DeltaV
```
> * Denoting these with a BEGIN and END clearly shows they are block of code without having to read the entire comment. This makes it easier to tell when you're dealing with single-line comments versus a block with merging in conflicts.
> * Case and order of the first two words is less of a concern. `// DeltaV Begin` or `// Begin DeltaV` will work fine too.
> * Try to make your blocks as small as possible, but use your discretion.
> * If you deleting multiple lines, use line comments (``//``) if its a few lines but if its a larger block (like commenting out an entire function), it is preferable to use block comments (`/* */`).
#### Soft Exceptions to the Multi-Line "Rules"
Some multi-line changes can use a single-line comment in certain scenarios. But if you are UNSURE, just use `// BEGIN DeltaV` and `// END DeltaV` comments like the previous section does and it'll be fine.
I'll give some examples.
```cs
/* This change comments out 3 lines but only needs a single line comment because commenting out the if statement implies that its logic will be commented out too. */
// if (obj.WasModified<TraitPrototype>()) // DeltaV - Refreshed in TraitsTab
// {
// _profileEditor.RefreshTraits();
// }
/* Same principle here. This adds two lines but the if statement implies the next line so commenting both lines isn't really needed. */
if (_flight.IsFlying(entity.Owner)) // DeltaV - Harpy Flight
return true;
```
### New Methods or Component Variables
Sometimes, you'll need to implement a whole new method or component variable and instead of wrapping it in `// BEGIN DeltaV` and `// END DeltaV`, you can just denote that it's a DeltaV function in the summary block before the function. This denotes the WHOLE function as a DeltaV addition.
```cs
/* New Method Example */
/// <summary>
/// DeltaV - Handle revealing ninja if cloaked when attacked by a hitscan attack.
/// </summary>
private void OnNinjaAttacked(Entity<SpaceNinjaComponent> ent, ref DamageChangedEvent args)
{
...
}
/* New Component Variable Example */
/// <summary>
/// DeltaV - If disabled the action will not disable when no charges remain. Use if you want to handle no charges differently.
/// </summary>
[DataField]
public bool DisableWhenEmpty = true;
```
In short:
* Use `// BEGIN DeltaV` and `// END Delta` to denote a *block* of changes.
* Keep blocks as small as possible.
* Use `// DeltaV` on or before the line if its not a block of changes.
* Use exceptions when they make sense.
### Changing Upstream Localization Fluent .ftl files
**Move all changed locale strings to a new DeltaV file** - use a `.ftl` file in the `_DV` folder. Comment out the old strings in the upstream file, and explain that they were moved.
Example:
Commented out old string in `Resources\Locale\en-US\xenoarchaeology\artifact-analyzer.ftl`
```
# DeltaV - moved to _DV file
# analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
# [true] {$info}
# *[false] Unlock nodes to gain info
# }[/color][/font]
```
The new version of the string in `Resources\Locale\en-US\_DV\xenoarchaeology\artifact-analyzer.ftl`
```
analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
[vagueandspecific] {$vagueInfo} ({$specificInfo})
[vagueonly] {$vagueInfo} (unable to detect details)
[simple] {$specificInfo}
[hidden] Unable to detect (unlock to discover)
*[noinfo] Unlock nodes to gain info
}[/color][/font]
```
Also keep in mind that fluent (.ftl) files **do not support comments on the same line** as a locale value, so be careful when commenting.
### Early merges
We mostly merge upstream changes in big chunks (e.g. a month of upstream PRs at a time), but urgent changes can be merged early, separately.
Early merges are an exception to the above rules - if cherry-picking a PR for an early merge, you don't need to add `#DeltaV` comments, since the code is coming directly from upstream without any changes.
# Mapping
If you want to make changes to a map, get in touch with its maintainer to make sure you don't both make changes at the same time.
Conflicts with maps make PRs mutually exclusive so either your work or the maintainer's work will be lost, communicate to avoid this!
Please make a detailed list of **all** changes(even minor changes) with locations when submitting a PR. This helps reviewers hone in on them without having to search an entire map for differences. Ex: [Map Edits](https://github.com/DeltaV-Station/Delta-v/pull/3165)
**Submitting a map PR**
Please limit changelogs on map PRs to **significant** map alterations or additions. Minor map edits do not need changelogs.
Format for map PRs looks like:
```
:cl: Yourname
MAPS:
- add: Mapname: Added fun!
- remove: Mapname: Removed fun!
- tweak: Mapname: Changed fun!
- fix: Mapname: Fixed fun!
```
# Before you submit
Double-check your diff on GitHub before submitting: look for unintended commits or changes and remove accidental whitespace or line-ending changes.
Additionally for long-lasting PRs, if you see `RobustToolbox` in the changed files you have to revert it, use `git checkout upstream/master RobustToolbox` (replacing `upstream` with the name of your DeltaV-Station/Delta-V remote)
# Changelogs
By default any changelogs goes in the DeltaV changelog, you can use the DeltaV admin changelog by putting `DELTAVADMIN:` in a line after `:cl:`.
Do not use `ADMIN:` as **it will mangle** the upstream admin changelog!
# Additional resources
If you are new to contributing to SS14 in general, have a look at the [SS14 docs](https://docs.spacestation14.io/) or ask for help in `#contribution-help` on [Discord](https://discord.gg/deltav)!
## AI-Generated Content
Code, sprites and any other AI-generated content is not allowed to be submitted to the repository.
Trying to PR AI-generated content may result in you being banned from contributing.
@@ -1,274 +0,0 @@
#nullable enable
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Doors.Components;
using Content.Shared.Item;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Benchmarks;
/// <summary>
/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
/// subscriptions
/// </summary>
[Virtual]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class ComponentQueryBenchmark
{
public const string Map = "Maps/saltern.yml";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private EntityQuery<ItemComponent> _itemQuery;
private EntityQuery<ClothingComponent> _clothingQuery;
private EntityQuery<MapComponent> _mapQuery;
private EntityUid[] _items = default!;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
_mapQuery = _entMan.GetEntityQuery<MapComponent>();
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var map = new ResPath(Map);
var opts = DeserializationOptions.Default with {InitializeMaps = true};
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(map, out _, out _, opts))
throw new Exception("Map load failed");
}).GetAwaiter().GetResult();
_items = new EntityUid[_entMan.Count<ItemComponent>()];
var i = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out _))
{
_items[i++] = uid;
}
}
[GlobalCleanup]
public async Task Cleanup()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
#region TryComp
/// <summary>
/// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TryComp")]
public int TryComp()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_clothingQuery.TryGetComponent(uid, out var clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that is meant to always fail to get a component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int TryCompFail()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_mapQuery.TryGetComponent(uid, out var map))
hashCode = HashCode.Combine(hashCode, map.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that is meant to always succeed getting a component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int TryCompSucceed()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_itemQuery.TryGetComponent(uid, out var item))
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that uses `Resolve()` to try get the component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int Resolve()
{
var hashCode = 0;
foreach (var uid in _items)
{
DoResolve(uid, ref hashCode);
}
return hashCode;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
{
if (_clothingQuery.Resolve(uid, ref clothing, false))
hash = HashCode.Combine(hash, clothing.GetHashCode());
}
#endregion
#region Enumeration
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int SingleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var item))
{
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int DoubleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent>();
while (enumerator.MoveNext(out _, out var item))
{
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int TripleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent, TransformComponent>();
while (enumerator.MoveNext(out _, out _, out var xform))
{
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int SingleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent>();
while (enumerator.MoveNext(out var airlock))
{
hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int DoubleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent>();
while (enumerator.MoveNext(out _, out var door))
{
hashCode = HashCode.Combine(hashCode, door.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int TripleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent, TransformComponent>();
while (enumerator.MoveNext(out _, out _, out var xform))
{
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
}
return hashCode;
}
#endregion
[Benchmark(Baseline = true)]
[BenchmarkCategory("Events")]
public int StructEvents()
{
var ev = new QueryBenchEvent();
foreach (var uid in _items)
{
_entMan.EventBus.RaiseLocalEvent(uid, ref ev);
}
return ev.HashCode;
}
}
[ByRefEvent]
public struct QueryBenchEvent
{
public int HashCode;
}
public sealed class QueryBenchSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ClothingComponent, QueryBenchEvent>(OnEvent);
}
private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
{
args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
}
}
+9 -15
View File
@@ -1,21 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
<Nullable>disable</Nullable>
<DefineConstants>$(DefineConstants);ALLOW_BAD_PRACTICES</DefineConstants>
<LangVersion>12</LangVersion>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<!-- pin transitive deps -->
<PackageReference Include="System.Management" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
@@ -23,12 +19,10 @@
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Server.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
<Import Project="..\RobustToolbox\Imports\Testing.props" />
</Project>
@@ -1,174 +0,0 @@
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
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(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
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();
}
}
-192
View File
@@ -1,192 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Destructible;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Maps;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Benchmarks;
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class DestructibleBenchmark
{
/// <summary>
/// Number of destructible entities per prototype to spawn with a <see cref="DestructibleComponent"/>.
/// </summary>
[Params(1, 10, 100, 1000, 5000)]
public int EntityCount;
/// <summary>
/// Amount of blunt damage we do to each entity.
/// </summary>
[Params(10000)]
public FixedPoint2 DamageAmount;
[Params("Blunt")]
public ProtoId<DamageTypePrototype> DamageType;
private static readonly EntProtoId WindowProtoId = "Window";
private static readonly EntProtoId WallProtoId = "WallReinforced";
private static readonly EntProtoId HumanProtoId = "MobHuman";
private static readonly ProtoId<ContentTileDefinition> TileRef = "Plating";
private readonly EntProtoId[] _prototypes = [WindowProtoId, WallProtoId, HumanProtoId];
private readonly List<Entity<DamageableComponent>> _damageables = new();
private readonly List<Entity<DamageableComponent, DestructibleComponent>> _destructbiles = new();
private TestMapData _currentMapData = default!;
private DamageSpecifier _damage;
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private IPrototypeManager _protoMan = default!;
private IRobustRandom _random = default!;
private ITileDefinitionManager _tileDefMan = default!;
private DamageableSystem _damageable = default!;
private DestructibleSystem _destructible = default!;
private SharedMapSystem _map = default!;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
var server = _pair.Server;
_entMan = server.ResolveDependency<IEntityManager>();
_protoMan = server.ResolveDependency<IPrototypeManager>();
_random = server.ResolveDependency<IRobustRandom>();
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
_damageable = _entMan.System<DamageableSystem>();
_destructible = _entMan.System<DestructibleSystem>();
_map = _entMan.System<SharedMapSystem>();
if (!_protoMan.Resolve(DamageType, out var type))
return;
_damage = new DamageSpecifier(type, DamageAmount);
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
}
[IterationSetup]
public void IterationSetup()
{
var plating = _tileDefMan[TileRef].TileId;
var server = _pair.Server;
_currentMapData = _pair.CreateTestMap().GetAwaiter().GetResult();
// We make a rectangular grid of destructible entities, and then damage them all simultaneously to stress test the system.
// Needed for managing the performance of destructive effects and damage application.
server.WaitPost(() =>
{
// Set up a thin line of tiles to place our objects on. They should be anchored for a "realistic" scenario...
for (var x = 0; x < EntityCount; x++)
{
for (var y = 0; y < _prototypes.Length; y++)
{
_map.SetTile(_currentMapData.Grid, _currentMapData.Grid, new Vector2i(x, y), new Tile(plating));
}
}
for (var x = 0; x < EntityCount; x++)
{
var y = 0;
foreach (var protoId in _prototypes)
{
var coords = new EntityCoordinates(_currentMapData.Grid, x + 0.5f, y + 0.5f);
_entMan.SpawnEntity(protoId, coords);
y++;
}
}
var query = _entMan.EntityQueryEnumerator<DamageableComponent, DestructibleComponent>();
_destructbiles.EnsureCapacity(EntityCount);
_damageables.EnsureCapacity(EntityCount);
while (query.MoveNext(out var uid, out var damageable, out var destructible))
{
_damageables.Add((uid, damageable));
_destructbiles.Add((uid, damageable, destructible));
}
})
.GetAwaiter()
.GetResult();
}
[Benchmark]
public async Task PerformDealDamage()
{
await _pair.Server.WaitPost(() =>
{
_damageable.ApplyDamageToAllEntities(_damageables, _damage);
});
}
[Benchmark]
public async Task PerformTestTriggers()
{
await _pair.Server.WaitPost(() =>
{
_destructible.TestAllTriggers(_destructbiles);
});
}
[Benchmark]
public async Task PerformTestBehaviors()
{
await _pair.Server.WaitPost(() =>
{
_destructible.TestAllBehaviors(_destructbiles);
});
}
[IterationCleanup]
public void IterationCleanupAsync()
{
// We need to nuke the entire map and respawn everything as some destructible effects
// spawn entities and whatnot.
_pair.Server.WaitPost(() =>
{
_map.QueueDeleteMap(_currentMapData.MapId);
})
.Wait();
// Deletion of entities is often queued (QueueDel) which must be processed by running ticks
// or else it will grow infinitely and leak memory.
_pair.Server.WaitRunTicks(2)
.GetAwaiter()
.GetResult();
_destructbiles.Clear();
_damageables.Clear();
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
@@ -61,7 +60,7 @@ public class DeviceNetworkingBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(DeviceNetworkingBenchmark).Assembly);
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
await server.WaitPost(() =>
+1 -1
View File
@@ -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,6 @@ namespace Content.Benchmarks
var componentFactory = new Mock<IComponentFactory>();
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
componentFactory.Setup(m => m.GetIndex(typeof(DummyComponent))).Returns(CompIdx.Index<DummyComponent>());
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg);
componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index<DummyComponent>() });
+137
View File
@@ -0,0 +1,137 @@
#nullable enable
using System;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Benchmarks;
[Virtual]
public class EntityQueryBenchmark
{
public const string Map = "Maps/atlas.yml";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new MapId(10);
private EntityQuery<ClothingComponent> _clothingQuery;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).GetAwaiter().GetResult();
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
// Apparently ~40% of entities are items, and 1 in 6 of those are clothing.
/*
var entCount = _entMan.EntityCount;
var itemCount = _entMan.Count<ItemComponent>();
var clothingCount = _entMan.Count<ClothingComponent>();
var itemRatio = (float) itemCount / entCount;
var clothingRatio = (float) clothingCount / entCount;
Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}.");
*/
}
[GlobalCleanup]
public async Task Cleanup()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
[Benchmark]
public int HasComponent()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_entMan.HasComponent<ClothingComponent>(uid))
hashCode = HashCode.Combine(hashCode, uid.Id);
}
return hashCode;
}
[Benchmark]
public int HasComponentQuery()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_clothingQuery.HasComponent(uid))
hashCode = HashCode.Combine(hashCode, uid.Id);
}
return hashCode;
}
[Benchmark]
public int TryGetComponent()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
[Benchmark]
public int TryGetComponentQuery()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_clothingQuery.TryGetComponent(uid, out var clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Enumerate all entities with both an item and clothing component.
/// </summary>
[Benchmark]
public int Enumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent, ClothingComponent>();
while (enumerator.MoveNext(out var _, out var clothing))
{
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
}
-254
View File
@@ -1,254 +0,0 @@
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Benchmarks;
/// <summary>
/// Benchmarks the performance of different gas reactions.
/// Tests each reaction type with realistic gas mixtures to measure computational cost.
/// </summary>
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class GasReactionBenchmark
{
private const int Iterations = 1000;
private TestPair _pair = default!;
private AtmosphereSystem _atmosphereSystem = default!;
// Grid and tile for reactions that need a holder
private EntityUid _testGrid = default!;
private TileAtmosphere _testTile = default!;
// Reaction instances
private PlasmaFireReaction _plasmaFireReaction = default!;
private TritiumFireReaction _tritiumFireReaction = default!;
private FrezonProductionReaction _frezonProductionReaction = default!;
private FrezonCoolantReaction _frezonCoolantReaction = default!;
private AmmoniaOxygenReaction _ammoniaOxygenReaction = default!;
private N2ODecompositionReaction _n2oDecompositionReaction = default!;
private WaterVaporReaction _waterVaporReaction = default!;
// Gas mixtures for each reaction type
private GasMixture _plasmaFireMixture = default!;
private GasMixture _tritiumFireMixture = default!;
private GasMixture _frezonProductionMixture = default!;
private GasMixture _frezonCoolantMixture = default!;
private GasMixture _ammoniaOxygenMixture = default!;
private GasMixture _n2oDecompositionMixture = default!;
private GasMixture _waterVaporMixture = default!;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
var server = _pair.Server;
// Create test map and grid
var mapData = await _pair.CreateTestMap();
_testGrid = mapData.Grid;
await server.WaitPost(() =>
{
var entMan = server.ResolveDependency<IEntityManager>();
_atmosphereSystem = entMan.System<AtmosphereSystem>();
_plasmaFireReaction = new PlasmaFireReaction();
_tritiumFireReaction = new TritiumFireReaction();
_frezonProductionReaction = new FrezonProductionReaction();
_frezonCoolantReaction = new FrezonCoolantReaction();
_ammoniaOxygenReaction = new AmmoniaOxygenReaction();
_n2oDecompositionReaction = new N2ODecompositionReaction();
_waterVaporReaction = new WaterVaporReaction();
SetupGasMixtures();
SetupTile();
});
}
private void SetupGasMixtures()
{
// Plasma Fire: Plasma + Oxygen at high temperature
// Temperature must be > PlasmaMinimumBurnTemperature for reaction to occur
_plasmaFireMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.PlasmaMinimumBurnTemperature + 100f // ~673K
};
_plasmaFireMixture.AdjustMoles(Gas.Plasma, 20f);
_plasmaFireMixture.AdjustMoles(Gas.Oxygen, 100f);
// Tritium Fire: Tritium + Oxygen at high temperature
// Temperature must be > FireMinimumTemperatureToExist for reaction to occur
_tritiumFireMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.FireMinimumTemperatureToExist + 100f // ~473K
};
_tritiumFireMixture.AdjustMoles(Gas.Tritium, 20f);
_tritiumFireMixture.AdjustMoles(Gas.Oxygen, 100f);
// Frezon Production: Oxygen + Tritium + Nitrogen catalyst
// Optimal temperature for efficiency (80% of max efficiency temp)
_frezonProductionMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.FrezonProductionMaxEfficiencyTemperature * 0.8f // ~48K
};
_frezonProductionMixture.AdjustMoles(Gas.Oxygen, 50f);
_frezonProductionMixture.AdjustMoles(Gas.Tritium, 50f);
_frezonProductionMixture.AdjustMoles(Gas.Nitrogen, 10f);
// Frezon Coolant: Frezon + Nitrogen
// Temperature must be > FrezonCoolLowerTemperature (23.15K) for reaction to occur
_frezonCoolantMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C + 50f // ~343K
};
_frezonCoolantMixture.AdjustMoles(Gas.Frezon, 30f);
_frezonCoolantMixture.AdjustMoles(Gas.Nitrogen, 100f);
// Ammonia + Oxygen reaction (concentration-dependent, no temp requirement)
_ammoniaOxygenMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C + 100f // ~393K
};
_ammoniaOxygenMixture.AdjustMoles(Gas.Ammonia, 40f);
_ammoniaOxygenMixture.AdjustMoles(Gas.Oxygen, 40f);
// N2O Decomposition (no temperature requirement, just needs N2O moles)
_n2oDecompositionMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C + 100f // ~393K
};
_n2oDecompositionMixture.AdjustMoles(Gas.NitrousOxide, 100f);
// Water Vapor - needs water vapor to condense
_waterVaporMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C
};
_waterVaporMixture.AdjustMoles(Gas.WaterVapor, 50f);
}
private void SetupTile()
{
// Create a tile atmosphere to use as holder for all reactions
var testIndices = new Vector2i(0, 0);
_testTile = new TileAtmosphere(_testGrid, testIndices, new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C
});
}
private static GasMixture CloneMixture(GasMixture original)
{
return new GasMixture(original);
}
[Benchmark]
public async Task PlasmaFireReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_plasmaFireMixture);
_plasmaFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task TritiumFireReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_tritiumFireMixture);
_tritiumFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task FrezonProductionReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_frezonProductionMixture);
_frezonProductionReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task FrezonCoolantReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_frezonCoolantMixture);
_frezonCoolantReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task AmmoniaOxygenReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_ammoniaOxygenMixture);
_ammoniaOxygenReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task N2ODecompositionReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_n2oDecompositionMixture);
_n2oDecompositionReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task WaterVaporReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_waterVaporMixture);
_waterVaporReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
-3
View File
@@ -1,3 +0,0 @@
// Global usings for Content.Benchmarks
global using Robust.UnitTesting.Pool;
@@ -1,84 +0,0 @@
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
namespace Content.Benchmarks;
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class HeatCapacityBenchmark
{
private TestPair _pair = default!;
private IEntityManager _sEntMan = default!;
private IEntityManager _cEntMan = default!;
private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
private AtmosphereSystem _sAtmos = default!;
private GasMixture _mix;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
await _pair.Connect();
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
_cAtmos = _cEntMan.System<Client.Atmos.EntitySystems.AtmosphereSystem>();
_sAtmos = _sEntMan.System<AtmosphereSystem>();
const float volume = 2500f;
const float temperature = 293.15f;
const float o2 = 12.3f;
const float n2 = 45.6f;
const float co2 = 0.42f;
const float plasma = 0.05f;
_mix = new GasMixture(volume) { Temperature = temperature };
_mix.AdjustMoles(Gas.Oxygen, o2);
_mix.AdjustMoles(Gas.Nitrogen, n2);
_mix.AdjustMoles(Gas.CarbonDioxide, co2);
_mix.AdjustMoles(Gas.Plasma, plasma);
}
[Benchmark]
public async Task ClientHeatCapacityBenchmark()
{
await _pair.Client.WaitPost(delegate
{
for (var i = 0; i < 10000; i++)
{
_cAtmos.GetHeatCapacity(_mix, applyScaling: true);
}
});
}
[Benchmark]
public async Task ServerHeatCapacityBenchmark()
{
await _pair.Server.WaitPost(delegate
{
for (var i = 0; i < 10000; i++)
{
_sAtmos.GetHeatCapacity(_mix, applyScaling: true);
}
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
+13 -15
View File
@@ -1,19 +1,17 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Maps;
using Content.Server.Maps;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Benchmarks;
@@ -22,15 +20,15 @@ public class MapLoadBenchmark
{
private TestPair _pair = default!;
private MapLoaderSystem _mapLoader = default!;
private SharedMapSystem _mapSys = default!;
private IMapManager _mapManager = default!;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var server = _pair.Server;
Paths = server.ResolveDependency<IPrototypeManager>()
@@ -38,7 +36,7 @@ public class MapLoadBenchmark
.ToDictionary(x => x.ID, x => x.MapPath.ToString());
_mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
_mapSys = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedMapSystem>();
_mapManager = server.ResolveDependency<IMapManager>();
}
[GlobalCleanup]
@@ -48,25 +46,23 @@ 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", "Box", "Aspid", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Gemini", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" };
[ParamsSource(nameof(MapsSource))]
public string Map;
public Dictionary<string, string> Paths;
private MapId _mapId;
[Benchmark]
public async Task LoadMap()
{
var mapPath = new ResPath(Paths[Map]);
var mapPath = Paths[Map];
var server = _pair.Server;
await server.WaitPost(() =>
{
var success = _mapLoader.TryLoadMap(mapPath, out var map, out _);
var success = _mapLoader.TryLoad(new MapId(10), mapPath, out _);
if (!success)
throw new Exception("Map load failed");
_mapId = map.Value.Comp.MapId;
});
}
@@ -74,7 +70,9 @@ public class MapLoadBenchmark
public void IterationCleanup()
{
var server = _pair.Server;
server.WaitPost(() => _mapSys.DeleteMap(_mapId))
.Wait();
server.WaitPost(() =>
{
_mapManager.DeleteMap(new MapId(10));
}).Wait();
}
}
+11 -7
View File
@@ -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
}
}
}
+34 -25
View File
@@ -1,22 +1,21 @@
#nullable enable
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Mind;
using Content.Shared.Warps;
using Content.Server.Warps;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Benchmarks;
@@ -37,6 +36,7 @@ public class PvsBenchmark
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new(10);
private ICommonSession[] _players = default!;
private EntityCoordinates[] _spawns = default!;
public int _cycleOffset = 0;
@@ -49,29 +49,24 @@ public class PvsBenchmark
#if !DEBUG
ProgramShared.PathOffset = "../../../../";
#endif
PoolManager.Startup();
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
_pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
_sys = _entMan.System<SharedTransformSystem>();
SetupAsync().Wait();
}
private async Task SetupAsync()
{
// Spawn the map
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
await _pair.Server.WaitPost(() =>
_pair.Server.WaitPost(() =>
{
var path = new ResPath(Map);
var opts = DeserializationOptions.Default with {InitializeMaps = true};
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(path, out _, out _, opts))
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
});
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).Wait();
// Get list of ghost warp positions
_spawns = _entMan.AllComponentsList<WarpPointComponent>()
@@ -81,19 +76,17 @@ public class PvsBenchmark
Array.Resize(ref _players, PlayerCount);
// Spawn "Players"
_players = await _pair.Server.AddDummySessions(PlayerCount);
await _pair.Server.WaitPost(() =>
// Spawn "Players".
_pair.Server.WaitPost(() =>
{
var mind = _pair.Server.System<MindSystem>();
for (var i = 0; i < PlayerCount; i++)
{
var pos = _spawns[i % _spawns.Length];
var uid =_entMan.SpawnEntity("MobHuman", pos);
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
mind.ControlMob(_players[i].UserId, uid);
_players[i] = new DummySession{AttachedEntity = uid};
}
});
}).Wait();
// Repeatedly move players around so that they "explore" the map and see lots of entities.
// This will populate their PVS data with out-of-view entities.
@@ -175,4 +168,20 @@ public class PvsBenchmark
}).Wait();
_pair.Server.PvsTick(_players);
}
private sealed class DummySession : ICommonSession
{
public SessionStatus Status => SessionStatus.InGame;
public EntityUid? AttachedEntity {get; set; }
public NetUserId UserId => default;
public string Name => string.Empty;
public short Ping => default;
public INetChannel Channel { get; set; } = default!;
public LoginType AuthType => default;
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
public DateTime ConnectedTime { get; set; }
public SessionState State => default!;
public SessionData Data => default!;
public bool ClientSide { get; set; }
}
}
-149
View File
@@ -1,149 +0,0 @@
#nullable enable
using System.IO;
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(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
var entMan = _pair.Server.EntMan;
var fact = _pair.Server.ResolveDependency<IComponentFactory>();
var bus = (EntityEventBus)entMan.EventBus;
_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);
_sys.NetId = fact.GetRegistration<TransformComponent>().NetID!.Value;
_sys.EvSubs = bus.GetNetCompEventHandlers<BenchSystem.BenchEv>();
})
.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 RaiseNetEvent()
{
return _sys.RaiseNetIdEvent();
}
[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 ushort NetId;
internal EntityEventBus.DirectedEventHandler?[] EvSubs = default!;
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();
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();
RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
return ev.N;
}
public int RaiseNetIdEvent()
{
// Raise a "IComponent" event using a net-id index delegate array (for PVS & client game-state events)
var ev = new BenchEv();
ref var unitEv = ref Unsafe.As<BenchEv, EntityEventBus.Unit>(ref ev);
EvSubs[NetId]?.Invoke(Ent2.Owner, Ent2.Comp, ref unitEv);
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]
[ComponentEvent(Exclusive = false)]
public struct BenchEv
{
public int N;
}
}
}
@@ -1,5 +1,4 @@
using System.IO;
using System.Threading.Tasks;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
@@ -9,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;
@@ -20,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;
@@ -36,14 +32,14 @@ public class SpawnEquipDeleteBenchmark
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
PoolManager.Startup(null);
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
var mapData = await _pair.CreateTestMap();
_coords = mapData.GridCoords;
_spawnSys = server.System<StationSpawningSystem>();
_gear = server.ProtoMan.Index(CaptainStartingGear);
_gear = server.ProtoMan.Index<StartingGearPrototype>("CaptainGear");
}
[GlobalCleanup]
@@ -62,7 +58,7 @@ public class SpawnEquipDeleteBenchmark
for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
_spawnSys.EquipStartingGear(_entity, _gear);
_spawnSys.EquipStartingGear(_entity, _gear, null);
server.EntMan.DeleteEntity(_entity);
}
});
+40 -53
View File
@@ -9,20 +9,20 @@ namespace Content.Client.Access;
public sealed class AccessOverlay : Overlay
{
private const string TextFontPath = "/Fonts/NotoSans/NotoSans-Regular.ttf";
private const int TextFontSize = 12;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _transformSystem;
private readonly EntityLookupSystem _lookup;
private readonly SharedTransformSystem _xform;
private readonly Font _font;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
{
_entityManager = entityManager;
_transformSystem = transformSystem;
_font = resourceCache.GetFont(TextFontPath, TextFontSize);
_entityManager = entManager;
_lookup = lookup;
_xform = xform;
_font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
}
protected override void Draw(in OverlayDrawArgs args)
@@ -30,65 +30,52 @@ public sealed class AccessOverlay : Overlay
if (args.ViewportControl == null)
return;
var textBuffer = new StringBuilder();
var query = _entityManager.EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var accessReader, out var transform))
var readerQuery = _entityManager.GetEntityQuery<AccessReaderComponent>();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
LookupFlags.Static | LookupFlags.Approximate))
{
textBuffer.Clear();
var entityName = _entityManager.ToPrettyString(uid);
textBuffer.AppendLine(entityName.Prototype);
textBuffer.Append("UID: ");
textBuffer.Append(entityName.Uid.Id);
textBuffer.Append(", NUID: ");
textBuffer.Append(entityName.Nuid.Id);
textBuffer.AppendLine();
if (!accessReader.Enabled)
if (!readerQuery.TryGetComponent(ent, out var reader) ||
!xformQuery.TryGetComponent(ent, out var xform))
{
textBuffer.AppendLine("-Disabled");
continue;
}
if (accessReader.AccessLists.Count > 0)
var text = new StringBuilder();
var index = 0;
var a = $"{_entityManager.ToPrettyString(ent)}";
text.Append(a);
foreach (var list in reader.AccessLists)
{
var groupNumber = 0;
foreach (var accessList in accessReader.AccessLists)
a = $"Tag {index}";
text.AppendLine(a);
foreach (var entry in list)
{
groupNumber++;
foreach (var entry in accessList)
{
textBuffer.Append("+Set ");
textBuffer.Append(groupNumber);
textBuffer.Append(": ");
textBuffer.Append(entry.Id);
textBuffer.AppendLine();
}
a = $"- {entry}";
text.AppendLine(a);
}
index++;
}
string textStr;
if (text.Length >= 2)
{
textStr = text.ToString();
textStr = textStr[..^2];
}
else
{
textBuffer.AppendLine("+Unrestricted");
textStr = "";
}
foreach (var key in accessReader.AccessKeys)
{
textBuffer.Append("+Key ");
textBuffer.Append(key.OriginStation);
textBuffer.Append(": ");
textBuffer.Append(key.Id);
textBuffer.AppendLine();
}
var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
foreach (var tag in accessReader.DenyTags)
{
textBuffer.Append("-Tag ");
textBuffer.AppendLine(tag.Id);
}
var accessInfoText = textBuffer.ToString();
var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
}
}
}
@@ -4,20 +4,32 @@ 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 override string Command => "showaccessreaders";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
public string Command => "showaccessreaders";
public string Description => "Shows all access readers in the viewport";
public string Help => $"{Command}";
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 lookup = entManager.System<EntityLookupSystem>();
var xform = entManager.System<SharedTransformSystem>();
overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
shell.WriteLine($"Set access reader debug overlay to true");
}
}
+3 -1
View File
@@ -2,4 +2,6 @@
namespace Content.Client.Access;
public sealed class IdCardSystem : SharedIdCardSystem;
public sealed class IdCardSystem : SharedIdCardSystem
{
}
@@ -1,39 +0,0 @@
using Content.Client.Overlays;
using Content.Shared.Access.Systems;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
namespace Content.Client.Access.Systems;
public sealed class JobStatusSystem : SharedJobStatusSystem
{
[Dependency] private readonly ShowJobIconsSystem _showJobIcons = default!;
[Dependency] private readonly ShowCrewIconsSystem _showCrewIcons = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private static readonly ProtoId<SecurityIconPrototype> CrewBorderIcon = "CrewBorderIcon";
private static readonly ProtoId<SecurityIconPrototype> CrewUncertainBorderIcon = "CrewUncertainBorderIcon";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<JobStatusComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
}
// show the status icons if the player has the correponding HUDs
private void OnGetStatusIconsEvent(Entity<JobStatusComponent> ent, ref GetStatusIconsEvent ev)
{
if (_showJobIcons.IsActive && ent.Comp.JobStatusIcon != null)
ev.StatusIcons.Add(_prototype.Index(ent.Comp.JobStatusIcon));
if (_showCrewIcons.IsActive)
{
if (_showCrewIcons.UncertainCrewBorder)
ev.StatusIcons.Add(_prototype.Index(CrewUncertainBorderIcon));
else if (ent.Comp.IsCrew)
ev.StatusIcons.Add(_prototype.Index(CrewBorderIcon));
}
}
}
@@ -1,4 +0,0 @@
<GridContainer xmlns="https://spacestation14.io"
Columns="4"
HorizontalAlignment="Center">
</GridContainer>
@@ -1,59 +0,0 @@
using System.Linq;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
namespace Content.Client.Access.UI;
[GenerateTypedNameReferences]
public sealed partial class AccessLevelControl : GridContainer
{
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
public readonly Dictionary<ProtoId<AccessLevelPrototype>, Button> ButtonsList = new();
public AccessLevelControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("accesslevelcontrol");
}
public void Populate(List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
{
foreach (var access in accessLevels)
{
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
_sawmill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
Text = accessLevel.GetAccessLevelName(),
ToggleMode = true,
};
AddChild(newButton);
ButtonsList.Add(accessLevel.ID, newButton);
}
}
public void UpdateState(
List<ProtoId<AccessLevelPrototype>> pressedList,
List<ProtoId<AccessLevelPrototype>>? enabledList = null)
{
foreach (var (accessName, button) in ButtonsList)
{
button.Pressed = pressedList.Contains(accessName);
button.Disabled = !(enabledList?.Contains(accessName) ?? true);
}
}
}
@@ -1,9 +1,7 @@
using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.AccessOverriderComponent;
@@ -25,52 +23,48 @@ namespace Content.Client.Access.UI
{
base.Open();
_window = this.CreateWindow<AccessOverriderWindow>();
RefreshAccess();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
_window.OnSubmit += SubmitData;
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
}
public override void OnProtoReload(PrototypesReloadedEventArgs args)
{
base.OnProtoReload(args);
if (!args.WasModified<AccessLevelPrototype>())
return;
RefreshAccess();
if (State != null)
_window?.UpdateState(_prototypeManager, (AccessOverriderBoundUserInterfaceState) State);
}
private void RefreshAccess()
{
List<ProtoId<AccessLevelPrototype>> accessLevels;
if (EntMan.TryGetComponent<AccessOverriderComponent>(Owner, out var accessOverrider))
{
// DeltaV - Sort Better
accessLevels = accessOverrider.AccessLevels.OrderBy(x => _prototypeManager.TryIndex<AccessLevelPrototype>(x.Id, out var access) ? access.GetAccessLevelName() : x.Id).ToList();
accessLevels = accessOverrider.AccessLevels;
accessLevels.Sort();
}
else
{
accessLevels = new List<ProtoId<AccessLevelPrototype>>();
_accessOverriderSystem.Log.Error($"No AccessOverrider component found for {EntMan.ToPrettyString(Owner)}!");
}
_window?.SetAccessLevels(_prototypeManager, accessLevels);
_window = new AccessOverriderWindow(this, _prototypeManager, accessLevels)
{
Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName
};
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
_window.OnClose += Close;
_window.OpenCentered();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (AccessOverriderBoundUserInterfaceState) state;
_window?.UpdateState(_prototypeManager, castState);
_window?.UpdateState(castState);
}
public void SubmitData(List<ProtoId<AccessLevelPrototype>> newAccessList)
public void SubmitData(List<string> newAccessList)
{
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
}
@@ -2,7 +2,7 @@
MinSize="650 290">
<BoxContainer Orientation="Vertical">
<GridContainer Columns="2">
<GridContainer Name="PrivilegedIdGrid" Columns="3" HorizontalExpand="True">
<GridContainer Columns="3" HorizontalExpand="True">
<Label Text="{Loc 'access-overrider-window-privileged-id'}" />
<Button Name="PrivilegedIdButton" Access="Public"/>
<Label Name="PrivilegedIdLabel" />
@@ -13,48 +13,52 @@ namespace Content.Client.Access.UI
[GenerateTypedNameReferences]
public sealed partial class AccessOverriderWindow : DefaultWindow
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly ISawmill _logMill = default!;
private readonly AccessOverriderBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new();
public event Action<List<ProtoId<AccessLevelPrototype>>>? OnSubmit;
public AccessOverriderWindow()
public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
{
RobustXamlLoader.Load(this);
}
IoCManager.InjectDependencies(this);
_logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels)
{
_accessButtons.Clear();
AccessLevelGrid.RemoveAllChildren();
_owner = owner;
foreach (var access in accessLevels)
{
if (!protoManager.Resolve(access, out var accessLevel))
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
_logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
Text = accessLevel.GetAccessLevelName(),
Text = GetAccessLevelName(accessLevel),
ToggleMode = true,
};
AccessLevelGrid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ =>
{
OnSubmit?.Invoke(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId<AccessLevelPrototype>(x.Key)).ToList());
};
newButton.OnPressed += _ => SubmitData();
}
}
public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUserInterfaceState state)
private static string GetAccessLevelName(AccessLevelPrototype prototype)
{
PrivilegedIdGrid.Visible = state.ShowPrivilegedIdGrid;
if (prototype.Name is { } name)
return Loc.GetString(name);
return prototype.ID;
}
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
{
PrivilegedIdLabel.Text = state.PrivilegedIdName;
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
? Loc.GetString("access-overrider-window-eject-button")
@@ -71,17 +75,15 @@ namespace Content.Client.Access.UI
if (state.MissingPrivilegesList != null && state.MissingPrivilegesList.Any())
{
var missingPrivileges = new List<string>();
List<string> missingPrivileges = new List<string>();
foreach (string tag in state.MissingPrivilegesList)
{
var privilege = Loc.GetString(protoManager.Index<AccessLevelPrototype>(tag)?.Name ?? "generic-unknown");
string privilege = Loc.GetString(_prototypeManager.Index<AccessLevelPrototype>(tag)?.Name ?? "generic-unknown");
missingPrivileges.Add(privilege);
}
MissingPrivilegesLabel.Text = state.ShowPrivilegedIdGrid ?
Loc.GetString("access-overrider-window-missing-privileges") :
Loc.GetString("access-overrider-window-missing-privileges-no-id");
MissingPrivilegesLabel.Text = Loc.GetString("access-overrider-window-missing-privileges");
MissingPrivilegesText.Text = string.Join(", ", missingPrivileges);
}
@@ -92,11 +94,18 @@ namespace Content.Client.Access.UI
button.Disabled = !interfaceEnabled;
if (interfaceEnabled)
{
// Explicit cast because Rider gives a false error otherwise.
button.Pressed = state.TargetAccessReaderIdAccessList?.Contains((ProtoId<AccessLevelPrototype>) accessName) ?? false;
button.Disabled = (!state.AllowedModifyAccessList?.Contains((ProtoId<AccessLevelPrototype>) accessName)) ?? true;
button.Pressed = state.TargetAccessReaderIdAccessList?.Contains(accessName) ?? false;
button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
}
}
}
private void SubmitData()
{
_owner.SubmitData(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
}
}
}
@@ -1,8 +1,5 @@
using Content.Shared.Access.Systems;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Access.UI
{
@@ -21,18 +18,16 @@ namespace Content.Client.Access.UI
{
base.Open();
_window = this.CreateWindow<AgentIDCardWindow>();
_window?.Dispose();
_window = new AgentIDCardWindow(this);
if (State != null)
UpdateState(State);
_window.OpenCentered();
_window.OnClose += Close;
_window.OnNameChanged += OnNameChanged;
_window.OnJobChanged += OnJobChanged;
_window.OnJobIconChanged += OnJobIconChanged;
_window.OnNumberChanged += OnNumberChanged; // DeltaV
}
// DeltaV - Add number change handler
private void OnNumberChanged(uint newNumber)
{
SendMessage(new AgentIDCardNumberChangedMessage(newNumber));
}
private void OnNameChanged(string newName)
@@ -45,9 +40,9 @@ namespace Content.Client.Access.UI
SendMessage(new AgentIDCardJobChangedMessage(newJob));
}
public void OnJobIconChanged(ProtoId<JobIconPrototype> newJobIconId)
public void OnJobIconChanged(string newJobIcon)
{
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIcon));
}
/// <summary>
@@ -62,8 +57,16 @@ namespace Content.Client.Access.UI
_window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob);
_window.SetAllowedIcons(cast.CurrentJobIconId);
_window.SetCurrentNumber(cast.CurrentNumber); // DeltaV
_window.SetAllowedIcons(cast.Icons);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window?.Dispose();
}
}
}
@@ -6,13 +6,12 @@
<LineEdit Name="NameLineEdit" />
<Label Name="CurrentJob" Text="{Loc 'agent-id-card-current-job'}" />
<LineEdit Name="JobLineEdit" />
<!-- DeltaV - Add NanoChat number field -->
<Label Name="CurrentNumber" Text="{Loc 'agent-id-card-current-number'}" />
<LineEdit Name="NumberLineEdit" PlaceHolder="#0000" />
<!-- DeltaV end -->
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
<GridContainer Name="IconGrid" Columns="10">
<!-- Job icon buttons are generated in the code -->
</GridContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'agent-id-card-job-icon-label'}"/>
<Control HorizontalExpand="True" MinSize="50 0"/>
<GridContainer Name="IconGrid" Columns="10">
<!-- Job icon buttons are generated in the code -->
</GridContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>
@@ -8,7 +8,6 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Numerics;
using System.Linq;
namespace Content.Client.Access.UI
{
@@ -18,78 +17,46 @@ namespace Content.Client.Access.UI
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _spriteSystem;
private readonly AgentIDCardBoundUserInterface _bui;
private const int JobIconColumnCount = 10;
private const int MaxNumberLength = 4; // DeltaV - Same as NewChatPopup
public event Action<string>? OnNameChanged;
public event Action<string>? OnJobChanged;
public event Action<uint>? OnNumberChanged; // DeltaV - Add event for number changes
public event Action<ProtoId<JobIconPrototype>>? OnJobIconChanged;
public AgentIDCardWindow()
public AgentIDCardWindow(AgentIDCardBoundUserInterface bui)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
_bui = bui;
NameLineEdit.OnTextEntered += e => OnNameChanged?.Invoke(e.Text);
NameLineEdit.OnFocusExit += e => OnNameChanged?.Invoke(e.Text);
JobLineEdit.OnTextEntered += e => OnJobChanged?.Invoke(e.Text);
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
// DeltaV - Add handlers for number changes
NumberLineEdit.OnTextEntered += OnNumberEntered;
NumberLineEdit.OnFocusExit += OnNumberEntered;
public void SetAllowedIcons(HashSet<string> icons)
{
IconGrid.DisposeAllChildren();
// DeltaV - Filter to only allow digits
NumberLineEdit.OnTextChanged += args =>
var jobIconGroup = new ButtonGroup();
var i = 0;
foreach (var jobIconId in icons)
{
if (args.Text.Length > MaxNumberLength)
if (!_prototypeManager.TryIndex<StatusIconPrototype>(jobIconId, out var jobIcon))
{
NumberLineEdit.Text = args.Text[..MaxNumberLength];
continue;
}
// Filter to digits only
var newText = string.Concat(args.Text.Where(char.IsDigit));
if (newText != args.Text)
NumberLineEdit.Text = newText;
};
}
// DeltaV - Add number validation and event
private void OnNumberEntered(LineEdit.LineEditEventArgs args)
{
if (uint.TryParse(args.Text, out var number) && number > 0)
OnNumberChanged?.Invoke(number);
}
// DeltaV - Add setter for current number
public void SetCurrentNumber(uint? number)
{
NumberLineEdit.Text = number?.ToString("D4") ?? "";
}
public void SetAllowedIcons(string currentJobIconId)
{
IconGrid.RemoveAllChildren();
var jobIconButtonGroup = new ButtonGroup();
var i = 0;
var icons = _prototypeManager.EnumeratePrototypes<JobIconPrototype>().Where(icon => icon.AllowSelection).ToList();
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
foreach (var jobIcon in icons)
{
String styleBase = StyleClass.ButtonOpenBoth;
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
if (modulo == 0)
styleBase = StyleClass.ButtonOpenRight;
styleBase = StyleBase.ButtonOpenRight;
else if (modulo == JobIconColumnCount - 1)
styleBase = StyleClass.ButtonOpenLeft;
styleBase = StyleBase.ButtonOpenLeft;
// Generate buttons
var jobIconButton = new Button
@@ -97,13 +64,12 @@ namespace Content.Client.Access.UI
Access = AccessLevel.Public,
StyleClasses = { styleBase },
MaxSize = new Vector2(42, 28),
Group = jobIconButtonGroup,
Pressed = currentJobIconId == jobIcon.ID,
ToolTip = jobIcon.LocalizedJobName
Group = jobIconGroup,
Pressed = i == 0,
};
// Generate buttons textures
var jobIconTexture = new TextureRect
TextureRect jobIconTexture = new TextureRect
{
Texture = _spriteSystem.Frame0(jobIcon.Icon),
TextureScale = new Vector2(2.5f, 2.5f),
@@ -111,9 +77,8 @@ namespace Content.Client.Access.UI
};
jobIconButton.AddChild(jobIconTexture);
jobIconButton.OnPressed += _ => OnJobIconChanged?.Invoke(jobIcon.ID);
jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID);
IconGrid.AddChild(jobIconButton);
i++;
}
}
@@ -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.RemoveAllChildren();
AccessLevelChecklist.RemoveAllChildren();
// 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(StyleClass.ButtonOpenLeft);
else if (_groupedAccessLevels.Count > 1 && AccessGroupList.ChildCount == (_groupedAccessLevels.Count - 1))
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenRight);
else
accessGroupButton.AddStyleClass(StyleClass.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.RemoveAllChildren();
_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,10 +1,8 @@
using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.IdCardConsoleComponent;
@@ -29,8 +27,8 @@ namespace Content.Client.Access.UI
if (EntMan.TryGetComponent<IdCardConsoleComponent>(Owner, out var idCard))
{
// DeltaV - Sort the access list
accessLevels = idCard.AccessLevels.OrderBy(x => _prototypeManager.TryIndex<AccessLevelPrototype>(x.Id, out var access) ? access.GetAccessLevelName() : x.Id).ToList();
accessLevels = idCard.AccessLevels;
accessLevels.Sort();
}
else
{
@@ -67,8 +65,14 @@ 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<string> newAccessList, string newJobPrototype)
{
if (newFullName.Length > MaxFullNameLength)
newFullName = newFullName[..MaxFullNameLength];
if (newJobTitle.Length > MaxJobTitleLength)
newJobTitle = newJobTitle[..MaxJobTitleLength];
SendMessage(new WriteToTargetIdMessage(
newFullName,
newJobTitle,
@@ -1,7 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
MinSize="650 290">
<BoxContainer Orientation="Vertical">
<!-- Privileged and target IDs, crew manifest button. -->
<GridContainer Columns="2">
<GridContainer Columns="3" HorizontalExpand="True">
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
@@ -17,7 +16,6 @@
</BoxContainer>
</GridContainer>
<Control MinSize="0 8" />
<!-- Full name and job title editing. -->
<GridContainer Columns="3" HSeparationOverride="4">
<Label Name="FullNameLabel" Text="{Loc 'id-card-console-window-full-name-label'}" />
<LineEdit Name="FullNameLineEdit" HorizontalExpand="True" />
@@ -28,19 +26,14 @@
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
</GridContainer>
<Control MinSize="0 8" />
<!-- Job preset selection, grant/revoke all access buttons. -->
<BoxContainer Margin="0 8 0 4">
<BoxContainer>
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" />
</BoxContainer>
<Control HorizontalExpand="True"/>
<BoxContainer>
<Button Name="SelectAllButton" Text="{Loc 'id-card-console-window-select-all-button'}" />
<Button Name="DeselectAllButton" Text="{Loc 'id-card-console-window-deselect-all-button'}" />
</BoxContainer>
</BoxContainer>
<!-- Individual access buttons -->
<Control Name="AccessLevelControlContainer" />
<GridContainer Columns="2">
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" />
</GridContainer>
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
<!-- Access level buttons are added here by the C# code -->
</GridContainer>
</BoxContainer>
</DefaultWindow>
@@ -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,23 +14,19 @@ 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;
private AccessLevelControl _accessButtons = new();
private readonly Dictionary<string, Button> _accessButtons = new();
private readonly List<string> _jobPrototypeIds = new();
private string? _lastFullName;
private string? _lastJobTitle;
private string? _lastJobProto;
// The job that will be picked if the ID doesn't have a job on the station.
private static ProtoId<JobPrototype> _defaultJob = "Passenger";
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
{
@@ -43,7 +37,6 @@ namespace Content.Client.Access.UI
_owner = owner;
FullNameLineEdit.OnTextEntered += _ => SubmitData();
FullNameLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxNameLength);
FullNameLineEdit.OnTextChanged += _ =>
{
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
@@ -51,7 +44,6 @@ namespace Content.Client.Access.UI
FullNameSaveButton.OnPressed += _ => SubmitData();
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
JobTitleLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxIdJobLength);
JobTitleLineEdit.OnTextChanged += _ =>
{
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
@@ -72,35 +64,43 @@ namespace Content.Client.Access.UI
JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobPrototypeIds.Count - 1);
}
SelectAllButton.OnPressed += _ =>
{
SetAllAccess(true);
SubmitData();
};
DeselectAllButton.OnPressed += _ =>
{
SetAllAccess(false);
SubmitData();
};
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
_accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons);
foreach (var (id, button) in _accessButtons.ButtonsList)
foreach (var access in accessLevels)
{
button.OnPressed += _ => SubmitData();
if (!prototypeManager.TryIndex<AccessLevelPrototype>(access, out var accessLevel))
{
_logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
Text = GetAccessLevelName(accessLevel),
ToggleMode = true,
};
AccessLevelGrid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ => SubmitData();
}
}
/// <param name="enabled">If true, every individual access button will be pressed. If false, each will be depressed.</param>
private void SetAllAccess(bool enabled)
private static string GetAccessLevelName(AccessLevelPrototype prototype)
{
foreach (var button in _accessButtons.ButtonsList.Values)
if (prototype.Name is { } name)
return Loc.GetString(name);
return prototype.ID;
}
private void ClearAllAccess()
{
foreach (var button in _accessButtons.Values)
{
if (!button.Disabled && button.Pressed != enabled)
button.Pressed = enabled;
if (button.Pressed)
{
button.Pressed = false;
}
}
}
@@ -114,12 +114,12 @@ namespace Content.Client.Access.UI
JobTitleLineEdit.Text = Loc.GetString(job.Name);
args.Button.SelectId(args.Id);
SetAllAccess(false);
ClearAllAccess();
// this is a sussy way to do this
foreach (var access in job.Access)
{
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -127,14 +127,14 @@ 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;
}
foreach (var access in groupPrototype.Tags)
{
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -184,21 +184,21 @@ namespace Content.Client.Access.UI
JobPresetOptionButton.Disabled = !interfaceEnabled;
_accessButtons.UpdateState(state.TargetIdAccessList?.ToList() ??
new List<ProtoId<AccessLevelPrototype>>(),
state.AllowedModifyAccessList?.ToList() ??
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.
// For example, a new ID from a box would have no job index.
if (jobIndex < 0)
foreach (var (accessName, button) in _accessButtons)
{
jobIndex = _jobPrototypeIds.IndexOf(_defaultJob);
button.Disabled = !interfaceEnabled;
if (interfaceEnabled)
{
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
}
}
JobPresetOptionButton.SelectId(jobIndex);
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
if (jobIndex >= 0)
{
JobPresetOptionButton.SelectId(jobIndex);
}
_lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle;
@@ -215,7 +215,7 @@ namespace Content.Client.Access.UI
FullNameLineEdit.Text,
JobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
}
}
-17
View File
@@ -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);
+155 -260
View File
@@ -1,23 +1,18 @@
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,12 +23,10 @@ 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 MetaDataSystem _metaData = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
public event Action<EntityUid>? OnActionAdded;
public event Action<EntityUid>? OnActionRemoved;
@@ -44,67 +37,114 @@ 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);
}
private void OnActionAutoHandleState(Entity<ActionComponent> ent, ref AfterAutoHandleStateEvent args)
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
{
UpdateAction(ent);
if (args.Current is not InstantActionComponentState state)
return;
BaseHandleState<InstantActionComponent>(uid, component, state);
}
public override void UpdateAction(Entity<ActionComponent> ent)
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent 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 EntityTargetActionComponentState state)
return;
component.Whitelist = state.Whitelist;
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 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.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.AutoPopulate = state.AutoPopulate;
component.Temporary = state.Temporary;
component.ItemIconStyle = state.ItemIconStyle;
component.Sound = state.Sound;
UpdateAction(uid, component);
}
protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
if (!ResolveActionData(actionId, ref action))
return;
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)
@@ -119,46 +159,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);
}
@@ -180,13 +219,13 @@ namespace Content.Client.Actions
public void LinkAllActions(ActionsComponent? actions = null)
{
if (_playerManager.LocalEntity is not { } user ||
!Resolve(user, ref actions, false))
{
return;
}
if (_playerManager.LocalEntity is not { } user ||
!Resolve(user, ref actions, false))
{
return;
}
LinkActions?.Invoke(actions);
LinkActions?.Invoke(actions);
}
public override void Shutdown()
@@ -195,24 +234,28 @@ 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)
{
if (instantAction.Event != null)
instantAction.Event.Performer = user;
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);
}
}
@@ -235,83 +278,41 @@ namespace Content.Client.Actions
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
return;
var actions = EnsureComp<ActionsComponent>(user);
ClearActions(); // DeltaV - this is needed here for mapping XX commands to work properly
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(null);
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 id = new EntProtoId(actionNode.Value);
actionId = Spawn(id);
}
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;
}
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(assignmentNode, notNullableOverride: true);
if (assignmentNode is SequenceDataNode sequenceAssignments)
foreach (var index in nodeAssignments)
{
try
{
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(sequenceAssignments, notNullableOverride: true);
foreach (var index in nodeAssignments)
{
assignments.Add(new SlotAssignment(index.Hotbar, index.Slot, actionId));
}
}
catch (Exception ex)
{
Log.Error($"Failed to parse action assignments: {ex}");
}
var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
assignments.Add(assignment);
}
AddActionDirect((user, actions), actionId);
}
AssignSlot?.Invoke(assignments);
}
// DeltaV - begin changes
/// <summary>
/// Load actions and their toolbar assignments from a file.
/// DeltaV - Load from an existing yaml stream instead
@@ -324,162 +325,56 @@ namespace Content.Client.Actions
if (stream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
return;
var actions = EnsureComp<ActionsComponent>(user);
// We need to clear previously loaded actions, otherwise they keep adding up, filling the screen
ClearActions();
ClearAssignments?.Invoke();
var assignments = new List<SlotAssignment>();
var existingActions = GetClientActions();
var existingActionsList = existingActions.ToList();
foreach (var entry in sequence.Sequence)
{
if (entry is not MappingDataNode map)
continue;
if (!map.TryGet("assignments", out var assignmentNode))
if (!map.TryGet("action", out var actionNode))
continue;
var actionId = EntityUid.Invalid;
if (map.TryGet<ValueDataNode>("action", out var actionNode))
if (!map.TryGet<ValueDataNode>("name", out var nameNode))
continue;
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
// Prevent spawning actions multiple times
var existing = existingActionsList.FirstOrNull(a =>
Name(a.Id) == nameNode.Value);
EntityUid actionId;
if (existing == null)
{
var id = new EntProtoId(actionNode.Value);
actionId = Spawn(id);
}
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));
actionId = Spawn(null);
AddComp(actionId, action);
_metaData.SetEntityName(actionId, nameNode.Value);
DirtyEntity(actionId);
AddActionDirect(user, actionId);
}
else
{
Log.Error($"Mapping actions from yamlstream had unknown action data!");
continue;
actionId = existing.Value.Id;
}
AddActionDirect((user, actions), actionId);
}
}
private void ClearActions()
{
var existingActions = GetClientActions().ToList();
foreach (var actionId in existingActions)
{
var meta = MetaData(actionId);
if (meta.EntityPrototype == null)
if (!map.TryGet("assignments", out var assignmentNode))
continue;
if (meta.EntityPrototype.ID == MappingEntityAction ||
meta.EntityPrototype.ID == "ActionMappingEraser")
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(assignmentNode, notNullableOverride: true);
foreach (var index in nodeAssignments)
{
RemoveAction(actionId.AsNullable());
var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
assignments.Add(assignment);
}
}
}
// DeltaV - end changes
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 is { Valid: true } entityUid &&
ValidateEntityTarget(user, entityUid, (uid, entity)))
{
targetEnt = 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)} requires 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);
+23 -19
View File
@@ -1,4 +1,4 @@
using Content.Client.Stylesheets;
using Content.Client.Stylesheets;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -21,12 +21,11 @@ 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)
{
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSystem;
_gameTiming = IoCManager.Resolve<IGameTiming>();
SetOnlyStyleClass(StyleClass.TooltipPanel);
SetOnlyStyleClass(StyleNano.StyleClassTooltipPanel);
BoxContainer vbox;
AddChild(vbox = new BoxContainer
@@ -37,7 +36,7 @@ namespace Content.Client.Actions.UI
var nameLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipTitle }
StyleClasses = {StyleNano.StyleClassTooltipActionTitle}
};
nameLabel.SetMessage(name);
vbox.AddChild(nameLabel);
@@ -47,16 +46,27 @@ namespace Content.Client.Actions.UI
var description = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc }
StyleClasses = {StyleNano.StyleClassTooltipActionDescription}
};
description.SetMessage(desc);
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,
StyleClasses = { StyleClass.TooltipDesc },
StyleClasses = {StyleNano.StyleClassTooltipActionCooldown},
Visible = false
});
@@ -65,14 +75,11 @@ namespace Content.Client.Actions.UI
var requiresLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc }
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
};
if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup))
return;
requiresLabel.SetMessage(markup);
requiresLabel.SetMessage(FormattedMessage.FromMarkup("[color=#635c5c]" +
requires +
"[/color]"));
vbox.AddChild(requiresLabel);
}
}
@@ -90,11 +97,8 @@ namespace Content.Client.Actions.UI
if (timeLeft > TimeSpan.Zero)
{
var duration = Cooldown.Value.End - Cooldown.Value.Start;
if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup))
return;
_cooldownLabel.SetMessage(markup);
_cooldownLabel.SetMessage(FormattedMessage.FromMarkup(
$"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]"));
_cooldownLabel.Visible = true;
}
else
+51 -291
View File
@@ -1,313 +1,73 @@
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 Content.Shared.SSDIndicator; // DeltaV - SSD time indicator
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; // DeltaV - SSD time indicator
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Administration;
internal sealed class AdminNameOverlay : Overlay
namespace Content.Client.Administration
{
private readonly AdminSystem _system;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup;
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly SharedRoleSystem _roles;
private readonly IPrototypeManager _prototypeManager;
private readonly IGameTiming _timing; // DeltaV - Add timing
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();
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
public AdminNameOverlay(
AdminSystem system,
IEntityManager entityManager,
IEyeManager eyeManager,
IResourceCache resourceCache,
EntityLookupSystem entityLookup,
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config,
SharedRoleSystem roles,
IPrototypeManager prototypeManager,
IGameTiming timing) // DeltaV - Add timing
internal sealed class AdminNameOverlay : Overlay
{
_system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_entityLookup = entityLookup;
_userInterfaceManager = userInterfaceManager;
_roles = roles;
_prototypeManager = prototypeManager;
_timing = timing; // DeltaV - Add timing
ZIndex = 200;
// Setting these to a specific ttf would break the antag symbols
_font = resourceCache.NotoStack();
_fontBold = resourceCache.NotoStack(variation: "Bold");
private readonly AdminSystem _system;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup;
private readonly Font _font;
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;
}
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
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)
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup)
{
var entity = _entityManager.GetEntity(info.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)
continue;
var aabb = _entityLookup.GetWorldAABB(entity.Value);
// if not on screen, skip
if (!aabb.Intersects(in viewport))
continue;
// Get on-screen coordinates of player
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center).Rounded();
sortable.Add((info, aabb, entity.Value, screenCoordinates));
_system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_entityLookup = entityLookup;
ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
// Draw overlays for visible players, starting from the top of the screen
foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList())
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
protected override void Draw(in OverlayDrawArgs args)
{
var playerInfo = info.Item1;
var rolePrototype = playerInfo.RoleProto == null
? null
: _prototypeManager.Index(playerInfo.RoleProto.Value);
var viewport = args.WorldAABB;
var roleName = 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))
foreach (var playerInfo in _system.PlayerList)
{
// 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;
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
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)
// Otherwise the entity can not exist yet
if (entity == null || !_entityManager.EntityExists(entity))
{
// additional entries after maximum stack size is reached will be drawn over the last entry
if (i <= _overlayStackMax - 1)
currentOffset = lineoffset + s.Item2 ;
i++;
continue;
}
// if not on the same map, continue
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != _eyeManager.CurrentMap)
{
continue;
}
var aabb = _entityLookup.GetWorldAABB(entity.Value);
// if not on screen, continue
if (!aabb.Intersects(in viewport))
{
continue;
}
var lineoffset = new Vector2(0f, 11f);
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
if (playerInfo.Antag)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed);
}
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
}
// Character name
var color = Color.Aquamarine;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
// Username
color = Color.Yellow;
color.A = alpha;
var usernameSpace = args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? color : colorDisconnected); // DeltaV - store return value
// DeltaV - add watchlist suffix START
if (playerInfo.Watchlisted)
{
color = Color.Red;
color.A = alpha;
args.ScreenHandle.DrawString(_fontBold,
screenCoordinates + currentOffset + usernameSpace with { Y = 0 },
" " + Loc.GetString("admin-overlay-watchlisted-username-suffix"),
uiScale,
color);
}
// DeltaV - add watchlist suffix END
currentOffset += lineoffset; // DeltaV - moved down from username block
// Playtime
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && _overlayPlaytime)
{
color = Color.Orange;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
}
// Job
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && _overlayStartingJob)
{
color = Color.GreenYellow;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.StartingJob, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
}
// DeltaV - SSD Time START
if (_entityManager.TryGetComponent<SSDIndicatorComponent>(entity, out var ssdIndicator)
&& ssdIndicator.SsdSince is {} ssdSince)
{
color = Color.MediumPurple;
color.A = alpha;
var ssdText = Loc.GetString("admin-overlay-ssd-time", ("time", (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss")));
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, ssdText, uiScale, color);
currentOffset += lineoffset;
}
// DeltaV END
// Determine antag symbol
string? symbol;
switch (_overlaySymbolStyle)
{
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;
}
// Determine antag/role type name
string? text;
switch (_overlayFormat)
{
case AdminOverlayAntagFormat.Roletype:
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? Loc.GetString(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;
}
// 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, NetworkedComponent]
public sealed partial class HeadstandComponent : SharedHeadstandComponent
{
}
@@ -0,0 +1,8 @@
using Content.Shared.Administration.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components;
[NetworkedComponent, RegisterComponent]
public sealed partial class KillSignComponent : SharedKillSignComponent
{ }
@@ -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);
}
@@ -127,15 +126,12 @@ namespace Content.Client.Administration.Managers
public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
{
if (uid == _player.LocalEntity && (_adminData?.Active ?? includeDeAdmin))
return _adminData;
return null;
return uid == _player.LocalEntity ? _adminData : null;
}
public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false)
{
if (_player.LocalUser == session.UserId && (_adminData?.Active ?? includeDeAdmin))
if (_player.LocalUser == session.UserId)
return _adminData;
return null;
@@ -1,15 +0,0 @@
namespace Content.Client.Administration;
public enum AdminOverlayAntagFormat
{
Binary,
Roletype,
Subtype
}
public enum AdminOverlayAntagSymbolStyle
{
Off,
Basic,
Specific
}
@@ -1,11 +1,6 @@
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;
using Robust.Shared.Timing; // DeltaV - SSD time indicator
namespace Content.Client.Administration.Systems
{
@@ -16,11 +11,6 @@ namespace Content.Client.Administration.Systems
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV - added for SSD time indicator
private AdminNameOverlay _adminNameOverlay = default!;
@@ -29,17 +19,7 @@ namespace Content.Client.Administration.Systems
private void InitializeOverlay()
{
_adminNameOverlay = new AdminNameOverlay(
this,
EntityManager,
_eyeManager,
_resourceCache,
_entityLookup,
_userInterfaceManager,
_configurationManager,
_roles,
_proto,
_timing); // DeltaV - Add timing
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}
@@ -55,8 +35,7 @@ namespace Content.Client.Administration.Systems
public void AdminOverlayOn()
{
if (_overlayManager.HasOverlay<AdminNameOverlay>())
return;
if (_overlayManager.HasOverlay<AdminNameOverlay>()) return;
_overlayManager.AddOverlay(_adminNameOverlay);
OverlayEnabled?.Invoke();
}
@@ -1,6 +1,3 @@
using Content.Shared.Administration;
using Content.Shared.Administration.Managers;
using Content.Shared.Mind.Components;
using Content.Shared.Verbs;
using Robust.Client.Console;
using Robust.Shared.Utility;
@@ -14,12 +11,10 @@ namespace Content.Client.Administration.Systems
{
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
[Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
[Dependency] private readonly ISharedAdminManager _admin = default!;
public override void Initialize()
{
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs);
}
private void AddAdminVerbs(GetVerbsEvent<Verb> args)
@@ -32,30 +27,12 @@ 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.
};
args.Verbs.Add(verb);
}
if (!_admin.IsAdmin(args.User))
return;
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
args.ExtraCategories.Add(VerbCategory.Admin);
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun) && HasComp<MindContainerComponent>(args.Target))
args.ExtraCategories.Add(VerbCategory.Antag);
if (_admin.HasAdminFlag(args.User, AdminFlags.Debug))
args.ExtraCategories.Add(VerbCategory.Debug);
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun))
args.ExtraCategories.Add(VerbCategory.Smite);
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
args.ExtraCategories.Add(VerbCategory.Tricks);
}
}
}
@@ -1,7 +0,0 @@
using Content.Shared.Administration.Systems;
namespace Content.Client.Administration.Systems;
public sealed class BufferingSystem : SharedBufferingSystem
{
}
@@ -19,11 +19,11 @@ namespace Content.Client.Administration.Systems
OnBwoinkTextMessageRecieved?.Invoke(this, message);
}
public void Send(NetUserId channelId, string text, bool playSound, bool adminOnly)
public void Send(NetUserId channelId, string text, bool playSound)
{
// Reuse the channel ID as the 'true sender'.
// Server will ignore this and if someone makes it not ignore this (which is bad, allows impersonation!!!), that will help.
RaiseNetworkEvent(new BwoinkTextMessage(channelId, channelId, text, playSound: playSound, adminOnly: adminOnly));
RaiseNetworkEvent(new BwoinkTextMessage(channelId, channelId, text, playSound: playSound));
SendInputTextUpdated(channelId, false);
}
@@ -1,4 +1,4 @@
using Content.Shared.Administration.Components;
using Content.Client.Administration.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Administration.Systems;
@@ -1,77 +1,44 @@
using System.Numerics;
using Content.Shared.Administration.Components;
using Content.Client.Administration.Components;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Utility;
namespace Content.Client.Administration.Systems;
public sealed class KillSignSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IPlayerManager _player = default!;
public override void Initialize()
{
SubscribeLocalEvent<KillSignComponent, ComponentStartup>(KillSignAdded);
SubscribeLocalEvent<KillSignComponent, ComponentShutdown>(KillSignRemoved);
SubscribeLocalEvent<KillSignComponent, AfterAutoHandleStateEvent>(AfterAutoHandleState);
}
private void KillSignRemoved(Entity<KillSignComponent> ent, ref ComponentShutdown args)
private void KillSignRemoved(EntityUid uid, KillSignComponent component, ComponentShutdown args)
{
RemoveKillsign(ent);
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (!sprite.LayerMapTryGet(KillSignKey.Key, out var layer))
return;
sprite.RemoveLayer(layer);
}
private void KillSignAdded(Entity<KillSignComponent> ent, ref ComponentStartup args)
private void KillSignAdded(EntityUid uid, KillSignComponent component, ComponentStartup args)
{
AddKillsign(ent);
}
private void AfterAutoHandleState(Entity<KillSignComponent> ent, ref AfterAutoHandleStateEvent args)
{
// After receiving a new state for the component, we remove the old killsign and build a new one.
// This is so changes to the sprite can be displayed live and allowing them to be edited via ViewVariables.
// This could just update an existing sprite, but this is both easier and runs rarely anyway.
RemoveKillsign(ent);
AddKillsign(ent);
}
private void AddKillsign(Entity<KillSignComponent> ent)
{
// If we hide from owner and we ARE the owner, don't add a killsign.
// This could use session specific networking to FULLY hide it, but I am too lazy right now.
if (ent.Comp.HideFromOwner && _player.LocalEntity == ent)
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (!TryComp<SpriteComponent>(ent, out var sprite))
if (sprite.LayerMapTryGet(KillSignKey.Key, out var _))
return;
if (_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var _, false))
return;
var adj = sprite.Bounds.Height / 2 + ((1.0f/32) * 6.0f);
if (ent.Comp.Sprite == null)
return;
var layer = sprite.AddLayer(new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
sprite.LayerMapSet(KillSignKey.Key, layer);
var adj = _sprite.GetLocalBounds((ent, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
var layer = _sprite.AddLayer((ent, sprite), ent.Comp.Sprite);
_sprite.LayerMapSet((ent, sprite), KillSignKey.Key, layer);
_sprite.LayerSetScale((ent, sprite), layer, ent.Comp.Scale);
_sprite.LayerSetOffset((ent, sprite), layer, ent.Comp.DoOffset ? new Vector2(0.0f, adj) : new Vector2(0.0f, 0.0f));
if (ent.Comp.ForceUnshaded)
sprite.LayerSetShader(layer, "unshaded");
}
private void RemoveKillsign(Entity<KillSignComponent> ent)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (!_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var layer, false))
return;
_sprite.RemoveLayer((ent, sprite), layer);
sprite.LayerSetOffset(layer, new Vector2(0.0f, adj));
sprite.LayerSetShader(layer, "unshaded");
}
private enum KillSignKey
@@ -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,5 +0,0 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc admin-camera-window-title-placeholder}"
SetSize="425 550"
MinSize="200 225">
</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));
}
}
@@ -3,52 +3,34 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI;
[GenerateTypedNameReferences]
public sealed partial class AdminMenuWindow : DefaultWindow
namespace Content.Client.Administration.UI
{
public event Action? OnDisposed;
public AdminMenuWindow()
[GenerateTypedNameReferences]
public sealed partial class AdminMenuWindow : DefaultWindow
{
MinSize = new Vector2(650, 250);
Title = Loc.GetString("admin-menu-title");
RobustXamlLoader.Load(this);
MasterTabContainer.SetTabTitle((int) TabIndex.Admin, Loc.GetString("admin-menu-admin-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Adminbus, Loc.GetString("admin-menu-adminbus-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Atmos, Loc.GetString("admin-menu-atmos-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Round, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Server, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.PanicBunker, Loc.GetString("admin-menu-panic-bunker-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Players, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle((int) TabIndex.Objects, Loc.GetString("admin-menu-objects-tab"));
MasterTabContainer.OnTabChanged += OnTabChanged;
}
public event Action? OnDisposed;
private void OnTabChanged(int tabIndex)
{
var tabEnum = (TabIndex)tabIndex;
if (tabEnum == TabIndex.Objects)
ObjectsTabControl.RefreshObjectList();
}
public AdminMenuWindow()
{
MinSize = new Vector2(650, 250);
Title = Loc.GetString("admin-menu-title");
RobustXamlLoader.Load(this);
MasterTabContainer.SetTabTitle(0, Loc.GetString("admin-menu-admin-tab"));
MasterTabContainer.SetTabTitle(1, Loc.GetString("admin-menu-adminbus-tab"));
MasterTabContainer.SetTabTitle(2, Loc.GetString("admin-menu-atmos-tab"));
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
}
protected override void Dispose(bool disposing)
{
OnDisposed?.Invoke();
base.Dispose(disposing);
OnDisposed = null;
}
private enum TabIndex
{
Admin = 0,
Adminbus,
Atmos,
Round,
Server,
PanicBunker,
Players,
Objects,
protected override void Dispose(bool disposing)
{
OnDisposed?.Invoke();
base.Dispose(disposing);
OnDisposed = null;
}
}
}
@@ -1,4 +1,4 @@
using Content.Shared.Administration.Notes;
using Content.Shared.Administration.Notes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
@@ -13,7 +13,7 @@ public sealed partial class AdminMessagePopupMessage : Control
{
RobustXamlLoader.Load(this);
Admin.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString(
Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
"admin-notes-message-admin",
("admin", message.AdminName),
("date", message.AddedOn.ToLocalTime()))));
@@ -7,7 +7,7 @@
</PanelContainer.PanelOverride>
<Control HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="600">
<PanelContainer StyleClasses="BackgroundPanel" />
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical" Margin="4">
<RichTextLabel Name="Description" />
@@ -12,19 +12,17 @@ namespace Content.Client.Administration.UI.AdminRemarks;
[GenerateTypedNameReferences]
public sealed partial class AdminMessagePopupWindow : Control
{
[Dependency] private readonly IStylesheetManager _styleMan = default!;
private float _timer = float.MaxValue;
public event Action? OnDismissPressed;
public event Action? OnAcceptPressed;
public AdminMessagePopupWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Stylesheet = _styleMan.SheetSystem;
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
AcceptButton.OnPressed += OnAcceptButtonPressed;
DismissButton.OnPressed += OnDismissButtonPressed;
@@ -51,8 +49,7 @@ public sealed partial class AdminMessagePopupWindow : Control
MessageContainer.AddChild(new AdminMessagePopupMessage(message));
}
Description.SetMessage(
FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
}
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)

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