Compare commits

..
Author SHA1 Message Date
Tobias Berger c1cc812cd3 Better admin logging for nanochat 2025-12-14 21:09:23 +01:00
Tobias Berger e273a1fa3a Add some more nanochat admin logging 2025-12-14 20:21:00 +01:00
Tobias Berger e297168eef Fix emoji some more 2025-12-12 10:43:22 +01:00
Tobias Berger 984a5384a5 Revert "Show numbers in message sender label"
This reverts commit 9e617db031.
2025-12-11 16:51:02 +01:00
Tobias Berger b37476785c Only lookup recipients inside a PDA 2025-12-11 16:40:32 +01:00
Tobias Berger 997d5b691f Disable NanoChat UI when no ID inserted 2025-12-11 15:40:48 +01:00
Tobias Berger 63e37a76bf Contact list update on create 2025-12-11 15:14:39 +01:00
Tobias Berger 66b8a7f74b Emoji ID for debugging 2025-12-11 14:19:12 +01:00
Tobias Berger 35b07aeacd Fix non-localized string 2025-12-11 14:05:49 +01:00
Tobias Berger fe2a30456f Escape nanochat messages 2025-12-11 14:04:25 +01:00
Tobias Berger e23d6f3ba7 Sort instead of Order 2025-12-11 12:44:50 +01:00
Tobias Berger f167435cfc Fix doubled sender number 2025-12-11 12:02:31 +01:00
Tobias Berger 3c93bfc943 Move 'view member' button 2025-12-11 12:02:12 +01:00
Tobias Berger 9e617db031 Show numbers in message sender label 2025-12-11 03:13:05 +01:00
Tobias Berger 258dd35754 Improve emoji insertion UX 2025-12-11 03:04:26 +01:00
Tobias Berger 126ee2ac35 Improve padding of NanoChat Message list 2025-12-11 03:04:15 +01:00
Tobias Berger 188e895d6a Clean up Funky commments in _DV 2025-12-11 03:03:36 +01:00
Tobias Berger 08ecb92fdc Clear emoji cache when prototypes reload 2025-12-11 02:51:38 +01:00
Tobias Berger 683b9db3d9 Touch up NanoChatRecipient 2025-12-11 02:50:50 +01:00
Tobias Berger 880c1bed31 Fix Emoji alignment 2025-12-11 02:48:22 +01:00
Tobias Berger aba4972866 Fix emoji 2025-12-10 19:51:01 +01:00
Tobias Berger 2758b202fa Remove redundant subscription 2025-12-10 18:40:32 +01:00
Tobias Berger f797d0bf8d Cherrypick fixes 2025-12-10 18:07:31 +01:00
Tobias Berger 5602dc5089 optimize nanochat emojis 2025-12-10 18:01:02 +01:00
Evaisa 19722eeec7 Fallback for listing sender names.
Sender names now show up if they are in your contacts, even if they do not have List Number enabled.
If their name can not be obtained then it will show "Unknown#0000" rather than just the nanochat number.

(cherry picked from commit 90775680d8b7b2d95636dd1027e56d48118e55e1)
2025-12-10 17:55:47 +01:00
Evaisa fc558e41ea compiles properly now
(cherry picked from commit a000c7b35937bbfc94909cf2da092e90a27161f1)
2025-12-10 17:55:47 +01:00
Evaisa daf070f876 Fix issues with nanochat
When pAI and PDA owner open nanochat at the same time it should no longer break.
"Invite To Group" list now shows accurate/live data instead of outdated cached recipient data
Popups should no longer stay open when switching apps or closing the PDA.
Group chats should now properly show when messages failed to deliver.
Invite menus now show job titles.
Invite menus are now sorted by department.

(cherry picked from commit f23580452a0a43cc105e5642ead35999a820c675)
2025-12-10 17:55:44 +01:00
funkystationbot 2cd02f1ea8 chore: Automatically update REUSE headers
(cherry picked from commit 661e8136af4c7b31d657531b667043e35b84190a)
2025-12-10 17:51:17 +01:00
Evaisa e1ce26896e Emoji definitions are done in yaml instead.
Emojis are now defined through yaml, hope I did not miss any.

(cherry picked from commit 48936a63248e7e76855157a36ff6214faa4c6051)
2025-12-10 17:51:17 +01:00
funkystationbot bd2245eee3 chore: Automatically update REUSE headers
(cherry picked from commit 84d2cc3a1f31878e5d18ebde63928d63f3b4dd95)
2025-12-10 17:51:13 +01:00
Evaisa 0b03791672 fixes
yippee

(cherry picked from commit 2d847cd7df0780f439cec6782414c86d859b0a56)
2025-12-10 17:50:35 +01:00
Evaisa 7250214259 Fix small bug
- Ownership did not get transferred when clicking the delete button in a group chat.

(cherry picked from commit d7ccc986155221dbbfb948ed0694347e72c50369)
2025-12-10 17:50:15 +01:00
Evaisa 46c3b5079d Fix some emojis not working because of RSI stuff & Bug with regular chats
- Regular chats were received as the wrong person.
- Certain emotes didn't load outside of debug builds seemingly.

(cherry picked from commit deb2b584c77b1715a1adb1327c52f80046f7c79a)
2025-12-10 17:50:15 +01:00
Evaisa 3e618923ea Added group chats and emojis
- You can now create group chats in NanoChat
- You can also send your meta friends your favourite :godo: emote.

