Compare commits

..
Author SHA1 Message Date
KOTOBandpre-commit-ci[bot] d8cfd70cc8 Akashic Fold Part 0 (#4350)
* lowkey might have messed up lighting

* make it awesome

uncopy code steamhappy

* yuh!

* goidagen

todo: steal EVERYTHING from goob

* goidamerge my beloved

* 1984 goidamerge (for now)

i dont think it worked properly anyways (?)

* chromite weather ops

* sobbing i hate thsi...

* Revert "sobbing i hate thsi..."

This reverts commit 5dff16743f.

* temp base fix

* the great culling

* untouch upstream

* the recommenting

* the great reindenting

* miniscule tweakops

* lgtm

* okay for real this time

* broken lockers

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* got eaten my my revert guh

* i am blind

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-10-05 08:07:51 +11:00
16978 changed files with 809083 additions and 1990393 deletions
+1 -3
View File
@@ -77,8 +77,6 @@ csharp_style_expression_bodied_methods = false:suggestion
#csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_namespace_declarations = file_scoped:suggestion
# Pattern matching preferences
#csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
#csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
@@ -351,7 +349,7 @@ 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]
+12 -8
View File
@@ -7,7 +7,14 @@
/Resources/ @DeltaV-Station/maintainers
# Server config files
/Resources/ConfigPresets/ @Toby222
/Resources/ConfigPresets/ @deltanedas
# YML files
/Resources/*.yml @DeltaV-Station/yaml-maintainers
/Resources/**/*.yml @DeltaV-Station/yaml-maintainers
# Sprites - Direction in lack of art director
/Resources/Textures/ @DeltaV-Station/direction
# Lobby art and music - automatically direction issues since its immediately visible to players
/Resources/Audio/Lobby/ @DeltaV-Station/direction
@@ -19,16 +26,13 @@
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers
# Server rules
/Resources/ServerInfo/Guidebook/_DV/Rules/ @DeltaV-Station/head-administrators
# Wiki
/.wiki/ @DeltaV-Station/direction
/Resources/ServerInfo/Guidebook/DeltaV/Rules/ @DeltaV-Station/head-administrators
# Tools and scripts
/Tools/ @Toby222
/Tools/ @deltanedas
# Workflows, codeowners, templates, etc.
/.github/ @Toby222
/.github/ @deltanedas
# Standalone files in the root repo
/* @Toby222
/* @deltanedas
+6 -12
View File
@@ -10,28 +10,22 @@
<!-- Summary of code changes for easier review. -->
## Media
<!-- Attach media if the PR makes in-game changes (clothing, items, features, etc).
<!-- Attach media if the PR makes ingame changes (clothing, items, features, etc).
Small fixes/refactors are exempt. -->
## Requirements
<!-- Confirm the following by placing an X in the brackets without spaces inside (for example: [X] ): -->
<!-- Confirm the following by placing an X in the brackets: [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.
- [ ] I have added media to this PR or it does not require an ingame showcase.
<!-- You should understand that not following the above may get your PR closed at maintainer’s discretion -->
## 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 -->
## Breaking changes
<!-- List any breaking changes, including namespaces, public class/method/field changes, prototype renames; and provide instructions for fixing them. -->
**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. -->
Changelogs must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog. -->
<!--
:cl:
- add: Added fun!
-4
View File
@@ -42,7 +42,3 @@
"Changes: Audio":
- changed-files:
- any-glob-to-any-file: '**/*.ogg'
"Changes: Wiki":
- changed-files:
- any-glob-to-any-file: '.wiki/_DV/**/*.txt'
+2 -11
View File
@@ -12,31 +12,22 @@ jobs:
- name: Setup submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
- name: Update Engine Submodules
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
dotnet-version: 9.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
+4 -12
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 ]
@@ -24,11 +24,7 @@ jobs:
- name: Setup Submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
@@ -36,21 +32,17 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
dotnet-version: 9.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
+3 -11
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 ]
@@ -24,10 +24,6 @@ jobs:
- name: Setup Submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
@@ -36,21 +32,17 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
dotnet-version: 9.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
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0
+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@v4
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@v4
with:
node-version: 22.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
+30 -1
View File
@@ -2,11 +2,19 @@
on:
pull_request_target:
types: [review_requested, opened]
types: [review_requested]
pull_request_review:
types: [submitted]
permissions:
pull-requests: write
issues: write
contents: write
jobs:
add_label:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_target'
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
@@ -14,3 +22,24 @@ jobs:
- uses: actions-ecosystem/action-remove-labels@v1
with:
labels: "S: Awaiting Changes"
# Doesn't work on prs from foreign repos currently
#handle_review:
# runs-on: ubuntu-latest
# if: github.event_name == 'pull_request_review'
# steps:
# - uses: actions-ecosystem/action-add-labels@v1
# if: |
# github.event.review.state == 'changes_requested' &&
# (github.event.review.author_association == 'OWNER' ||
# github.event.review.author_association == 'MEMBER')
# with:
# labels: "S: Awaiting Changes"
#
# - uses: actions-ecosystem/action-remove-labels@v1
# if: |
# github.event.review.state == 'changes_requested' &&
# (github.event.review.author_association == 'OWNER' ||
# github.event.review.author_association == 'MEMBER')
# with:
# labels: "S: Needs Review"
-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"
+3 -19
View File
@@ -7,7 +7,7 @@ concurrency:
on:
workflow_dispatch:
schedule:
- cron: '45 16 * * *'
- cron: '0 10 * * *'
jobs:
build:
@@ -20,23 +20,16 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
dotnet-version: 9.0.x
- name: Get Engine Tag
run: |
cd RobustToolbox
git fetch --depth=1
- name: Get Engine Tag
run: |
cd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
- name: Install dependencies
run: dotnet restore
@@ -44,7 +37,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
@@ -61,15 +54,6 @@ jobs:
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
-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
+3 -11
View File
@@ -2,7 +2,7 @@
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
paths:
- '**.cs'
- '**.csproj'
@@ -39,10 +39,6 @@ jobs:
- name: Setup Submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
@@ -51,15 +47,11 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
@@ -68,7 +60,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
+5 -18
View File
@@ -34,19 +34,17 @@ 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
@@ -54,14 +52,3 @@ jobs:
body: This is an automated Pull Request. This PR updates the github contributors in the credits section.
author: DeltaV-Bot <github@deltav.gay>
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 }}
+1 -1
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 ]
+1 -1
View File
@@ -2,7 +2,7 @@ name: RSI Validator
on:
push:
branches: [ master ]
branches: [ staging, trying ]
merge_group:
pull_request:
paths:
+1 -1
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 ]
+2 -10
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 ]
@@ -17,24 +17,16 @@ jobs:
- name: Setup submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
- name: Update Engine Submodules
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
dotnet-version: 9.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.
-58
View File
@@ -260,62 +260,4 @@ Emergency alert status''. The station's deep-spectrum sensors have identified cr
| 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}}
-131
View File
@@ -129,137 +129,6 @@ Should any person or party aboard a NanoTrasen vessel be found to be currently a
== 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.
+54 -56
View File
@@ -4,17 +4,15 @@ Space Law is a broad set of rules and regulations, collected and regulated by Te
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#Sophonts and Synthetics|Sophonts & Synthetics]]:''' Sophonts are entities are able to utilize a structured language, communicate through the use of written, verbal, and/or body language (or by other means), and possess reasoning and introspection. Sophonts are legal persons, able to press charges under Space Law, and can be victims of certain crimes that specify sophont status. An entity's status as a sophont may be challenged during legal proceedings; the Defense and/or Prosecution must present evidence arguing that it meets, or does not meet, the criteria established above. The Judge will then arbitrate on the entity's status. Any synthetic, sillicon, or nonorganic entity with non-sophont status is to be considered a Synthetic. Synthetics are the property of the entity that constructs or employs them, unless otherwise stated. Damage or destruction of such Synthetics is considered vandalism of the entity's property.
* '''[[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.
* '''[[Standard Operating Procedure#Detainment and Arrest|Detainment & Arrest]]:''' Members of law enforcement may detain sophonts under '''[[Standard Operating Procedure#Probable Cause|Probable Cause]]''' of criminal activity. These detainees must be informed of the crime they are suspected of committing, and may be moved to a secure location and/or restrained while law enforcement determine if a crime was committed. Such detention should last no longer than five minutes. Any sophont 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|Sentencing]]:''' Any member of law enforcement is allowed to pass sentence on Misdemeanor, Felony, and Grand Felony charges. The Prosecutor is allowed to adjust such sentences, within reason.
* '''[[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 Extended 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. A parole hearing requires only a Judge, as discussed within Trial Procedure. 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 sophont may press charges against any other sophont. Individuals may only be charged with a crime if it can be proveny beyond reasonable doubt that they have committed the crime.
* '''[[Standard Operating Procedure#Trial Procedure|Trial Procedure]]:''' A criminal trial’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. If insufficient crew are available, the Judge is fully authorized and strongly encouraged to pass a sentence of Extended Confinement until a proper trial can be held.
* '''[[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 disallow openly carrying firearms by non-Security 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:
@@ -27,7 +25,7 @@ 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.
For example, an attack upon a sophont 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
@@ -121,27 +119,27 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
=== 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.
:**Any sophont found guilty of encouraging, bribing, requesting, or instructing someone to commit a crime, with the intent that the sophont 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.
:**Any sophont 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.
:**Any sophont 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.
:**Any sophont 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.
:**Any sophont 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.
:**Any sophont 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.'''
:**Any sophont 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.
:**Any sophont 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.
:**Any sophont 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.
:**Any sophont 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.
@@ -159,13 +157,13 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| 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;" | To maliciously commit an act resulting in the death of a sophont, 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;" | 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 entity to act or abstain from acting.
|-
| style="border: 1px solid black;" | 403
| style="border: 1px solid black;" | [[File:SL_GrandSabotage.png]]
@@ -177,7 +175,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| 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;" | To commit an act that prevents the revival of a sophont by standard medical care following their death.
|-
| style="border: 1px solid black;" | 405
| style="border: 1px solid black;" | [[File:SL_Sedition.png]]
@@ -204,49 +202,49 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|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;"| 16 minutes
|style= "border:1px solid black;"| To act without malice in a manner which, directly or indirectly, leads to the death of a sophont.
|-
|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;"| 16 minutes
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an entity against its 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;" | 15-25 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;" | 14 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;" | 14 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;" | 14 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 entity.
|-
| 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;" | 12 minutes
| style="border:1px solid black;" | To deprive an entity 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;" | 12 minutes
| style="border:1px solid black;" | To sell, distribute, or otherwise circulate Controlled Items to unauthorized entities.
|}
----
@@ -262,56 +260,56 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| 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;" | 10 minutes
| style="border: 1px solid black;" | To cause physical harm or to effect unwanted physical contact on a sophont, 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;" | 10 minutes
| style="border: 1px solid black;" | To break and enter into a high security area where one is not authorised nor invited, with intent to commit a crime within.
|-
| 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;" | 8 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;" | 8 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;" | 5-10 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;" | 8 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;" | 6 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;" | 6 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.
|style= "border:1px solid black;"| To maliciously assume the identity of an entity, such as through writing, attire, and/or speech.
|}
@@ -329,14 +327,14 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| 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;" | 5 minutes
| style="border: 1px solid black;" | To inflict unnecessary suffering on a sapient being 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;"| 5 minutes
|style= "border:1px solid black;"| To verbally demean, humiliate, or harass an entity with malice; or maliciously spread false information amongst a population.
|-
|style= "border:1px solid black;"| 103
|style= "border:1px solid black;"| WIP
@@ -347,20 +345,20 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| 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;" | 4 minutes
| style="border: 1px solid black;" | To deprive an entity 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;" | 3 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;" | 3 minutes
| style="border: 1px solid black;" | To deface or superficially damage public property, or property belonging to another person.
|-
| style="border: 1px solid black;" | 107
| style="border: 1px solid black;" | [[File:SL_Hooliganism.png]]
+33 -88
View File
@@ -3,63 +3,21 @@ This page covers the standardized guidelines and procedures aboard the station,
== '''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.
:*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.
:* Crew: All sophonts employed by Station Command, or by NT to reside on the station and perform an established function therein. This includes any sophont 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 indivudal legally appointed to preside over a hearing, as specified within Hearing Procedure.
:* Detainee: Any sophont whose free movement has been curtailed by law enforcement on suspicion of criminal activity. They have not, however, been officially charged with a crime.
:* Arrestee: Any sophont who has been formally charged with a crime.
:* Prisoner: Any sophont 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.
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 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. Irrevocable destruction of a Synthetic may leave the offender liable to the crime of Grand 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.
==Hiring Procedure==
While Nanotrasen handles the staffing and recruiting needs of the station, Station Command is additionally authorized to hire any sophont into their respective department, as well as terminate the employment of any crew in their respective department per company policy. For a member of Station Command to personally employ a sophont into their department, a contract must be written stating the name of the employee, the department of employment, the position, and finally bearing the stamp of the Station Command member.
=Controlled Items, Substances, & Areas=
== Controlled Items ==
@@ -79,7 +37,6 @@ Controlled Items include any item that is restricted from general possession and
* 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.
@@ -101,27 +58,27 @@ A list of such Substances follow:
== 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.
Controlled Armaments are grouped into categories of increasing severity.
'''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.''
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 5min brig for each separate and distinct item, confiscation of the offending item(s), 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.''
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 10min brig for each separate and distinct item, confiscation of the offending item(s), 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.''
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 15min brig for each separate and distinct item, confiscation of the offending item(s), 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.''
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 20min brig for each separate and distinct item, confiscation of the offending item(s), and further legal action as the situation demands.''
* Any semi-automatic ballistic arms that are easily concealable
* Any fully-automatic ballistic arms
@@ -129,7 +86,7 @@ Controlled Armaments are grouped into categories of increasing severity. If the
* 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.''
'''Category E:''' ''Illegal possession of Cat. E controlled armaments may incur a sentence of up to 25min brig for each separate and distinct item, confiscation of the offending item(s), 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
@@ -152,7 +109,7 @@ Unlocked crates, orders stored outside the department, and supplies illegally di
=== 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.
* Any sophont 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.
@@ -179,7 +136,16 @@ Common HSAs for commercial and military vessels are as follows:
* Telecommunications\Telemetry
* Permanent Brig\Holding Cells
== Sophonts and Synthetics ==
To be defined as a sophont, a being must be able to utilize a structured language, communicate through the use of written, verbal, and/or body language (or by other means), and possess reasoning and introspection.
Any such being is considered a sophont, and thus a legal person, regardless of their make, origin, disposition, or prior cognition. As part of their rights, they are to be governed under the same laws as any other sophont, and given the ability to press charges as a sophont.
The unauthorised jailbreaking or uplifting of property into the status of Sophont is still a criminal offense however, and those found guilty of doing so may be found guilty of sabotage or vandalism.
An entity’s status as a sophont may be challenged during the process of charging the individual for crimes which utilize the term sophont in the legal language of the crime. The Defense or Prosecution will present evidence indicating that it meets, or does not meet, the criteria above. The Judge will then arbitrate upon the entity's status as a sophont.
Any synthetic/silicon/inorganic entity with a non-sophont status, is to be classified as a standard synthetic entity. Synthetic entities are the property of the party that constructs or employs the entity, unless otherwise stated by relevant contract or legal document. To damage or destroy such an entity is to damage or destroy the aforementioned party’s property.
=Commanding Officer=
==Captain’s Authority==
The Commanding Officer of a station or vessel is afforded several rights, broadly referred to as "Captain's Authority".
@@ -218,7 +184,7 @@ Should the mutiny be unsuccessful, only the head officer(s) who approved the mut
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.
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 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.
@@ -243,7 +209,7 @@ Law enforcement personnel hold a Category D Armaments License, and are authorize
* 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.
…wherein an “adversary” is an entity 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]].
@@ -253,7 +219,7 @@ Martial Law may only be declared by Central Command. Under Martial Law Security
* 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.
* not obligated to provide any medical care to prisoners and detainees, 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]].
@@ -269,6 +235,7 @@ Prisoners have certain rights that must be upheld by law enforcement and Station
* 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.
* Power for silicon prisoners such as IPCs and cyborgs, at minimum a cyborg recharging station must be provided.
* 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.
@@ -283,7 +250,7 @@ Should prisoners repeatedly and maliciously antagonize law enforcement, to an ex
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.
Any sophont, 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.
@@ -297,7 +264,7 @@ Appeals are handled by the Chief Justice. Attorneys may appeal a past ruling on
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.
A pardon may be issued to any sophont 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.
@@ -323,7 +290,7 @@ A hearing must be held for capital charges, and for sentences that exceed a tota
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.
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 sophont 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.
@@ -372,25 +339,3 @@ In order for a medical evaluation to be considered lawfully applicable, the CMO
==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.
+21 -19
View File
@@ -1,19 +1,17 @@
#!/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 os
import shutil
import time
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 +25,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 +43,7 @@ def update_submodules():
Updates all submodules.
"""
if 'GITHUB_ACTIONS' in os.environ:
if ('GITHUB_ACTIONS' in os.environ):
return
if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
@@ -76,21 +76,22 @@ def install_hooks():
print("No hooks change detected.")
return
with open("INSTALLED_HOOKS_VERSION", "w") as f:
f.write(CURRENT_HOOKS_VERSION)
print("Hooks need updating.")
hooks_target_dir = Path(run_command(["git", "rev-parse", "--git-path", "hooks"], True).stdout.strip())
hooks_target_dir = Path("..")/".git"/"hooks"
hooks_source_dir = Path("hooks")
# Clear entire tree since we need to kill deleted files too.
for filename in os.listdir(hooks_target_dir):
os.remove(hooks_target_dir / filename)
for filename in os.listdir(str(hooks_target_dir)):
os.remove(str(hooks_target_dir/filename))
for filename in os.listdir(hooks_source_dir):
for filename in os.listdir(str(hooks_source_dir)):
print("Copying hook {}".format(filename))
shutil.copy2(hooks_source_dir / filename, hooks_target_dir / filename)
with open("INSTALLED_HOOKS_VERSION", "w") as f:
f.write(CURRENT_HOOKS_VERSION)
shutil.copy2(str(hooks_source_dir/filename),
str(hooks_target_dir/filename))
# Begin DeltaV Additions - add pre-commit hooks if you have it installed
if shutil.which("pre-commit") is not None:
@@ -110,7 +111,8 @@ def reset_solution():
def check_for_zip_download():
# Check if .git exists,
if run_command(["git", "rev-parse"]).returncode != 0:
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if not os.path.isdir(os.path.join(cur_dir, ".git")):
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"
+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"
+38 -150
View File
@@ -22,173 +22,61 @@ Examples:
# 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)
If you make a change to an upstream C# or YAML file **you must add comments on or around the changed lines**.
The comments should clarify what changed, to make conflict resolution simpler when a file is changed upstream.
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.
For YAML specifically, if you add a new component to a prototype add the comment to the `type: ...` line.
If you just modify some fields of a component, comment the fields instead.
### Changing Upstream YAML .yml files
For C# files, if you are adding a lot of code try to put it in a partial class when it makes sense.
**Add comments on or around any changed lines.**
The exception to this is early merging commits that are going to be cherry picked in the future regardless, there's no harm in leaving them as-is.
If you add a new component to a prototype, add an explanation to the `type: ...` line. Example:
As an aside, fluent (.ftl) files **do not support comments on the same line** as a locale value, so be careful when changing them.
## Examples of comments in upstream files
A single line comment on a changed yml field:
```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
parent: BasePDA
id: SciencePDA
name: epistemics PDA # DeltaV - Epistemics Department replacing Science
```
Whereas if you just modify some fields of a component, comment the fields instead, using inline or block comments. Examples:
A pair of comments enclosing a list of added items to starting gear:
```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
storage:
back:
- EmergencyRollerBedSpawnFolded
# Begin DeltaV additions
- id: WeaponEnergyShotgun
- id: BoxPDAPrisoner
- id: LunchboxSecurityFilledRandom
prob: 0.3
- BodyBagFolded
- Portafib
# 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.
A comment on a new imported namespace:
```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
using Content.Server.Psionics.Glimmer; // DeltaV
```
> * 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:
A pair of comments enclosing a block of added code:
```cs
// BEGIN DeltaV - Remove innate radio and radios from pockets
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
private EntityUid Slice(...)
{
if (_inventory.TryGetSlotEntity(target, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
_inventory.TryUnequip(target, $"pocket{i}", true, true);
...
_transform.SetLocalRotation(sliceUid, 0);
// DeltaV - start of deep frier stuff
var slicedEv = new FoodSlicedEvent(user, uid, sliceUid);
RaiseLocalEvent(uid, ref slicedEv);
// DeltaV - end of deep frier stuff
...
}
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.
@@ -204,11 +92,11 @@ Please limit changelogs on map PRs to **significant** map alterations or additio
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!
MAPS: Mapname
- add: Added fun!
- remove: Removed fun!
- tweak: Changed fun!
- fix: Fixed fun!
```
# Before you submit
+9 -14
View File
@@ -1,20 +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>
<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" />
@@ -22,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.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Benchmarks;
/// <summary>
/// Spawns N number of entities with a <see cref="DeltaPressureComponent"/> and
/// simulates them for a number of ticks M.
/// </summary>
[Virtual]
[GcServer(true)]
//[MemoryDiagnoser]
//[ThreadingDiagnoser]
public class DeltaPressureBenchmark
{
/// <summary>
/// Number of entities (windows, really) to spawn with a <see cref="DeltaPressureComponent"/>.
/// </summary>
[Params(1, 10, 100, 1000, 5000, 10000, 50000, 100000)]
public int EntityCount;
/// <summary>
/// Number of entities that each parallel processing job will handle.
/// </summary>
// [Params(1, 10, 100, 1000, 5000, 10000)] For testing how multithreading parameters affect performance (THESE TESTS TAKE 16+ HOURS TO RUN)
[Params(10)]
public int BatchSize;
/// <summary>
/// Number of entities to process per iteration in the DeltaPressure
/// processing loop.
/// </summary>
// [Params(100, 1000, 5000, 10000, 50000)]
[Params(1000)]
public int EntitiesPerIteration;
private readonly EntProtoId _windowProtoId = "Window";
private readonly EntProtoId _wallProtoId = "WallPlastitaniumIndestructible";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private SharedMapSystem _map = default!;
private IRobustRandom _random = default!;
private IConfigurationManager _cvar = default!;
private ITileDefinitionManager _tileDefMan = default!;
private AtmosphereSystem _atmospereSystem = default!;
private Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>
_testEnt;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
var mapdata = await _pair.CreateTestMap();
_entMan = server.ResolveDependency<IEntityManager>();
_map = _entMan.System<SharedMapSystem>();
_random = server.ResolveDependency<IRobustRandom>();
_cvar = server.ResolveDependency<IConfigurationManager>();
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
_atmospereSystem = _entMan.System<AtmosphereSystem>();
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
_cvar.SetCVar(CCVars.DeltaPressureParallelToProcessPerIteration, EntitiesPerIteration);
_cvar.SetCVar(CCVars.DeltaPressureParallelBatchSize, BatchSize);
var plating = _tileDefMan["Plating"].TileId;
/*
Basically, we want to have a 5-wide grid of tiles.
Edges are walled, and the length of the grid is determined by N + 2.
Windows should only touch the top and bottom walls, and each other.
*/
var length = EntityCount + 2; // ensures we can spawn exactly N windows between side walls
const int height = 5;
await server.WaitPost(() =>
{
// Fill required tiles (extend grid) with plating
for (var x = 0; x < length; x++)
{
for (var y = 0; y < height; y++)
{
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
}
}
// Spawn perimeter walls and windows row in the middle (y = 2)
const int midY = height / 2;
for (var x = 0; x < length; x++)
{
for (var y = 0; y < height; y++)
{
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
var isPerimeter = x == 0 || x == length - 1 || y == 0 || y == height - 1;
if (isPerimeter)
{
_entMan.SpawnEntity(_wallProtoId, coords);
continue;
}
// Spawn windows only on the middle row, spanning interior (excluding side walls)
if (y == midY)
{
_entMan.SpawnEntity(_windowProtoId, coords);
}
}
}
});
// Next we run the fixgridatmos command to ensure that we have some air on our grid.
// Wait a little bit as well.
// TODO: Unhardcode command magic string when fixgridatmos is an actual command we can ref and not just
// a stamp-on in AtmosphereSystem.
await _pair.WaitCommand("fixgridatmos " + mapdata.Grid.Owner, 1);
var uid = mapdata.Grid.Owner;
_testEnt = new Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>(
uid,
_entMan.GetComponent<GridAtmosphereComponent>(uid),
_entMan.GetComponent<GasTileOverlayComponent>(uid),
_entMan.GetComponent<MapGridComponent>(uid),
_entMan.GetComponent<TransformComponent>(uid));
}
[Benchmark]
public async Task PerformFullProcess()
{
await _pair.Server.WaitPost(() =>
{
while (!_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure)) { }
});
}
[Benchmark]
public async Task PerformSingleRunProcess()
{
await _pair.Server.WaitPost(() =>
{
_atmospereSystem.RunProcessingStage(_testEnt, AtmosphereProcessingState.DeltaPressure);
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
-191
View File
@@ -1,191 +0,0 @@
using System.Collections.Generic;
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();
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();
}
}
-253
View File
@@ -1,253 +0,0 @@
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();
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,83 +0,0 @@
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();
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();
}
}
+2 -2
View File
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Maps;
using Content.Server.Maps;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization.Systems;
@@ -47,7 +47,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown();
}
public static string[] MapsSource { get; } = { "Empty", "Saltern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
public static readonly string[] MapsSource = { "Empty", "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;
+1
View File
@@ -6,6 +6,7 @@ using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Mind;
using Content.Server.Warps;
using Content.Shared.Warps;
using Robust.Shared;
using Robust.Shared.Analyzers;
+2 -24
View File
@@ -23,8 +23,6 @@ public class RaiseEventBenchmark
PoolManager.Startup(typeof(BenchSystem).Assembly);
_pair = PoolManager.GetServerClient().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(() =>
@@ -32,8 +30,6 @@ public class RaiseEventBenchmark
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();
@@ -64,12 +60,6 @@ public class RaiseEventBenchmark
return _sys.RaiseICompEvent();
}
[Benchmark]
public int RaiseNetEvent()
{
return _sys.RaiseNetIdEvent();
}
[Benchmark]
public int RaiseCSharpEvent()
{
@@ -84,8 +74,6 @@ public class RaiseEventBenchmark
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()
{
@@ -104,7 +92,7 @@ public class RaiseEventBenchmark
public int RaiseCompEvent()
{
var ev = new BenchEv();
RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
EntityManager.EventBus.RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
return ev.N;
}
@@ -112,16 +100,7 @@ public class RaiseEventBenchmark
{
// 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);
EntityManager.EventBus.RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
return ev.N;
}
@@ -139,7 +118,6 @@ public class RaiseEventBenchmark
}
[ByRefEvent]
[ComponentEvent(Exclusive = false)]
public struct BenchEv
{
public int N;
@@ -8,7 +8,6 @@ using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Benchmarks;
@@ -19,11 +18,9 @@ namespace Content.Benchmarks;
[Virtual, MemoryDiagnoser]
public class SpawnEquipDeleteBenchmark
{
private static readonly EntProtoId Mob = "MobHuman";
private static readonly ProtoId<StartingGearPrototype> CaptainStartingGear = "CaptainGear";
private TestPair _pair = default!;
private StationSpawningSystem _spawnSys = default!;
private const string Mob = "MobHuman";
private StartingGearPrototype _gear = default!;
private EntityUid _entity;
private EntityCoordinates _coords;
@@ -42,7 +39,7 @@ public class SpawnEquipDeleteBenchmark
var mapData = await _pair.CreateTestMap();
_coords = mapData.GridCoords;
_spawnSys = server.System<StationSpawningSystem>();
_gear = server.ProtoMan.Index(CaptainStartingGear);
_gear = server.ProtoMan.Index<StartingGearPrototype>("CaptainGear");
}
[GlobalCleanup]
@@ -1,4 +1,4 @@
<GridContainer xmlns="https://spacestation14.io"
Columns="4"
Columns="5"
HorizontalAlignment="Center">
</GridContainer>
@@ -1,4 +1,3 @@
using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -51,8 +50,8 @@ namespace Content.Client.Access.UI
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
{
@@ -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" />
@@ -25,11 +25,11 @@ namespace Content.Client.Access.UI
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels)
{
_accessButtons.Clear();
AccessLevelGrid.RemoveAllChildren();
AccessLevelGrid.DisposeAllChildren();
foreach (var access in accessLevels)
{
if (!protoManager.Resolve(access, out var accessLevel))
if (!protoManager.TryIndex(access, out var accessLevel))
{
continue;
}
@@ -53,8 +53,6 @@ namespace Content.Client.Access.UI
public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUserInterfaceState state)
{
PrivilegedIdGrid.Visible = state.ShowPrivilegedIdGrid;
PrivilegedIdLabel.Text = state.PrivilegedIdName;
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
? Loc.GetString("access-overrider-window-eject-button")
@@ -79,9 +77,7 @@ namespace Content.Client.Access.UI
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);
}
@@ -76,7 +76,7 @@ namespace Content.Client.Access.UI
public void SetAllowedIcons(string currentJobIconId)
{
IconGrid.RemoveAllChildren();
IconGrid.DisposeAllChildren();
var jobIconButtonGroup = new ButtonGroup();
var i = 0;
@@ -84,12 +84,12 @@ namespace Content.Client.Access.UI
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
@@ -15,8 +15,6 @@ 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;
@@ -57,7 +55,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
foreach (var accessGroup in _accessGroups)
{
if (!_protoManager.Resolve(accessGroup, out var accessGroupProto))
if (!_protoManager.TryIndex(accessGroup, out var accessGroupProto))
continue;
_groupedAccessLevels.Add(accessGroupProto, new());
@@ -65,13 +63,13 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
// 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))
if (_protoManager.TryIndex<AccessGroupPrototype>("General", 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))
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto))
continue;
var assigned = false;
@@ -99,8 +97,8 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
private bool TryRebuildAccessGroupControls()
{
AccessGroupList.RemoveAllChildren();
AccessLevelChecklist.RemoveAllChildren();
AccessGroupList.DisposeAllChildren();
AccessLevelChecklist.DisposeAllChildren();
// No access level prototypes were assigned to any of the access level groups.
// Either the turret controller has no assigned access levels or their names were invalid.
@@ -119,11 +117,11 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
if (_groupedAccessLevels.Count > 1)
{
if (AccessGroupList.ChildCount == 0)
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenLeft);
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenLeft);
else if (_groupedAccessLevels.Count > 1 && AccessGroupList.ChildCount == (_groupedAccessLevels.Count - 1))
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenRight);
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenRight);
else
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenBoth);
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenBoth);
}
accessGroupButton.Pressed = _accessGroupTabIndex == orderedAccessGroups.IndexOf(accessGroup);
@@ -165,7 +163,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
/// </summary>
public void RebuildAccessLevelsControls()
{
AccessLevelChecklist.RemoveAllChildren();
AccessLevelChecklist.DisposeAllChildren();
_accessLevelEntries.Clear();
// No access level prototypes were assigned to any of the access level groups
@@ -1,11 +1,9 @@
using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.IdCardConsoleComponent;
@@ -39,8 +37,7 @@ 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;
}
else
{
@@ -56,6 +53,7 @@ namespace Content.Client.Access.UI
_window.CrewManifestButton.OnPressed += _ => SendMessage(new CrewManifestOpenUiMessage());
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
_window.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(TargetIdCardSlotId));
_window.OnToggleAccess += id => SendMessage(new IdCardConsoleToggleMessage(id)); // DeltaV
_window.OnClose += Close;
_window.OpenCentered();
@@ -77,7 +75,7 @@ namespace Content.Client.Access.UI
_window?.UpdateState(castState);
}
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, string newJobPrototype)
{
if (newFullName.Length > _maxNameLength)
newFullName = newFullName[.._maxNameLength];
@@ -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,10 @@
<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 -->
<GridContainer Columns="2">
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" />
</GridContainer>
<Control Name="AccessLevelControlContainer" />
</BoxContainer>
</DefaultWindow>
@@ -37,6 +37,8 @@ namespace Content.Client.Access.UI
// The job that will be picked if the ID doesn't have a job on the station.
private static ProtoId<JobPrototype> _defaultJob = "Passenger";
public event Action<ProtoId<AccessLevelPrototype>>? OnToggleAccess; // DeltaV
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
{
@@ -79,35 +81,25 @@ 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)
{
button.OnPressed += _ => SubmitData();
var copied = id; // DeltaV
button.OnPressed += _ => OnToggleAccess?.Invoke(id);
}
}
/// <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 void ClearAllAccess()
{
foreach (var button in _accessButtons.ButtonsList.Values)
{
if (!button.Disabled && button.Pressed != enabled)
button.Pressed = enabled;
if (button.Pressed)
{
button.Pressed = false;
}
}
}
@@ -121,7 +113,7 @@ 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)
@@ -134,7 +126,7 @@ namespace Content.Client.Access.UI
foreach (var group in job.AccessGroups)
{
if (!_prototypeManager.Resolve(group, out AccessGroupPrototype? groupPrototype))
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
{
continue;
}
@@ -222,7 +214,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(),
[], // DeltaV - don't send list of accesses
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
}
}
+7 -30
View File
@@ -33,7 +33,6 @@ namespace Content.Client.Actions
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceManager _resources = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
public event Action<EntityUid>? OnActionAdded;
public event Action<EntityUid>? OnActionRemoved;
@@ -212,7 +211,7 @@ namespace Content.Client.Actions
else
{
var request = new RequestPerformActionEvent(GetNetEntity(action));
RaisePredictiveEvent(request);
EntityManager.RaisePredictiveEvent(request);
}
}
@@ -288,27 +287,8 @@ namespace Content.Client.Actions
continue;
}
if (assignmentNode is SequenceDataNode sequenceAssignments)
{
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}");
}
}
AddActionDirect((user, actions), actionId);
}
AssignSlot?.Invoke(assignments);
}
// DeltaV - begin changes
@@ -421,10 +401,10 @@ namespace Content.Client.Actions
// 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)))
args.Input.EntityUid != null &&
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
{
targetEnt = entityUid;
targetEnt = args.Input.EntityUid;
}
if (action.ClientExclusive)
@@ -446,12 +426,7 @@ namespace Content.Client.Actions
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)
if (args.Handled || args.Input.EntityUid is not { Valid: true } entity)
return;
// let world target component handle it
@@ -462,6 +437,8 @@ namespace Content.Client.Actions
return;
}
args.Handled = true;
var action = args.Action;
var user = args.User;
@@ -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,7 +75,7 @@ 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))
@@ -1,4 +1,3 @@
using System.Collections.Frozen;
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Systems;
@@ -8,14 +7,12 @@ 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
namespace Content.Client.Administration;
@@ -27,8 +24,6 @@ internal sealed class AdminNameOverlay : Overlay
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;
@@ -41,9 +36,8 @@ internal sealed class AdminNameOverlay : Overlay
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 ProtoId<RoleTypePrototype>[] _filter =
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
@@ -55,9 +49,7 @@ internal sealed class AdminNameOverlay : Overlay
EntityLookupSystem entityLookup,
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config,
SharedRoleSystem roles,
IPrototypeManager prototypeManager,
IGameTiming timing) // DeltaV - Add timing
SharedRoleSystem roles)
{
_system = system;
_entityManager = entityManager;
@@ -65,8 +57,6 @@ internal sealed class AdminNameOverlay : Overlay
_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();
@@ -135,14 +125,6 @@ internal sealed class AdminNameOverlay : Overlay
foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList())
{
var playerInfo = info.Item1;
var rolePrototype = playerInfo.RoleProto == null
? null
: _prototypeManager.Index(playerInfo.RoleProto.Value);
var roleName = 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;
@@ -201,22 +183,8 @@ internal sealed class AdminNameOverlay : Overlay
// 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
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
// Playtime
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && _overlayPlaytime)
@@ -232,28 +200,16 @@ internal sealed class AdminNameOverlay : Overlay
{
color = Color.GreenYellow;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.StartingJob, uiScale, playerInfo.Connected ? color : colorDisconnected);
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(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;
symbol = playerInfo.RoleProto.Symbol;
break;
case AdminOverlayAntagSymbolStyle.Basic:
symbol = Loc.GetString("player-tab-antag-prefix");
@@ -269,17 +225,17 @@ internal sealed class AdminNameOverlay : Overlay
switch (_overlayFormat)
{
case AdminOverlayAntagFormat.Roletype:
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? Loc.GetString(roleName).ToUpper()
color = playerInfo.RoleProto.Color;
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
text = _filter.Contains(playerInfo.RoleProto)
? Loc.GetString(playerInfo.RoleProto.Name).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()
color = playerInfo.RoleProto.Color;
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
text = _filter.Contains(playerInfo.RoleProto)
? _roles.GetRoleSubtypeLabel(playerInfo.RoleProto.Name, playerInfo.Subtype).ToUpper()
: string.Empty;
break;
default:
@@ -302,12 +258,4 @@ internal sealed class AdminNameOverlay : Overlay
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
}
}
private static bool IsFiltered(ProtoId<RoleTypePrototype>? roleProtoId)
{
if (roleProtoId == null)
return false;
return Filter.Contains(roleProtoId.Value);
}
}
@@ -0,0 +1,10 @@
using Content.Shared.Administration.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components;
[RegisterComponent]
public sealed partial class HeadstandComponent : SharedHeadstandComponent
{
}
@@ -0,0 +1,7 @@
using Content.Shared.Administration.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components;
[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);
}
@@ -0,0 +1,7 @@
using Content.Shared.Administration;
namespace Content.Client.Administration.Systems;
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
{
}
@@ -4,8 +4,6 @@ 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
{
@@ -19,8 +17,6 @@ namespace Content.Client.Administration.Systems
[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!;
@@ -37,9 +33,7 @@ namespace Content.Client.Administration.Systems
_entityLookup,
_userInterfaceManager,
_configurationManager,
_roles,
_proto,
_timing); // DeltaV - Add timing
_roles);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}
@@ -1,7 +0,0 @@
using Content.Shared.Administration.Systems;
namespace Content.Client.Administration.Systems;
public sealed class BufferingSystem : SharedBufferingSystem
{
}
@@ -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,46 @@
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((uid, sprite), KillSignKey.Key, out var layer, false))
return;
_sprite.RemoveLayer((uid, sprite), 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((uid, sprite), KillSignKey.Key, out var _, false))
return;
if (_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var _, false))
return;
var adj = _sprite.GetLocalBounds((uid, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
if (ent.Comp.Sprite == null)
return;
var layer = _sprite.AddLayer((uid, sprite), new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
_sprite.LayerMapSet((uid, sprite), 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((uid, sprite), 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));
}
}
@@ -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.FromMarkupOrThrow(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
}
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
@@ -1,7 +1,7 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc ban-panel-title}" MinSize="410 500">
Title="{Loc ban-panel-title}" MinSize="350 500">
<BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" VerticalExpand="True">
<!-- Basic info -->
@@ -1,14 +1,12 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using Content.Client.Administration.UI.CustomControls;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -24,7 +22,7 @@ namespace Content.Client.Administration.UI.BanPanel;
[GenerateTypedNameReferences]
public sealed partial class BanPanel : DefaultWindow
{
public event Action<Ban>? BanSubmitted;
public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
public event Action<string>? PlayerChanged;
private string? PlayerUsername { get; set; }
private (IPAddress, int)? IpAddress { get; set; }
@@ -33,21 +31,14 @@ public sealed partial class BanPanel : DefaultWindow
private uint Multiplier { get; set; }
private bool HasBanFlag { get; set; }
private TimeSpan? ButtonResetOn { get; set; }
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated.
// Role group name -> the role buttons themselves.
private readonly Dictionary<string, List<(Button, IPrototype)>> _roleCheckboxes = new();
private readonly ISawmill _banPanelSawmill;
private readonly List<CheckBox> _roleCheckboxes = new();
private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
private const string ExpandedArrow = "▼";
private const string ContractedArrow = "▶";
private enum TabNumbers
{
@@ -79,7 +70,7 @@ public sealed partial class BanPanel : DefaultWindow
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_banPanelSawmill = _logManager.GetSawmill("admin.banpanel");
_banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
PlayerCheckbox.OnPressed += _ =>
@@ -110,7 +101,7 @@ public sealed partial class BanPanel : DefaultWindow
TypeOption.SelectId(args.Id);
OnTypeChanged();
};
LastConnCheckbox.OnPressed += _ =>
LastConnCheckbox.OnPressed += args =>
{
IpLine.ModulateSelfOverride = null;
HwidLine.ModulateSelfOverride = null;
@@ -153,63 +144,87 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
var departmentJobs = _protoMan.EnumeratePrototypes<DepartmentPrototype>()
.OrderBy(x => x.Weight);
foreach (var proto in departmentJobs)
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
var roles = proto.Roles.Select(x => _protoMan.Index(x))
.OrderBy(x => x.ID);
CreateRoleGroup(proto.ID, proto.Color, roles);
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
}
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
.OrderBy(x => x.ID);
CreateRoleGroup(AntagPrototype.GroupName, AntagPrototype.GroupColor, antagRoles);
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
}
/// <summary>
/// Creates a "Role group" which stores information and logic for one "group" of roll bans.
/// For example, all antags are one group, logi is a group, medical is a group, etc...
/// </summary>
private void CreateRoleGroup<T>(string groupName, Color color, IEnumerable<T> roles) where T : class, IPrototype
private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
{
var outerContainer = new BoxContainer
{
Name = $"{groupName}GroupOuterBox",
Name = $"{roleName}GroupOuterBox",
HorizontalExpand = true,
VerticalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(4),
Margin = new Thickness(4)
};
// Stores stuff like ban all and expand buttons.
var roleGroupHeader = new BoxContainer
var departmentCheckbox = new CheckBox
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Name = $"{roleName}GroupCheckbox",
Text = roleName,
Modulate = color,
HorizontalAlignment = HAlignment.Left
};
// Stores the role checkboxes themselves.
var innerContainer = new GridContainer
outerContainer.AddChild(departmentCheckbox);
var innerContainer = new BoxContainer
{
Name = $"{groupName}GroupInnerBox",
Name = $"{roleName}GroupInnerBox",
HorizontalExpand = true,
Columns = 2,
Visible = false,
Margin = new Thickness(15, 5, 0, 5),
Orientation = BoxContainer.LayoutOrientation.Horizontal
};
var roleGroupCheckbox = CreateRoleGroupHeader(groupName, roleGroupHeader, color, innerContainer);
outerContainer.AddChild(roleGroupHeader);
// Add the roles themselves
foreach (var role in roles)
departmentCheckbox.OnToggled += args =>
{
AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox);
}
foreach (var child in innerContainer.Children)
{
if (child is CheckBox c)
{
c.Pressed = args.Pressed;
}
}
if (args.Pressed)
{
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banpanelSawmill
.Warning("Departmental role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
else
{
foreach (var childContainer in RolesContainer.Children)
{
if (childContainer is Container)
{
foreach (var child in childContainer.Children)
{
if (child is CheckBox { Pressed: true })
return;
}
}
}
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
};
outerContainer.AddChild(innerContainer);
foreach (var role in roleList)
{
AddRoleCheckbox(role, innerContainer, departmentCheckbox);
}
RolesContainer.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat
@@ -221,134 +236,22 @@ public sealed partial class BanPanel : DefaultWindow
RolesContainer.AddChild(new HSeparator());
}
private Button CreateRoleGroupHeader(string groupName, BoxContainer header, Color color, GridContainer innerContainer)
private void AddRoleCheckbox(string role, Control container, CheckBox header)
{
var roleGroupCheckbox = new Button
var roleCheckbox = new CheckBox
{
Name = $"{groupName}GroupCheckbox",
Text = Loc.GetString("role-bans-ban-group"),
Margin = new Thickness(0, 0, 5, 0),
ToggleMode = true,
Name = $"{role}RoleCheckbox",
Text = role
};
// When this is toggled, toggle all buttons in this group so they match.
roleGroupCheckbox.OnToggled += args =>
roleCheckbox.OnToggled += args =>
{
foreach (var role in _roleCheckboxes[groupName])
{
role.Item1.Pressed = args.Pressed;
}
if (args.Pressed)
{
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banPanelSawmill
.Warning("Departmental role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed))
header.Pressed = args.Pressed;
else
{
foreach (var roleButtons in _roleCheckboxes.Values)
{
foreach (var button in roleButtons)
{
if (button.Item1.Pressed)
return;
}
}
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banPanelSawmill
.Warning("Role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
header.Pressed = false;
};
var hideButton = new Button
{
Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow,
ToggleMode = true,
};
hideButton.OnPressed += args =>
{
innerContainer.Visible = args.Button.Pressed;
((Button)args.Button).Text = args.Button.Pressed
? Loc.GetString("role-bans-contract-roles") + " " + ExpandedArrow
: Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow;
};
header.AddChild(new Label
{
Text = groupName,
Modulate = color,
Margin = new Thickness(0, 0, 5, 0),
});
header.AddChild(roleGroupCheckbox);
header.AddChild(hideButton);
return roleGroupCheckbox;
}
/// <summary>
/// Adds a toggle button specifically for one "role" in a "group"
/// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
/// </summary>
private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
{
var roleCheckboxContainer = new BoxContainer();
var roleToggleButton = new Button
{
Name = role,
Text = role,
ToggleMode = true,
};
roleToggleButton.OnToggled += args =>
{
// Checks the role group checkbox if all the children are pressed
if (args.Pressed && _roleCheckboxes[group].All(e => e.Item1.Pressed))
roleGroupCheckbox.Pressed = args.Pressed;
else
roleGroupCheckbox.Pressed = false;
};
IPrototype rolePrototype;
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype))
rolePrototype = jobPrototype;
else if (_protoMan.TryIndex<AntagPrototype>(role, out var antagPrototype))
rolePrototype = antagPrototype;
else
{
_banPanelSawmill.Error($"Adding a role checkbox for role {role}: role is not a JobPrototype or AntagPrototype.");
return;
}
// This is adding the icon before the role name
// TODO: This should not be using raw strings for prototypes as it means it won't be validated at all.
// // I know the ban manager is doing the same thing, but that should not leak into UI code.
if (jobPrototype is not null && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
{
var jobIconTexture = new TextureRect
{
Texture = _entMan.System<SpriteSystem>().Frame0(iconProto.Icon),
TextureScale = new Vector2(2.5f, 2.5f),
Stretch = TextureRect.StretchMode.KeepCentered,
Margin = new Thickness(5, 0, 0, 0),
};
roleCheckboxContainer.AddChild(jobIconTexture);
}
roleCheckboxContainer.AddChild(roleToggleButton);
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
_roleCheckboxes.TryAdd(group, []);
_roleCheckboxes[group].Add((roleToggleButton, rolePrototype));
container.AddChild(roleCheckbox);
_roleCheckboxes.Add(roleCheckbox);
}
public void UpdateBanFlag(bool newFlag)
@@ -391,7 +294,7 @@ public sealed partial class BanPanel : DefaultWindow
TimeLine.Text = args.Text;
if (!double.TryParse(args.Text, out var result))
{
ExpiresLabel.Text = Loc.GetString("ban-panel-expiry-error");
ExpiresLabel.Text = "err";
ErrorLevel |= ErrorLevelEnum.Minutes;
TimeLine.ModulateSelfOverride = Color.Red;
UpdateSubmitEnabled();
@@ -501,7 +404,7 @@ public sealed partial class BanPanel : DefaultWindow
newSeverity = serverSeverity;
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Server ban severity could not be parsed from config!");
}
@@ -514,7 +417,7 @@ public sealed partial class BanPanel : DefaultWindow
}
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
}
break;
@@ -559,51 +462,28 @@ public sealed partial class BanPanel : DefaultWindow
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
ProtoId<JobPrototype>[]? jobs = null;
ProtoId<AntagPrototype>[]? antags = null;
string[]? roles = null;
if (TypeOption.SelectedId == (int) Types.Role)
{
var jobList = new List<ProtoId<JobPrototype>>();
var antagList = new List<ProtoId<AntagPrototype>>();
var rolesList = new List<string>();
if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty");
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
{
if (button.Item1 is { Pressed: true, Name: not null })
{
switch (button.Item2)
{
case JobPrototype:
jobList.Add(button.Item2.ID);
rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
break;
case AntagPrototype:
antagList.Add(button.Item2.ID);
break;
}
}
}
if (jobList.Count + antagList.Count == 0)
if (rolesList.Count == 0)
{
Tabs.CurrentTab = (int) TabNumbers.Roles;
return;
}
jobs = jobList.ToArray();
antags = antagList.ToArray();
roles = rolesList.ToArray();
}
if (TypeOption.SelectedId == (int) Types.None)
{
TypeOption.ModulateSelfOverride = Color.Red;
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
return;
}
@@ -615,7 +495,6 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.GrabKeyboardFocus();
ReasonTextEdit.ModulateSelfOverride = Color.Red;
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
return;
}
@@ -624,7 +503,6 @@ public sealed partial class BanPanel : DefaultWindow
ButtonResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
SubmitButton.ModulateSelfOverride = Color.Red;
SubmitButton.Text = Loc.GetString("ban-panel-confirm");
return;
}
@@ -633,22 +511,7 @@ public sealed partial class BanPanel : DefaultWindow
var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null;
var severity = (NoteSeverity) SeverityOption.SelectedId;
var erase = EraseCheckbox.Pressed;
var ban = new Ban(
player,
IpAddress,
useLastIp,
Hwid,
useLastHwid,
(uint)(TimeEntered * Multiplier),
reason,
severity,
jobs,
antags,
erase
);
BanSubmitted?.Invoke(ban);
BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles, erase);
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -14,7 +14,8 @@ public sealed class BanPanelEui : BaseEui
{
BanPanel = new BanPanel();
BanPanel.OnClose += () => SendMessage(new CloseEuiMessage());
BanPanel.BanSubmitted += ban => SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(ban));
BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase)
=> SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase));
BanPanel.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player));
}
@@ -18,7 +18,7 @@
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
<Control HorizontalExpand="True" />
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
<Button Visible="False" Access="Public" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
<controls:ConfirmButton Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
<controls:ConfirmButton Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
@@ -15,7 +15,7 @@ public sealed class AdminLogLabel : RichTextLabel
OnVisibilityChanged += VisibilityChanged;
}
public new SharedAdminLog Log { get; }
public SharedAdminLog Log { get; }
public HSeparator Separator { get; }
@@ -11,9 +11,6 @@ namespace Content.Client.Administration.UI.CustomControls;
[GenerateTypedNameReferences]
public sealed partial class PlayerListEntry : BoxContainer
{
private readonly ResPath _pinnedResPath = new("/Textures/Interface/Bwoink/pinned.png");
private readonly ResPath _unPinnedResPath = new("/Textures/Interface/Bwoink/un_pinned.png");
public PlayerListEntry()
{
RobustXamlLoader.Load(this);
@@ -47,6 +44,15 @@ public sealed partial class PlayerListEntry : BoxContainer
private void UpdatePinButtonTexture(bool isPinned)
{
PlayerEntryPinButton.TexturePath = isPinned ? _pinnedResPath.CanonPath : _unPinnedResPath.CanonPath;
if (isPinned)
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned);
}
else
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned);
}
}
}
@@ -47,14 +47,8 @@
<BoxContainer Name="TopRightContainer">
<BoxContainer Name="LogImpactContainer" Orientation="Horizontal"/>
<Control HorizontalExpand="True"/>
<BoxContainer Name="LogOrderContainer" Orientation="Horizontal">
<Button Name="AscendingOrderButton" Access="Public" Text="{Loc admin-logs-order-ascending}" StyleClasses="OpenRight" />
<Button Name="DescendingOrderButton" Access="Public" Text="{Loc admin-logs-order-descending}" StyleClasses="OpenLeft" />
</BoxContainer>
<Control HorizontalExpand="True"/>
<Label Name="Count" Access="Public"/>
<Control HorizontalExpand="True"/>
<Button Name="ExportLogs" Access="Public" Text="{Loc admin-logs-export}"/>
<Button Name="PopOutButton" Access="Public" Text="{Loc admin-logs-pop-out}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
@@ -44,14 +44,6 @@ public sealed partial class AdminLogsControl : Control
ResetRoundButton.OnPressed += ResetRoundPressed;
// START DEN - Admin Log Sorting
AscendingOrderButton.Pressed = this.ReverseLogOrder;
DescendingOrderButton.Pressed = !this.ReverseLogOrder;
AscendingOrderButton.OnPressed += SetLogOrderAscending;
DescendingOrderButton.OnPressed += SetLogOrderDescending;
// END DEN - Admin Log Sorting
SetImpacts(Enum.GetValues<LogImpact>().OrderBy(impact => impact).ToArray());
SetTypes(Enum.GetValues<LogType>());
}
@@ -65,18 +57,6 @@ public sealed partial class AdminLogsControl : Control
private int RoundLogs { get; set; }
public bool IncludeNonPlayerLogs { get; set; }
/// <summary>
/// THE DEN - Admin Log Sorting
/// Reverses the logs displayed when updating logs. True means logs will be ascending order.
/// The server sends the logs in descending order, so reversing means it will be ascending order.
/// </summary>
public bool ReverseLogOrder { get; set; } = true;
/// <summary>
/// THE DEN - Admin Log Sorting
/// The raw log entries recieved from the server. It should always be in descdending order.
/// </summary>
private List<SharedAdminLog> RawAdminLogs { get; set; } = new();
public HashSet<LogType> SelectedTypes { get; } = new();
public HashSet<Guid> SelectedPlayers { get; } = new();
@@ -348,70 +328,6 @@ public sealed partial class AdminLogsControl : Control
UpdateLogs();
}
// BEGIN DEN - Admin Log Sorting
private void SetLogOrderAscending(ButtonEventArgs args)
{
// Invert the buttons. There's only two possible states, so probably not a big deal to directly reference the buttons.
AscendingOrderButton.Pressed = true;
DescendingOrderButton.Pressed = false;
this.ReverseLogOrder = true; // true = ascending
RedrawLogs();
}
private void SetLogOrderDescending(ButtonEventArgs args)
{
// Invert the buttons. There's only two possible states, so probably not a big deal to directly reference the buttons.
AscendingOrderButton.Pressed = false;
DescendingOrderButton.Pressed = true;
this.ReverseLogOrder = false; // false = descending
RedrawLogs();
}
private void RedrawLogs()
{
// Need to copy the list RawAdminLogs to a new list because we don't want to screw up the ordering from
// the 'source' of truth. If we did reverse the order of that list, we'd need to keep track of the order
// of what we get from the server and how we append/prepend those logs.
var tempLogs = new List<SharedAdminLog>(RawAdminLogs);
if (ReverseLogOrder) tempLogs.Reverse();
LogsContainer.RemoveAllChildren();
UpdateCount(0, 0);
for (var i = 0; i < tempLogs.Count; i++)
{
var log = tempLogs[i];
ref var logRef = ref log; // It didn't like me doing this as one line lmao
var separator = new HSeparator();
var label = new AdminLogLabel(ref logRef, separator);
label.Visible = ShouldShowLog(label);
if (label.Visible) ShownLogs++;
LogsContainer.AddChild(label);
LogsContainer.AddChild(separator);
}
UpdateLogs();
}
/*
var span = CollectionsMarshal.AsSpan(logs);
for (var i = 0; i < span.Length; i++)
{
ref var log = ref span[i];
var entry = new AdminLogEntry(ref log);
TotalLogs++;
LogsContainer.AddChild(entry);
}
UpdateLogs();
*/
// END DEN - Admin Log Sorting
private void ImpactButtonPressed(ButtonEventArgs args)
{
var button = (AdminLogImpactButton) args.Button;
@@ -548,15 +464,31 @@ public sealed partial class AdminLogsControl : Control
public void AddLogs(List<SharedAdminLog> logs)
{
RawAdminLogs.AddRange(logs); // THE DEN - Admin Log Sorting - Logs will be sent in descending order
RedrawLogs(); // Drawing the logs has been encaspulated to its own function
var span = CollectionsMarshal.AsSpan(logs);
for (var i = 0; i < span.Length; i++)
{
ref var log = ref span[i];
var separator = new HSeparator();
var label = new AdminLogLabel(ref log, separator);
label.Visible = ShouldShowLog(label);
TotalLogs++;
if (label.Visible)
{
ShownLogs++;
}
LogsContainer.AddChild(label);
LogsContainer.AddChild(separator);
}
UpdateCount();
}
public void SetLogs(List<SharedAdminLog> logs)
{
LogsContainer.RemoveAllChildren();
UpdateCount(0, 0);
RawAdminLogs.Clear(); // THE DEN - Admin Log Sorting
AddLogs(logs);
}
@@ -598,11 +530,6 @@ public sealed partial class AdminLogsControl : Control
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;
// START DEN - Admin Log Sorting
AscendingOrderButton.OnPressed -= SetLogOrderAscending;
DescendingOrderButton.OnPressed -= SetLogOrderDescending;
// END DEN - Admin Log Sorting
RoundSpinBox.IsValid = null;
RoundSpinBox.ValueChanged -= RoundSpinBoxChanged;
@@ -1,6 +1,4 @@
using System.IO;
using System.Linq;
using Content.Client.Administration.UI.CustomControls;
using System.Linq;
using Content.Client.Eui;
using Content.Shared.Administration.Logs;
using Content.Shared.Eui;
@@ -17,16 +15,6 @@ public sealed class AdminLogsEui : BaseEui
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IFileDialogManager _dialogManager = default!;
[Dependency] private readonly ILogManager _log = default!;
private const char CsvSeparator = ',';
private const string CsvQuote = "\"";
private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message";
private ISawmill _sawmill;
private bool _currentlyExportingLogs = false;
public AdminLogsEui()
{
@@ -38,9 +26,6 @@ public sealed class AdminLogsEui : BaseEui
LogsControl.RefreshButton.OnPressed += _ => RequestLogs();
LogsControl.NextButton.OnPressed += _ => NextLogs();
LogsControl.PopOutButton.OnPressed += _ => PopOut();
LogsControl.ExportLogs.OnPressed += _ => ExportLogs();
_sawmill = _log.GetSawmill("admin.logs.ui");
}
private WindowRoot? Root { get; set; }
@@ -89,71 +74,6 @@ public sealed class AdminLogsEui : BaseEui
SendMessage(request);
}
private async void ExportLogs()
{
if (_currentlyExportingLogs)
return;
_currentlyExportingLogs = true;
LogsControl.ExportLogs.Disabled = true;
var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("csv")));
if (file == null)
return;
try
{
// Buffer is set to 4KB for performance reasons. As the average export of 1000 logs is ~200KB
await using var writer = new StreamWriter(file.Value.fileStream, bufferSize: 4096);
await writer.WriteLineAsync(CsvHeader);
foreach (var child in LogsControl.LogsContainer.Children)
{
if (child is not AdminLogLabel logLabel || !child.Visible)
continue;
var log = logLabel.Log;
// Date
// I swear to god if someone adds ,s or "s to the other fields...
await writer.WriteAsync(log.Date.ToString("s", System.Globalization.CultureInfo.InvariantCulture));
await writer.WriteAsync(CsvSeparator);
// ID
await writer.WriteAsync(log.Id.ToString());
await writer.WriteAsync(CsvSeparator);
// PlayerID
var players = log.Players;
for (var i = 0; i < players.Length; i++)
{
await writer.WriteAsync(players[i] + (i == players.Length - 1 ? "" : " "));
}
await writer.WriteAsync(CsvSeparator);
// Severity
await writer.WriteAsync(log.Impact.ToString());
await writer.WriteAsync(CsvSeparator);
// Type
await writer.WriteAsync(log.Type.ToString());
await writer.WriteAsync(CsvSeparator);
// Message
await writer.WriteAsync(CsvQuote);
await writer.WriteAsync(log.Message.Replace(CsvQuote, CsvQuote + CsvQuote));
await writer.WriteAsync(CsvQuote);
await writer.WriteLineAsync();
}
}
catch (Exception exc)
{
_sawmill.Error($"Error when exporting admin log:\n{exc.StackTrace}");
}
finally
{
await file.Value.fileStream.DisposeAsync();
_currentlyExportingLogs = false;
LogsControl.ExportLogs.Disabled = false;
}
}
private void PopOut()
{
if (LogsWindow == null)
@@ -166,7 +86,7 @@ public sealed class AdminLogsEui : BaseEui
ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters
{
Maximized = false,
Title = Loc.GetString("admin-logs-title"),
Title = "Admin Logs",
Monitor = monitor,
Width = 1100,
Height = 400
@@ -8,21 +8,6 @@
<OptionButton Name="SolutionOption" HorizontalExpand="True"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 4">
<Button Name="VVButton"
Text="{Loc 'admin-solutions-window-vv-button'}"
ToolTip="{Loc 'admin-solutions-window-vv-button-tooltip'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenRight"/>
<Button Name="SolutionButton"
Text="{Loc 'admin-solutions-window-solution-button'}"
ToolTip="{Loc 'admin-solutions-window-solution-button-tooltip'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenLeft"/>
</BoxContainer>
<!-- The total volume / capacity of the solution -->
<BoxContainer Name="VolumeBox" Orientation="Vertical" HorizontalExpand="True" Margin="0 4"/>
@@ -1,4 +1,3 @@
using Content.Client.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
@@ -21,7 +20,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
private NetEntity _target = NetEntity.Invalid;
private string? _selectedSolution;
@@ -36,11 +34,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
SolutionOption.OnItemSelected += SolutionSelected;
AddButton.OnPressed += OpenAddReagentWindow;
VVButton.OnPressed += OpenVVWindow;
SolutionButton.OnPressed += OpenSolutionWindow;
VVButton.Disabled = !_admin.CanViewVar();
SolutionButton.Disabled = !_admin.CanViewVar();
}
public override void Close()
@@ -67,7 +60,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// </summary>
public void UpdateReagents()
{
ReagentList.RemoveAllChildren();
ReagentList.DisposeAllChildren();
if (_selectedSolution == null || _solutions == null)
return;
@@ -92,7 +85,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// <param name="solution">The selected solution.</param>
private void UpdateVolumeBox(Solution solution)
{
VolumeBox.RemoveAllChildren();
VolumeBox.DisposeAllChildren();
var volumeLabel = new Label();
volumeLabel.HorizontalExpand = true;
@@ -131,7 +124,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// <param name="solution">The selected solution.</param>
private void UpdateThermalBox(Solution solution)
{
ThermalBox.RemoveAllChildren();
ThermalBox.DisposeAllChildren();
var heatCap = solution.GetHeatCapacity(null);
var specificHeatLabel = new Label();
specificHeatLabel.HorizontalExpand = true;
@@ -278,32 +271,6 @@ namespace Content.Client.Administration.UI.ManageSolutions
_addReagentWindow.OpenCentered();
}
/// <summary>
/// Open the corresponding solution entity in a ViewVariables window.
/// </summary>
private void OpenVVWindow(BaseButton.ButtonEventArgs obj)
{
if (_solutions == null
|| _selectedSolution == null
|| !_solutions.TryGetValue(_selectedSolution, out var uid)
|| !_entityManager.TryGetNetEntity(uid, out var netEntity))
return;
_consoleHost.ExecuteCommand($"vv {netEntity}");
}
/// <summary>
/// Open the corresponding Solution instance in a ViewVariables window.
/// </summary>
private void OpenSolutionWindow(BaseButton.ButtonEventArgs obj)
{
if (_solutions == null
|| _selectedSolution == null
|| !_solutions.TryGetValue(_selectedSolution, out var uid)
|| !_entityManager.TryGetNetEntity(uid, out var netEntity))
return;
_consoleHost.ExecuteCommand($"vv /entity/{netEntity}/Solution/Solution");
}
/// <summary>
/// When a new solution is selected, set _selectedSolution and update the reagent list.
/// </summary>
@@ -5,7 +5,7 @@
<BoxContainer Orientation="Vertical" Name="Notes" Access="Public" VerticalExpand="True"/>
</ScrollContainer>
<Button Name="ShowMoreButton" Text="{Loc admin-notes-show-more}" Visible="False" HorizontalAlignment="Center" />
<Button Name="NewNoteButton" Access="Public" Text="{Loc admin-notes-new-note}" Disabled="True" />
<Button Name="NewNoteButton" Text="{Loc admin-notes-new-note}" Disabled="True" />
</BoxContainer>
</PanelContainer>
</Control>
@@ -82,11 +82,7 @@ public sealed partial class AdminNotesLine : BoxContainer
if (Note.UnbannedTime is not null)
{
ExtraLabel.Text = Loc.GetString(
"admin-notes-unbanned",
("admin", Note.UnbannedByName ?? "[error]"),
("date", Note.UnbannedTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))
);
ExtraLabel.Text = Loc.GetString("admin-notes-unbanned", ("admin", Note.UnbannedByName ?? "[error]"), ("date", Note.UnbannedTime));
ExtraLabel.Visible = true;
}
else if (Note.ExpiryTime is not null)
@@ -143,7 +139,7 @@ public sealed partial class AdminNotesLine : BoxContainer
private string FormatRoleBanMessage()
{
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new []{"unknown"})} ");
return FormatBanMessageCommon(banMessage);
}
@@ -3,7 +3,7 @@
Title="Loading..."
MinSize="400 200">
<BoxContainer Orientation="Vertical" Margin="4">
<TextEdit Name="NoteTextEdit" Access="Public" HorizontalExpand="True" VerticalExpand="True" />
<TextEdit Name="NoteTextEdit" HorizontalExpand="True" VerticalExpand="True" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
@@ -17,7 +17,7 @@
ToolTip="{Loc admin-note-editor-secret-tooltip}" />
<CheckBox Name="PermanentCheckBox" Pressed="True" Text="{Loc admin-note-editor-expiry-checkbox}"
ToolTip="{Loc admin-note-editor-expiry-checkbox-tooltip}" />
<Button Name="SubmitButton" Access="Public" Text="{Loc admin-note-editor-submit}" HorizontalAlignment="Right" />
<Button Name="SubmitButton" Text="{Loc admin-note-editor-submit}" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -133,7 +133,7 @@ public sealed partial class NoteEdit : FancyWindow
private bool IsSecret { get; set; }
private NoteType NoteType { get; set; }
public NoteSeverity? NoteSeverity
private NoteSeverity? NoteSeverity
{
get => _noteSeverity;
set
@@ -27,7 +27,7 @@ namespace Content.Client.Administration.UI
[Dependency] private readonly IClientAdminManager _adminManager = default!;
private readonly Menu _menu;
private readonly List<BaseWindow> _subWindows = new();
private readonly List<DefaultWindow> _subWindows = new();
private Dictionary<int, PermissionsEuiState.AdminRankData> _ranks =
new();
@@ -216,7 +216,7 @@ namespace Content.Client.Administration.UI
var titleControl = new Label { Text = admin.Title ?? Loc.GetString("permissions-eui-edit-admin-title-control-text").ToLowerInvariant() };
if (admin.Title == null) // none
{
titleControl.StyleClasses.Add(StyleClass.Italic);
titleControl.StyleClasses.Add(StyleBase.StyleClassItalic);
}
al.AddChild(titleControl);
@@ -240,7 +240,7 @@ namespace Content.Client.Administration.UI
var rankControl = new Label { Text = rank };
if (italic)
{
rankControl.StyleClasses.Add(StyleClass.Italic);
rankControl.StyleClasses.Add(StyleBase.StyleClassItalic);
}
al.AddChild(rankControl);
@@ -340,9 +340,10 @@ namespace Content.Client.Administration.UI
tab.AddChild(adminVBox);
tab.AddChild(rankVBox);
ContentsContainer.AddChild(tab);
ContentsContainer.MinSize = new(600, 400);
Contents.AddChild(tab);
}
protected override Vector2 ContentsMinimumSize => new Vector2(600, 400);
}
private sealed class EditAdminWindow : DefaultWindow
@@ -418,21 +419,21 @@ namespace Content.Client.Administration.UI
var inherit = new Button
{
Text = "I",
StyleClasses = { StyleClass.ButtonOpenRight },
StyleClasses = { StyleBase.ButtonOpenRight },
Disabled = disable,
Group = group,
};
var sub = new Button
{
Text = "-",
StyleClasses = { StyleClass.ButtonOpenBoth },
StyleClasses = { StyleBase.ButtonOpenBoth },
Disabled = disable,
Group = group
};
var plus = new Button
{
Text = "+",
StyleClasses = { StyleClass.ButtonOpenLeft },
StyleClasses = { StyleBase.ButtonOpenLeft },
Disabled = disable,
Group = group
};
@@ -478,7 +479,7 @@ namespace Content.Client.Administration.UI
bottomButtons.AddChild(SaveButton);
ContentsContainer.AddChild(new BoxContainer
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
@@ -604,7 +605,7 @@ namespace Content.Client.Administration.UI
bottomButtons.AddChild(SaveButton);
ContentsContainer.AddChild(new BoxContainer
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
@@ -31,7 +31,6 @@
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
<Button Name="CameraButton" Text="{Loc player-panel-camera}" Disabled="True"/>
</GridContainer>
<Button Name="JobWhitelistsButton" Text="{Loc player-panel-job-whitelists}" SetWidth="136" SetHeight="27" Disabled="True"/> <!-- DeltaV: Job whitelists -->
</BoxContainer>
@@ -18,7 +18,6 @@ public sealed partial class PlayerPanel : FancyWindow
public event Action<NetUserId?>? OnOpenBans;
public event Action<NetUserId?>? OnAhelp;
public event Action<string?>? OnKick;
public event Action<string?>? OnCamera;
public event Action<NetUserId?>? OnOpenBanPanel;
public event Action<NetUserId?, bool>? OnWhitelistToggle;
public event Action? OnFollow;
@@ -35,29 +34,28 @@ public sealed partial class PlayerPanel : FancyWindow
public PlayerPanel(IClientAdminManager adminManager)
{
RobustXamlLoader.Load(this);
_adminManager = adminManager;
RobustXamlLoader.Load(this);
_adminManager = adminManager;
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
CameraButton.OnPressed += _ => OnCamera?.Invoke(TargetUsername);
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
WhitelistToggle.OnPressed += _ =>
{
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
SetWhitelisted(!_isWhitelisted);
};
FollowButton.OnPressed += _ => OnFollow?.Invoke();
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
LogsButton.OnPressed += _ => OnLogs?.Invoke();
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
WhitelistToggle.OnPressed += _ =>
{
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
SetWhitelisted(!_isWhitelisted);
};
FollowButton.OnPressed += _ => OnFollow?.Invoke();
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
LogsButton.OnPressed += _ => OnLogs?.Invoke();
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
JobWhitelistsButton.OnPressed += _ => OnOpenJobWhitelists?.Invoke(TargetPlayer); // DeltaV: Job whitelists
JobWhitelistsButton.OnPressed += _ => OnOpenJobWhitelists?.Invoke(TargetPlayer); // DeltaV: Job whitelists
}
public void SetUsername(string player)
@@ -127,7 +125,6 @@ public sealed partial class PlayerPanel : FancyWindow
{
BanButton.Disabled = !_adminManager.CanCommand("banpanel");
KickButton.Disabled = !_adminManager.CanCommand("kick");
CameraButton.Disabled = !_adminManager.CanCommand("camera");
NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
WhitelistToggle.Disabled =
@@ -15,7 +15,7 @@ public sealed class PlayerPanelEui : BaseEui
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IClipboardManager _clipboard = default!;
private PlayerPanel PlayerPanel { get; }
private PlayerPanel PlayerPanel { get; }
public PlayerPanelEui()
{
@@ -25,7 +25,6 @@ public sealed class PlayerPanelEui : BaseEui
PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
// Kick command does not support GUIDs
PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
PlayerPanel.OnCamera += username => _console.ExecuteCommand($"camera \"{username}\"");
PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
@@ -38,7 +37,7 @@ public sealed class PlayerPanelEui : BaseEui
PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
PlayerPanel.OnDelete += () => SendMessage(new PlayerPanelDeleteMessage());
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
PlayerPanel.OnOpenJobWhitelists += id => _console.ExecuteCommand($"jobwhitelists \"{id}\""); // DeltaV
PlayerPanel.OnFollow += () => SendMessage(new PlayerPanelFollowMessage());
@@ -8,8 +8,10 @@
MinSize="50 50">
<BoxContainer Orientation="Vertical">
<GridContainer Columns="3">
<cc:UICommandButton Command="kick" Text="{Loc admin-player-actions-window-title}" WindowType="{x:Type at:PlayerActionsWindow}" />
<cc:CommandButton Command="banpanel" Text="{Loc admin-player-actions-window-ban}" />
<cc:CommandButton Command="aghost" Text="{Loc admin-player-actions-window-admin-ghost}" />
<cc:UICommandButton Command="tpto" Text="{Loc admin-player-actions-window-teleport}" WindowType="{x:Type at:TeleportWindow}" />
<cc:CommandButton Command="permissions" Text="{Loc admin-player-actions-window-permissions}" />
<cc:CommandButton Command="announceui" Text="{Loc admin-player-actions-window-announce}"/>
<cc:UICommandButton Command="callshuttle" Text="{Loc admin-player-actions-window-shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
@@ -0,0 +1,19 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-player-actions-reason}" MinWidth="100" />
<Control MinWidth="50" />
<LineEdit Name="ReasonLine" MinWidth="100" HorizontalExpand="True" />
</BoxContainer>
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
<BoxContainer Orientation="Horizontal">
<controls:ConfirmButton Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
<Button Name="SubmitAHelpButton" Text="{Loc admin-player-actions-ahelp}" Disabled="True"/>
<controls:ConfirmButton Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
</BoxContainer>
</BoxContainer>
</DefaultWindow>
@@ -0,0 +1,64 @@
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Tabs.AdminTab
{
[GenerateTypedNameReferences]
[UsedImplicitly]
public sealed partial class PlayerActionsWindow : DefaultWindow
{
private PlayerInfo? _selectedPlayer;
public PlayerActionsWindow()
{
RobustXamlLoader.Load(this);
SubmitKickButton.OnPressed += SubmitKickButtonOnPressed;
SubmitAHelpButton.OnPressed += SubmitAhelpButtonOnPressed;
SubmitRespawnButton.OnPressed += SubmitRespawnButtonOnPressed;
PlayerList.OnSelectionChanged += OnListOnOnSelectionChanged;
}
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
{
_selectedPlayer = obj;
var disableButtons = _selectedPlayer == null;
SubmitKickButton.Disabled = disableButtons;
SubmitAHelpButton.Disabled = disableButtons;
SubmitRespawnButton.Disabled = disableButtons;
}
private void SubmitKickButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"kick \"{_selectedPlayer.Username}\" \"{CommandParsing.Escape(ReasonLine.Text)}\"");
}
private void SubmitAhelpButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"openahelp \"{_selectedPlayer.SessionId}\"");
}
private void SubmitRespawnButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"respawn \"{_selectedPlayer.Username}\"");
}
}
}
@@ -0,0 +1,9 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc admin-ui-teleport}" MinSize="425 230">
<BoxContainer Orientation="Vertical">
<cc:PlayerListControl Name="PlayerList" />
<Button Name="SubmitButton" Text="{Loc admin-ui-teleport}" />
</BoxContainer>
</DefaultWindow>
@@ -0,0 +1,38 @@
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
namespace Content.Client.Administration.UI.Tabs.AdminTab
{
[GenerateTypedNameReferences]
[UsedImplicitly]
public sealed partial class TeleportWindow : DefaultWindow
{
private PlayerInfo? _selectedPlayer;
protected override void EnteredTree()
{
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
PlayerList.OnSelectionChanged += OnListOnOnSelectionChanged;
}
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
{
_selectedPlayer = obj;
SubmitButton.Disabled = _selectedPlayer == null;
}
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
// Execute command
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"tpto \"{_selectedPlayer.Username}\"");
}
}
}
@@ -57,43 +57,12 @@ public sealed partial class ObjectsTab : Control
private void TeleportTo(NetEntity nent)
{
var selection = _selections[ObjectTypeOptions.SelectedId];
switch (selection)
{
case ObjectsTabSelection.Grids:
{
// directly teleport to the entity
_console.ExecuteCommand($"tpto {nent}");
}
break;
case ObjectsTabSelection.Maps:
{
// teleport to the map, not to the map entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var map) || !_entityManager.TryGetComponent<MapComponent>(map, out var mapComp))
break;
_console.ExecuteCommand($"tp 0 0 {mapComp.MapId}");
break;
}
case ObjectsTabSelection.Stations:
{
// teleport to the station's largest grid, not to the station entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var station))
break;
var largestGrid = _entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetLargestGrid(station.Value);
if (largestGrid == null)
break;
_console.ExecuteCommand($"tpto {largestGrid.Value}");
break;
}
default:
throw new NotImplementedException();
}
_console.ExecuteCommand($"tpto {nent}");
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
RefreshObjectList();
}
public void RefreshObjectList()
@@ -107,24 +76,28 @@ public sealed partial class ObjectsTab : Control
switch (selection)
{
case ObjectsTabSelection.Stations:
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetStationNames());
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().Stations);
break;
case ObjectsTabSelection.Grids:
{
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
break;
}
case ObjectsTabSelection.Maps:
{
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
@@ -159,8 +132,8 @@ public sealed partial class ObjectsTab : Control
entry.OnTeleport += TeleportTo;
entry.OnDelete += Delete;
button.ToolTip = $"{info.Name}, {info.Entity}";
button.AddChild(entry);
button.StyleClasses.Clear();
}
private bool DataFilterCondition(string filter, ListData listData)
@@ -1,6 +1,5 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
@@ -21,7 +20,7 @@
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<controls:ConfirmButton Name="DeleteButton"
<Button Name="DeleteButton"
Text="{Loc object-tab-entity-delete}"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
@@ -35,8 +35,6 @@ public sealed partial class PlayerTab : Control
private AdminPlayerTabColorOption _playerTabColorSetting;
private AdminPlayerTabRoleTypeOption _playerTabRoleSetting;
private AdminPlayerTabSymbolOption _playerTabSymbolSetting;
private bool _markGhosted; // DeltaV
private bool _markWatchlisted; // DeltaV
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
@@ -53,8 +51,6 @@ public sealed partial class PlayerTab : Control
_config.OnValueChanged(CCVars.AdminPlayerTabRoleSetting, RoleSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabMarkGhosted, MarkGhostedChanged, true); // DeltaV
_config.OnValueChanged(CCVars.AdminPlayerTabMarkWatchlisted, MarkWatchlistedChanged, true); // DeltaV
OverlayButton.OnPressed += OverlayButtonPressed;
@@ -72,20 +68,6 @@ public sealed partial class PlayerTab : Control
}
// DeltaV - mark ghosted, watchlisted START
private void MarkGhostedChanged(bool value)
{
_markGhosted = value;
RefreshPlayerList(_players);
}
private void MarkWatchlistedChanged(bool value)
{
_markWatchlisted = value;
RefreshPlayerList(_players);
}
// DeltaV - mark ghosted, watchlisted END
#region Antag Overlay
private void OverlayEnabled()
@@ -172,25 +154,8 @@ public sealed partial class PlayerTab : Control
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info =>
{
// DeltaV - additions START
var filteringStringAdditions = "";
if (_markGhosted && info.Ghost)
{
filteringStringAdditions += " (G)";
}
if (_markWatchlisted && info.Watchlisted)
{
filteringStringAdditions += " (WL)";
}
// DeltaV - additions END
return new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}" +
filteringStringAdditions); // DeltaV - append filteringStringAdditions
})
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
@@ -204,13 +169,9 @@ public sealed partial class PlayerTab : Control
new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor),
_playerTabColorSetting,
_playerTabRoleSetting,
_playerTabSymbolSetting,
_markGhosted, // DeltaV - Add _markGhosted
_markWatchlisted // DeltaV - Add _markWatchlisted
);
_playerTabSymbolSetting);
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
button.StyleClasses.Clear();
}
/// <summary>
@@ -1,11 +1,9 @@
using Content.Shared.Administration;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
@@ -13,24 +11,18 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab;
public sealed partial class PlayerTabEntry : PanelContainer
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
public PlayerTabEntry(
PlayerInfo player,
StyleBoxFlat styleBoxFlat,
AdminPlayerTabColorOption colorOption,
AdminPlayerTabRoleTypeOption roleSetting,
AdminPlayerTabSymbolOption symbolSetting,
bool markGhosted, // DeltaV - Add markGhosted
bool markWatchlisted // DeltaV - Add markWatchlisted
)
AdminPlayerTabSymbolOption symbolSetting)
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
var roles = _entMan.System<SharedRoleSystem>();
var rolePrototype = player.RoleProto == null ? null : _prototype.Index(player.RoleProto.Value);
UsernameLabel.Text = player.Username;
if (!player.Connected)
UsernameLabel.StyleClasses.Add("Disabled");
@@ -65,30 +57,19 @@ public sealed partial class PlayerTabEntry : PanelContainer
break;
default:
case AdminPlayerTabSymbolOption.Specific:
symbol = player.Antag ? rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol : string.Empty;
symbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
break;
}
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
if (player.Antag && colorAntags)
CharacterLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
CharacterLabel.FontColorOverride = player.RoleProto.Color;
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
// DeltaV - Mark ghosted/watchlisted players START
if (player.Ghost && markGhosted)
{
CharacterLabel.Text = $"(G) {CharacterLabel.Text}";
}
if (player.Watchlisted && markWatchlisted)
{
CharacterLabel.Text = $"(WL) {CharacterLabel.Text}";
}
// DeltaV - Mark ghosted/watchlisted players END
var roletype = RoleTypeLabel.Text = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var subtype = roles.GetRoleSubtypeLabel(rolePrototype?.Name ?? RoleTypePrototype.FallbackName, player.Subtype);
var roletype = RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
var subtype = roles.GetRoleSubtypeLabel(player.RoleProto.Name, player.Subtype);
switch (roleSetting)
{
case AdminPlayerTabRoleTypeOption.RoleTypeSubtype:
@@ -111,7 +92,7 @@ public sealed partial class PlayerTabEntry : PanelContainer
}
if (colorRoles)
RoleTypeLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
BackgroundColorPanel.PanelOverride = styleBoxFlat;
OverallPlaytimeLabel.Text = player.PlaytimeString;
}
@@ -60,24 +60,8 @@ public sealed class ClientAlertsSystem : AlertsSystem
if (args.Current is not AlertComponentState cast)
return;
// Save all client-sided alerts to later put back in
var clientAlerts = new Dictionary<AlertKey, AlertState>();
foreach (var alert in alerts.Comp.Alerts)
{
if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto))
{
if (alertProto.ClientHandled)
clientAlerts[alert.Key] = alert.Value;
}
}
alerts.Comp.Alerts = new(cast.Alerts);
foreach (var alert in clientAlerts)
{
alerts.Comp.Alerts[alert.Key] = alert.Value;
}
UpdateHud(alerts);
}
@@ -1,90 +0,0 @@
using System.Numerics;
using Content.Shared.Alert.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
namespace Content.Client.Alerts;
/// <summary>
/// This handles <see cref="GenericCounterAlertComponent"/>
/// </summary>
public sealed class GenericCounterAlertSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<GenericCounterAlertComponent, UpdateAlertSpriteEvent>(OnUpdateAlertSprite);
}
private void OnUpdateAlertSprite(Entity<GenericCounterAlertComponent> ent, ref UpdateAlertSpriteEvent args)
{
var sprite = args.SpriteViewEnt.Comp;
var ev = new GetGenericAlertCounterAmountEvent(args.Alert);
RaiseLocalEvent(args.ViewerEnt, ref ev);
if (!ev.Handled)
return;
// It cannot be null if its handled, but good to check to avoid ugly null ignores.
if (ev.Amount == null)
return;
// How many digits can we display
var maxDigitCount = GetMaxDigitCount((ent, ent, sprite));
// Clamp it to a positive number that we can actually display in full (no rollover to 0)
var amount = (int) Math.Clamp(ev.Amount.Value, 0, Math.Pow(10, maxDigitCount) - 1);
// This is super wack but ig it works?
var digitCount = ent.Comp.HideLeadingZeroes
? amount.ToString().Length
: maxDigitCount;
if (ent.Comp.HideLeadingZeroes)
{
for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
{
if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
continue;
_sprite.LayerSetVisible(ent.Owner, layer, i <= digitCount - 1);
}
}
// ReSharper disable once PossibleLossOfFraction
var baseOffset = (ent.Comp.AlertSize.X - digitCount * ent.Comp.GlyphWidth) / 2 * (1f / EyeManager.PixelsPerMeter);
for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
{
if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
continue;
var result = amount / (int) Math.Pow(10, i) % 10;
_sprite.LayerSetRsiState(ent.Owner, layer, result.ToString());
if (ent.Comp.CenterGlyph)
{
var offset = baseOffset + (digitCount - 1 - i) * ent.Comp.GlyphWidth * (1f / EyeManager.PixelsPerMeter);
_sprite.LayerSetOffset(ent.Owner, layer, new Vector2(offset, 0));
}
}
}
/// <summary>
/// Gets the number of digits that we can display.
/// </summary>
/// <returns>The number of digits.</returns>
private int GetMaxDigitCount(Entity<GenericCounterAlertComponent, SpriteComponent> ent)
{
for (var i = ent.Comp1.DigitKeys.Count - 1; i >= 0; i--)
{
if (_sprite.LayerExists((ent.Owner, ent.Comp2), ent.Comp1.DigitKeys[i]))
return i + 1;
}
return 0;
}
}
@@ -11,14 +11,11 @@ public record struct UpdateAlertSpriteEvent
{
public Entity<SpriteComponent> SpriteViewEnt;
public EntityUid ViewerEnt;
public AlertPrototype Alert;
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, EntityUid viewerEnt, AlertPrototype alert)
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, AlertPrototype alert)
{
SpriteViewEnt = spriteViewEnt;
ViewerEnt = viewerEnt;
Alert = alert;
}
}
@@ -1,5 +0,0 @@
using Content.Shared.Animals.Systems;
namespace Content.Client.Animals.Systems;
public sealed class ParrotMemorySystem : SharedParrotMemorySystem;
@@ -1,40 +0,0 @@
using Robust.Client.Graphics;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Anomaly;
/// <summary>
/// This component creates and handles the drawing of a ScreenTexture to be used on the Anomaly Scanner
/// for an indicator of Anomaly Severity.
/// </summary>
/// <remarks>
/// In the future I would like to make this a more generic "DynamicTextureComponent" that can contain a dictionary
/// of texture components like "Bar(offset, size, minimumValue, maximumValue, AppearanceKey, LayerMapKey)" that can
/// just draw a bar or other basic drawn element that will show up on a texture layer.
/// </remarks>
[RegisterComponent]
[Access(typeof(AnomalyScannerSystem))]
public sealed partial class AnomalyScannerScreenComponent : Component
{
/// <summary>
/// This is the texture drawn as a layer on the Anomaly Scanner device.
/// </summary>
public OwnedTexture? ScreenTexture;
/// <summary>
/// A small buffer that we can reuse to draw the severity bar.
/// </summary>
public Rgba32[]? BarBuf;
/// <summary>
/// The position of the top-left of the severity bar in pixels.
/// </summary>
[DataField(readOnly: true)]
public Vector2i Offset = new Vector2i(12, 17);
/// <summary>
/// The width and height of the severity bar in pixels.
/// </summary>
[DataField(readOnly: true)]
public Vector2i Size = new Vector2i(10, 3);
}
@@ -1,110 +0,0 @@
using System.Numerics;
using Content.Shared.Anomaly;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Anomaly;
/// <inheritdoc cref="SharedAnomalyScannerSystem"/>
public sealed class AnomalyScannerSystem : SharedAnomalyScannerSystem
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
private const float MaxHueDegrees = 360f;
private const float GreenHueDegrees = 110f;
private const float RedHueDegrees = 0f;
private const float GreenHue = GreenHueDegrees / MaxHueDegrees;
private const float RedHue = RedHueDegrees / MaxHueDegrees;
// Just an array to initialize the pixels of a new OwnedTexture
private static readonly Rgba32[] EmptyTexture = new Rgba32[32*32];
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnomalyScannerScreenComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<AnomalyScannerScreenComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<AnomalyScannerScreenComponent, AppearanceChangeEvent>(OnScannerAppearanceChanged);
}
private void OnComponentInit(Entity<AnomalyScannerScreenComponent> ent, ref ComponentInit args)
{
if(!_sprite.TryGetLayer(ent.Owner, AnomalyScannerVisualLayers.Base, out var layer, true))
return;
// Allocate the OwnedTexture
ent.Comp.ScreenTexture = _clyde.CreateBlankTexture<Rgba32>(layer.PixelSize);
if (layer.PixelSize.X < ent.Comp.Offset.X + ent.Comp.Size.X ||
layer.PixelSize.Y < ent.Comp.Offset.Y + ent.Comp.Size.Y)
{
// If the bar doesn't fit, just bail here, ScreenTexture and BarBuf will remain null, and appearance updates
// will do nothing.
DebugTools.Assert(false, "AnomalyScannerScreenComponent: Bar does not fit within sprite");
return;
}
// Initialize the texture
ent.Comp.ScreenTexture.SetSubImage((0, 0), layer.PixelSize, new ReadOnlySpan<Rgba32>(EmptyTexture));
// Initialize bar drawing buffer
ent.Comp.BarBuf = new Rgba32[ent.Comp.Size.X * ent.Comp.Size.Y];
}
private void OnComponentStartup(Entity<AnomalyScannerScreenComponent> ent, ref ComponentStartup args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
_sprite.LayerSetTexture((ent, sprite), AnomalyScannerVisualLayers.Screen, ent.Comp.ScreenTexture);
}
private void OnScannerAppearanceChanged(Entity<AnomalyScannerScreenComponent> ent, ref AppearanceChangeEvent args)
{
if (args.Sprite is null || ent.Comp.ScreenTexture is null || ent.Comp.BarBuf is null)
return;
args.AppearanceData.TryGetValue(AnomalyScannerVisuals.AnomalySeverity, out var severityObj);
if (severityObj is not float severity)
severity = 0;
// Get the bar length
var barLength = (int)(severity * ent.Comp.Size.X);
// Calculate the bar color
// Hue "angle" of two colors to interpolate between depending on severity
// Just a lerp from Green hue at severity = 0.5 to Red hue at 1.0
var hue = Math.Clamp(2*GreenHue * (1 - severity), RedHue, GreenHue);
var color = new Rgba32(Color.FromHsv(new Vector4(hue, 1f, 1f, 1f)).RGBA);
var transparent = new Rgba32(0, 0, 0, 255);
for(var y = 0; y < ent.Comp.Size.Y; y++)
{
for (var x = 0; x < ent.Comp.Size.X; x++)
{
ent.Comp.BarBuf[y*ent.Comp.Size.X + x] = x < barLength ? color : transparent;
}
}
// Copy the buffer to the texture
try
{
ent.Comp.ScreenTexture.SetSubImage(
ent.Comp.Offset,
ent.Comp.Size,
new ReadOnlySpan<Rgba32>(ent.Comp.BarBuf)
);
}
catch (IndexOutOfRangeException)
{
Log.Warning($"Bar dimensions out of bounds with the texture on entity {ent.Owner}");
}
}
}
+4 -5
View File
@@ -7,7 +7,7 @@ using Robust.Shared.Timing;
namespace Content.Client.Anomaly;
public sealed partial class AnomalySystem : SharedAnomalySystem
public sealed class AnomalySystem : SharedAnomalySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly FloatingVisualizerSystem _floating = default!;
@@ -24,7 +24,6 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
}
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
{
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
@@ -63,11 +62,11 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
{
base.Update(frameTime);
var query = EntityQueryEnumerator<AnomalyComponent, AnomalySupercriticalComponent, SpriteComponent>();
var query = EntityQueryEnumerator<AnomalySupercriticalComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out var anomaly, out var super, out var sprite))
while (query.MoveNext(out var uid, out var super, out var sprite))
{
var completion = 1f - (float) ((super.EndTime - _timing.CurTime) / anomaly.SupercriticalDuration);
var completion = 1f - (float)((super.EndTime - _timing.CurTime) / super.SupercriticalDuration);
var scale = completion * (super.MaxScaleAmount - 1f) + 1f;
_sprite.SetScale((uid, sprite), new Vector2(scale, scale));
@@ -1,6 +1,6 @@
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects;
using Content.Shared.Humanoid;
using Content.Shared.Body.Components;
using Robust.Client.GameObjects;
namespace Content.Client.Anomaly.Effects;
@@ -25,8 +25,9 @@ public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
var index = _sprite.LayerMapReserve((ent.Owner, sprite), ent.Comp.LayerMap);
if (TryComp<HumanoidAppearanceComponent>(ent, out var humanoidAppearance) &&
ent.Comp.SpeciesSprites.TryGetValue(humanoidAppearance.Species, out var speciesSprite))
if (TryComp<BodyComponent>(ent, out var body) &&
body.Prototype is not null &&
ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite))
{
_sprite.LayerSetSprite((ent.Owner, sprite), index, speciesSprite);
}
@@ -44,7 +45,7 @@ public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index)) // imp. added this check to prevent errors on anomalites - not having it was bad code on upstream's part
sprite.LayerSetVisible(index, false);
var index = _sprite.LayerMapGet((ent.Owner, sprite), ent.Comp.LayerMap);
_sprite.LayerSetVisible((ent.Owner, sprite), index, false);
}
}

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