mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Compare commits
2
Commits
master
..
bible-user
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37e44ee3ee | ||
|
|
fc28b60175 |
+1
-1
@@ -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]
|
||||
|
||||
+20
-11
@@ -1,25 +1,34 @@
|
||||
# Last match in file takes precedence.
|
||||
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#example-of-a-codeowners-file
|
||||
|
||||
* @DeltaV-Station/maintainers
|
||||
# C# code
|
||||
/Content.*/ @DeltaV-Station/maintainers
|
||||
|
||||
# Any assets
|
||||
/Resources/ @DeltaV-Station/maintainers
|
||||
|
||||
# Server config files
|
||||
/Resources/ConfigPresets/ @DeltaV-Station/lead-maintainers
|
||||
|
||||
# Github workflows
|
||||
/.github/ @DeltaV-Station/lead-maintainers
|
||||
/Resources/ConfigPresets/ @Toby222
|
||||
|
||||
# Lobby art and music - automatically direction issues since its immediately visible to players
|
||||
/Resources/Audio/Lobby/ @DeltaV-Station/direction @DeltaV-Station/maintainers
|
||||
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction @DeltaV-Station/maintainers
|
||||
/Resources/Audio/Lobby/ @DeltaV-Station/direction
|
||||
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction
|
||||
|
||||
# Maps
|
||||
/Resources/Maps/ @DeltaV-Station/maptainers
|
||||
/Resources/Prototypes/Maps/ @DeltaV-Station/maptainers
|
||||
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers @DeltaV-Station/maintainers
|
||||
/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
|
||||
/.wiki/ @DeltaV-Station/direction
|
||||
|
||||
# Tools and scripts
|
||||
/Tools/ @Toby222
|
||||
|
||||
# Workflows, codeowners, templates, etc.
|
||||
/.github/ @Toby222
|
||||
|
||||
# Standalone files in the root repo
|
||||
/* @Toby222
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- 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,7 +2,7 @@
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ master, staging, trying ]
|
||||
merge_group:
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- 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,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 ]
|
||||
@@ -32,12 +32,11 @@ jobs:
|
||||
run: |
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
@@ -46,23 +45,13 @@ jobs:
|
||||
run: dotnet build --configuration DebugOpt --no-restore /m
|
||||
|
||||
- name: Run Content.Tests
|
||||
shell: pwsh
|
||||
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0 NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
|
||||
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0
|
||||
|
||||
- name: Run Content.IntegrationTests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:DOTNET_gcServer=1
|
||||
dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
|
||||
|
||||
- name: Archive NUnit3 test results.
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nunit3-results-${{ matrix.os }}
|
||||
path: test_results/*
|
||||
retention-days: 7
|
||||
compression-level: 9
|
||||
dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed
|
||||
ci-success:
|
||||
name: Build & Test Debug
|
||||
needs:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
@@ -23,13 +23,12 @@ jobs:
|
||||
- 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
|
||||
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ master, staging, trying ]
|
||||
paths:
|
||||
- '**.cs'
|
||||
- '**.csproj'
|
||||
@@ -48,11 +48,10 @@ jobs:
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
|
||||
# ubuntu-latest has .NET 10
|
||||
# - name: Setup .NET Core
|
||||
# uses: actions/setup-dotnet@v4.1.0
|
||||
# with:
|
||||
# dotnet-version: 10.0.x
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
@@ -61,13 +60,7 @@ jobs:
|
||||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --log-build --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
|
||||
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --log-build --no-wipe-release
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binlogs
|
||||
path: release/*.binlog
|
||||
retention-days: 7
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
@@ -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,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 ]
|
||||
|
||||
@@ -2,7 +2,7 @@ name: RSI Validator
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ staging, trying ]
|
||||
merge_group:
|
||||
pull_request:
|
||||
paths:
|
||||
|
||||
@@ -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,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 ]
|
||||
@@ -23,11 +23,10 @@ jobs:
|
||||
run: |
|
||||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
dotnet-version: 9.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# MSbuild binlog files
|
||||
*.binlog
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
+22
-22
@@ -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;"| 16 minutes
|
||||
|style= "border:1px solid black;"| To unlawfully confine or restrict the free movement of an Employee against their will.
|
||||
|-
|
||||
| style="border:1px solid black;" | 303
|
||||
| style="border:1px solid black;" | WIP
|
||||
| style="border:1px solid black;" | Grand Possession
|
||||
| style="border:1px solid black;" | 12-20 minutes
|
||||
| style="border:1px solid black;" | 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;" | 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 Employee.
|
||||
|-
|
||||
| style="border:1px solid black;" | 307
|
||||
| style="border:1px solid black;" | [[File:SL_GrandTheft.png]]
|
||||
| style="border:1px solid black;" | Grand Larceny
|
||||
| style="border:1px solid black;" | 8 minutes
|
||||
| style="border:1px solid black;" | 12 minutes
|
||||
| style="border:1px solid black;" | To deprive an Employee of any Controlled Item in their lawful possession.
|
||||
|-
|
||||
| style="border:1px solid black;" | 308
|
||||
| style="border:1px solid black;" | [[File:SL_BlackMarketeering.png]]
|
||||
| style="border:1px solid black;" | Black Marketeering
|
||||
| style="border:1px solid black;" | 8 minutes
|
||||
| style="border:1px solid black;" | 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,13 +329,13 @@ 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;"| 5 minutes
|
||||
|style= "border:1px solid black;"| To verbally demean, humiliate, or harass an Employee with malice; or maliciously spread false information amongst a population.
|
||||
|-
|
||||
|style= "border:1px solid black;"| 103
|
||||
@@ -347,19 +347,19 @@ 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;" | 4 minutes
|
||||
| style="border: 1px solid black;" | To deprive an Employee of an item in their lawful property without consent.
|
||||
|-
|
||||
| style="border: 1px solid black;" | 105
|
||||
| style="border: 1px solid black;" | [[File:SL_Trespass.png]]
|
||||
! style="border: 1px solid black;" | {{anchor|Trespass}}Trespass
|
||||
| style="border: 1px solid black;" | 2 minutes
|
||||
| style="border: 1px solid black;" | 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;" | 3 minutes
|
||||
| style="border: 1px solid black;" | To deface or superficially damage public property, or property belonging to another Employee.
|
||||
|-
|
||||
| style="border: 1px solid black;" | 107
|
||||
|
||||
@@ -13,7 +13,7 @@ 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.
|
||||
@@ -31,7 +31,7 @@ Examples of Pets include, but are not limited to:
|
||||
:* hand-held "personal AI" devices
|
||||
|
||||
== Synthetic ==
|
||||
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage.
|
||||
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage. Irrevocable destruction of a Synthetic may leave the offender liable to the crime of Grand Sabotage.
|
||||
Examples of Synthetics include, but are not limited to:
|
||||
:* Crew-constructed Cyborgs
|
||||
:* The Station AI
|
||||
@@ -79,14 +79,13 @@ 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.
|
||||
|
||||
Should the holder commit crimes through the use of such authorized items, the authorizing party may be found guilty of Accessory to said crimes, in addition to Endangerment and Abuse of Power where applicable. The Commanding Officer is also authorized to supervise the approval and revocation of documentation as they see fit.
|
||||
|
||||
== Controlled Substances ==
|
||||
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a prescription or special dispensation from the Chief Medical Officer. Medical personnel are also permitted to [[Standard Operating Procedure#Prescribing Medication|prescribe medication]] for chronic illnesses or mental health. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
|
||||
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a perscription or special dispensation from the Chief Medical Officer. The Psychologist is also permitted to grant prescriptions for psychoactive drugs. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
|
||||
|
||||
A list of such Substances follow:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -370,59 +370,8 @@ Given good medical reason and with documentation, the Chief Medical Officer of a
|
||||
|
||||
In order for a medical evaluation to be considered lawfully applicable, the CMO and relevant head of department must both agree that the specified crewmate is unfit for duty in order to proceed with the demotion. In the case of the Commanding Officer, Central Command should be contacted as soon as possible.
|
||||
|
||||
==Prescribing Medication==
|
||||
In the event that a patient requires stronger medication outside of medbay, Medical personnel have the ability to prescribe medicine to a patient. Prescriptions must be written on valid prescription paper, as well as signed and stamped with a valid medical personnel's stamp and signature. Any such document must state the substance(s) being prescribed as well as the amount(s) where applicable. The chief medical officer also has the right to revoke any prescription with valid medical reasoning. Prescriptions are not valid if they are both intended for and signed and stamped by the same person (e.g. You cannot prescribe something to yourself as a doctor).
|
||||
|
||||
=== Prescription Medicine ===
|
||||
Prescriptions that are valid with any medical personnel's stamp and signature are as follows:
|
||||
* diphenylmethylamine
|
||||
* equilibrazine
|
||||
* tranquinase
|
||||
* neurozenium
|
||||
* serenitol
|
||||
* soretizone
|
||||
|
||||
Highly-addictive mental health prescriptions that are only valid with the chief medical officer's or psychologist's stamp and signature are as follows:
|
||||
* blissifylovene
|
||||
* happiness
|
||||
|
||||
Highly-addictive or potentially-harmful prescriptions that are only valid with the chief medical officer's stamp and signature are as follows:
|
||||
* agonolexyne
|
||||
* desoxyephedrine
|
||||
* nocturine
|
||||
|
||||
=== Dangerous Chemical Authorizations ===
|
||||
|
||||
Other dangerous chemicals that may be authorized and given out ONLY with valid written permission with the chief medical officer's stamp are as follows:
|
||||
* mindbreaker, heartbreaker, and mute toxins
|
||||
* amatoxin and other dangerous poisons
|
||||
* phlogiston, napalm, and chlorine trifluoride
|
||||
* vestine and romerol
|
||||
* Any other [[Standard Operating Procedure#Controlled Substances|controlled substances]] not listed here
|
||||
|
||||
The chief medical officer is also required to inform and/or forward authorization of any dangerous chemical to Security.
|
||||
|
||||
==Quarantine==
|
||||
Medical staff are afforded the right to quarantine individuals that currently suffer from infectious or contagious diseases, and may confine them within pre-designated quarantine areas in the course of treatment, until the point that they are no longer infectious or harmful to the rest of the crew.
|
||||
|
||||
==Foreign Bodies==
|
||||
Any parasitic alien organism that requires a living host to live and reproduce must be eliminated. An exception can be made for experimental purposes, provided a CO, CMO and Mystagogue were to approve it.
|
||||
|
||||
=Engineering Regulations=
|
||||
==Health & Safety==
|
||||
* In public areas, all wiring must be concealed under tiles. In non-public areas, catwalks are recommended to be placed for the safety and mobility of workers.
|
||||
* The atmospheric distribution pipe ("distro") pressure should be no more than 500 kPa and composition should be a ratio of 70-80% nitrogen and 20-30% oxygen.
|
||||
* The atmospheric waste pipe ("waste") shall be kept free of clogging, and directed either to recycling, gas storage, or space.
|
||||
* Newly constructed burn chambers may not be located within the station or directly adjacent to the station's hull, unless receiving written authorization from Central Command.
|
||||
|
||||
==Building Permits==
|
||||
Any renovations larger than small modifications to the station's structural layout, power network, or atmospheric system shall require a building permit or work order approved expressly by the Chief Engineer, or if a Chief Engineer is not present, the Captain or Acting Commanding Officer. Modifications within departments shall additionally require the approval of the relevant department head. Areas within maintenance tunnels do not require permission to modify. However, any modifications to maintenance tunnel passageways should ensure they remain traversable and unobstructed by personnel who need to use them as part of their job.
|
||||
|
||||
==Emergency Access==
|
||||
In emergency situations, engineers are permitted to bypass door access (i.e. hack) and gain access to unauthorized locations if said locations require repairs.
|
||||
|
||||
==Hazard Zones==
|
||||
An area with a hull breach, hazardous material leak, or other similar danger shall be considered a hazard zone. To ensure crew safety, engineering personnel are required to demarcate hazard zones with holofans, inflatable walls, and/or engineering tape prior to beginning work. Any non-authorized personnel impeding or preventing repairs in hazardous areas may be liable for endangerment.
|
||||
|
||||
==Condemned Areas==
|
||||
The Chief Engineer may declare a section of the station condemned if repairs cannot be reasonably completed. In the event that there is no Chief Engineer, any engineer may condemn a section of the station with authorization from the Captain or Acting Commanding Officer. Doors leading to such areas shall be welded or bolted to prevent crew access.
|
||||
|
||||
@@ -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
@@ -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`
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
@@ -45,7 +44,7 @@ public class ComponentQueryBenchmark
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
|
||||
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
|
||||
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<PropertyGroup>
|
||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
|
||||
<Nullable>disable</Nullable>
|
||||
<DefineConstants>$(DefineConstants);ALLOW_BAD_PRACTICES</DefineConstants>
|
||||
<LangVersion>12</LangVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" />
|
||||
|
||||
<!-- pin transitive deps -->
|
||||
<PackageReference Include="System.Management" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
|
||||
@@ -23,12 +19,10 @@
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
|
||||
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Server.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
|
||||
<Import Project="..\RobustToolbox\Imports\Testing.props" />
|
||||
</Project>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
@@ -69,7 +69,7 @@ public class DeltaPressureBenchmark
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapdata = await _pair.CreateTestMap();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
@@ -52,8 +51,6 @@ public class DestructibleBenchmark
|
||||
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!;
|
||||
@@ -70,9 +67,11 @@ public class DestructibleBenchmark
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapdata = await _pair.CreateTestMap();
|
||||
|
||||
_entMan = server.ResolveDependency<IEntityManager>();
|
||||
_protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||
_random = server.ResolveDependency<IRobustRandom>();
|
||||
@@ -87,25 +86,19 @@ public class DestructibleBenchmark
|
||||
_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(() =>
|
||||
await 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));
|
||||
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +107,7 @@ public class DestructibleBenchmark
|
||||
var y = 0;
|
||||
foreach (var protoId in _prototypes)
|
||||
{
|
||||
var coords = new EntityCoordinates(_currentMapData.Grid, x + 0.5f, y + 0.5f);
|
||||
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
|
||||
_entMan.SpawnEntity(protoId, coords);
|
||||
y++;
|
||||
}
|
||||
@@ -122,17 +115,12 @@ public class DestructibleBenchmark
|
||||
|
||||
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]
|
||||
@@ -162,26 +150,6 @@ public class DestructibleBenchmark
|
||||
});
|
||||
}
|
||||
|
||||
[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()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
@@ -61,7 +60,7 @@ public class DeviceNetworkingBenchmark
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(DeviceNetworkingBenchmark).Assembly);
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
@@ -52,7 +51,7 @@ public class GasReactionBenchmark
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
// Create test map and grid
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
[Virtual]
|
||||
[GcServer(true)]
|
||||
[MemoryDiagnoser]
|
||||
public class HeatCapacityBenchmark
|
||||
{
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _sEntMan = default!;
|
||||
private IEntityManager _cEntMan = default!;
|
||||
private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
|
||||
private AtmosphereSystem _sAtmos = default!;
|
||||
private GasMixture _mix;
|
||||
|
||||
[GlobalSetup]
|
||||
public async Task SetupAsync()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
await _pair.Connect();
|
||||
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
|
||||
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
_cAtmos = _cEntMan.System<Client.Atmos.EntitySystems.AtmosphereSystem>();
|
||||
_sAtmos = _sEntMan.System<AtmosphereSystem>();
|
||||
|
||||
const float volume = 2500f;
|
||||
const float temperature = 293.15f;
|
||||
|
||||
const float o2 = 12.3f;
|
||||
const float n2 = 45.6f;
|
||||
const float co2 = 0.42f;
|
||||
const float plasma = 0.05f;
|
||||
|
||||
_mix = new GasMixture(volume) { Temperature = temperature };
|
||||
|
||||
_mix.AdjustMoles(Gas.Oxygen, o2);
|
||||
_mix.AdjustMoles(Gas.Nitrogen, n2);
|
||||
_mix.AdjustMoles(Gas.CarbonDioxide, co2);
|
||||
_mix.AdjustMoles(Gas.Plasma, plasma);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ClientHeatCapacityBenchmark()
|
||||
{
|
||||
await _pair.Client.WaitPost(delegate
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_cAtmos.GetHeatCapacity(_mix, applyScaling: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ServerHeatCapacityBenchmark()
|
||||
{
|
||||
await _pair.Server.WaitPost(delegate
|
||||
{
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
_sAtmos.GetHeatCapacity(_mix, applyScaling: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public async Task CleanupAsync()
|
||||
{
|
||||
await _pair.DisposeAsync();
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Server.Maps;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
@@ -30,7 +29,7 @@ public class MapLoadBenchmark
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
var server = _pair.Server;
|
||||
|
||||
Paths = server.ResolveDependency<IPrototypeManager>()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
@@ -51,7 +50,7 @@ public class PvsBenchmark
|
||||
#endif
|
||||
PoolManager.Startup();
|
||||
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
_pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
|
||||
_pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
@@ -22,10 +21,8 @@ public class RaiseEventBenchmark
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(BenchSystem).Assembly);
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
_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(() =>
|
||||
@@ -33,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();
|
||||
@@ -65,12 +60,6 @@ public class RaiseEventBenchmark
|
||||
return _sys.RaiseICompEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseNetEvent()
|
||||
{
|
||||
return _sys.RaiseNetIdEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseCSharpEvent()
|
||||
{
|
||||
@@ -85,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()
|
||||
{
|
||||
@@ -105,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;
|
||||
}
|
||||
|
||||
@@ -113,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;
|
||||
}
|
||||
|
||||
@@ -140,7 +118,6 @@ public class RaiseEventBenchmark
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
[ComponentEvent(Exclusive = false)]
|
||||
public struct BenchEv
|
||||
{
|
||||
public int N;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
@@ -37,7 +36,7 @@ public class SpawnEquipDeleteBenchmark
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapData = await _pair.CreateTestMap();
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using Content.Client.Overlays;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Access.Systems;
|
||||
|
||||
public sealed class JobStatusSystem : SharedJobStatusSystem
|
||||
{
|
||||
[Dependency] private readonly ShowJobIconsSystem _showJobIcons = default!;
|
||||
[Dependency] private readonly ShowCrewIconsSystem _showCrewIcons = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
private static readonly ProtoId<SecurityIconPrototype> CrewBorderIcon = "CrewBorderIcon";
|
||||
private static readonly ProtoId<SecurityIconPrototype> CrewUncertainBorderIcon = "CrewUncertainBorderIcon";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<JobStatusComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
// show the status icons if the player has the correponding HUDs
|
||||
private void OnGetStatusIconsEvent(Entity<JobStatusComponent> ent, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (_showJobIcons.IsActive && ent.Comp.JobStatusIcon != null)
|
||||
ev.StatusIcons.Add(_prototype.Index(ent.Comp.JobStatusIcon));
|
||||
|
||||
if (_showCrewIcons.IsActive)
|
||||
{
|
||||
if (_showCrewIcons.UncertainCrewBorder)
|
||||
ev.StatusIcons.Add(_prototype.Index(CrewUncertainBorderIcon));
|
||||
else if (ent.Comp.IsCrew)
|
||||
ev.StatusIcons.Add(_prototype.Index(CrewBorderIcon));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<GridContainer xmlns="https://spacestation14.io"
|
||||
Columns="4"
|
||||
Columns="5"
|
||||
HorizontalAlignment="Center">
|
||||
</GridContainer>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ 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;
|
||||
|
||||
@@ -13,13 +15,21 @@ namespace Content.Client.Access.UI
|
||||
public sealed class IdCardConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
private readonly SharedIdCardConsoleSystem _idCardConsoleSystem = default!;
|
||||
|
||||
private IdCardConsoleWindow? _window;
|
||||
|
||||
// CCVar.
|
||||
private int _maxNameLength;
|
||||
private int _maxIdJobLength;
|
||||
|
||||
public IdCardConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_idCardConsoleSystem = EntMan.System<SharedIdCardConsoleSystem>();
|
||||
|
||||
_maxNameLength =_cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -69,6 +79,12 @@ namespace Content.Client.Access.UI
|
||||
|
||||
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
|
||||
{
|
||||
if (newFullName.Length > _maxNameLength)
|
||||
newFullName = newFullName[.._maxNameLength];
|
||||
|
||||
if (newJobTitle.Length > _maxIdJobLength)
|
||||
newJobTitle = newJobTitle[.._maxIdJobLength];
|
||||
|
||||
SendMessage(new WriteToTargetIdMessage(
|
||||
newFullName,
|
||||
newJobTitle,
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace Content.Client.Access.UI
|
||||
|
||||
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||
|
||||
// CCVar.
|
||||
private int _maxNameLength;
|
||||
private int _maxIdJobLength;
|
||||
|
||||
private AccessLevelControl _accessButtons = new();
|
||||
private readonly List<string> _jobPrototypeIds = new();
|
||||
|
||||
@@ -42,8 +46,11 @@ namespace Content.Client.Access.UI
|
||||
|
||||
_owner = owner;
|
||||
|
||||
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
|
||||
FullNameLineEdit.OnTextEntered += _ => SubmitData();
|
||||
FullNameLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
FullNameLineEdit.IsValid = s => s.Length <= _maxNameLength;
|
||||
FullNameLineEdit.OnTextChanged += _ =>
|
||||
{
|
||||
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
|
||||
@@ -51,7 +58,7 @@ namespace Content.Client.Access.UI
|
||||
FullNameSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
|
||||
JobTitleLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
JobTitleLineEdit.IsValid = s => s.Length <= _maxIdJobLength;
|
||||
JobTitleLineEdit.OnTextChanged += _ =>
|
||||
{
|
||||
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
|
||||
|
||||
@@ -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,4 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.UI.BanList.Bans;
|
||||
using Content.Client.Administration.UI.BanList.RoleBans;
|
||||
using Content.Client.Eui;
|
||||
@@ -74,7 +73,7 @@ public sealed class BanListEui : BaseEui
|
||||
return date.ToString("MM/dd/yyyy h:mm tt");
|
||||
}
|
||||
|
||||
public static void SetData<T>(IBanListLine<T> line, SharedBan ban) where T : SharedBan
|
||||
public static void SetData<T>(IBanListLine<T> line, SharedServerBan ban) where T : SharedServerBan
|
||||
{
|
||||
line.Reason.Text = ban.Reason;
|
||||
line.BanTime.Text = FormatDate(ban.BanTime);
|
||||
@@ -95,20 +94,20 @@ public sealed class BanListEui : BaseEui
|
||||
line.BanningAdmin.Text = ban.BanningAdminName;
|
||||
}
|
||||
|
||||
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedBan
|
||||
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedServerBan
|
||||
{
|
||||
_popup?.Close();
|
||||
_popup = null;
|
||||
|
||||
var ban = line.Ban;
|
||||
var id = ban.Id == null ? string.Empty : Loc.GetString("ban-list-id", ("id", ban.Id.Value));
|
||||
var ip = ban.Addresses.Length == 0
|
||||
var ip = ban.Address == null
|
||||
? string.Empty
|
||||
: Loc.GetString("ban-list-ip", ("ip", string.Join(',', ban.Addresses.Select(a => a.address))));
|
||||
var hwid = ban.HWIds.Length == 0 ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", string.Join(',', ban.HWIds)));
|
||||
var guid = ban.UserIds.Length == 0
|
||||
: Loc.GetString("ban-list-ip", ("ip", ban.Address.Value.address));
|
||||
var hwid = ban.HWId == null ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", ban.HWId));
|
||||
var guid = ban.UserId == null
|
||||
? string.Empty
|
||||
: Loc.GetString("ban-list-guid", ("guid", string.Join(',', ban.UserIds)));
|
||||
: Loc.GetString("ban-list-guid", ("guid", ban.UserId.Value.ToString()));
|
||||
|
||||
_popup = new BanListIdsPopup(id, ip, hwid, guid);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed partial class BanListControl : Control
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetBans(List<SharedBan> bans)
|
||||
public void SetBans(List<SharedServerBan> bans)
|
||||
{
|
||||
for (var i = Bans.ChildCount - 1; i >= 1; i--)
|
||||
{
|
||||
|
||||
@@ -7,13 +7,13 @@ using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
namespace Content.Client.Administration.UI.BanList.Bans;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedBan>
|
||||
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedServerBan>
|
||||
{
|
||||
public SharedBan Ban { get; }
|
||||
public SharedServerBan Ban { get; }
|
||||
|
||||
public event Action<BanListLine>? IdsClicked;
|
||||
|
||||
public BanListLine(SharedBan ban)
|
||||
public BanListLine(SharedServerBan ban)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Administration.UI.BanList;
|
||||
|
||||
public interface IBanListLine<T> where T : SharedBan
|
||||
public interface IBanListLine<T> where T : SharedServerBan
|
||||
{
|
||||
T Ban { get; }
|
||||
Label Reason { get; }
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed partial class RoleBanListControl : Control
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetRoleBans(List<SharedBan> bans)
|
||||
public void SetRoleBans(List<SharedServerRoleBan> bans)
|
||||
{
|
||||
for (var i = RoleBans.ChildCount - 1; i >= 1; i--)
|
||||
{
|
||||
|
||||
@@ -7,13 +7,13 @@ using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
namespace Content.Client.Administration.UI.BanList.RoleBans;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedBan>
|
||||
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedServerRoleBan>
|
||||
{
|
||||
public SharedBan Ban { get; }
|
||||
public SharedServerRoleBan Ban { get; }
|
||||
|
||||
public event Action<RoleBanListLine>? IdsClicked;
|
||||
|
||||
public RoleBanListLine(SharedBan ban)
|
||||
public RoleBanListLine(SharedServerRoleBan ban)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedB
|
||||
IdsHidden.OnPressed += IdsPressed;
|
||||
|
||||
BanListEui.SetData(this, ban);
|
||||
Role.Text = string.Join(", ", ban.Roles ?? []);
|
||||
Role.Text = ban.Role;
|
||||
}
|
||||
|
||||
private void IdsPressed(ButtonEventArgs buttonEventArgs)
|
||||
|
||||
@@ -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,7 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public sealed partial class AdminNotesLine : BoxContainer
|
||||
|
||||
TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
ServerLabel.Text = Note.ServerName ?? "Unknown";
|
||||
RoundLabel.Text = Note.Rounds.Length == 0 ? "Unknown round" : "Round " + string.Join(',', Note.Rounds);
|
||||
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
|
||||
AdminLabel.Text = Note.CreatedByName;
|
||||
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
|
||||
|
||||
@@ -143,12 +143,7 @@ public sealed partial class AdminNotesLine : BoxContainer
|
||||
|
||||
private string FormatRoleBanMessage()
|
||||
{
|
||||
var rolesText = string.Join(
|
||||
", ",
|
||||
// Explicit cast here to avoid sandbox violation.
|
||||
(IEnumerable<BanRoleDef>?)Note.BannedRoles ?? [new BanRoleDef("what", "You should not be seeing this")]);
|
||||
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {rolesText} ");
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
|
||||
return FormatBanMessageCommon(banMessage);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ public sealed partial class AdminNotesLinePopup : Popup
|
||||
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
|
||||
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
|
||||
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
|
||||
RoundIdLabel.Text = note.Rounds.Length == 0
|
||||
RoundIdLabel.Text = note.Round == null
|
||||
? Loc.GetString("admin-notes-round-id-unknown")
|
||||
: Loc.GetString("admin-notes-round-id", ("id", string.Join(',', note.Rounds)));
|
||||
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
|
||||
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
|
||||
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
|
||||
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
|
||||
var index = _sprite.LayerMapReserve((ent.Owner, sprite), ent.Comp.LayerMap);
|
||||
|
||||
if (TryComp<HumanoidProfileComponent>(ent, out var humanoid) &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(humanoid.Species, out var speciesSprite))
|
||||
if (TryComp<HumanoidAppearanceComponent>(ent, out var humanoidAppearance) &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(humanoidAppearance.Species, out var speciesSprite))
|
||||
{
|
||||
_sprite.LayerSetSprite((ent.Owner, sprite), index, speciesSprite);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'anomaly-generator-ui-title'}"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</BoxContainer>
|
||||
<!--Sprite View-->
|
||||
<PanelContainer Margin="12 0 0 0"
|
||||
StyleClasses="BackgroundPanelDark"
|
||||
StyleClasses="Inset"
|
||||
VerticalAlignment="Center">
|
||||
<SpriteView Name="EntityView"
|
||||
SetSize="96 96"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
|
||||
namespace Content.Client.Atmos.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Temperature;
|
||||
@@ -23,7 +22,6 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IResourceCache _cache;
|
||||
private readonly SharedAtmosphereSystem _atmosphere;
|
||||
|
||||
private Dictionary<AtmosAlarmType, string> _alarmStrings = new Dictionary<AtmosAlarmType, string>()
|
||||
{
|
||||
@@ -39,7 +37,6 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
||||
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_cache = IoCManager.Resolve<IResourceCache>();
|
||||
_atmosphere = _entManager.System<SharedAtmosphereSystem>();
|
||||
|
||||
NetEntity = uid;
|
||||
Coordinates = coordinates;
|
||||
@@ -152,7 +149,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
||||
foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
|
||||
{
|
||||
FixedPoint2 gasPercent = percent * 100f;
|
||||
var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation);
|
||||
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
|
||||
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -20,14 +19,12 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IResourceCache _cache;
|
||||
private readonly SharedAtmosphereSystem _atmosphere;
|
||||
|
||||
public AtmosMonitoringEntryContainer(AtmosMonitoringConsoleEntry data)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_cache = IoCManager.Resolve<IResourceCache>();
|
||||
_atmosphere = _entManager.System<SharedAtmosphereSystem>();
|
||||
|
||||
Data = data;
|
||||
|
||||
@@ -135,7 +132,7 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer
|
||||
var gasPercent = (FixedPoint2)0f;
|
||||
gasPercent = percent * 100f;
|
||||
|
||||
var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation);
|
||||
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
|
||||
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
|
||||
@@ -10,9 +10,7 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
[UsedImplicitly]
|
||||
internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
|
||||
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = [];
|
||||
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = new();
|
||||
|
||||
// Configuration set by debug commands and used by AtmosDebugOverlay {
|
||||
/// <summary>Value source for display</summary>
|
||||
@@ -27,8 +25,6 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
public bool CfgCBM = false;
|
||||
// }
|
||||
|
||||
private AtmosDebugOverlay? _overlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -38,6 +34,10 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
SubscribeNetworkEvent<AtmosDebugOverlayDisableMessage>(HandleAtmosDebugOverlayDisableMessage);
|
||||
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if(!overlayManager.HasOverlay<AtmosDebugOverlay>())
|
||||
overlayManager.AddOverlay(new AtmosDebugOverlay(this));
|
||||
}
|
||||
|
||||
private void OnGridRemoved(GridRemovalEvent ev)
|
||||
@@ -51,25 +51,19 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
private void HandleAtmosDebugOverlayMessage(AtmosDebugOverlayMessage message)
|
||||
{
|
||||
TileData[GetEntity(message.GridId)] = message;
|
||||
|
||||
if (_overlay is not null)
|
||||
return;
|
||||
|
||||
_overlay = new AtmosDebugOverlay(this);
|
||||
_overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void HandleAtmosDebugOverlayDisableMessage(AtmosDebugOverlayDisableMessage ev)
|
||||
{
|
||||
TileData.Clear();
|
||||
RemoveOverlay();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
RemoveOverlay();
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if (overlayManager.HasOverlay<AtmosDebugOverlay>())
|
||||
overlayManager.RemoveOverlay<AtmosDebugOverlay>();
|
||||
}
|
||||
|
||||
public void Reset(RoundRestartCleanupEvent ev)
|
||||
@@ -81,15 +75,6 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
{
|
||||
return TileData.ContainsKey(gridId);
|
||||
}
|
||||
|
||||
private void RemoveOverlay()
|
||||
{
|
||||
if (_overlay is null)
|
||||
return;
|
||||
|
||||
_overlayManager.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum AtmosDebugOverlayMode : byte
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>No-op on client as reactions aren't entirely in shared.
|
||||
/// Don't call it. Smile.</remarks>
|
||||
public override ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
|
||||
{
|
||||
// Reactions don't work on client so don't even try.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.Epsilon)
|
||||
{
|
||||
var tmp = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
NumericsHelpers.Multiply(mixture.Moles, GasFuelMask, tmp);
|
||||
return NumericsHelpers.HorizontalAdd(tmp) > epsilon;
|
||||
}
|
||||
|
||||
public override bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.Epsilon)
|
||||
{
|
||||
var tmp = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
NumericsHelpers.Multiply(mixture.Moles, GasOxidizerMask, tmp);
|
||||
return NumericsHelpers.HorizontalAdd(tmp) > epsilon;
|
||||
}
|
||||
|
||||
[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, GasMolarHeatCapacities, 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,30 +0,0 @@
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// System responsible for rendering atmos fire animations using <see cref="GasTileFireOverlay"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileFireOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private GasTileFireOverlay _fireOverlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_fireOverlay = new GasTileFireOverlay();
|
||||
_overlayMan.AddOverlay(_fireOverlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileFireOverlay>();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// System responsible for rendering heat distortion using <see cref="GasTileHeatBlurOverlay"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileHeatBlurOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private GasTileHeatBlurOverlay _gasTileHeatBlurOverlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_gasTileHeatBlurOverlay = new GasTileHeatBlurOverlay();
|
||||
_overlayMan.AddOverlay(_gasTileHeatBlurOverlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileHeatBlurOverlay>();
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,106 @@
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
namespace Content.Client.Atmos.EntitySystems
|
||||
{
|
||||
public override void Initialize()
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSys = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
private GasTileOverlay _overlay = default!;
|
||||
|
||||
switch (args.Current)
|
||||
public override void Initialize()
|
||||
{
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileOverlay>();
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
|
||||
switch (args.Current)
|
||||
{
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
{
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
break;
|
||||
}
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
{
|
||||
comp.Chunks[index] = data;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
|
||||
{
|
||||
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
|
||||
{
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var index in removedIndicies)
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
{
|
||||
comp.Chunks.Remove(index);
|
||||
comp.Chunks[index] = data;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (nent, gridData) in ev.UpdatedChunks)
|
||||
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
|
||||
{
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var chunkData in gridData)
|
||||
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
|
||||
{
|
||||
comp.Chunks[chunkData.Index] = chunkData;
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var index in removedIndicies)
|
||||
{
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (nent, gridData) in ev.UpdatedChunks)
|
||||
{
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var chunkData in gridData)
|
||||
{
|
||||
comp.Chunks[chunkData.Index] = chunkData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// System responsible for rendering visible atmos gasses (like plasma for example) using <see cref="GasTileVisibleGasOverlay"/>.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileVisibleGasOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
private GasTileVisibleGasOverlay _visibleGasOverlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_visibleGasOverlay = new GasTileVisibleGasOverlay();
|
||||
_overlayMan.AddOverlay(_visibleGasOverlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileVisibleGasOverlay>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<!-- Status (pressure, temperature, alarm state, device total, address, etc) -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 2">
|
||||
<!-- Left column (view of entity) -->
|
||||
<PanelContainer Margin="2 0 6 0" StyleClasses="BackgroundPanelDark" VerticalAlignment="Center" VerticalExpand="True">
|
||||
<PanelContainer Margin="2 0 6 0" StyleClasses="Inset" VerticalAlignment="Center" VerticalExpand="True">
|
||||
<SpriteView Name="EntityView" OverrideDirection="South" Scale="2 2" SetSize="64 64"/>
|
||||
</PanelContainer>
|
||||
<!-- Center column (pressure, temperature, alarm state) -->
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Client.Graphics;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Renders a thermal heatmap overlay for gas tiles, used for equipment like thermal glasses.
|
||||
/// /// </summary>
|
||||
public sealed class GasTileDangerousTemperatureOverlay : Overlay
|
||||
{
|
||||
public override bool RequestScreenTexture { get; set; } = false;
|
||||
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
private GasTileOverlaySystem? _gasTileOverlay;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
private EntityQuery<GasTileOverlayComponent> _overlayQuery;
|
||||
|
||||
private readonly OverlayResourceCache<CachedResources> _resources = new();
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
// Cache used to transform ThermalByte into Color for overlay
|
||||
private readonly Color[] _colorCache = new Color[256];
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
public GasTileDangerousTemperatureOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
_overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
|
||||
for (byte i = 0; i <= ThermalByte.TempResolution; i++)
|
||||
{
|
||||
_colorCache[i] = PreCalculateColor(i);
|
||||
}
|
||||
|
||||
_colorCache[ThermalByte.StateVacuum] = Color.Teal;
|
||||
_colorCache[ThermalByte.StateVacuum].A = 0.6f;
|
||||
_colorCache[ThermalByte.AtmosImpossible] = Color.Transparent;
|
||||
|
||||
#if DEBUG // This shouldn't happend so tell me if you see this LimeGreen on the screen
|
||||
_colorCache[ThermalByte.ReservedFuture0] = Color.LimeGreen;
|
||||
_colorCache[ThermalByte.ReservedFuture1] = Color.LimeGreen;
|
||||
_colorCache[ThermalByte.ReservedFuture2] = Color.LimeGreen;
|
||||
#else
|
||||
_colorCache[ThermalByte.ReservedFuture0] = Color.Transparent;
|
||||
_colorCache[ThermalByte.ReservedFuture1] = Color.Transparent;
|
||||
_colorCache[ThermalByte.ReservedFuture2] = Color.Transparent;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used for Calculating onscreen color from ThermalByte core value
|
||||
/// /// </summary>
|
||||
private static Color PreCalculateColor(byte byteTemp)
|
||||
{
|
||||
// Color Thresholds in Kelvin
|
||||
// -150 C
|
||||
const float deepFreezeK = 123.15f;
|
||||
// -50 C
|
||||
const float freezeStartK = 223.15f;
|
||||
// 0 C
|
||||
const float waterFreezeK = 273.15f;
|
||||
// 50 C
|
||||
const float heatStartK = 323.15f;
|
||||
// 100 C
|
||||
const float waterBoilK = 373.15f;
|
||||
// 300 C
|
||||
const float superHeatK = 573.15f;
|
||||
|
||||
var tempK = byteTemp * ThermalByte.TempDegreeResolution;
|
||||
|
||||
// Neutral Zone Check (0C to 50C)
|
||||
// If between 273.15K and 323.15K, it's transparent.
|
||||
if (tempK >= waterFreezeK && tempK < heatStartK)
|
||||
{
|
||||
return Color.Transparent;
|
||||
}
|
||||
|
||||
Color resultingColor;
|
||||
|
||||
switch (tempK)
|
||||
{
|
||||
case < deepFreezeK:
|
||||
resultingColor = Color.FromHex("#330066");
|
||||
resultingColor.A = 0.7f;
|
||||
break;
|
||||
case < freezeStartK:
|
||||
// Interpolate Deep Purple -> Blue
|
||||
// Range: 123.15 to 223.15 (Span: 100)
|
||||
resultingColor = Color.InterpolateBetween(
|
||||
Color.FromHex("#330066"),
|
||||
Color.Blue,
|
||||
(tempK - deepFreezeK) * 0.01f);
|
||||
resultingColor.A = 0.6f;
|
||||
break;
|
||||
case < waterFreezeK:
|
||||
// Interpolate Blue -> Transparent
|
||||
// Range: 223.15 to 273.15 (Span: 50)
|
||||
|
||||
resultingColor = Color.InterpolateBetween(
|
||||
new Color(Color.Blue.R, Color.Blue.G, Color.Blue.B, 0.6f),
|
||||
new Color(Color.Blue.R, Color.Blue.G, Color.Blue.B, 0.2f),
|
||||
(tempK - freezeStartK) * 0.02f);
|
||||
break;
|
||||
case < waterBoilK:
|
||||
// Interpolate Transparent -> Yellow
|
||||
// Range: 323.15 to 373.15 (Span: 50)
|
||||
|
||||
resultingColor = Color.InterpolateBetween(
|
||||
new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 0.2f),
|
||||
new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 0.6f),
|
||||
(tempK - heatStartK) * 0.02f);
|
||||
break;
|
||||
case < superHeatK:
|
||||
// Interpolate Yellow -> Red
|
||||
// Range: 373.15 to 573.15 (Span: 200)
|
||||
resultingColor = Color.InterpolateBetween(
|
||||
Color.Yellow,
|
||||
Color.Red,
|
||||
(tempK - waterBoilK) * 0.005f);
|
||||
resultingColor.A = 0.6f;
|
||||
break;
|
||||
default:
|
||||
resultingColor = Color.DarkRed;
|
||||
resultingColor.A = 0.7f;
|
||||
break;
|
||||
}
|
||||
|
||||
return resultingColor;
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
_gasTileOverlay ??= _entManager.System<GasTileOverlaySystem>();
|
||||
if (_gasTileOverlay == null)
|
||||
return false;
|
||||
|
||||
var target = args.Viewport.RenderTarget;
|
||||
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
if (res.TemperatureTarget is null || res.TemperatureTarget.Texture.Size != target.Size)
|
||||
{
|
||||
res.TemperatureTarget?.Dispose();
|
||||
res.TemperatureTarget = _clyde.CreateRenderTarget(
|
||||
target.Size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: nameof(GasTileDangerousTemperatureOverlay));
|
||||
}
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var worldAABB = args.WorldAABB;
|
||||
var mapId = args.MapId;
|
||||
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
|
||||
|
||||
drawHandle.RenderInRenderTarget(res.TemperatureTarget,
|
||||
() =>
|
||||
{
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _grids);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
if (!_overlayQuery.TryGetComponent(grid.Owner, out var comp))
|
||||
continue;
|
||||
|
||||
var gridTileSizeVec = grid.Comp.TileSizeVector;
|
||||
var gridTileCenterVec = grid.Comp.TileSizeHalfVector;
|
||||
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
|
||||
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
|
||||
|
||||
drawHandle.SetTransform(gridEntToViewportLocal);
|
||||
|
||||
var worldToGridLocal = _xformSys.GetInvWorldMatrix(grid.Owner);
|
||||
var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
|
||||
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
while (enumerator.MoveNext(out var tileGas))
|
||||
{
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
var gasColor = _colorCache[tileGas.ByteGasTemperature.Value];
|
||||
|
||||
if (gasColor.A <= 0f)
|
||||
continue;
|
||||
|
||||
drawHandle.DrawRect(
|
||||
Box2.CenteredAround(tilePosition + gridTileCenterVec, gridTileSizeVec),
|
||||
gasColor
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Color(0, 0, 0, 0));
|
||||
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
if (res.TemperatureTarget != null)
|
||||
args.WorldHandle.DrawTextureRect(res.TemperatureTarget.Texture, args.WorldBounds);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_resources.Dispose();
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private sealed class CachedResources : IDisposable
|
||||
{
|
||||
public IRenderTexture? TemperatureTarget;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
TemperatureTarget?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Species;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay responsible for rendering atmos fire animation.
|
||||
/// </summary>
|
||||
public sealed class GasTileFireOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
private readonly SharedMapSystem _mapSystem = default!;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
private readonly float[] _timer;
|
||||
private readonly float[][] _frameDelays;
|
||||
private readonly int[] _frameCounter;
|
||||
|
||||
// TODO combine textures into a single texture atlas.
|
||||
private readonly Texture[][] _frames;
|
||||
|
||||
private const int FireStates = 3;
|
||||
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
|
||||
|
||||
public const int GasOverlayZIndex = (int)Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
|
||||
|
||||
public GasTileFireOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_shader = _protoMan.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_timer = new float[FireStates];
|
||||
_frameDelays = new float[FireStates][];
|
||||
_frameCounter = new int[FireStates];
|
||||
_frames = new Texture[FireStates][];
|
||||
|
||||
var fire = _resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
if (!fire.TryGetState((i + 1).ToString(), out var state))
|
||||
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_timer[i] < time) continue;
|
||||
_timer[i] -= time;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
var gridState = (args.WorldBounds,
|
||||
args.WorldHandle,
|
||||
_frames,
|
||||
_frameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (Box2Rotated WorldBounds,
|
||||
DrawingHandleWorld drawHandle,
|
||||
Texture[][] frames,
|
||||
int[] frameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
|
||||
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
|
||||
// by chunk, even though its currently slower.
|
||||
|
||||
state.drawHandle.UseShader(state.shader);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.FireState == 0)
|
||||
continue;
|
||||
|
||||
var index = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(index))
|
||||
continue;
|
||||
|
||||
var fireState = gas.FireState - 1;
|
||||
var texture = state.frames[fireState][state.frameCounter[fireState]];
|
||||
state.drawHandle.DrawTexture(texture, index);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Client.Graphics;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
using Texture = Robust.Client.Graphics.Texture;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay responsible for rendering heat distortion shader.
|
||||
/// </summary>
|
||||
public sealed class GasTileHeatBlurOverlay : Overlay
|
||||
{
|
||||
public override bool RequestScreenTexture { get; set; } = true;
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
private static readonly ProtoId<ShaderPrototype> HeatOverlayShader = "HeatBlur";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
private readonly Texture _noiseTexture;
|
||||
private readonly Texture _heatGradientTexture;
|
||||
private List<Entity<MapGridComponent>> _intersectingGrids = new();
|
||||
private readonly OverlayResourceCache<CachedResources> _resources = new();
|
||||
|
||||
// Overlay settings
|
||||
private const float
|
||||
ShaderSpilling = 2.5f; // for example 4f - spills shader one tile from hotspot, 2.5f - spills it half tile
|
||||
|
||||
private const float ShaderStrength = 0.04f; // Makes waves stronger
|
||||
private const float ShaderScale = 1f; // Makes more waves
|
||||
private const float ShaderSpeed = 0.4f; // Makes waves run faster
|
||||
|
||||
// Overlay settings for reduced motion setting
|
||||
private const float ShaderStrengthForReducedMotion = 0.01f;
|
||||
private const float ShaderScaleReducedMotion = 0.5f;
|
||||
private const float ShaderSpeedReducedMotion = 0.25f;
|
||||
|
||||
private const int MinDistortionTemp = 300; // Distortion starts to show up at this temperature in Kelvins
|
||||
private const int MaxDistortionTemp = 2000; // Maximum distortion strength at this temperature in Kelvins
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public GasTileHeatBlurOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
_noiseTexture = _resourceCache.GetTexture("/Textures/Effects/HeatBlur/perlin_noise.png");
|
||||
_heatGradientTexture = _resourceCache.GetTexture("/Textures/Effects/HeatBlur/soft_circle.png");
|
||||
|
||||
_shader = _proto.Index(HeatOverlayShader).InstanceUnique();
|
||||
_configManager.OnValueChanged(CCVars.ReducedMotion, SetReducedMotion, invokeImmediately: true);
|
||||
}
|
||||
|
||||
private void SetReducedMotion(bool reducedMotion)
|
||||
{
|
||||
_shader.SetParameter("strength_scale", reducedMotion ? ShaderStrengthForReducedMotion : ShaderStrength);
|
||||
_shader.SetParameter("spatial_scale", reducedMotion ? ShaderScaleReducedMotion : ShaderScale);
|
||||
_shader.SetParameter("speed_scale", reducedMotion ? ShaderSpeedReducedMotion : ShaderSpeed);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
var target = args.Viewport.RenderTarget;
|
||||
|
||||
// Probably the resolution of the game window changed, remake the textures.
|
||||
if (res.HeatTarget?.Texture.Size != target.Size)
|
||||
{
|
||||
res.HeatTarget?.Dispose();
|
||||
res.HeatTarget = _clyde.CreateRenderTarget(
|
||||
target.Size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: nameof(GasTileHeatBlurOverlaySystem));
|
||||
}
|
||||
|
||||
if (res.HeatBlurTarget?.Texture.Size != target.Size)
|
||||
{
|
||||
res.HeatBlurTarget?.Dispose();
|
||||
res.HeatBlurTarget = _clyde.CreateRenderTarget(
|
||||
target.Size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: $"{nameof(GasTileHeatBlurOverlaySystem)}-blur");
|
||||
}
|
||||
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
|
||||
args.WorldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
|
||||
|
||||
var mapId = args.MapId;
|
||||
var worldAABB = args.WorldAABB;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
|
||||
|
||||
// If there is no distortion after checking all visible tiles, we can bail early
|
||||
var anyDistortion = false;
|
||||
|
||||
// We're rendering in the context of the heat target texture, which will encode data as to where and how strong
|
||||
// the heat distortion will be
|
||||
args.WorldHandle.RenderInRenderTarget(res.HeatTarget,
|
||||
() =>
|
||||
{
|
||||
_intersectingGrids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _intersectingGrids);
|
||||
foreach (var grid in _intersectingGrids)
|
||||
{
|
||||
if (!overlayQuery.TryGetComponent(grid.Owner, out var comp))
|
||||
continue;
|
||||
|
||||
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
|
||||
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
|
||||
|
||||
if (!Matrix3x2.Invert(gridEntToViewportLocal, out var viewportLocalToGridEnt))
|
||||
continue;
|
||||
|
||||
var uvToUi = Matrix3Helpers.CreateScale(res.HeatTarget.Size.X, -res.HeatTarget.Size.Y);
|
||||
var uvToGridEnt = uvToUi * viewportLocalToGridEnt;
|
||||
|
||||
// Because we want the actual distortion to be calculated based on the grid coordinates*, we need
|
||||
// to pass a matrix transformation to go from the viewport coordinates to grid coordinates.
|
||||
// * (why? because otherwise the effect would shimmer like crazy as you moved around, think
|
||||
// moving a piece of warped glass above a picture instead of placing the warped glass on the
|
||||
// paper and moving them together)
|
||||
_shader.SetParameter("grid_ent_from_viewport_local", uvToGridEnt);
|
||||
|
||||
// Draw commands (like DrawRect) will be using grid coordinates from here
|
||||
worldHandle.SetTransform(gridEntToViewportLocal);
|
||||
|
||||
// We only care about tiles that fit in these bounds
|
||||
var worldToGridLocal = _xformSys.GetInvWorldMatrix(grid.Owner);
|
||||
var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
|
||||
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// for each tile and its gas --->
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var tileGas))
|
||||
{
|
||||
// Check and make sure the tile is within the viewport/screen
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
// Get the distortion strength from the temperature and bail if it's not hot enough
|
||||
var strength = GetHeatDistortionStrength(tileGas.ByteGasTemperature);
|
||||
if (strength <= 0f)
|
||||
continue;
|
||||
|
||||
anyDistortion = true;
|
||||
|
||||
// Encode the strength in the red channel
|
||||
// alpha set to 1 as tile is active
|
||||
worldHandle.DrawTextureRect(
|
||||
_heatGradientTexture,
|
||||
Box2.CenteredAround(tilePosition + grid.Comp.TileSizeHalfVector,
|
||||
grid.Comp.TileSizeVector * ShaderSpilling),
|
||||
new Color(strength, 0f, 0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// This clears the buffer to all zero first...
|
||||
new Color(0, 0, 0, 0));
|
||||
|
||||
// no distortion, no need to render
|
||||
if (!anyDistortion)
|
||||
{
|
||||
args.WorldHandle.UseShader(null);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
|
||||
|
||||
if (ScreenTexture is null || res.HeatTarget is null || res.HeatBlurTarget is null)
|
||||
return;
|
||||
|
||||
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_shader.SetParameter("NOISE_TEXTURE", _noiseTexture);
|
||||
|
||||
args.WorldHandle.UseShader(_shader);
|
||||
args.WorldHandle.DrawTextureRect(res.HeatTarget.Texture, args.WorldBounds);
|
||||
|
||||
args.WorldHandle.UseShader(null);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_resources.Dispose();
|
||||
|
||||
_configManager.UnsubValueChanged(CCVars.ReducedMotion, SetReducedMotion);
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the strength of the heat distortion effect based on the temperature of the tile.
|
||||
/// The strength is a value between 0 and 1, where 0 means no distortion and 1 means maximum distortion.
|
||||
/// </summary>
|
||||
/// <param name="temp">The temperature of the tile.</param>
|
||||
/// <returns>The strength of the heat distortion effect.</returns>
|
||||
/// <seealso cref="ThermalByte"/>
|
||||
private static float GetHeatDistortionStrength(ThermalByte temp)
|
||||
{
|
||||
if (!temp.TryGetTemperature(out var kelvinTemp))
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var strength = (kelvinTemp - MinDistortionTemp) / (MaxDistortionTemp - MinDistortionTemp);
|
||||
|
||||
return MathHelper.Clamp01(strength);
|
||||
}
|
||||
|
||||
internal sealed class CachedResources : IDisposable
|
||||
{
|
||||
public IRenderTexture? HeatTarget;
|
||||
public IRenderTexture? HeatBlurTarget;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
HeatTarget?.Dispose();
|
||||
HeatBlurTarget?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Atmos.Components;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays
|
||||
{
|
||||
public sealed class GasTileOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
// Gas overlays
|
||||
private readonly float[] _timer;
|
||||
private readonly float[][] _frameDelays;
|
||||
private readonly int[] _frameCounter;
|
||||
|
||||
// TODO combine textures into a single texture atlas.
|
||||
private readonly Texture[][] _frames;
|
||||
|
||||
// Fire overlays
|
||||
private const int FireStates = 3;
|
||||
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
|
||||
|
||||
private readonly float[] _fireTimer = new float[FireStates];
|
||||
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
||||
private readonly int[] _fireFrameCounter = new int[FireStates];
|
||||
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
||||
|
||||
private int _gasCount;
|
||||
|
||||
public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
|
||||
|
||||
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = IoCManager.Resolve<IMapManager>();
|
||||
_mapSystem = entManager.System<SharedMapSystem>();
|
||||
_xformSys = xformSys;
|
||||
_shader = protoMan.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_gasCount = system.VisibleGasId.Length;
|
||||
_timer = new float[_gasCount];
|
||||
_frameDelays = new float[_gasCount][];
|
||||
_frameCounter = new int[_gasCount];
|
||||
_frames = new Texture[_gasCount][];
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var gasPrototype = protoMan.Index<GasPrototype>(system.VisibleGasId[i].ToString());
|
||||
|
||||
SpriteSpecifier overlay;
|
||||
|
||||
if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
|
||||
overlay = new SpriteSpecifier.Rsi(new (gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
|
||||
else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
|
||||
overlay = new SpriteSpecifier.Texture(new (gasPrototype.GasOverlayTexture));
|
||||
else
|
||||
continue;
|
||||
|
||||
switch (overlay)
|
||||
{
|
||||
case SpriteSpecifier.Rsi animated:
|
||||
var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
var stateId = animated.RsiState;
|
||||
|
||||
if (!rsi.TryGetState(stateId, out var state))
|
||||
continue;
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
_frames[i] = new[] { spriteSys.Frame0(texture) };
|
||||
_frameDelays[i] = Array.Empty<float>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var fire = resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
if (!fire.TryGetState((i + 1).ToString(), out var state))
|
||||
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
|
||||
|
||||
_fireFrames[i] = state.GetFrames(RsiDirection.South);
|
||||
_fireFrameDelays[i] = state.GetDelays();
|
||||
_fireFrameCounter[i] = 0;
|
||||
}
|
||||
}
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_timer[i] < time)
|
||||
continue;
|
||||
|
||||
_timer[i] -= time;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
var delays = _fireFrameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _fireFrameCounter[i];
|
||||
_fireTimer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_fireTimer[i] < time) continue;
|
||||
_fireTimer[i] -= time;
|
||||
_fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
var gridState = (args.WorldBounds,
|
||||
args.WorldHandle,
|
||||
_gasCount,
|
||||
_frames,
|
||||
_frameCounter,
|
||||
_fireFrames,
|
||||
_fireFrameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
|
||||
DrawMapOverlay(drawHandle, args, mapUid, atmos);
|
||||
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (Box2Rotated WorldBounds,
|
||||
DrawingHandleWorld drawHandle,
|
||||
int gasCount,
|
||||
Texture[][] frames,
|
||||
int[] frameCounter,
|
||||
Texture[][] fireFrames,
|
||||
int[] fireFrameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int) MathF.Floor(floatBounds.Left),
|
||||
(int) MathF.Floor(floatBounds.Bottom),
|
||||
(int) MathF.Ceiling(floatBounds.Right),
|
||||
(int) MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
|
||||
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
|
||||
// by chunk, even though its currently slower.
|
||||
|
||||
state.drawHandle.UseShader(null);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.Opacity == null!)
|
||||
continue;
|
||||
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < state.gasCount; i++)
|
||||
{
|
||||
var opacity = gas.Opacity[i];
|
||||
if (opacity > 0)
|
||||
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And again for fire, with the unshaded shader
|
||||
state.drawHandle.UseShader(state.shader);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.FireState == 0)
|
||||
continue;
|
||||
|
||||
var index = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(index))
|
||||
continue;
|
||||
|
||||
var fireState = gas.FireState - 1;
|
||||
var texture = state.fireFrames[fireState][state.fireFrameCounter[fireState]];
|
||||
state.drawHandle.DrawTexture(texture, index);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void DrawMapOverlay(
|
||||
DrawingHandleWorld handle,
|
||||
OverlayDrawArgs args,
|
||||
EntityUid map,
|
||||
MapAtmosphereComponent atmos)
|
||||
{
|
||||
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
|
||||
|
||||
// map-grid atmospheres get drawn above grids
|
||||
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// Normal map atmospheres get drawn below grids
|
||||
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
|
||||
return;
|
||||
|
||||
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
|
||||
var topRight = args.WorldAABB.TopRight.Ceiled();
|
||||
|
||||
for (var x = bottomLeft.X; x <= topRight.X; x++)
|
||||
{
|
||||
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
|
||||
{
|
||||
var tilePosition = new Vector2(x, y);
|
||||
|
||||
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
|
||||
{
|
||||
var opacity = atmos.OverlayData.Opacity[i];
|
||||
|
||||
if (opacity > 0)
|
||||
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
using Content.Client.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics;
|
||||
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Overlay responsible for rendering visible atmos gasses (like plasma for example) usin.
|
||||
/// </summary>
|
||||
public sealed class GasTileVisibleGasOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
private readonly SharedAtmosphereSystem _atmosphereSystem;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
private readonly SharedGasTileOverlaySystem _gasTileOverlaySystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
// Gas overlays
|
||||
private readonly float[] _timer;
|
||||
private readonly float[][] _frameDelays;
|
||||
private readonly int[] _frameCounter;
|
||||
|
||||
// TODO combine textures into a single texture atlas.
|
||||
private readonly Texture[][] _frames;
|
||||
|
||||
private readonly int _gasCount;
|
||||
|
||||
public const int GasOverlayZIndex = (int)DrawDepth.Gasses; // Under ghosts and fire, above mostly everything else
|
||||
|
||||
public GasTileVisibleGasOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_atmosphereSystem = _entManager.System<SharedAtmosphereSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
_gasTileOverlaySystem = _entManager.System<SharedGasTileOverlaySystem>();
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
_shader = _protoManager.Index(UnshadedShader).Instance();
|
||||
ZIndex = GasOverlayZIndex;
|
||||
|
||||
_gasCount = _gasTileOverlaySystem.VisibleGasId.Length;
|
||||
_timer = new float[_gasCount];
|
||||
_frameDelays = new float[_gasCount][];
|
||||
_frameCounter = new int[_gasCount];
|
||||
_frames = new Texture[_gasCount][];
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var gasPrototype = _atmosphereSystem.GetGas(_gasTileOverlaySystem.VisibleGasId[i]);
|
||||
|
||||
switch (gasPrototype.GasOverlaySprite)
|
||||
{
|
||||
case SpriteSpecifier.Rsi animated:
|
||||
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
var stateId = animated.RsiState;
|
||||
|
||||
if (!rsi.TryGetState(stateId, out var state))
|
||||
continue;
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
_frameCounter[i] = 0;
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
_frames[i] = new[] { _spriteSystem.Frame0(texture) };
|
||||
_frameDelays[i] = Array.Empty<float>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
var time = delays[frameCount];
|
||||
|
||||
if (_timer[i] < time)
|
||||
continue;
|
||||
|
||||
_timer[i] -= time;
|
||||
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var drawHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
var gridState = (args.WorldBounds,
|
||||
args.WorldHandle,
|
||||
_gasCount,
|
||||
_frames,
|
||||
_frameCounter,
|
||||
_shader,
|
||||
overlayQuery,
|
||||
xformQuery,
|
||||
_xformSys);
|
||||
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
|
||||
DrawMapOverlay(drawHandle, args, mapUid, atmos);
|
||||
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId,
|
||||
args.WorldAABB,
|
||||
ref gridState,
|
||||
static (EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
ref (Box2Rotated WorldBounds,
|
||||
DrawingHandleWorld drawHandle,
|
||||
int gasCount,
|
||||
Texture[][] frames,
|
||||
int[] frameCounter,
|
||||
ShaderInstance shader,
|
||||
EntityQuery<GasTileOverlayComponent> overlayQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
SharedTransformSystem xformSys) state) =>
|
||||
{
|
||||
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
|
||||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
|
||||
state.drawHandle.SetTransform(worldMatrix);
|
||||
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
|
||||
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
|
||||
// by chunk, even though its currently slower.
|
||||
|
||||
state.drawHandle.UseShader(null);
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var gas))
|
||||
{
|
||||
if (gas.Opacity == null!)
|
||||
continue;
|
||||
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < state.gasCount; i++)
|
||||
{
|
||||
var opacity = gas.Opacity[i];
|
||||
if (opacity > 0)
|
||||
{
|
||||
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]],
|
||||
tilePosition,
|
||||
Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void DrawMapOverlay(
|
||||
DrawingHandleWorld handle,
|
||||
OverlayDrawArgs args,
|
||||
EntityUid map,
|
||||
MapAtmosphereComponent atmos)
|
||||
{
|
||||
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
|
||||
|
||||
// map-grid atmospheres get drawn above grids
|
||||
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// Normal map atmospheres get drawn below grids
|
||||
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
|
||||
return;
|
||||
|
||||
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
|
||||
var topRight = args.WorldAABB.TopRight.Ceiled();
|
||||
|
||||
for (var x = bottomLeft.X; x <= topRight.X; x++)
|
||||
{
|
||||
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
|
||||
{
|
||||
var tilePosition = new Vector2(x, y);
|
||||
|
||||
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
|
||||
{
|
||||
var opacity = atmos.OverlayData.Opacity[i];
|
||||
|
||||
if (opacity > 0)
|
||||
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,41 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
|
||||
namespace Content.Client.Atmos.UI;
|
||||
|
||||
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
|
||||
namespace Content.Client.Atmos.UI
|
||||
{
|
||||
[ViewVariables]
|
||||
private GasAnalyzerWindow? _window;
|
||||
|
||||
public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
}
|
||||
[ViewVariables]
|
||||
private GasAnalyzerWindow? _window;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
|
||||
if (message is not GasAnalyzerUserMessage cast)
|
||||
return;
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
if (message is not GasAnalyzerUserMessage cast)
|
||||
return;
|
||||
_window.Populate(cast);
|
||||
}
|
||||
|
||||
_window.Populate(cast);
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -10,6 +8,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
@@ -17,17 +16,25 @@ namespace Content.Client.Atmos.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasAnalyzerWindow : DefaultWindow
|
||||
{
|
||||
private readonly SharedAtmosphereSystem _atmosphere;
|
||||
private NetEntity _currentEntity = NetEntity.Invalid;
|
||||
|
||||
public GasAnalyzerWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_atmosphere = IoCManager.Resolve<IEntityManager>().System<SharedAtmosphereSystem>();
|
||||
}
|
||||
|
||||
public void Populate(GasAnalyzerUserMessage msg)
|
||||
{
|
||||
if (msg.Error != null)
|
||||
{
|
||||
CTopBox.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-error-text", ("errorText", msg.Error)),
|
||||
FontColorOverride = Color.Red
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.NodeGasMixes.Length == 0)
|
||||
{
|
||||
CTopBox.AddChild(new Label
|
||||
@@ -322,31 +329,31 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
for (var j = 0; j < gasMix.Gases.Length; j++)
|
||||
{
|
||||
var gasEntry = gasMix.Gases[j];
|
||||
var gasProto = _atmosphere.GetGas(gasEntry.Gas);
|
||||
var gas = gasMix.Gases[j];
|
||||
var color = Color.FromHex($"#{gas.Color}", Color.White);
|
||||
// Add to the table
|
||||
tableKey.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString(gasProto.Name)
|
||||
Text = Loc.GetString(gas.Name)
|
||||
});
|
||||
tableVal.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-molarity-text",
|
||||
("mol", $"{gasEntry.Amount:0.00}")),
|
||||
("mol", $"{gas.Amount:0.00}")),
|
||||
Align = Label.AlignMode.Right,
|
||||
});
|
||||
tablePercent.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-percentage-text",
|
||||
("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.0}")),
|
||||
("percentage", $"{(gas.Amount / totalGasAmount * 100):0.0}")),
|
||||
Align = Label.AlignMode.Right
|
||||
});
|
||||
|
||||
// Add to the gas bar //TODO: highlight the currently hover one
|
||||
gasBar.AddEntry(gasEntry.Amount, gasProto.Color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text",
|
||||
("gasName", Loc.GetString(gasProto.Name)),
|
||||
("amount", $"{gasEntry.Amount:0.##}"),
|
||||
("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.#}")));
|
||||
gasBar.AddEntry(gas.Amount, color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text",
|
||||
("gasName", gas.Name),
|
||||
("amount", $"{gas.Amount:0.##}"),
|
||||
("percentage", $"{(gas.Amount / totalGasAmount * 100):0.#}")));
|
||||
}
|
||||
|
||||
dataContainer.AddChild(gasBar);
|
||||
|
||||
@@ -37,9 +37,10 @@ namespace Content.Client.Atmos.UI
|
||||
_window.SelectGasPressed += OnSelectGasPressed;
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
SendMessage(new GasFilterToggleStatusMessage(status));
|
||||
if (_window is null) return;
|
||||
SendMessage(new GasFilterToggleStatusMessage(_window.FilterStatus));
|
||||
}
|
||||
|
||||
private void OnFilterTransferRatePressed(string value)
|
||||
@@ -51,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="480 400" Title="Filter">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-filter-ui-filter-status}"/>
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-filter-ui-filter-transfer-rate}"/>
|
||||
|
||||
@@ -18,10 +18,11 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
|
||||
public bool FilterStatus = true;
|
||||
public string? SelectedGas;
|
||||
public string? CurrentGasId;
|
||||
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<string>? FilterTransferRateChanged;
|
||||
public event Action? SelectGasPressed;
|
||||
|
||||
@@ -29,7 +30,8 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
ToggleStatusButton.OnPressed += _ => SetFilterStatus(!FilterStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
|
||||
FilterTransferRateInput.OnTextChanged += _ => SetFilterRate.Disabled = false;
|
||||
SetFilterRate.OnPressed += _ =>
|
||||
@@ -51,7 +53,15 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
public void SetFilterStatus(bool enabled)
|
||||
{
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
FilterStatus = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-filter-ui-status-enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-filter-ui-status-disabled");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGasFiltered(string? id, string name)
|
||||
|
||||
@@ -33,9 +33,10 @@ namespace Content.Client.Atmos.UI
|
||||
_window.MixerNodePercentageChanged += OnMixerSetPercentagePressed;
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
SendMessage(new GasMixerToggleStatusMessage(status));
|
||||
if (_window is null) return;
|
||||
SendMessage(new GasMixerToggleStatusMessage(_window.MixerStatus));
|
||||
}
|
||||
|
||||
private void OnMixerOutputPressurePressed(string value)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="200 200" Title="Gas Mixer">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-mixer-ui-mixer-status}"/>
|
||||
<Control MinSize="5 0" />
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-mixer-ui-mixer-output-pressure}"/>
|
||||
<Control MinSize="5 0" />
|
||||
|
||||
@@ -21,7 +21,9 @@ namespace Content.Client.Atmos.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasMixerWindow : DefaultWindow
|
||||
{
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public bool MixerStatus = true;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<string>? MixerOutputPressureChanged;
|
||||
public event Action<string>? MixerNodePercentageChanged;
|
||||
|
||||
@@ -31,7 +33,8 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
ToggleStatusButton.OnPressed += _ => SetMixerStatus(!MixerStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
|
||||
MixerPressureOutputInput.OnTextChanged += _ => SetOutputPressureButton.Disabled = false;
|
||||
SetOutputPressureButton.OnPressed += _ =>
|
||||
@@ -80,7 +83,15 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
public void SetMixerStatus(bool enabled)
|
||||
{
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
MixerStatus = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-mixer-ui-status-enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-mixer-ui-status-disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,12 @@ public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKe
|
||||
_window.SetOutputPressure(pump.TargetPressure);
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(status));
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
|
||||
}
|
||||
|
||||
private void OnPumpOutputPressurePressed(float value)
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
SetSize="340 110" MinSize="340 110" Title="Pressure Pump">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
<Label Text="{Loc comp-gas-pump-ui-pump-status}" Margin="0 0 5 0"/>
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Button HorizontalAlignment="Right" Name="SetOutputPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" Disabled="True" Margin="0 0 5 0"/>
|
||||
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace Content.Client.Atmos.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasPressurePumpWindow : FancyWindow
|
||||
{
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public bool PumpStatus = true;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<float>? PumpOutputPressureChanged;
|
||||
|
||||
public float MaxPressure
|
||||
@@ -31,7 +33,8 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
|
||||
PumpPressureOutputInput.OnValueChanged += _ => SetOutputPressureButton.Disabled = false;
|
||||
|
||||
@@ -56,7 +59,15 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
public void SetPumpStatus(bool enabled)
|
||||
{
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
PumpStatus = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
_window = this.CreateWindow<GasThermomachineWindow>();
|
||||
|
||||
_window.ToggleStatusButton.OnToggled += _ => OnToggleStatusButtonPressed();
|
||||
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
|
||||
_window.TemperatureSpinbox.OnValueChanged += _ => OnTemperatureChanged(_window.TemperatureSpinbox.Value);
|
||||
_window.Entity = Owner;
|
||||
Update();
|
||||
@@ -45,6 +45,9 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
if (_window is null) return;
|
||||
|
||||
_window.SetActive(!_window.Active);
|
||||
SendPredictedMessage(new GasThermomachineToggleMessage());
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="300 120" Title="{Loc comp-gas-thermomachine-ui-title-freezer}">
|
||||
<BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True"
|
||||
Access="Public" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-thermomachine-ui-toggle}"/>
|
||||
<Control MinSize="5 0" />
|
||||
<Button Access="Public" Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="SpinboxHBox" Orientation="Horizontal">
|
||||
<Label Text="{Loc comp-gas-thermomachine-ui-temperature}"/>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed partial class GasThermomachineWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public bool Active = true;
|
||||
|
||||
public FloatSpinBox TemperatureSpinbox;
|
||||
|
||||
public EntityUid Entity;
|
||||
@@ -28,7 +30,15 @@ public sealed partial class GasThermomachineWindow : FancyWindow
|
||||
|
||||
public void SetActive(bool active)
|
||||
{
|
||||
ToggleStatusButton.Pressed = active;
|
||||
Active = active;
|
||||
if (active)
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-thermomachine-ui-status-enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-thermomachine-ui-status-disabled");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTemperature(float temperature)
|
||||
|
||||
@@ -38,9 +38,11 @@ namespace Content.Client.Atmos.UI
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(status));
|
||||
if (_window is null) return;
|
||||
|
||||
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
|
||||
}
|
||||
|
||||
private void OnPumpTransferRatePressed(string value)
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="200 120" Title="Volume Pump">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-pump-ui-pump-status}"/>
|
||||
<Control MinSize="5 0" />
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-pump-ui-pump-transfer-rate}"/>
|
||||
|
||||
@@ -19,14 +19,17 @@ namespace Content.Client.Atmos.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasVolumePumpWindow : FancyWindow
|
||||
{
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public bool PumpStatus = true;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<string>? PumpTransferRateChanged;
|
||||
|
||||
public GasVolumePumpWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
|
||||
PumpTransferRateInput.OnTextChanged += _ => SetTransferRateButton.Disabled = false;
|
||||
SetTransferRateButton.OnPressed += _ =>
|
||||
@@ -49,7 +52,15 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
public void SetPumpStatus(bool enabled)
|
||||
{
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
PumpStatus = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
|
||||
|
||||
_window = this.CreateWindow<SpaceHeaterWindow>();
|
||||
|
||||
_window.ToggleStatusButton.OnToggled += _ => OnToggleStatusButtonPressed();
|
||||
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
|
||||
_window.IncreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(_window.TemperatureChangeDelta);
|
||||
_window.DecreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(-_window.TemperatureChangeDelta);
|
||||
_window.ModeSelector.OnItemSelected += OnModeChanged;
|
||||
@@ -34,6 +34,7 @@ public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
_window?.SetActive(!_window.Active);
|
||||
SendMessage(new SpaceHeaterToggleMessage());
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user