(cherry picked from commit eedb378b9f33033faf4e9dd139f4b2366dcf0632)
2025-12-10 17:50:02 +01:00
11596 changed files with 405209 additions and 1019539 deletions
+1 -1
View File
@@ -351,7 +351,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]
+4 -1
View File
@@ -9,6 +9,9 @@
# Server config files
/Resources/ConfigPresets/ @Toby222
# 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
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction
@@ -19,7 +22,7 @@
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers
# Server rules
/Resources/ServerInfo/Guidebook/_DV/Rules/ @DeltaV-Station/head-administrators
/Resources/ServerInfo/Guidebook/DeltaV/Rules/ @DeltaV-Station/head-administrators
# Wiki
/.wiki/ @DeltaV-Station/direction
+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!
+1 -10
View File
@@ -12,25 +12,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
+3 -11
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,15 +32,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
+2 -10
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,15 +32,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
+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 -10
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
+6 -11
View File
@@ -20,16 +20,11 @@ jobs:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
exempt-issue-labels: 'Priority: 1-Urgent,ignore-stale'
days-before-stale: 90
stale-issue-message: 'This issue will close in 2 days due to inactivity'
days-before-close: 2
stale-pr-message: 'This Pull Request will close in 2 days due to inactivity'
stale-issue-label: 'stale'
stale-pr-label: 'stale'
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.
-29
View File
@@ -289,33 +289,4 @@ Emergency alert status. ''Central Command has ordered the Epsilon security level
| 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}}
+36 -36
View File
@@ -6,15 +6,15 @@ It is highly recommended to read the '''[[Standard Operating Procedure|Standard
* '''[[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#Entity Definitions|Pet]]:''' Any Entity that is provided to Company's Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support.
* '''[[Standard Operating Procedure#Security Jurisdiction|Security Jurisdiction]]:''' Station law enforcement officers are bound by Space Law; they cannot operate outside of standard accessible areas, or perform searches, unless such action is allowed by either Alert Procedure or a valid warrant.
* '''[[Standard Operating Procedure#Arrest|Arrest and Sentencing]]:''' Any entity may be arrested, during which they are restrained, informed of their charges, and taken to the brig for search and processing under '''[[Standard Operating Procedure#Brig Procedure|Brig Procedure]]'''.
* '''[[Standard Operating Procedure#Sentencing|Arrest and Sentencing]]:''' Any member of law enforcement is allowed to pass sentence on Misdemeanor, Felony, and Grand Felony charges. The Chief Justice, or in their absence, the Head of Security are allowed to pass sentence on all Charges.
* '''[[Standard Operating Procedure#Treatment of Prisoners|Treatment of Prisoners]]:''' Prisoners must be provided with food, water, clothing, free movement within the brig, adequate medical care, access to the Common and Prison radio channels, and moral, spiritual, or legal counseling if requested and available. If the brig becomes uninhabitable, prisoners must be securely and safely relocated to another area until the brig is functional once more. Prisoners sentenced to Permanent Confinement have additional rights and firmer restrictions that must be upheld by law enforcement officials.
* '''[[Standard Operating Procedure#Paroles and Pardons|Paroles & Pardons]]:''' Parole may be offered under the discretion of the Warden, Head of Security, or CO. Pardons may be issued by the arresting officer, the Judge of a trial, by the Warden (to those within the permanent brig), or by the CO, when deemed in the best interest of the crew or vessel, or when the circumstances of the crime warrant suspension of sentence.
* '''[[Standard Operating Procedure#Arrest|Arrest]]:'''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|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 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 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#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:
@@ -165,7 +165,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| 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]]
@@ -204,49 +204,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;"| 16 minutes
|style= "border:1px solid black;"| To act without malice in a manner which, directly or indirectly, leads to the death of an Employee.
|-
|style= "border:1px solid black;"| 302
|style= "border:1px solid black;"| [[File:SL_Kidnapping.png]]
|style= "border:1px solid black;"| Kidnapping
|style= "border:1px solid black;"| 10 minutes
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an Employee against their will.
|style= "border:1px solid black;"| 16 minutes
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an Employee 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,49 +262,49 @@ 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;" | 10 minutes
| style="border: 1px solid black;" | To cause physical harm or to effect unwanted physical contact on an Employee, without the apparent intent to kill them, or to threaten such actions with both capability and intent to do so.
|-
| style="border: 1px solid black;" | 202
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
| style="border: 1px solid black;" | 5 minutes
| style="border: 1px solid black;" | To break and enter into an area where one is not authorised nor invited.
| style="border: 1px solid black;" | 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
@@ -329,14 +329,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;" | 5 minutes
| style="border: 1px solid black;" | To inflict unnecessary suffering on a Pet with malicious intent.
|-
|style= "border:1px solid black;"| 102
|style= "border:1px solid black;"| WIP
!style= "border:1px solid black;"| Harassment
|style= "border:1px solid black;"| 3 minutes
|style= "border:1px solid black;"| To verbally demean, humiliate, or harass an Employee with malice; or maliciously spread false information amongst a population.
|style= "border:1px solid black;"| 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 +347,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]]
+8 -27
View File
@@ -13,13 +13,13 @@ The terms specified below are defined for further use within the procedures.
:* 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.
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 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:
@@ -79,7 +79,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.
@@ -103,25 +102,25 @@ A list of such Substances follow:
Controlled Armaments are grouped into categories of increasing severity. If the offender possesses several categories of contraband, only the highest severity of said items should be charged.
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 3min brig and further legal action as the situation demands.''
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 5min brig and further legal action as the situation demands.''
* Any object or instrument specifically designed, adapted, or used to cause physical harm in close combat
* Any object or equipment specifically designed or adapted to protect against physical harm caused by weapons, projectiles, or other forms of direct assault
* Any object or instrument specifically designed or adapted to incapacitate or disorient by using light or sound without causing permanent injury
* Any object or equipment specifically designed or adapted for explicitly training purposes that would otherwise fall under another category
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 8min brig and further legal action as the situation demands.''
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 10min brig and further legal action as the situation demands.''
* Any object or instrument specifically designed or adapted to cause physical harm in close combat, which employs a powered system to enhance performance
* Any manually-operated ballistic arms that possess an internal magazine or feeding system, or lack an internal magazine or feeding system altogether
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 12min brig and further legal action as the situation demands.''
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 15min brig and further legal action as the situation demands.''
* Any manually-operated ballistic arms that use an external magazine or feeding system
* Any semi-automatic ballistic arms not easily concealable within a pocket or coat
* Any object or instrument specifically designed or adapted for explicitly less-lethal purposes that would otherwise fall under another category
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 15min brig and further legal action as the situation demands.''
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 20min brig and further legal action as the situation demands.''
* Any semi-automatic ballistic arms that are easily concealable
* Any fully-automatic ballistic arms
@@ -129,7 +128,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 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
@@ -269,6 +268,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.
@@ -375,22 +375,3 @@ Medical staff are afforded the right to quarantine individuals that currently su
==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.
+1 -1
View File
@@ -11,7 +11,7 @@ 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"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
+18 -78
View File
@@ -73,88 +73,28 @@ If you are adding a lot of C# code, then take advantage of partial classes. Put
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.
@@ -164,10 +104,10 @@ 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]
#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`
+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>
-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();
}
}
+1 -1
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;
+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;
@@ -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
@@ -57,7 +57,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 +65,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(GeneralAccessGroup, out var generalAccessProto))
_groupedAccessLevels.TryAdd(generalAccessProto, new());
// Assign known access levels with their associated groups
foreach (var accessLevel in _accessLevels)
{
if (!_protoManager.Resolve(accessLevel, out var accessLevelProto))
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto))
continue;
var assigned = false;
@@ -99,8 +99,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 +119,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 +165,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,37 +81,19 @@ 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)
{
foreach (var button in _accessButtons.ButtonsList.Values)
{
if (!button.Disabled && button.Pressed != enabled)
button.Pressed = enabled;
}
}
// DeltaV - removed as part of job preset access fix
// private void ClearAllAccess()
private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
{
@@ -121,34 +105,28 @@ namespace Content.Client.Access.UI
JobTitleLineEdit.Text = Loc.GetString(job.Name);
args.Button.SelectId(args.Id);
SetAllAccess(false);
// this is a sussy way to do this
foreach (var access in job.Access)
{
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
}
// DeltaV - start of job preset access fix
SubmitData();
var targetAccesses = job.Access.ToHashSet();
foreach (var group in job.AccessGroups)
{
if (!_prototypeManager.Resolve(group, out AccessGroupPrototype? groupPrototype))
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
{
continue;
}
foreach (var access in groupPrototype.Tags)
{
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
}
targetAccesses.UnionWith(groupPrototype.Tags);
}
SubmitData();
// this is a sussy way to do this
foreach (var (id, button) in _accessButtons.ButtonsList)
{
if (!button.Disabled && button.Pressed != targetAccesses.Contains(id))
{
OnToggleAccess?.Invoke(id);
}
}
// DeltaV - end of job preset access fix
}
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
@@ -222,7 +200,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);
}
}
+3 -23
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;
@@ -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)
@@ -23,10 +23,9 @@ namespace Content.Client.Actions.UI
public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = 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,7 +46,7 @@ namespace Content.Client.Actions.UI
var description = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc }
StyleClasses = {StyleNano.StyleClassTooltipActionDescription}
};
description.SetMessage(desc);
vbox.AddChild(description);
@@ -56,7 +55,7 @@ namespace Content.Client.Actions.UI
vbox.AddChild(_cooldownLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc },
StyleClasses = {StyleNano.StyleClassTooltipActionCooldown},
Visible = false
});
@@ -65,7 +64,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))
@@ -8,14 +8,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;
@@ -28,7 +26,6 @@ internal sealed class AdminNameOverlay : Overlay
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;
@@ -56,8 +53,7 @@ internal sealed class AdminNameOverlay : Overlay
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config,
SharedRoleSystem roles,
IPrototypeManager prototypeManager,
IGameTiming timing) // DeltaV - Add timing
IPrototypeManager prototypeManager)
{
_system = system;
_entityManager = entityManager;
@@ -66,7 +62,6 @@ internal sealed class AdminNameOverlay : Overlay
_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();
@@ -139,7 +134,7 @@ internal sealed class AdminNameOverlay : Overlay
? null
: _prototypeManager.Index(playerInfo.RoleProto.Value);
var roleName = rolePrototype?.Name ?? RoleTypePrototype.FallbackName;
var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
@@ -201,22 +196,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,22 +213,10 @@ 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)
@@ -272,7 +241,7 @@ internal sealed class AdminNameOverlay : Overlay
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? Loc.GetString(roleName).ToUpper()
? roleName.ToUpper()
: string.Empty;
break;
case AdminOverlayAntagFormat.Subtype:
@@ -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;
@@ -5,7 +5,6 @@ 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
{
@@ -20,7 +19,6 @@ namespace Content.Client.Administration.Systems
[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!;
@@ -38,8 +36,7 @@ namespace Content.Client.Administration.Systems
_userInterfaceManager,
_configurationManager,
_roles,
_proto,
_timing); // DeltaV - Add timing
_proto);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}
@@ -1,7 +0,0 @@
using Content.Shared.Administration.Systems;
namespace Content.Client.Administration.Systems;
public sealed class BufferingSystem : SharedBufferingSystem
{
}
@@ -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,5 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc admin-camera-window-title-placeholder}"
SetSize="425 550"
MinSize="200 225">
MinSize="200 225"
Name="Window">
</DefaultWindow>
@@ -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)
@@ -24,7 +24,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; }
@@ -37,8 +37,8 @@ public sealed partial class BanPanel : DefaultWindow
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// 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 Dictionary<string, List<Button>> _roleCheckboxes = new();
private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -79,7 +79,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 +110,7 @@ public sealed partial class BanPanel : DefaultWindow
TypeOption.SelectId(args.Id);
OnTypeChanged();
};
LastConnCheckbox.OnPressed += _ =>
LastConnCheckbox.OnPressed += args =>
{
IpLine.ModulateSelfOverride = null;
HwidLine.ModulateSelfOverride = null;
@@ -164,7 +164,7 @@ public sealed partial class BanPanel : DefaultWindow
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
.OrderBy(x => x.ID);
CreateRoleGroup(AntagPrototype.GroupName, AntagPrototype.GroupColor, antagRoles);
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
}
/// <summary>
@@ -226,7 +226,7 @@ public sealed partial class BanPanel : DefaultWindow
var roleGroupCheckbox = new Button
{
Name = $"{groupName}GroupCheckbox",
Text = Loc.GetString("role-bans-ban-group"),
Text = "Ban all",
Margin = new Thickness(0, 0, 5, 0),
ToggleMode = true,
};
@@ -236,14 +236,14 @@ public sealed partial class BanPanel : DefaultWindow
{
foreach (var role in _roleCheckboxes[groupName])
{
role.Item1.Pressed = args.Pressed;
role.Pressed = args.Pressed;
}
if (args.Pressed)
{
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banPanelSawmill
_banpanelSawmill
.Warning("Departmental role ban severity could not be parsed from config!");
return;
}
@@ -255,14 +255,14 @@ public sealed partial class BanPanel : DefaultWindow
{
foreach (var button in roleButtons)
{
if (button.Item1.Pressed)
if (button.Pressed)
return;
}
}
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banPanelSawmill
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
return;
}
@@ -294,44 +294,30 @@ public sealed partial class BanPanel : DefaultWindow
}
/// <summary>
/// Adds a toggle button specifically for one "role" in a "group"
/// Adds a checkbutton specifically for one "role" in a "group"
/// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
/// </summary>
private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
{
var roleCheckboxContainer = new BoxContainer();
var roleToggleButton = new Button
var roleCheckButton = new Button
{
Name = role,
Name = $"{role}RoleCheckbox",
Text = role,
ToggleMode = true,
};
roleToggleButton.OnToggled += args =>
roleCheckButton.OnToggled += args =>
{
// Checks the role group checkbox if all the children are pressed
if (args.Pressed && _roleCheckboxes[group].All(e => e.Item1.Pressed))
if (args.Pressed && _roleCheckboxes[group].All(e => e.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))
// Yeah, this is sus, but having to split the functions up and stuff is worse imo.
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
{
var jobIconTexture = new TextureRect
{
@@ -343,12 +329,12 @@ public sealed partial class BanPanel : DefaultWindow
roleCheckboxContainer.AddChild(jobIconTexture);
}
roleCheckboxContainer.AddChild(roleToggleButton);
roleCheckboxContainer.AddChild(roleCheckButton);
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
_roleCheckboxes.TryAdd(group, []);
_roleCheckboxes[group].Add((roleToggleButton, rolePrototype));
_roleCheckboxes[group].Add(roleCheckButton);
}
public void UpdateBanFlag(bool newFlag)
@@ -391,7 +377,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 +487,7 @@ public sealed partial class BanPanel : DefaultWindow
newSeverity = serverSeverity;
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Server ban severity could not be parsed from config!");
}
@@ -514,7 +500,7 @@ public sealed partial class BanPanel : DefaultWindow
}
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
}
break;
@@ -559,51 +545,34 @@ 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 })
if (button is { Pressed: true, Text: not null })
{
switch (button.Item2)
{
case JobPrototype:
jobList.Add(button.Item2.ID);
break;
case AntagPrototype:
antagList.Add(button.Item2.ID);
break;
}
rolesList.Add(button.Text);
}
}
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 +584,6 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.GrabKeyboardFocus();
ReasonTextEdit.ModulateSelfOverride = Color.Red;
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
return;
}
@@ -624,7 +592,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 +600,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" />
@@ -1,33 +0,0 @@
using Content.Shared.Administration.Logs;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Administration.UI.CustomControls;
public sealed class AdminLogLabel : RichTextLabel
{
public AdminLogLabel(ref SharedAdminLog log, HSeparator separator)
{
Log = log;
Separator = separator;
SetMessage($"{log.Date:HH:mm:ss}: {log.Message}");
OnVisibilityChanged += VisibilityChanged;
}
public new SharedAdminLog Log { get; }
public HSeparator Separator { get; }
private void VisibilityChanged(Control control)
{
Separator.Visible = Visible;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
OnVisibilityChanged -= VisibilityChanged;
}
}
@@ -1,16 +1,15 @@
using System.Linq;
using System.Text.RegularExpressions;
using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Controls;
using Content.Client.Verbs.UI;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
@@ -96,13 +95,26 @@ public sealed partial class PlayerListControl : BoxContainer
private void FilterList()
{
_sortedPlayerList.Clear();
Regex filterRegex;
// There is no neat way to handle invalid regex being submitted other than
// catching and ignoring the exception which gets thrown when it's invalid.
try
{
filterRegex = new Regex(FilterLineEdit.Text, RegexOptions.IgnoreCase);
}
catch (ArgumentException)
{
return;
}
foreach (var info in _playerList)
{
var displayName = $"{info.CharacterName} ({info.Username})";
if (info.IdentityName != info.CharacterName)
displayName += $" [{info.IdentityName}]";
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
&& !filterRegex.IsMatch(displayName))
continue;
_sortedPlayerList.Add(info);
}
@@ -1,19 +1,14 @@
using Content.Client.Stylesheets;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
[GenerateTypedNameReferences]
public sealed partial class PlayerListEntry : BoxContainer
{
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 +42,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);
}
}
}
@@ -1,5 +1,6 @@
<Control xmlns="https://spacestation14.io"
xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls">
xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ui="clr-namespace:Content.Client.Options.UI">
<PanelContainer StyleClasses="BackgroundDark">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical">
@@ -54,9 +55,15 @@
<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 HorizontalExpand="True">
<Button Name="RenderRichTextButton" Access="Public" Text="{Loc admin-logs-render-rich-text}"
StyleClasses="OpenRight" ToggleMode="True"/>
<Button Name="RemoveMarkupButton" Access="Public" Text="{Loc admin-logs-remove-markup}"
StyleClasses="OpenLeft" ToggleMode="True"/>
<Control HorizontalExpand="True"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<LineEdit Name="LogSearch" Access="Public" StyleClasses="actionSearchBox"
HorizontalExpand="true" PlaceHolder="{Loc admin-logs-search-logs-placeholder}"/>
@@ -1,6 +1,8 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Administration.UI.Logs.Entries;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
@@ -38,6 +40,9 @@ public sealed partial class AdminLogsControl : Control
SelectAllPlayersButton.OnPressed += SelectAllPlayers;
SelectNoPlayersButton.OnPressed += SelectNoPlayers;
RenderRichTextButton.OnPressed += RenderRichTextChanged;
RemoveMarkupButton.OnPressed += RemoveMarkupChanged;
RoundSpinBox.IsValid = i => i > 0 && i <= CurrentRound;
RoundSpinBox.ValueChanged += RoundSpinBoxChanged;
RoundSpinBox.InitDefaultButtons();
@@ -58,6 +63,8 @@ public sealed partial class AdminLogsControl : Control
private int CurrentRound { get; set; }
private Regex LogSearchRegex { get; set; } = new("");
public int SelectedRoundId => RoundSpinBox.Value;
public string Search => LogSearch.Text;
private int ShownLogs { get; set; }
@@ -77,6 +84,8 @@ public sealed partial class AdminLogsControl : Control
/// </summary>
private List<SharedAdminLog> RawAdminLogs { get; set; } = new();
private bool RenderRichText { get; set; }
private bool RemoveMarkup { get; set; }
public HashSet<LogType> SelectedTypes { get; } = new();
public HashSet<Guid> SelectedPlayers { get; } = new();
@@ -123,6 +132,19 @@ public sealed partial class AdminLogsControl : Control
private void LogSearchChanged(LineEditEventArgs args)
{
// This exception is thrown if the regex is invalid, which happens often, so we ignore it.
try
{
LogSearchRegex = new Regex(
"(" + LogSearch.Text + ")",
RegexOptions.IgnoreCase,
TimeSpan.FromSeconds(1));
}
catch (ArgumentException)
{
return;
}
UpdateLogs();
}
@@ -204,6 +226,26 @@ public sealed partial class AdminLogsControl : Control
UpdateLogs();
}
private void RenderRichTextChanged(ButtonEventArgs args)
{
RenderRichText = args.Button.Pressed;
RemoveMarkup = RemoveMarkup && !RenderRichText;
RemoveMarkupButton.Pressed = RemoveMarkup;
UpdateLogs();
}
private void RemoveMarkupChanged(ButtonEventArgs args)
{
RemoveMarkup = args.Button.Pressed;
RenderRichText = !RemoveMarkup && RenderRichText;
RenderRichTextButton.Pressed = RenderRichText;
UpdateLogs();
}
public void SetTypesSelection(HashSet<LogType> selectedTypes, bool invert = false)
{
SelectedTypes.Clear();
@@ -262,16 +304,15 @@ public sealed partial class AdminLogsControl : Control
foreach (var child in LogsContainer.Children)
{
if (child is not AdminLogLabel log)
{
if (child is not AdminLogEntry log)
continue;
}
child.Visible = ShouldShowLog(log);
if (child.Visible)
{
ShownLogs++;
}
if (!child.Visible)
continue;
log.RenderResults(LogSearchRegex, RenderRichText, RemoveMarkup);
ShownLogs++;
}
UpdateCount();
@@ -289,30 +330,30 @@ public sealed partial class AdminLogsControl : Control
button.Text.Contains(PlayerSearch.Text, StringComparison.OrdinalIgnoreCase);
}
private bool LogMatchesPlayerFilter(AdminLogLabel label)
private bool LogMatchesPlayerFilter(AdminLogEntry entry)
{
if (label.Log.Players.Length == 0)
if (entry.Log.Players.Length == 0)
return SelectedPlayers.Count == 0 || IncludeNonPlayerLogs;
return SelectedPlayers.Overlaps(label.Log.Players);
return SelectedPlayers.Overlaps(entry.Log.Players);
}
private bool ShouldShowLog(AdminLogLabel label)
private bool ShouldShowLog(AdminLogEntry entry)
{
// Check log type
if (!SelectedTypes.Contains(label.Log.Type))
if (!SelectedTypes.Contains(entry.Log.Type))
return false;
// Check players
if (!LogMatchesPlayerFilter(label))
if (!LogMatchesPlayerFilter(entry))
return false;
// Check impact
if (!SelectedImpacts.Contains(label.Log.Impact))
if (!SelectedImpacts.Contains(entry.Log.Impact))
return false;
// Check search
if (!label.Log.Message.Contains(LogSearch.Text, StringComparison.OrdinalIgnoreCase))
if (!LogSearchRegex.IsMatch(entry.Log.Message))
return false;
return true;
@@ -386,15 +427,16 @@ public sealed partial class AdminLogsControl : Control
{
var log = tempLogs[i];
ref var logRef = ref log; // It didn't like me doing this as one line lmao
var entry = new AdminLogEntry(ref log);
var separator = new HSeparator();
var label = new AdminLogLabel(ref logRef, separator);
label.Visible = ShouldShowLog(label);
//var label = new AdminLogLabel(ref logRef, separator);
//label.Visible = ShouldShowLog(label);
if (label.Visible) ShownLogs++;
TotalLogs++;
//if (label.Visible) ShownLogs++;
LogsContainer.AddChild(label);
LogsContainer.AddChild(separator);
//LogsContainer.AddChild(label);
LogsContainer.AddChild(entry);
}
UpdateLogs();
}
@@ -594,6 +636,7 @@ public sealed partial class AdminLogsControl : Control
SelectAllTypesButton.OnPressed -= SelectAllTypes;
SelectNoTypesButton.OnPressed -= SelectNoTypes;
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;
@@ -1,6 +1,6 @@
using System.IO;
using System.Linq;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Administration.UI.Logs.Entries;
using Content.Client.Eui;
using Content.Shared.Administration.Logs;
using Content.Shared.Eui;
@@ -17,16 +17,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 +28,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 +76,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 +88,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
@@ -0,0 +1,14 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Orientation="Vertical">
<BoxContainer Margin="2">
<Collapsible>
<CollapsibleHeading Name="DetailsHeading" Access="Public">
<RichTextLabel Margin="20 0 0 0" Name="Message" MinSize="50 10" VerticalExpand="True" Access="Public" />
</CollapsibleHeading>
<CollapsibleBody Name="DetailsBody" Access="Public" />
</Collapsible>
</BoxContainer>
<cc:HSeparator/>
</BoxContainer>
@@ -0,0 +1,79 @@
using System.Text.RegularExpressions;
using Content.Client.Message;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Logs.Entries;
[GenerateTypedNameReferences]
public sealed partial class AdminLogEntry : BoxContainer
{
private readonly IConfigurationManager _cfgManager;
public SharedAdminLog Log { get; }
private readonly string _rawMessage;
public AdminLogEntry(ref SharedAdminLog log)
{
_cfgManager = IoCManager.Resolve<IConfigurationManager>();
RobustXamlLoader.Load(this);
Log = log;
_rawMessage = $"{log.Date:HH:mm:ss}: {log.Message}";
Message.SetMessage(_rawMessage);
DetailsHeading.OnToggled += DetailsToggled;
}
/// <summary>
/// Sets text to be highlighted from a search result, and renders rich text, or removes all rich text markup.
/// </summary>
public void RenderResults(Regex highlightRegex, bool renderRichText, bool removeMarkup)
{
var color = _cfgManager.GetCVar(CCVars.AdminLogsHighlightColor);
var formattedMessage = renderRichText
? _rawMessage
: removeMarkup
? FormattedMessage.RemoveMarkupPermissive(_rawMessage)
: FormattedMessage.EscapeText(_rawMessage);
// Want to avoid highlighting smaller strings
if (highlightRegex.ToString().Length > 4)
{
try
{
formattedMessage = highlightRegex.Replace(formattedMessage, $"[color={color}]$1[/color]", 3);
}
catch (RegexMatchTimeoutException)
{
// if we time out then don't bother highlighting results
}
}
if (!FormattedMessage.TryFromMarkup(formattedMessage, out var outputMessage))
return;
Message.SetMessage(outputMessage);
}
/// <summary>
/// We perform some extra calculations in the dropdown, so we want to render that only when
/// the dropdown is actually opened.
/// This also removes itself from the event listener so it doesn't trigger again.
/// </summary>
private void DetailsToggled(BaseButton.ButtonToggledEventArgs args)
{
if (!args.Pressed || DetailsBody.ChildCount > 0)
return;
DetailsBody.AddChild(new AdminLogEntryDetails(Log));
DetailsHeading.OnToggled -= DetailsToggled;
}
}
@@ -0,0 +1,44 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
StyleClasses="BackgroundDark">
<BoxContainer Orientation="Vertical" Margin="4">
<BoxContainer Orientation="Vertical">
<Label Text="{Loc admin-logs-field-type}" Margin="0 0 4 0"/>
<Label Name="Type" Text="None" StyleClasses="LabelSecondaryColor" Access="Public"/>
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Vertical">
<Label Text="{Loc admin-logs-field-impact}" Margin="0 0 4 0"/>
<Label Name="Impact" Text="None" StyleClasses="LabelSecondaryColor" Access="Public"/>
</BoxContainer>
</BoxContainer>
<cc:VSeparator/>
<BoxContainer Orientation="Vertical" Margin="4">
<Label Text="{Loc admin-logs-field-time-header}"/>
<cc:HSeparator/>
<BoxContainer>
<Label Text="{Loc admin-logs-field-time-local}" Margin="0 0 4 0" HorizontalExpand="True"/>
<Label Name="LocalTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-logs-field-time-utc}" Margin="0 0 4 0" HorizontalExpand="True"/>
<Label Name="UTCTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-logs-field-time-round}" Margin="0 0 4 0" HorizontalExpand="True"/>
<Label Name="CurTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
<cc:VSeparator/>
<BoxContainer Orientation="Vertical" Margin="4" HorizontalExpand="True">
<Label Text="{Loc admin-logs-field-players-header}"/>
<cc:HSeparator/>
<BoxContainer Orientation="Horizontal" Margin="4" VerticalExpand="True">
<controls:ListContainer Name="PlayerListContainer" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
@@ -0,0 +1,98 @@
using System.Linq;
using Content.Client.Administration.Systems;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.UserInterface.Controls;
using Content.Client.Verbs.UI;
using Content.Shared.Administration.Logs;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Network;
namespace Content.Client.Administration.UI.Logs.Entries;
[GenerateTypedNameReferences]
public sealed partial class AdminLogEntryDetails : BoxContainer
{
private readonly AdminSystem _adminSystem;
private readonly IUserInterfaceManager _uiManager;
private readonly IEntityManager _entManager;
public AdminLogEntryDetails(SharedAdminLog log)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
_adminSystem = _entManager.System<AdminSystem>();
Type.Text = log.Type.ToString();
Impact.Text = log.Impact.ToString();
LocalTime.Text = $"{log.Date.ToLocalTime():HH:mm:ss}";
UTCTime.Text = $"{log.Date:HH:mm:ss}";
// TimeSpan and DateTime use different formatting string conventions for some completely logical reason
// that mere mortals such as myself will never be able to understand.
CurTime.Text = new TimeSpan(log.CurTime).ToString(@"hh\:mm\:ss");
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PopulateList(log.Players);
}
private void PopulateList(Guid[] players)
{
if (players.Length == 0)
return;
if (_adminSystem.PlayerList is not { } allPlayers || allPlayers.Count == 0)
return;
var listData = new List<PlayerListData>();
foreach (var playerGuid in players)
{
var netUserId = new NetUserId(playerGuid);
// Linq here is fine since this runs in response to admin input in the UI and
// this loop only tends to go through 1-4 iterations.
if (allPlayers.FirstOrDefault(player => player.SessionId == netUserId) is not { } playerInfo)
continue;
listData.Add(new PlayerListData(playerInfo));
}
if (listData.Count == 0)
return;
PlayerListContainer.PopulateList(listData);
}
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
return;
if (!(args.Function == EngineKeyFunctions.UIRightClick
|| args.Function == EngineKeyFunctions.UIClick)
|| selectedPlayer.NetEntity == null)
return;
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
args.Handle();
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var info })
return;
var entryLabel = new Label();
entryLabel.Text = $"{info.CharacterName} ({info.Username})";
var entry = new BoxContainer();
entry.AddChild(entryLabel);
button.AddChild(entry);
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
}
}
@@ -67,7 +67,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// </summary>
public void UpdateReagents()
{
ReagentList.RemoveAllChildren();
ReagentList.DisposeAllChildren();
if (_selectedSolution == null || _solutions == null)
return;
@@ -92,7 +92,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 +131,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;
@@ -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 =
@@ -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}\"");
}
}
}
@@ -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,9 +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;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
@@ -69,23 +64,8 @@ public sealed partial class PlayerTab : Control
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
// 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 +152,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,10 +167,7 @@ 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();
@@ -20,10 +20,7 @@ public sealed partial class PlayerTabEntry : PanelContainer
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);
@@ -76,17 +73,6 @@ public sealed partial class PlayerTabEntry : PanelContainer
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);
switch (roleSetting)
@@ -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,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}");
}
}
}
+1 -2
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);
@@ -14,7 +14,7 @@
VerticalExpand="True"
Margin="0 0 0 0"
VerticalAlignment="Center">
<Label Text="{Loc 'anomaly-generator-fuel-display'}" StyleClasses="highlight" />
<Label Text="{Loc 'anomaly-generator-fuel-display'}" StyleClasses="StatusFieldTitle" />
<ProgressBar Name="FuelBar"
HorizontalExpand="True"
MaxValue="1"
@@ -27,8 +27,8 @@
Text="0 %" />
</ProgressBar>
</BoxContainer>
<RichTextLabel Name="CooldownLabel" StyleClasses="highlight" />
<RichTextLabel Name="ReadyLabel" StyleClasses="highlight" />
<RichTextLabel Name="CooldownLabel" StyleClasses="StatusFieldTitle" />
<RichTextLabel Name="ReadyLabel" StyleClasses="StatusFieldTitle" />
</BoxContainer>
<!--Sprite View-->
<PanelContainer Margin="12 0 0 0"
+1 -1
View File
@@ -338,7 +338,7 @@ namespace Content.Client.Arcade
menuInnerPanel.AddChild(menuContainer);
#endregion
ContentsContainer.AddChild(_mainPanel);
Contents.AddChild(_mainPanel);
CanKeyboardFocus = true;
}
@@ -86,7 +86,7 @@ namespace Content.Client.Arcade
newGame.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
grid.AddChild(newGame);
ContentsContainer.AddChild(grid);
Contents.AddChild(grid);
}
private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage message)
-1
View File
@@ -1,4 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Content.Tests")]
[assembly: InternalsVisibleTo("Content.IntegrationTests")]
+1 -1
View File
@@ -134,7 +134,7 @@ public sealed class AlignAtmosPipeLayers : SnapgridCenter
var newProtoId = altPrototypes[(int)layer];
if (!_protoManager.Resolve(newProtoId, out var newProto))
if (!_protoManager.TryIndex(newProtoId, out var newProto))
return;
if (newProto.Type != ConstructionType.Structure)
@@ -1,35 +0,0 @@
using System.Runtime.CompilerServices;
using Content.Shared.Atmos;
namespace Content.Client.Atmos.EntitySystems;
public sealed partial class AtmosphereSystem
{
/*
Partial class for operations involving GasMixtures.
Any method that is overridden here is usually because the server-sided implementation contains
code that would escape sandbox. As such these methods are overridden here with a safe
implementation.
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override float GetHeatCapacityCalculation(float[] moles, bool space)
{
// Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
{
return Atmospherics.SpaceHeatCapacity;
}
// explicit stackalloc call is banned on client tragically.
// the JIT does not stackalloc this during runtime,
// though this isnt the hottest code path so it should be fine
// the gc can eat a little as a treat
var tmp = new float[moles.Length];
NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
// Adjust heat capacity by speedup, because this is primarily what
// determines how quickly gases heat up/cool.
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
}
}
@@ -5,7 +5,7 @@ using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems;
public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
public sealed class AtmosphereSystem : SharedAtmosphereSystem
{
public override void Initialize()
{
@@ -1,5 +0,0 @@
using Content.Shared.Atmos.EntitySystems;
namespace Content.Client.Atmos.EntitySystems;
public sealed class DeltaPressureSystem : SharedDeltaPressureSystem;
@@ -1,6 +1,6 @@
using Content.Client.Atmos.Monitor.UI.Widgets;
using Content.Client.Message;
using Content.Client.Stylesheets.Palette;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
@@ -17,7 +17,7 @@ namespace Content.Client.Atmos.Monitor.UI;
public sealed partial class AirAlarmWindow : FancyWindow
{
public event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
public event Action<IAtmosDeviceData>? AtmosDeviceDataCopied;
public event Action<IAtmosDeviceData>? AtmosDeviceDataCopied;
public event Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
public event Action<AirAlarmMode>? AirAlarmModeChanged;
public event Action<bool>? AutoModeChanged;
@@ -131,7 +131,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
case GasVentPumpData pump:
if (!_pumps.TryGetValue(addr, out var pumpControl))
{
var control = new PumpControl(pump, addr);
var control= new PumpControl(pump, addr);
control.PumpDataChanged += AtmosDeviceDataChanged;
control.PumpDataCopied += AtmosDeviceDataCopied;
_pumps.Add(addr, control);
@@ -186,9 +186,11 @@ public sealed partial class AirAlarmWindow : FancyWindow
{
return curAlarm switch
{
AtmosAlarmType.Danger => Palettes.Status.Critical,
AtmosAlarmType.Warning => Palettes.Status.Warning,
_ => Palettes.Status.Good,
AtmosAlarmType.Danger => StyleNano.DangerousRedFore,
AtmosAlarmType.Warning => StyleNano.ConcerningOrangeFore,
_ => StyleNano.GoodGreenFore,
};
}
}
@@ -1,4 +1,3 @@
using Content.Client.Stylesheets;
using Content.Shared.Atmos.Monitor;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@@ -31,10 +30,7 @@ public sealed partial class ThresholdBoundControl : BoxContainer
public void SetValue(float value)
{
_value = value;
if (!CSpinner.HasKeyboardFocus())
{
CSpinner.Value = ScaledValue;
}
CSpinner.Value = ScaledValue;
}
public void SetEnabled(bool enabled)
@@ -43,11 +39,11 @@ public sealed partial class ThresholdBoundControl : BoxContainer
if (enabled)
{
CBoundLabel.RemoveStyleClass(StyleClass.LabelWeak);
CBoundLabel.RemoveStyleClass("Disabled");
}
else
{
CBoundLabel.SetOnlyStyleClass(StyleClass.LabelWeak);
CBoundLabel.SetOnlyStyleClass("Disabled");
}
}
@@ -3,7 +3,6 @@ using Content.Client.Atmos.Components;
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -24,7 +23,6 @@ namespace Content.Client.Atmos.Overlays
private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager;
private readonly SharedAtmosphereSystem _atmosphereSystem;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _xformSys;
@@ -56,7 +54,6 @@ namespace Content.Client.Atmos.Overlays
{
_entManager = entManager;
_mapManager = IoCManager.Resolve<IMapManager>();
_atmosphereSystem = entManager.System<SharedAtmosphereSystem>();
_mapSystem = entManager.System<SharedMapSystem>();
_xformSys = xformSys;
_shader = protoMan.Index(UnshadedShader).Instance();
@@ -70,7 +67,7 @@ namespace Content.Client.Atmos.Overlays
for (var i = 0; i < _gasCount; i++)
{
var gasPrototype = _atmosphereSystem.GetGas(system.VisibleGasId[i]);
var gasPrototype = protoMan.Index<GasPrototype>(system.VisibleGasId[i].ToString());
SpriteSpecifier overlay;
@@ -4,7 +4,7 @@
MinSize="480 400" Title="Canister">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc comp-gas-canister-ui-canister-status}" StyleClasses="LabelHeading"/>
<Label Text="{Loc comp-gas-canister-ui-canister-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc comp-gas-canister-ui-canister-pressure}"/>
<Label Name="CanisterPressureLabel" Align="Center" HorizontalExpand="True"/>
@@ -16,7 +16,7 @@
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc comp-gas-canister-ui-holding-tank-status}" StyleClasses="LabelHeading"/>
<Label Text="{Loc comp-gas-canister-ui-holding-tank-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc comp-gas-canister-ui-holding-tank-label}"/>
<Label Name="TankLabelLabel" Text="{Loc comp-gas-canister-ui-holding-tank-label-empty}" Align="Center" HorizontalExpand="True"/>
@@ -29,7 +29,7 @@
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc comp-gas-canister-ui-release-valve-status}" StyleClasses="LabelHeading"/>
<Label Text="{Loc comp-gas-canister-ui-release-valve-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical">
<Label Text="{Loc comp-gas-canister-ui-release-pressure}"/>
@@ -52,18 +52,14 @@ namespace Content.Client.Atmos.UI
private void OnSelectGasPressed()
{
if (_window is null)
return;
if (_window is null) return;
if (_window.SelectedGas is null)
{
SendMessage(new GasFilterSelectGasMessage(null));
}
else
{
if (!Enum.TryParse<Gas>(_window.SelectedGas, out var gas))
return;
if (!int.TryParse(_window.SelectedGas, out var gas)) return;
SendMessage(new GasFilterSelectGasMessage(gas));
}
}
@@ -58,7 +58,7 @@ public sealed class JukeboxBoundUserInterface : BoundUserInterface
_menu.SetAudioStream(jukebox.AudioStream);
if (_protoManager.Resolve(jukebox.SelectedSongId, out var songProto))
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto))
{
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString());
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);
@@ -1,4 +1,3 @@
using System.Linq; // DeltaV
using Content.Shared.Audio.Jukebox;
using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
@@ -89,7 +88,7 @@ public sealed partial class JukeboxMenu : FancyWindow
{
MusicList.Clear();
foreach (var entry in jukeboxProtos.OrderBy(x => x.Name)) // DeltaV - Sort the jukebox list
foreach (var entry in jukeboxProtos)
{
MusicList.AddItem(entry.Name, metadata: entry.ID);
}
+53
View File
@@ -0,0 +1,53 @@
using Content.Client.BarSign.Ui;
using Content.Shared.BarSign;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign;
public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BarSignComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(EntityUid uid, BarSignComponent component, ref AfterAutoHandleStateEvent args)
{
if (_ui.TryGetOpenUi<BarSignBoundUserInterface>(uid, BarSignUiKey.Key, out var bui))
bui.Update(component.Current);
UpdateAppearance(uid, component);
}
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
{
UpdateAppearance(uid, component, args.Component, args.Sprite);
}
private void UpdateAppearance(EntityUid id, BarSignComponent sign, AppearanceComponent? appearance = null, SpriteComponent? sprite = null)
{
if (!Resolve(id, ref appearance, ref sprite))
return;
AppearanceSystem.TryGetData<bool>(id, PowerDeviceVisuals.Powered, out var powered, appearance);
if (powered
&& sign.Current != null
&& _prototypeManager.TryIndex(sign.Current, out var proto))
{
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
sprite.LayerSetShader(0, "unshaded");
}
else
{
SpriteSystem.LayerSetRsiState((id, sprite), 0, "empty");
sprite.LayerSetShader(0, null, null);
}
}
}
@@ -1,30 +0,0 @@
using Content.Shared.BarSign;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign;
public sealed class BarSignVisualizerSystem : VisualizerSystem<BarSignComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
{
AppearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component);
AppearanceSystem.TryGetData<string>(uid, BarSignVisuals.BarSignPrototype, out var currentSign, args.Component);
if (powered
&& currentSign != null
&& _prototypeManager.Resolve<BarSignPrototype>(currentSign, out var proto))
{
SpriteSystem.LayerSetSprite((uid, args.Sprite), 0, proto.Icon);
args.Sprite?.LayerSetShader(0, "unshaded");
}
else
{
SpriteSystem.LayerSetRsiState((uid, args.Sprite), 0, "empty");
args.Sprite?.LayerSetShader(0, null, null);
}
}
}
@@ -19,27 +19,32 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
var sign = EntMan.GetComponentOrNull<BarSignComponent>(Owner)?.Current is { } current
? _prototype.Index(current)
: null;
var allSigns = BarSignSystem.GetAllBarSigns(_prototype)
var allSigns = Shared.BarSign.BarSignSystem.GetAllBarSigns(_prototype)
.OrderBy(p => Loc.GetString(p.Name))
.ToList();
_menu = new(sign, allSigns);
_menu.OnSignSelected += id =>
{
SendPredictedMessage(new SetBarSignMessage(id));
SendMessage(new SetBarSignMessage(id));
};
_menu.OnClose += Close;
_menu.OpenCentered();
}
public override void Update()
public void Update(ProtoId<BarSignPrototype>? sign)
{
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp))
return;
if (_prototype.Resolve(signComp.Current, out var signPrototype))
if (_prototype.TryIndex(sign, out var signPrototype))
_menu?.UpdateState(signPrototype);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
}
}
+8
View File
@@ -0,0 +1,8 @@
using Content.Shared.Bed;
namespace Content.Client.Bed;
public sealed class BedSystem : SharedBedSystem
{
}
@@ -7,7 +7,10 @@
Orientation="Vertical"
HorizontalExpand="True"
Margin="0 0 0 5">
<PanelContainer StyleClasses="PanelDark">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="{xNamespace:Static style:StyleNano.ButtonColorDisabled}" />
</PanelContainer.PanelOverride>
<Collapsible Name="Collapsible">
<CollapsibleHeading Name="Heading" MinHeight="35"/>
<CollapsibleBody Name="Body">
@@ -14,8 +14,10 @@
<PanelContainer
VerticalExpand="True"
HorizontalExpand="True"
Margin="15"
StyleClasses="PanelDark">
Margin="15">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="{xNamespace:Static style:StyleNano.PanelDark}" />
</PanelContainer.PanelOverride>
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
<Control>
<Label Text="{Loc 'cryostorage-ui-label-no-bodies'}" Name="EmptyLabel" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+69
View File
@@ -1,7 +1,76 @@
using Content.Shared.Body.Systems;
// Shitmed Change Start
using Content.Shared._Shitmed.Body.Part;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
using Content.Shared.Body.Components;
// Shitmed Change End
namespace Content.Client.Body.Systems;
public sealed class BodySystem : SharedBodySystem
{
// Shitmed Change Start
[Dependency] private readonly MarkingManager _markingManager = default!;
private void ApplyMarkingToPart(MarkingPrototype markingPrototype,
IReadOnlyList<Color>? colors,
bool visible,
SpriteComponent sprite)
{
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
{
var markingSprite = markingPrototype.Sprites[j];
if (markingSprite is not SpriteSpecifier.Rsi rsi)
continue;
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
if (!sprite.LayerMapTryGet(layerId, out _))
{
var layer = sprite.AddLayer(markingSprite, j + 1);
sprite.LayerMapSet(layerId, layer);
sprite.LayerSetSprite(layerId, rsi);
}
sprite.LayerSetVisible(layerId, visible);
if (!visible)
continue;
// Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
// and we need to check the index is correct. So if that happens just default to white?
if (colors != null && j < colors.Count)
sprite.LayerSetColor(layerId, colors[j]);
else
sprite.LayerSetColor(layerId, Color.White);
}
}
protected override void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component)
{
if (!TryComp(target, out SpriteComponent? sprite))
return;
if (component.Color != null)
sprite.Color = component.Color.Value;
foreach (var (visualLayer, markingList) in component.Markings)
foreach (var marking in markingList)
{
if (!_markingManager.TryGetMarking(marking, out var markingPrototype))
continue;
ApplyMarkingToPart(markingPrototype, marking.MarkingColors, marking.Visible, sprite);
}
}
protected override void RemoveBodyMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance)
{
return;
}
// Shitmed Change End
}

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