Compare commits

..
Author SHA1 Message Date
pre-commit-ci[bot] 37e44ee3ee [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-01-11 19:10:58 +00:00
Vanessa fc28b60175 Added shared bible cooldown for BibleUsers 2026-01-11 13:01:32 -06:00
4990 changed files with 230249 additions and 520920 deletions
+1 -1
View File
@@ -351,7 +351,7 @@ resharper_csharp_qualified_using_at_nested_scope = false
resharper_csharp_prefer_qualified_reference = false
resharper_csharp_allow_alias = false
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,slnx}]
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
indent_size = 2
[nuget.config]
+6 -12
View File
@@ -10,28 +10,22 @@
<!-- Summary of code changes for easier review. -->
## Media
<!-- Attach media if the PR makes in-game changes (clothing, items, features, etc).
<!-- Attach media if the PR makes ingame changes (clothing, items, features, etc).
Small fixes/refactors are exempt. -->
## Requirements
<!-- Confirm the following by placing an X in the brackets without spaces inside (for example: [X] ): -->
<!-- Confirm the following by placing an X in the brackets: [X] -->
- [ ] I have tested all added content and changes.
- [ ] I have added media to this PR or it does not require an in-game showcase.
- [ ] I have added media to this PR or it does not require an ingame showcase.
<!-- You should understand that not following the above may get your PR closed at maintainer’s discretion -->
## Licensing
<!-- This is for the benefit of other forks wishing to port features from DeltaV
Our repository is AGPL, meaning projects using the MIT license would have to ask for permission from the PR author (ideally that's you, reading this) before being allowed to port it over
This just saves them the trouble. Note that AGPL or MIT licensing is only applicable to SOFTWARE, and that assets such as textures and audio have their own licensing scheme that is defined in the codebase itself. -->
- [ ] I confirm that I am the creator of the code in this PR, and allow licensing it under the following license(s), or that the original author(s) has given me permission to do so:
- [X] AGPL (https://github.com/DeltaV-Station/Delta-v/blob/master/LICENSE-AGPLv3.txt) <!-- This one is required to contribute to DeltaV -->
- [X] MIT (https://github.com/DeltaV-Station/Delta-v/blob/master/LICENSE-MIT.txt) <!-- Optional - A lot of other SS14 forks use MIT -->
<!-- Feel free to add more licenses as you see fit -->
## Breaking changes
<!-- List any breaking changes, including namespaces, public class/method/field changes, prototype renames; and provide instructions for fixing them. -->
**Changelog**
<!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay.
Make sure to read the guidelines and take this Changelog template out of the comment block in order for it to show up.
Changelog must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog. -->
Changelogs must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog. -->
<!--
:cl:
- add: Added fun!
+1 -1
View File
@@ -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 -2
View File
@@ -2,7 +2,7 @@
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -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 -2
View File
@@ -2,7 +2,7 @@ name: Build & Test Debug
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -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
+30 -1
View File
@@ -2,11 +2,19 @@
on:
pull_request_target:
types: [review_requested, opened]
types: [review_requested]
pull_request_review:
types: [submitted]
permissions:
pull-requests: write
issues: write
contents: write
jobs:
add_label:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_target'
steps:
- uses: actions-ecosystem/action-add-labels@v1
with:
@@ -14,3 +22,24 @@ jobs:
- uses: actions-ecosystem/action-remove-labels@v1
with:
labels: "S: Awaiting Changes"
# Doesn't work on prs from foreign repos currently
#handle_review:
# runs-on: ubuntu-latest
# if: github.event_name == 'pull_request_review'
# steps:
# - uses: actions-ecosystem/action-add-labels@v1
# if: |
# github.event.review.state == 'changes_requested' &&
# (github.event.review.author_association == 'OWNER' ||
# github.event.review.author_association == 'MEMBER')
# with:
# labels: "S: Awaiting Changes"
#
# - uses: actions-ecosystem/action-remove-labels@v1
# if: |
# github.event.review.state == 'changes_requested' &&
# (github.event.review.author_association == 'OWNER' ||
# github.event.review.author_association == 'MEMBER')
# with:
# labels: "S: Needs Review"
-24
View File
@@ -1,24 +0,0 @@
# The PAT does not have the correct permissions to make this work; Eventually we'll want to replace this workflow with a proper bot
# name: "Labels: Approved"
# on:
# pull_request_review:
# types: [submitted]
# jobs:
# add_label:
# # Change the repository name after you've made sure the team name is correct for your fork!
# if: ${{ (github.repository == 'DeltaV-Station/Delta-v') && (github.event.review.state == 'APPROVED') }}
# permissions:
# contents: read
# pull-requests: write
# runs-on: ubuntu-latest
# steps:
# - uses: tspascoal/get-user-teams-membership@v3
# id: checkUserMember
# with:
# username: ${{ github.actor }}
# team: "maintainers"
# GITHUB_TOKEN: ${{ secrets.LABELER_PAT }}
# - if: ${{ steps.checkUserMember.outputs.isTeamMember == 'true' }}
# uses: actions-ecosystem/action-add-labels@v1
# with:
# labels: "S: Approved"
+1 -1
View File
@@ -23,7 +23,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: Get Engine Tag
run: |
+2 -2
View File
@@ -2,7 +2,7 @@
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
paths:
- '**.cs'
- '**.csproj'
@@ -51,7 +51,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
+5 -18
View File
@@ -34,19 +34,17 @@ jobs:
# Uncomment this and comment the other line if you do this.
# https://github.com/stefanzweifel/git-auto-commit-action#push-to-protected-branches
# - name: Commit new credit files
# uses: stefanzweifel/git-auto-commit-action@v7
# with:
# commit_message: Update Credits
# commit_author: DeltaV-Bot <github@deltav.gay>
# token: ${{ secrets.GITHUB_TOKEN }}
#- name: Commit new credit files
# uses: stefanzweifel/git-auto-commit-action@v4
# with:
# commit_message: Update Credits
# commit_author: PJBot <pieterjan.briers+bot@gmail.com>
# This will make a PR
- name: Set current date as env variable
run: echo "NOW=$(date +'%Y-%m-%dT%H-%M-%S')" >> $GITHUB_ENV
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v5
with:
commit-message: Update Credits
@@ -54,14 +52,3 @@ jobs:
body: This is an automated Pull Request. This PR updates the github contributors in the credits section.
author: DeltaV-Bot <github@deltav.gay>
branch: automated/credits-${{env.NOW}}
- name: Auto-merge PR with bypass
if: steps.cpr.outputs.pull-request-number != ''
env:
GH_TOKEN: ${{secrets.CREDITS_TOKEN}}
run: |
gh pr merge ${{ steps.cpr.outputs.pull-request-number }} \
--squash \
--admin \
--delete-branch \
--repo ${{ github.repository }}
+1 -1
View File
@@ -1,7 +1,7 @@
name: RGA schema validator
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
+1 -1
View File
@@ -2,7 +2,7 @@ name: RSI Validator
on:
push:
branches: [ master ]
branches: [ staging, trying ]
merge_group:
pull_request:
paths:
+1 -1
View File
@@ -1,7 +1,7 @@
name: Map file schema validator
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
+2 -2
View File
@@ -2,7 +2,7 @@ name: YAML Linter
on:
push:
branches: [ master ]
branches: [ master, staging, trying ]
merge_group:
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
@@ -26,7 +26,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
- name: Build
-3
View File
@@ -1,6 +1,3 @@
# MSbuild binlog files
*.binlog
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+21 -21
View File
@@ -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;" | 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
+6 -26
View File
@@ -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.
@@ -79,7 +79,6 @@ Controlled Items include any item that is restricted from general possession and
* Technology utilizing bluespace as an integral part of its function.
* Teleportation and portal technology.
* Hydraulic or powered equipment designed for the electronic or mechanical bypass of secure-access systems.
* Rapid construction/piping technology
Any individual requiring any above item in the course of their job, or for regular function, may request a document of authorization from the relevant departmental head. Such a document must have an itemized list of the approved Controlled Items, the name and position of the holder, and a signed and stamped statement by the authorizing party.
@@ -103,25 +102,25 @@ A list of such Substances follow:
Controlled Armaments are grouped into categories of increasing severity. If the offender possesses several categories of contraband, only the highest severity of said items should be charged.
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 3min brig and further legal action as the situation demands.''
'''Category A:''' ''Illegal possession of Cat. A controlled armaments may incur a sentence of up to 5min brig and further legal action as the situation demands.''
* Any object or instrument specifically designed, adapted, or used to cause physical harm in close combat
* Any object or equipment specifically designed or adapted to protect against physical harm caused by weapons, projectiles, or other forms of direct assault
* Any object or instrument specifically designed or adapted to incapacitate or disorient by using light or sound without causing permanent injury
* Any object or equipment specifically designed or adapted for explicitly training purposes that would otherwise fall under another category
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 8min brig and further legal action as the situation demands.''
'''Category B:''' ''Illegal possession of Cat. B controlled armaments may incur a sentence of up to 10min brig and further legal action as the situation demands.''
* Any object or instrument specifically designed or adapted to cause physical harm in close combat, which employs a powered system to enhance performance
* Any manually-operated ballistic arms that possess an internal magazine or feeding system, or lack an internal magazine or feeding system altogether
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 12min brig and further legal action as the situation demands.''
'''Category C:''' ''Illegal possession of Cat. C controlled armaments may incur a sentence of up to 15min brig and further legal action as the situation demands.''
* Any manually-operated ballistic arms that use an external magazine or feeding system
* Any semi-automatic ballistic arms not easily concealable within a pocket or coat
* Any object or instrument specifically designed or adapted for explicitly less-lethal purposes that would otherwise fall under another category
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 15min brig and further legal action as the situation demands.''
'''Category D:''' ''Illegal possession of Cat. D controlled armaments may incur a sentence of up to 20min brig and further legal action as the situation demands.''
* Any semi-automatic ballistic arms that are easily concealable
* Any fully-automatic ballistic arms
@@ -129,7 +128,7 @@ Controlled Armaments are grouped into categories of increasing severity. If the
* Any object or device specifically designed or adapted to cause physical harm through detonation of an explosive charge
* Any object or equipment specifically designed or adapted to protect against both a space environment and physical harm caused by weapons, projectiles, or other forms of direct assault
'''Category E:''' ''Illegal possession of Cat. E controlled armaments may incur a sentence of up to 20min brig and further legal action as the situation demands.''
'''Category E:''' ''Illegal possession of Cat. E controlled armaments may incur a sentence of up to 25min brig and further legal action as the situation demands.''
* Any crew-served or emplaced weapon
* Any object or instrument specifically designed or adapted to launch large-calibre explosives or projectiles for offensive or defensive purposes
@@ -376,22 +375,3 @@ Medical staff are afforded the right to quarantine individuals that currently su
==Foreign Bodies==
Any parasitic alien organism that requires a living host to live and reproduce must be eliminated. An exception can be made for experimental purposes, provided a CO, CMO and Mystagogue were to approve it.
=Engineering Regulations=
==Health & Safety==
* In public areas, all wiring must be concealed under tiles. In non-public areas, catwalks are recommended to be placed for the safety and mobility of workers.
* The atmospheric distribution pipe ("distro") pressure should be no more than 500 kPa and composition should be a ratio of 70-80% nitrogen and 20-30% oxygen.
* The atmospheric waste pipe ("waste") shall be kept free of clogging, and directed either to recycling, gas storage, or space.
* Newly constructed burn chambers may not be located within the station or directly adjacent to the station's hull, unless receiving written authorization from Central Command.
==Building Permits==
Any renovations larger than small modifications to the station's structural layout, power network, or atmospheric system shall require a building permit or work order approved expressly by the Chief Engineer, or if a Chief Engineer is not present, the Captain or Acting Commanding Officer. Modifications within departments shall additionally require the approval of the relevant department head. Areas within maintenance tunnels do not require permission to modify. However, any modifications to maintenance tunnel passageways should ensure they remain traversable and unobstructed by personnel who need to use them as part of their job.
==Emergency Access==
In emergency situations, engineers are permitted to bypass door access (i.e. hack) and gain access to unauthorized locations if said locations require repairs.
==Hazard Zones==
An area with a hull breach, hazardous material leak, or other similar danger shall be considered a hazard zone. To ensure crew safety, engineering personnel are required to demarcate hazard zones with holofans, inflatable walls, and/or engineering tape prior to beginning work. Any non-authorized personnel impeding or preventing repairs in hazardous areas may be liable for endangerment.
==Condemned Areas==
The Chief Engineer may declare a section of the station condemned if repairs cannot be reasonably completed. In the event that there is no Chief Engineer, any engineer may condemn a section of the station with authorization from the Captain or Acting Commanding Officer. Doors leading to such areas shall be welded or bolted to prevent crew access.
+1 -1
View File
@@ -11,7 +11,7 @@ import time
from pathlib import Path
from typing import List
SOLUTION_PATH = Path("..") / "SpaceStation14.slnx"
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
# If this doesn't match the saved version we overwrite them all.
CURRENT_HOOKS_VERSION = "4"
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
+9 -14
View File
@@ -1,20 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
<Nullable>disable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<!-- pin transitive deps -->
<PackageReference Include="System.Management" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
@@ -22,12 +19,10 @@
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Server.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
<Import Project="..\RobustToolbox\Imports\Testing.props" />
</Project>
+6 -37
View File
@@ -51,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!;
@@ -72,6 +70,8 @@ public class DestructibleBenchmark
_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>();
@@ -86,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));
}
}
@@ -113,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++;
}
@@ -121,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]
@@ -161,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,83 +0,0 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
namespace Content.Benchmarks;
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class HeatCapacityBenchmark
{
private TestPair _pair = default!;
private IEntityManager _sEntMan = default!;
private IEntityManager _cEntMan = default!;
private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
private AtmosphereSystem _sAtmos = default!;
private GasMixture _mix;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
await _pair.Connect();
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
_cAtmos = _cEntMan.System<Client.Atmos.EntitySystems.AtmosphereSystem>();
_sAtmos = _sEntMan.System<AtmosphereSystem>();
const float volume = 2500f;
const float temperature = 293.15f;
const float o2 = 12.3f;
const float n2 = 45.6f;
const float co2 = 0.42f;
const float plasma = 0.05f;
_mix = new GasMixture(volume) { Temperature = temperature };
_mix.AdjustMoles(Gas.Oxygen, o2);
_mix.AdjustMoles(Gas.Nitrogen, n2);
_mix.AdjustMoles(Gas.CarbonDioxide, co2);
_mix.AdjustMoles(Gas.Plasma, plasma);
}
[Benchmark]
public async Task ClientHeatCapacityBenchmark()
{
await _pair.Client.WaitPost(delegate
{
for (var i = 0; i < 10000; i++)
{
_cAtmos.GetHeatCapacity(_mix, applyScaling: true);
}
});
}
[Benchmark]
public async Task ServerHeatCapacityBenchmark()
{
await _pair.Server.WaitPost(delegate
{
for (var i = 0; i < 10000; i++)
{
_sAtmos.GetHeatCapacity(_mix, applyScaling: true);
}
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Maps;
using Content.Server.Maps;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization.Systems;
+2 -24
View File
@@ -23,8 +23,6 @@ public class RaiseEventBenchmark
PoolManager.Startup(typeof(BenchSystem).Assembly);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var entMan = _pair.Server.EntMan;
var fact = _pair.Server.ResolveDependency<IComponentFactory>();
var bus = (EntityEventBus)entMan.EventBus;
_sys = entMan.System<BenchSystem>();
_pair.Server.WaitPost(() =>
@@ -32,8 +30,6 @@ public class RaiseEventBenchmark
var uid = entMan.Spawn();
_sys.Ent = new(uid, entMan.GetComponent<TransformComponent>(uid));
_sys.Ent2 = new(_sys.Ent.Owner, _sys.Ent.Comp);
_sys.NetId = fact.GetRegistration<TransformComponent>().NetID!.Value;
_sys.EvSubs = bus.GetNetCompEventHandlers<BenchSystem.BenchEv>();
})
.GetAwaiter()
.GetResult();
@@ -64,12 +60,6 @@ public class RaiseEventBenchmark
return _sys.RaiseICompEvent();
}
[Benchmark]
public int RaiseNetEvent()
{
return _sys.RaiseNetIdEvent();
}
[Benchmark]
public int RaiseCSharpEvent()
{
@@ -84,8 +74,6 @@ public class RaiseEventBenchmark
public delegate void EntityEventHandler(EntityUid uid, TransformComponent comp, ref BenchEv ev);
public event EntityEventHandler? OnCSharpEvent;
public ushort NetId;
internal EntityEventBus.DirectedEventHandler?[] EvSubs = default!;
public override void Initialize()
{
@@ -104,7 +92,7 @@ public class RaiseEventBenchmark
public int RaiseCompEvent()
{
var ev = new BenchEv();
RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
EntityManager.EventBus.RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
return ev.N;
}
@@ -112,16 +100,7 @@ public class RaiseEventBenchmark
{
// Raise with an IComponent instead of concrete type
var ev = new BenchEv();
RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
return ev.N;
}
public int RaiseNetIdEvent()
{
// Raise a "IComponent" event using a net-id index delegate array (for PVS & client game-state events)
var ev = new BenchEv();
ref var unitEv = ref Unsafe.As<BenchEv, EntityEventBus.Unit>(ref ev);
EvSubs[NetId]?.Invoke(Ent2.Owner, Ent2.Comp, ref unitEv);
EntityManager.EventBus.RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
return ev.N;
}
@@ -139,7 +118,6 @@ public class RaiseEventBenchmark
}
[ByRefEvent]
[ComponentEvent(Exclusive = false)]
public struct BenchEv
{
public int N;
@@ -1,4 +1,4 @@
<GridContainer xmlns="https://spacestation14.io"
Columns="4"
Columns="5"
HorizontalAlignment="Center">
</GridContainer>
@@ -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);
}
@@ -134,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;
@@ -196,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)
@@ -227,7 +213,7 @@ 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;
}
@@ -255,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;
@@ -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,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);
}
}
@@ -35,8 +35,6 @@ public sealed partial class PlayerTab : Control
private AdminPlayerTabColorOption _playerTabColorSetting;
private AdminPlayerTabRoleTypeOption _playerTabRoleSetting;
private AdminPlayerTabSymbolOption _playerTabSymbolSetting;
private bool _markGhosted; // DeltaV
private bool _markWatchlisted; // DeltaV
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
@@ -53,9 +51,6 @@ public sealed partial class PlayerTab : Control
_config.OnValueChanged(CCVars.AdminPlayerTabRoleSetting, RoleSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true);
_config.OnValueChanged(CCVars.AdminPlayerTabMarkGhosted, MarkGhostedChanged, true); // DeltaV
_config.OnValueChanged(CCVars.AdminPlayerTabMarkWatchlisted, MarkWatchlistedChanged, true); // DeltaV
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
@@ -69,23 +64,8 @@ public sealed partial class PlayerTab : Control
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
// DeltaV - mark ghosted, watchlisted START
private void MarkGhostedChanged(bool value)
{
_markGhosted = value;
RefreshPlayerList(_players);
}
private void MarkWatchlistedChanged(bool value)
{
_markWatchlisted = value;
RefreshPlayerList(_players);
}
// DeltaV - mark ghosted, watchlisted END
#region Antag Overlay
private void OverlayEnabled()
@@ -172,25 +152,8 @@ public sealed partial class PlayerTab : Control
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info =>
{
// DeltaV - additions START
var filteringStringAdditions = "";
if (_markGhosted && info.Ghost)
{
filteringStringAdditions += " (G)";
}
if (_markWatchlisted && info.Watchlisted)
{
filteringStringAdditions += " (WL)";
}
// DeltaV - additions END
return new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}" +
filteringStringAdditions); // DeltaV - append filteringStringAdditions
})
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
@@ -204,10 +167,7 @@ public sealed partial class PlayerTab : Control
new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor),
_playerTabColorSetting,
_playerTabRoleSetting,
_playerTabSymbolSetting,
_markGhosted, // DeltaV - Add _markGhosted
_markWatchlisted // DeltaV - Add _markWatchlisted
);
_playerTabSymbolSetting);
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
button.StyleClasses.Clear();
@@ -20,10 +20,7 @@ public sealed partial class PlayerTabEntry : PanelContainer
StyleBoxFlat styleBoxFlat,
AdminPlayerTabColorOption colorOption,
AdminPlayerTabRoleTypeOption roleSetting,
AdminPlayerTabSymbolOption symbolSetting,
bool markGhosted, // DeltaV - Add markGhosted
bool markWatchlisted // DeltaV - Add markWatchlisted
)
AdminPlayerTabSymbolOption symbolSetting)
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
@@ -76,17 +73,6 @@ public sealed partial class PlayerTabEntry : PanelContainer
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
// DeltaV - Mark ghosted/watchlisted players START
if (player.Ghost && markGhosted)
{
CharacterLabel.Text = $"(G) {CharacterLabel.Text}";
}
if (player.Watchlisted && markWatchlisted)
{
CharacterLabel.Text = $"(WL) {CharacterLabel.Text}";
}
// DeltaV - Mark ghosted/watchlisted players END
var roletype = RoleTypeLabel.Text = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var subtype = roles.GetRoleSubtypeLabel(rolePrototype?.Name ?? RoleTypePrototype.FallbackName, player.Subtype);
switch (roleSetting)
@@ -60,24 +60,8 @@ public sealed class ClientAlertsSystem : AlertsSystem
if (args.Current is not AlertComponentState cast)
return;
// Save all client-sided alerts to later put back in
var clientAlerts = new Dictionary<AlertKey, AlertState>();
foreach (var alert in alerts.Comp.Alerts)
{
if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto))
{
if (alertProto.ClientHandled)
clientAlerts[alert.Key] = alert.Value;
}
}
alerts.Comp.Alerts = new(cast.Alerts);
foreach (var alert in clientAlerts)
{
alerts.Comp.Alerts[alert.Key] = alert.Value;
}
UpdateHud(alerts);
}
@@ -1,35 +0,0 @@
using System.Runtime.CompilerServices;
using Content.Shared.Atmos;
namespace Content.Client.Atmos.EntitySystems;
public sealed partial class AtmosphereSystem
{
/*
Partial class for operations involving GasMixtures.
Any method that is overridden here is usually because the server-sided implementation contains
code that would escape sandbox. As such these methods are overridden here with a safe
implementation.
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override float GetHeatCapacityCalculation(float[] moles, bool space)
{
// Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
{
return Atmospherics.SpaceHeatCapacity;
}
// explicit stackalloc call is banned on client tragically.
// the JIT does not stackalloc this during runtime,
// though this isnt the hottest code path so it should be fine
// the gc can eat a little as a treat
var tmp = new float[moles.Length];
NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
// Adjust heat capacity by speedup, because this is primarily what
// determines how quickly gases heat up/cool.
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
}
}
@@ -5,7 +5,7 @@ using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems;
public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
public sealed class AtmosphereSystem : SharedAtmosphereSystem
{
public override void Initialize()
{
@@ -3,7 +3,6 @@ using Content.Client.Atmos.Components;
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -24,7 +23,6 @@ namespace Content.Client.Atmos.Overlays
private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager;
private readonly SharedAtmosphereSystem _atmosphereSystem;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _xformSys;
@@ -56,7 +54,6 @@ namespace Content.Client.Atmos.Overlays
{
_entManager = entManager;
_mapManager = IoCManager.Resolve<IMapManager>();
_atmosphereSystem = entManager.System<SharedAtmosphereSystem>();
_mapSystem = entManager.System<SharedMapSystem>();
_xformSys = xformSys;
_shader = protoMan.Index(UnshadedShader).Instance();
@@ -70,7 +67,7 @@ namespace Content.Client.Atmos.Overlays
for (var i = 0; i < _gasCount; i++)
{
var gasPrototype = _atmosphereSystem.GetGas(system.VisibleGasId[i]);
var gasPrototype = protoMan.Index<GasPrototype>(system.VisibleGasId[i].ToString());
SpriteSpecifier overlay;
@@ -52,18 +52,14 @@ namespace Content.Client.Atmos.UI
private void OnSelectGasPressed()
{
if (_window is null)
return;
if (_window is null) return;
if (_window.SelectedGas is null)
{
SendMessage(new GasFilterSelectGasMessage(null));
}
else
{
if (!Enum.TryParse<Gas>(_window.SelectedGas, out var gas))
return;
if (!int.TryParse(_window.SelectedGas, out var gas)) return;
SendMessage(new GasFilterSelectGasMessage(gas));
}
}
+53
View File
@@ -0,0 +1,53 @@
using Content.Client.BarSign.Ui;
using Content.Shared.BarSign;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign;
public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BarSignComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(EntityUid uid, BarSignComponent component, ref AfterAutoHandleStateEvent args)
{
if (_ui.TryGetOpenUi<BarSignBoundUserInterface>(uid, BarSignUiKey.Key, out var bui))
bui.Update(component.Current);
UpdateAppearance(uid, component);
}
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
{
UpdateAppearance(uid, component, args.Component, args.Sprite);
}
private void UpdateAppearance(EntityUid id, BarSignComponent sign, AppearanceComponent? appearance = null, SpriteComponent? sprite = null)
{
if (!Resolve(id, ref appearance, ref sprite))
return;
AppearanceSystem.TryGetData<bool>(id, PowerDeviceVisuals.Powered, out var powered, appearance);
if (powered
&& sign.Current != null
&& _prototypeManager.Resolve(sign.Current, out var proto))
{
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
sprite.LayerSetShader(0, "unshaded");
}
else
{
SpriteSystem.LayerSetRsiState((id, sprite), 0, "empty");
sprite.LayerSetShader(0, null, null);
}
}
}
@@ -1,30 +0,0 @@
using Content.Shared.BarSign;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign;
public sealed class BarSignVisualizerSystem : VisualizerSystem<BarSignComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
{
AppearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component);
AppearanceSystem.TryGetData<string>(uid, BarSignVisuals.BarSignPrototype, out var currentSign, args.Component);
if (powered
&& currentSign != null
&& _prototypeManager.Resolve<BarSignPrototype>(currentSign, out var proto))
{
SpriteSystem.LayerSetSprite((uid, args.Sprite), 0, proto.Icon);
args.Sprite?.LayerSetShader(0, "unshaded");
}
else
{
SpriteSystem.LayerSetRsiState((uid, args.Sprite), 0, "empty");
args.Sprite?.LayerSetShader(0, null, null);
}
}
}
@@ -19,27 +19,32 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
var sign = EntMan.GetComponentOrNull<BarSignComponent>(Owner)?.Current is { } current
? _prototype.Index(current)
: null;
var allSigns = BarSignSystem.GetAllBarSigns(_prototype)
var allSigns = Shared.BarSign.BarSignSystem.GetAllBarSigns(_prototype)
.OrderBy(p => Loc.GetString(p.Name))
.ToList();
_menu = new(sign, allSigns);
_menu.OnSignSelected += id =>
{
SendPredictedMessage(new SetBarSignMessage(id));
SendMessage(new SetBarSignMessage(id));
};
_menu.OnClose += Close;
_menu.OpenCentered();
}
public override void Update()
public void Update(ProtoId<BarSignPrototype>? sign)
{
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp))
return;
if (_prototype.Resolve(signComp.Current, out var signPrototype))
if (_prototype.Resolve(sign, out var signPrototype))
_menu?.UpdateState(signPrototype);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
}
}
+8
View File
@@ -0,0 +1,8 @@
using Content.Shared.Bed;
namespace Content.Client.Bed;
public sealed class BedSystem : SharedBedSystem
{
}
@@ -7,6 +7,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Serilog;
namespace Content.Client.Cargo.UI;
@@ -1,12 +1,11 @@
using Content.Client.Message;
using Content.Client.RichText;
using Content.Client.UserInterface.RichText;
using Content.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
using Content.Shared.MassMedia.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.RichText;
using Robust.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using Robust.Shared.Utility; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
namespace Content.Client.CartridgeLoader.Cartridges;
@@ -18,6 +17,18 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
public event Action? OnNotificationSwithPressed;
// DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
private static readonly Type[] AllowedTags =
[
typeof(BoldItalicTag),
typeof(BoldTag),
typeof(BulletTag),
typeof(ColorTag),
typeof(HeadingTag),
typeof(ItalicTag),
typeof(MonoTag),
];
public NewsReaderUiFragment()
{
RobustXamlLoader.Load(this);
@@ -35,7 +46,7 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
Author.Visible = true;
PageName.Text = article.Title;
PageText.SetMessage(FormattedMessage.FromMarkupPermissive(article.Content), UserFormattableTags.BaseAllowedTags);
PageText.SetMessage(FormattedMessage.FromMarkupPermissive(article.Content), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
PageNum.Text = $"{targetNum}/{totalNum}";
@@ -44,8 +55,8 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
var shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
var author = Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author ?? Loc.GetString("news-read-ui-no-author"));
Author.SetMessage(FormattedMessage.FromMarkupPermissive(author), UserFormattableTags.BaseAllowedTags);
var author = Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author ?? Loc.GetString("news-read-ui-no-author")); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
Author.SetMessage(FormattedMessage.FromMarkupPermissive(author), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
Prev.Disabled = targetNum <= 1;
Next.Disabled = targetNum >= totalNum;
@@ -44,10 +44,6 @@ namespace Content.Client.Chemistry.UI
(uint) _window.BottleDosage.Value, _window.LabelLine));
_window.BufferSortButton.OnPressed += _ => SendMessage(
new ChemMasterSortingTypeCycleMessage());
_window.OutputBufferDraw.OnPressed += _ => SendMessage(
new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.Internal));
_window.OutputBeakerDraw.OnPressed += _ => SendMessage(
new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.External));
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
{
@@ -83,13 +83,10 @@
<!-- Packaging -->
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'chem-master-output-source'}" StyleClasses="LabelSecondaryColor" Margin="0 0 5 0"/>
<Button MinSize="80 0" Name="OutputBufferDraw" Access="Public" Text="{Loc 'chem-master-output-buffer-draw'}" ToggleMode="True" StyleClasses="OpenRight" />
<Button MinSize="80 0" Name="OutputBeakerDraw" Access="Public" Text="{Loc 'chem-master-output-beaker-draw'}" ToggleMode="True" StyleClasses="OpenLeft" />
<Label Text="{Loc 'chem-master-window-packaging-text'}" />
<Control HorizontalExpand="True"/>
<!-- Output Draw Source -->
<Label Name="DrawSource"/>
<Label Name="BufferCurrentVolume" StyleClasses="LabelSecondaryColor" />
<Label Text="{Loc 'chem-master-window-container-label'}" /> <!-- DeltaV-->
<Label Name="BufferCurrentVolume" StyleClasses="LabelWeak" />
</BoxContainer>
<!-- Wrap the packaging info-->
@@ -150,17 +150,7 @@ namespace Content.Client.Chemistry.UI
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
UpdatePanelInfo(castState);
switch (castState.DrawSource)
{
case ChemMasterDrawSource.Internal:
SetBufferText(castState.BufferCurrentVolume, "chem-master-output-buffer-draw");
break;
case ChemMasterDrawSource.External:
SetBufferText(castState.InputContainerInfo?.CurrentVolume, "chem-master-output-beaker-draw");
break;
default:
throw new($"Chemmaster {castState.OutputContainerInfo} draw source is not set");
}
BufferCurrentVolume.Text = $" {castState.InputContainerInfo?.CurrentVolume.Int() ?? 0}u"; // DeltaV
InputEjectButton.Disabled = castState.InputContainerInfo is null;
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
@@ -178,14 +168,9 @@ namespace Content.Client.Chemistry.UI
var holdsReagents = output?.Reagents != null;
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
var outputVolume = castState.DrawSource switch
{
ChemMasterDrawSource.Internal => castState.BufferCurrentVolume?.Int() ?? 0,
ChemMasterDrawSource.External => castState.InputContainerInfo?.CurrentVolume.Int() ?? 0,
_ => 0,
};
var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
PillDosage.Value = (int)Math.Min(outputVolume, castState.PillDosageLimit);
PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
PillTypeButtons[castState.SelectedPillType].Pressed = true;
@@ -201,35 +186,25 @@ namespace Content.Client.Chemistry.UI
// Avoid division by zero
if (PillDosage.Value > 0)
{
PillNumber.Value = Math.Min(outputVolume / PillDosage.Value, pillNumberMax);
PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax);
}
else
{
PillNumber.Value = 0;
}
BottleDosage.Value = Math.Min(bottleAmountMax, outputVolume);
BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume);
}
/// <summary>
/// Generate a product label based on reagents in the buffer or beaker.
/// Generate a product label based on reagents in the buffer.
/// </summary>
/// <param name="state">State data sent by the server.</param>
private string GenerateLabel(ChemMasterBoundUserInterfaceState state)
{
if (
state.BufferCurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.Internal ||
state.InputContainerInfo?.CurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.External ||
state.InputContainerInfo?.Reagents == null
)
if (state.BufferCurrentVolume == 0)
return "";
var reagent = (state.DrawSource switch
{
ChemMasterDrawSource.Internal => state.BufferReagents,
ChemMasterDrawSource.External => state.InputContainerInfo.Reagents ?? [],
_ => throw new($"Chemmaster {state.OutputContainerInfo} draw source is not set"),
}).MinBy(r => r.Quantity)
.Reagent;
var reagent = state.BufferReagents.OrderBy(r => r.Quantity).First().Reagent;
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
return proto?.LocalizedName ?? "";
}
@@ -255,8 +230,6 @@ namespace Content.Client.Chemistry.UI
_ => Loc.GetString("chem-master-window-sort-type-none")
};
OutputBufferDraw.Pressed = state.DrawSource == ChemMasterDrawSource.Internal;
OutputBeakerDraw.Pressed = state.DrawSource == ChemMasterDrawSource.External;
if (!state.BufferReagents.Any())
{
@@ -438,12 +411,6 @@ namespace Content.Client.Chemistry.UI
get => LabelLineEdit.Text;
set => LabelLineEdit.Text = value;
}
private void SetBufferText(FixedPoint2? volume, string text)
{
BufferCurrentVolume.Text = $" {volume ?? FixedPoint2.Zero}u";
DrawSource.Text = Loc.GetString(text);
}
}
public sealed class ReagentButton : Button
@@ -2,31 +2,41 @@ using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.Chemistry.UI;
[UsedImplicitly]
public sealed class TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
namespace Content.Client.Chemistry.UI
{
[ViewVariables]
private TransferAmountWindow? _window;
protected override void Open()
[UsedImplicitly]
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
{
base.Open();
_window = this.CreateWindow<TransferAmountWindow>();
private IEntityManager _entManager;
private EntityUid _owner;
[ViewVariables]
private TransferAmountWindow? _window;
if (EntMan.TryGetComponent<SolutionTransferComponent>(Owner, out var comp))
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ =>
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
_owner = owner;
_entManager = IoCManager.Resolve<IEntityManager>();
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<TransferAmountWindow>();
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
_window.ApplyButton.OnPressed += _ =>
{
SendPredictedMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
_window.Close();
}
};
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
{
SendMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
_window.Close();
}
};
}
}
}
@@ -3,33 +3,34 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Chemistry.UI;
[GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow
namespace Content.Client.Chemistry.UI
{
private int _max = Int32.MaxValue;
private int _min = 1;
public TransferAmountWindow()
[GenerateTypedNameReferences]
public sealed partial class TransferAmountWindow : DefaultWindow
{
RobustXamlLoader.Load(this);
AmountLineEdit.OnTextChanged += OnValueChanged;
}
private int _max = Int32.MaxValue;
private int _min = 1;
public void SetBounds(int min, int max)
{
_min = min;
_max = max;
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
}
public TransferAmountWindow()
{
RobustXamlLoader.Load(this);
AmountLineEdit.OnTextChanged += OnValueChanged;
}
private void OnValueChanged(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
ApplyButton.Disabled = true;
else
ApplyButton.Disabled = false;
public void SetBounds(int min, int max)
{
_min = min;
_max = max;
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
}
private void OnValueChanged(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
ApplyButton.Disabled = true;
else
ApplyButton.Disabled = false;
}
}
}
@@ -4,8 +4,10 @@ using Content.Shared.Weapons.Ranged.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Serialization;
using Robust.Client.UserInterface;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Utility;
namespace Content.Client.CombatMode;
@@ -1,52 +0,0 @@
using System.Linq;
using Content.Shared.CCVar;
using Content.Shared.Input;
using Robust.Client.Input;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Client.Commands;
/// <summary>
/// Sets the a <see cref="CCVars.DebugQuickInspect"/> CVar to the name of a component, which allows the client to quickly open a VV window for that component
/// by using the Alt+C or Alt+B hotkeys.
/// </summary>
public sealed class QuickInspectCommand : LocalizedEntityCommands
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
public override string Command => "quickinspect";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("shell-wrong-arguments-number"));
return;
}
_configurationManager.SetCVar(CCVars.DebugQuickInspect, args[0]);
var serverKey = _inputManager.GetKeyFunctionButtonString(ContentKeyFunctions.InspectServerComponent);
var clientKey = _inputManager.GetKeyFunctionButtonString(ContentKeyFunctions.InspectClientComponent);
shell.WriteLine(Loc.GetString($"cmd-quickinspect-success", ("component", args[0]), ("serverKeybind", serverKey), ("clientKeybind", clientKey)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
// Not ideal since it only shows client-side components, but you can still type in any name you want.
// If you know how to get server component names on the client then please fix this.
var options = EntityManager.ComponentFactory.AllRegisteredTypes
.Select(p => new CompletionOption(
EntityManager.ComponentFactory.GetComponentName(p)
));
return CompletionResult.FromOptions(options);
}
return CompletionResult.Empty;
}
}
@@ -1,24 +0,0 @@
using Content.Client.Wall;
using Robust.Client.Graphics;
using Robust.Shared.Console;
namespace Content.Client.Commands;
/// <summary>
/// Shows the area in which entities with <see cref="Content.Shared.Wall.WallMountComponent" /> can be interacted from.
/// </summary>
public sealed class ShowWallmountsCommand : LocalizedCommands
{
[Dependency] private readonly IOverlayManager _overlay = default!;
public override string Command => "showwallmounts";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var existing = _overlay.RemoveOverlay<WallmountDebugOverlay>();
if (!existing)
_overlay.AddOverlay(new WallmountDebugOverlay());
shell.WriteLine(Loc.GetString("cmd-showwallmounts-status", ("status", !existing)));
}
}
@@ -30,10 +30,7 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly ISawmill _sawmill;
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
@@ -93,7 +90,6 @@ namespace Content.Client.Construction.UI
_constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
_sawmill = _logManager.GetSawmill("construction.ui");
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@@ -288,7 +284,7 @@ namespace Content.Client.Construction.UI
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
{
_sawmill.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
recipe.ID,
nameof(ConstructionPrototype));
continue;
@@ -8,7 +8,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical">
<EntityPrototypeView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
</BoxContainer>
<Control MinHeight="10"/>
@@ -1,11 +1,13 @@
using System.Linq;
using Content.Client.Materials;
using Content.Client.Materials.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Materials;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -22,12 +24,14 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
private readonly ItemSlotsSystem _itemSlots;
private readonly FlatpackSystem _flatpack;
private readonly MaterialStorageSystem _materialStorage;
private readonly SpriteSystem _spriteSystem;
private EntityUid _owner;
public static readonly EntProtoId NoBoardEffectId = "FlatpackerNoBoardEffect";
private EntityUid? _currentBoard = EntityUid.Invalid;
private EntityUid? _machinePreview;
public event Action? PackButtonPressed;
@@ -39,6 +43,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
_itemSlots = _entityManager.System<ItemSlotsSystem>();
_flatpack = _entityManager.System<FlatpackSystem>();
_materialStorage = _entityManager.System<MaterialStorageSystem>();
_spriteSystem = _entityManager.System<SpriteSystem>();
PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
@@ -55,54 +60,76 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
{
base.FrameUpdate(args);
if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
{
_machinePreview = null;
MachineSprite.SetEntity(_machinePreview);
}
if (!_entityManager.TryGetComponent<FlatpackCreatorComponent>(_owner, out var flatpacker) ||
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
var flatpackerEnt = (_owner, flatpacker);
MachineBoardComponent? machineBoardComp = null;
if (flatpacker.Packing)
{
PackButton.Disabled = true;
}
else if (_currentBoard != null)
{
PackButton.Disabled = !_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var curCost)
|| !_materialStorage.CanChangeMaterialAmount(_owner, curCost);
Dictionary<string, int> cost;
if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
else
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
}
if (_currentBoard == itemSlot.Item)
return;
if (_machinePreview != null)
_entityManager.DeleteEntity(_machinePreview);
_currentBoard = itemSlot.Item;
CostHeaderLabel.Visible = false;
CostHeaderLabel.Visible = _currentBoard != null;
InsertLabel.Visible = _currentBoard == null;
if (_currentBoard is null)
if (_currentBoard is not null)
{
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(string.Empty);
PackButton.Disabled = true;
return;
}
string? prototype = null;
Dictionary<string, int>? cost = null;
if (_flatpack.TryGetFlatpackResultPrototype(_currentBoard.Value, out var prototype) &&
_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var cost))
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
CostHeaderLabel.Visible = true;
if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
{
prototype = machineBoardComp.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
}
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
{
prototype = computerBoard.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
}
if (prototype is not null && cost is not null)
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
_machinePreview = _entityManager.Spawn(proto.ID);
_spriteSystem.ForceUpdate(_machinePreview.Value);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
}
}
else
{
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMarkup(Loc.GetString("flatpacker-ui-board-invalid-label"));
MachineNameLabel.SetMessage(string.Empty);
_machinePreview = _entityManager.Spawn(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(" ");
PackButton.Disabled = true;
}
MachineSprite.SetEntity(_machinePreview);
}
private string GetCostString(Dictionary<string, int> costs)
@@ -124,7 +151,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
("amount", amountText),
("material", Loc.GetString(matProto.Name)));
msg.TryAddMarkup(text, out _);
msg.AddMarkup(text);
if (i != orderedCosts.Length - 1)
msg.PushNewline();
@@ -132,4 +159,12 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
return msg.ToMarkup();
}
public override void Close()
{
base.Close();
_entityManager.DeleteEntity(_machinePreview);
_machinePreview = null;
}
}
+11 -9
View File
@@ -1,24 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Client\</OutputPath>
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;Tools;DebugOpt</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
<PackageReference Include="Nett" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="Pidgin" />
<PackageReference Include="Robust.Shared.AuthLib" />
</ItemGroup>
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
<Import Project="..\RobustToolbox\Imports\Client.props" />
<Import Project="..\RobustToolbox\Imports\Shared.props" />
<ItemGroup>
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
</ItemGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
@@ -1,5 +0,0 @@
using Content.Shared.DeviceLinking.Systems;
namespace Content.Client.DeviceLinking.Systems;
public sealed class RandomGateSystem : SharedRandomGateSystem;
@@ -1,37 +0,0 @@
using Content.Shared.DeviceLinking;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.DeviceLinking.UI;
[UsedImplicitly]
public sealed class RandomGateBoundUserInterface : BoundUserInterface
{
private RandomGateSetupWindow? _window;
public RandomGateBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
protected override void Open()
{
base.Open();
_window = this.CreateWindow<RandomGateSetupWindow>();
_window.OnApplyPressed += OnProbabilityChanged;
}
private void OnProbabilityChanged(string value)
{
if (!float.TryParse(value, out var probability))
return;
SendPredictedMessage(new RandomGateProbabilityChangedMessage(probability));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not RandomGateBoundUserInterfaceState castState || _window == null)
return;
_window.SetProbability(castState.SuccessProbability * 100);
}
}
@@ -1,19 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'random-gate-menu-setup'}"
MinSize="260 115">
<BoxContainer Orientation="Vertical" Margin="5" SeparationOverride="10">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc 'random-gate-menu-settings'}" VerticalAlignment="Center" />
<Control HorizontalExpand="True" />
<LineEdit Name="ProbabilityInput" MinSize="70 0" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Control HorizontalExpand="True" />
<Button Name="ApplyButton"
Text="{Loc 'random-gate-menu-apply'}"
HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -1,28 +0,0 @@
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.DeviceLinking.UI;
/// <summary>
/// Window for setting up the random gate probability.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class RandomGateSetupWindow : FancyWindow
{
/// <summary>
/// Event triggered when the "Apply" button is pressed.
/// </summary>
public event Action<string>? OnApplyPressed;
public RandomGateSetupWindow()
{
RobustXamlLoader.Load(this);
ApplyButton.OnPressed += _ => OnApplyPressed?.Invoke(ProbabilityInput.Text);
}
public void SetProbability(float probability)
{
ProbabilityInput.Text = probability.ToString("0.00");
}
}
+1 -13
View File
@@ -73,19 +73,7 @@ public sealed class PuddleSystem : SharedPuddleSystem
// Maybe someday we'll have clientside prediction for entity spawning, but not today.
// Until then, these methods do nothing on the client.
/// <inheritdoc/>
public override bool TrySplashSpillAt(Entity<SpillableComponent?> entity, EntityCoordinates coordinates, out EntityUid puddleUid, out Solution solution, bool sound = true, EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
solution = new Solution();
return false;
}
public override bool TrySplashSpillAt(EntityUid entity,
EntityCoordinates coordinates,
Solution spilled,
out EntityUid puddleUid,
bool sound = true,
EntityUid? user = null)
public override bool TrySplashSpillAt(EntityUid uid, EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true, EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
return false;
-7
View File
@@ -1,7 +0,0 @@
using Content.Shared.Fluids.Components;
using Content.Shared.Fluids.EntitySystems;
using Robust.Shared.Map;
namespace Content.Client.Fluids;
public sealed class SpraySystem : SharedSpraySystem;
@@ -3,7 +3,6 @@ using System.Numerics;
using Content.Client.Clickable;
using Content.Client.UserInterface;
using Content.Client.Viewport;
using Content.Shared.CCVar;
using Content.Shared.Input;
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
@@ -14,7 +13,6 @@ using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
@@ -42,7 +40,6 @@ namespace Content.Client.Gameplay
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IViewVariablesManager _vvm = default!;
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
private ClickableEntityComparer _comparer = default!;
@@ -86,8 +83,6 @@ namespace Content.Client.Gameplay
_comparer = new ClickableEntityComparer();
CommandBinds.Builder
.Bind(ContentKeyFunctions.InspectEntity, new PointerInputCmdHandler(HandleInspect, outsidePrediction: true))
.Bind(ContentKeyFunctions.InspectServerComponent, new PointerInputCmdHandler(HandleInspectServerComponent, outsidePrediction: true))
.Bind(ContentKeyFunctions.InspectClientComponent, new PointerInputCmdHandler(HandleInspectClientComponent, outsidePrediction: true))
.Register<GameplayStateBase>();
}
@@ -104,21 +99,6 @@ namespace Content.Client.Gameplay
return true;
}
private bool HandleInspectServerComponent(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
var component = _configurationManager.GetCVar(CCVars.DebugQuickInspect);
if (_entityManager.TryGetNetEntity(uid, out var net))
_conHost.ExecuteCommand($"vv /entity/{net.Value.Id}/{component}");
return true;
}
private bool HandleInspectClientComponent(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
var component = _configurationManager.GetCVar(CCVars.DebugQuickInspect);
_conHost.ExecuteCommand($"vv /c/entity/{uid}/{component}");
return true;
}
public EntityUid? GetClickedEntity(MapCoordinates coordinates)
{
return GetClickedEntity(coordinates, _eyeManager.CurrentEye);
@@ -42,7 +42,7 @@ public sealed class GuidebookRichPrototypeLink : Control, IPrototypeLinkControl
public void SetMessage(FormattedMessage message)
{
_message = message;
_richTextLabel.SetMessage(_message, tagsAllowed: null);
_richTextLabel.SetMessage(_message);
}
public IPrototype? LinkedPrototype { get; set; }
@@ -82,7 +82,7 @@ public sealed partial class DocumentParsingManager
}
msg.Pop();
rt.SetMessage(msg, tagsAllowed: null);
rt.SetMessage(msg);
return rt;
},
TextParser)
@@ -21,9 +21,9 @@ namespace Content.Client.HealthAnalyzer.UI
base.Open();
_window = this.CreateWindow<HealthAnalyzerWindow>();
_window.HealthAnalyzer.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
_window.HealthAnalyzer.OnTriageStatusChanged += SendTriageStatusMessage; // DeltaV - Medical Records
_window.HealthAnalyzer.OnClaimPatient += SendTriageClaimMessage; // DeltaV - Medical Records
_window.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
_window.OnTriageStatusChanged += SendTriageStatusMessage; // DeltaV - Medical Records
_window.OnClaimPatient += SendTriageClaimMessage; // DeltaV - Medical Records
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
}
@@ -48,7 +48,7 @@ namespace Content.Client.HealthAnalyzer.UI
return;
if (_window != null)
_window.HealthAnalyzer.OnBodyPartSelected -= SendBodyPartMessage;
_window.OnBodyPartSelected -= SendBodyPartMessage;
_window?.Dispose();
}
@@ -1,219 +0,0 @@
<BoxContainer
xmlns="https://spacestation14.io"
VerticalExpand="True"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" />
<!-- Shitmed Change Start -->
<Button Name="ReturnButton"
Text="{Loc 'health-analyzer-window-return-button-text'}"
Margin="0 0 0 10"
HorizontalExpand="False"/>
<!-- Shitmed Change End -->
<BoxContainer
Name="PatientDataContainer"
Margin="0 0 0 5"
Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
<PanelContainer>
<!-- Shitmed Change Start -->
<SpriteView OverrideDirection="South" Name="SpriteView" Access="Public" SetSize="96 96" />
<PanelContainer
Name="PartView"
SetSize="57 96"
Margin="18 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<PanelContainer
SetSize="15 33"
Margin="4 0 0 0"
HorizontalAlignment="Left">
<TextureButton
Name="RightArmButton"
MinSize="15 25"
StyleClasses="TargetDollButtonRightArm"
VerticalAlignment="Top">
</TextureButton>
<TextureButton
Name="RightHandButton"
MinSize="15 15"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonRightHand">
</TextureButton>
</PanelContainer>
<PanelContainer
SetSize="43 75"
Margin="0 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextureButton
Name="HeadButton"
MinSize="28 23"
VerticalAlignment="Top"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonHead">
<!--<PanelContainer
SetSize="14 15"
Margin="-1 9 0 0"
HorizontalAlignment="Center">
<TextureButton
Name="EyesButton"
MinSize="14 9"
VerticalAlignment="Top"
StyleClasses="TargetDollButtonEyes">
<TextureRect
TexturePath="/Textures/Interface/Targeting/Doll/eyes.png"
Stretch="KeepAspectCentered"
SetSize="14 9"/>
</TextureButton>
<TextureButton
Name="MouthButton"
SetSize="8 6"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonMouth">
<TextureRect
TexturePath="/Textures/Interface/Targeting/Doll/mouth.png"
Stretch="KeepAspectCentered"
SetSize="8 6"/>
</TextureButton>
</PanelContainer>-->
</TextureButton>
<TextureButton
Name="ChestButton"
SetSize="28 30"
Margin="0 18 0 0"
VerticalAlignment="Top"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonChest">
</TextureButton>
<PanelContainer
MinSize="38 35"
VerticalAlignment="Bottom"
HorizontalAlignment="Center">
<TextureButton
Name="GroinButton"
MinSize="28 15"
VerticalAlignment="Top"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonGroin">
</TextureButton>
<PanelContainer
MinSize="20 30"
VerticalAlignment="Bottom"
HorizontalAlignment="Right">
<TextureButton
Name="LeftLegButton"
MinSize="15 28"
VerticalAlignment="Top"
HorizontalAlignment="Left"
StyleClasses="TargetDollButtonLeftLeg">
</TextureButton>
<TextureButton
Name="LeftFootButton"
MinSize="20 10"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonLeftFoot">
</TextureButton>
</PanelContainer>
<PanelContainer
MinSize="20 30"
VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<TextureButton
Name="RightLegButton"
MinSize="15 28"
VerticalAlignment="Top"
HorizontalAlignment="Right"
StyleClasses="TargetDollButtonRightLeg">
</TextureButton>
<TextureButton
Name="RightFootButton"
MinSize="20 10"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonRightFoot">
</TextureButton>
</PanelContainer>
</PanelContainer>
</PanelContainer>
<PanelContainer
SetSize="15 33"
Margin="0 0 4 0"
HorizontalAlignment="Right">
<TextureButton
Name="LeftArmButton"
MinSize="15 25"
StyleClasses="TargetDollButtonLeftArm"
VerticalAlignment="Top">
</TextureButton>
<TextureButton
Name="LeftHandButton"
MinSize="15 15"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonLeftHand">
</TextureButton>
</PanelContainer>
</PanelContainer>
</PanelContainer>
<!-- Shitmed Change Start -->
<TextureRect Name="NoDataTex" Access="Public" SetSize="96 96" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/> <!-- DeltaV - Size changed to 96 96 to be consistent with shitmed -->
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
<RichTextLabel Name="NameLabel" SetWidth="150" />
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
<Label Name="PartNameLabel"
VerticalAlignment="Bottom"
StyleClasses="LabelSubText"/>
</BoxContainer>
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Top" Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<!-- Begin DeltaV - Medical Records -->
<BoxContainer Name="TriageControls" Orientation="Vertical">
<BoxContainer Name="StatusBox"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0 8 0 0">
</BoxContainer>
<Button Name="ClaimButton"
Text="{Loc 'health-analyzer-window-triage-claim'}"
MinSize="60 0" />
<PanelContainer StyleClasses="LowDivider" Margin="0 8 0 0"/>
</BoxContainer>
<!-- End DeltaV - Medical Records -->
<GridContainer Margin="0 5 0 0" Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<Label Name="StatusLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
<Label Name="TemperatureLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
<Label Name="BloodLabel" />
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<Label Name="DamageLabel" />
</GridContainer>
</BoxContainer>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
</BoxContainer>
@@ -1,441 +0,0 @@
using System.Linq;
using System.Numerics;
using Content.Shared.Atmos;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI;
using Content.Client._DV.Traits.Assorted; // DeltaV
using Content.Shared._DV.Traits.Assorted; // DeltaV
using Content.Shared._DV.Medical; // DeltaV - Uncloneable
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
using Content.Shared._Shitmed.Targeting; // Shitmed
// Health analyzer UI is split from its window because it's used by both the
// health analyzer item and the cryo pod UI.
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerControl : BoxContainer
{
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache;
private readonly UnborgableSystem _unborgable; // DeltaV
private readonly RedshirtSystem _redshirt; // DeltaV
private readonly UncloneableSystem _uncloneable; // DeltaV
// Shitmed Change Start
public event Action<TargetBodyPart?, EntityUid>? OnBodyPartSelected;
private EntityUid _spriteViewEntity;
private readonly EntProtoId _bodyView = "AlertSpriteView";
private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
private EntityUid? _target;
// Shitmed Change End
// Begin DeltaV - Medical Records
private readonly ButtonGroup _triageStatusGroup = new();
private readonly Dictionary<TriageStatus, Button> _triageControls = new();
public event Action<TriageStatus>? OnTriageStatusChanged;
public event Action? OnClaimPatient;
// End DeltaV - Medical Records
public HealthAnalyzerControl()
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_entityManager = dependencies.Resolve<IEntityManager>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>();
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
_uncloneable = _entityManager.System<UncloneableSystem>(); // DeltaV
// Shitmed Change Start
_bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
{
{ TargetBodyPart.Head, HeadButton },
{ TargetBodyPart.Torso, ChestButton },
{ TargetBodyPart.Groin, GroinButton },
{ TargetBodyPart.LeftArm, LeftArmButton },
{ TargetBodyPart.LeftHand, LeftHandButton },
{ TargetBodyPart.RightArm, RightArmButton },
{ TargetBodyPart.RightHand, RightHandButton },
{ TargetBodyPart.LeftLeg, LeftLegButton },
{ TargetBodyPart.LeftFoot, LeftFootButton },
{ TargetBodyPart.RightLeg, RightLegButton },
{ TargetBodyPart.RightFoot, RightFootButton },
};
foreach (var bodyPartButton in _bodyPartControls)
{
bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key, bodyPartButton.Value);
}
ReturnButton.OnPressed += _ => ResetBodyPart();
// Shitmed Change End
// Begin DeltaV - Medical Records
foreach (var item in Enum.GetValues<TriageStatus>())
{
var btn = new Button();
var ftlKey = item.ToString();
btn.Group = _triageStatusGroup;
btn.Text = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}");
btn.ToolTip = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}.ToolTip");
btn.OnPressed += _ => OnTriageStatusChanged?.Invoke(item);
btn.AddStyleClass("ButtonSquare");
StatusBox.AddChild(btn);
_triageControls[item] = btn;
}
StatusBox.Children.First().AddStyleClass("OpenRight");
StatusBox.Children.First().RemoveStyleClass("ButtonSquare");
StatusBox.Children.Last().AddStyleClass("OpenLeft");
StatusBox.Children.Last().RemoveStyleClass("ButtonSquare");
ClaimButton.OnPressed += _ => OnClaimPatient?.Invoke();
// End DeltaV - Medical Records
}
public void Populate(HealthAnalyzerUiState state)
{
var target = _entityManager.GetEntity(state.TargetEntity);
// Begin Shitmed
_target = target;
EntityUid? part = state.Part != null ? _entityManager.GetEntity(state.Part.Value) : null;
var isPart = part != null;
// End Shitmed
if (target == null
|| !_entityManager.TryGetComponent<DamageableComponent>(isPart ? part : target, out var damageable)) // Shitmed
{
NoPatientDataText.Visible = true;
return;
}
// Begin Shitmed
ReturnButton.Visible = isPart;
PartNameLabel.Visible = isPart;
if (part != null)
PartNameLabel.Text = _entityManager.HasComponent<MetaDataComponent>(part)
? Identity.Name(part.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
// End Shitmed
NoPatientDataText.Visible = false;
// Scan Mode
ScanModeLabel.Text = state.ScanMode.HasValue
? state.ScanMode.Value
? Loc.GetString("health-analyzer-window-scan-mode-active")
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
: Loc.GetString("health-analyzer-window-entity-unknown-text");
ScanModeLabel.FontColorOverride = state.ScanMode.HasValue && state.ScanMode.Value ? Color.Green : Color.Red;
// Patient Information
SpriteView.SetEntity(SetupIcon(state.Body) ?? target.Value); // Shitmed Change
SpriteView.Visible = state.ScanMode.HasValue && state.ScanMode.Value;
PartView.Visible = SpriteView.Visible; // Shitmed Change
NoDataTex.Visible = !SpriteView.Visible;
var name = new FormattedMessage();
name.PushColor(Color.White);
name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
? Identity.Name(target.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
NameLabel.SetMessage(name);
SpeciesLabel.Text =
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
out var humanoidAppearanceComponent)
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
// Basic Diagnostic
TemperatureLabel.Text = !float.IsNaN(state.Temperature)
? $"{state.Temperature - Atmospherics.T0C:F1} °C ({state.Temperature:F1} K)"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
BloodLabel.Text = !float.IsNaN(state.BloodLevel)
? $"{state.BloodLevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
StatusLabel.Text =
_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
? GetStatus(mobStateComponent.CurrentState)
: Loc.GetString("health-analyzer-window-entity-unknown-text");
// Total Damage
DamageLabel.Text = damageable.TotalDamage.ToString();
// Alerts
// DeltaV traits - This is going to be horrid if we just keep adding things like this.
var unborgable = _unborgable.IsUnborgable(target.Value);
var redshirt = _redshirt.IsRedshirt(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Redshirt
var uncloneable = _uncloneable.IsUncloneable(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Unclonable
// END DeltaV
var showAlerts = state.Unrevivable == true || state.Bleeding == true;
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.RemoveAllChildren();
if (state.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (state.Bleeding == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (unborgable) // DeltaV
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unborgable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (redshirt) // DeltaV
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-redshirt-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (uncloneable) // DeltaV - Uncloneable
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-uncloneable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
// Damage Groups
var damageSortedGroups =
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value);
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
// Begin DeltaV - Medical Records
if (state.MedicalRecord is not { } records)
{
TriageControls.Visible = false;
return;
}
TriageControls.Visible = true;
_triageControls[records.Status].Pressed = true;
// Update claim button based on claimed status
if (records.ClaimedName != null)
{
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-unclaim", ("claimedBy", records.ClaimedName));
}
else
{
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-claim");
}
// End DeltaV - Medical Records
}
private static string GetStatus(MobState mobState)
{
return mobState switch
{
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
};
}
private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups,
IReadOnlyDictionary<string, FixedPoint2> damageDict)
{
GroupsContainer.RemoveAllChildren();
foreach (var (damageGroupId, damageAmount) in groups)
{
if (damageAmount == 0)
continue;
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
var groupContainer = new BoxContainer
{
Align = AlignMode.Begin,
Orientation = LayoutOrientation.Vertical,
};
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
foreach (var type in group.DamageTypes)
{
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
continue;
var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
}
// Shitmed Change Start
public void SetActiveBodyPart(TargetBodyPart part, TextureButton button)
{
if (_target == null)
return;
// Bit of the ole shitcode until we have Groins in the prototypes.
OnBodyPartSelected?.Invoke(part == TargetBodyPart.Groin ? TargetBodyPart.Torso : part, _target.Value);
}
public void ResetBodyPart()
{
if (_target == null)
return;
OnBodyPartSelected?.Invoke(null, _target.Value);
}
public void SetActiveButtons(bool isHumanoid)
{
foreach (var button in _bodyPartControls)
button.Value.Visible = isHumanoid;
}
/// <summary>
/// Sets up the Body Doll using Alert Entity to use in Health Analyzer.
/// </summary>
private EntityUid? SetupIcon(Dictionary<TargetBodyPart, TargetIntegrity>? body)
{
if (body is null)
return null;
if (!_entityManager.Deleted(_spriteViewEntity))
_entityManager.QueueDeleteEntity(_spriteViewEntity);
_spriteViewEntity = _entityManager.Spawn(_bodyView);
if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
return null;
int layer = 0;
foreach (var (bodyPart, integrity) in body)
{
// TODO: PartStatusUIController and make it use layers instead of TextureRects when EE refactors alerts.
string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
int enumValue = (int) integrity;
var rsi = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
// Shitcode with love from Russia :)
if (!_spriteSystem.TryGetLayer(_spriteViewEntity, layer, out _, false))
_spriteSystem.AddTextureLayer(_spriteViewEntity, _spriteSystem.Frame0(rsi));
else
_spriteSystem.LayerSetTexture(_spriteViewEntity, layer, _spriteSystem.Frame0(rsi));
_spriteSystem.LayerSetScale(_spriteViewEntity, layer, new Vector2(3f, 3f));
layer++;
}
return _spriteViewEntity;
}
// Shitmed Change End
private Texture GetTexture(string texture)
{
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
{
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
}
return _spriteSystem.Frame0(rsiSprite);
}
private static Label CreateDiagnosticItemLabel(string text)
{
return new Label
{
Text = text,
};
}
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{
Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom,
Orientation = LayoutOrientation.Horizontal,
};
rootContainer.AddChild(new TextureRect
{
SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower())
});
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
return rootContainer;
}
}
@@ -1,16 +1,253 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.HealthAnalyzer.UI"
MaxHeight="525"
MinWidth="350"> <!-- Shitmed Change -->
<ScrollContainer
Margin="5 5 5 5"
ReturnMeasure="True"
VerticalExpand="True">
<BoxContainer
Name="RootContainer"
VerticalExpand="True"
Orientation="Vertical">
<Label
Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}"/>
<!-- Shitmed Change Start -->
<Button Name="ReturnButton"
Text="{Loc 'health-analyzer-window-return-button-text'}"
Margin="0 0 0 10"
HorizontalExpand="False"/>
<!-- Shitmed Change End -->
<BoxContainer
Name="PatientDataContainer"
Margin="0 0 0 5"
Orientation="Vertical">
<!-- Shitmed Change Start -->
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<PanelContainer>
<SpriteView OverrideDirection="South"
Name="SpriteView"
Access="Public"
SetSize="96 96"/>
<PanelContainer
Name="PartView"
SetSize="57 96"
Margin="18 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Left">
<PanelContainer
SetSize="15 33"
Margin="4 0 0 0"
HorizontalAlignment="Left">
<TextureButton
Name="RightArmButton"
MinSize="15 25"
StyleClasses="TargetDollButtonRightArm"
VerticalAlignment="Top">
</TextureButton>
<TextureButton
Name="RightHandButton"
MinSize="15 15"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonRightHand">
</TextureButton>
</PanelContainer>
<PanelContainer
SetSize="43 75"
Margin="0 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextureButton
Name="HeadButton"
MinSize="28 23"
VerticalAlignment="Top"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonHead">
<!--<PanelContainer
SetSize="15 15"
Margin="0 9 0 0"
HorizontalAlignment="Center">
<TextureButton
Name="EyesButton"
MinSize="15 9"
VerticalAlignment="Top"
StyleClasses="TargetDollButtonEyes">
<TextureRect
TexturePath="/Textures/Interface/Targeting/Doll/eyes.png"
Stretch="KeepAspectCentered"
SetSize="15 9"/>
</TextureButton>
<TextureButton
Name="MouthButton"
SetSize="9 6"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonMouth">
<TextureRect
TexturePath="/Textures/Interface/Targeting/Doll/mouth.png"
Stretch="KeepAspectCentered"
SetSize="9 6"/>
</TextureButton>
</PanelContainer>-->
</TextureButton>
<TextureButton
Name="ChestButton"
SetSize="28 30"
Margin="0 18 0 0"
VerticalAlignment="Top"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonChest">
</TextureButton>
<PanelContainer
MinSize="38 35"
VerticalAlignment="Bottom"
HorizontalAlignment="Center">
<TextureButton
Name="GroinButton"
MinSize="28 15"
VerticalAlignment="Top"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonGroin">
</TextureButton>
<PanelContainer
MinSize="20 30"
VerticalAlignment="Bottom"
HorizontalAlignment="Right">
<TextureButton
Name="LeftLegButton"
MinSize="15 28"
VerticalAlignment="Top"
HorizontalAlignment="Left"
StyleClasses="TargetDollButtonLeftLeg">
</TextureButton>
<TextureButton
Name="LeftFootButton"
MinSize="20 10"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonLeftFoot">
</TextureButton>
</PanelContainer>
<PanelContainer
MinSize="20 30"
VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<TextureButton
Name="RightLegButton"
MinSize="15 28"
VerticalAlignment="Top"
HorizontalAlignment="Right"
StyleClasses="TargetDollButtonRightLeg">
</TextureButton>
<TextureButton
Name="RightFootButton"
MinSize="20 10"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
StyleClasses="TargetDollButtonRightFoot">
</TextureButton>
</PanelContainer>
</PanelContainer>
</PanelContainer>
<PanelContainer
SetSize="15 33"
Margin="0 0 4 0"
HorizontalAlignment="Right">
<TextureButton
Name="LeftArmButton"
MinSize="15 25"
StyleClasses="TargetDollButtonLeftArm"
VerticalAlignment="Top">
</TextureButton>
<TextureButton
Name="LeftHandButton"
MinSize="15 15"
VerticalAlignment="Bottom"
StyleClasses="TargetDollButtonLeftHand">
</TextureButton>
</PanelContainer>
</PanelContainer>
</PanelContainer>
<TextureRect Name="NoDataTex"
Access="Public"
SetSize="96 96"
Visible="false"
Stretch="KeepAspectCentered"
TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/> <!-- DeltaV - Size changed to 96 96 to be consistent with shitmed -->
<BoxContainer Margin="5 0 0 0"
Orientation="Vertical"
VerticalAlignment="Top">
<RichTextLabel Name="NameLabel"
SetWidth="150"/>
<Label Name="SpeciesLabel"
VerticalAlignment="Top"
StyleClasses="LabelSubText"/>
<Label Name="PartNameLabel"
VerticalAlignment="Bottom"
StyleClasses="LabelSubText"/>
</BoxContainer>
<Label Margin="0 0 5 0"
HorizontalExpand="True"
HorizontalAlignment="Right"
VerticalExpand="True"
VerticalAlignment="Top"
Name="ScanModeLabel"
Text="{Loc 'health-analyzer-window-entity-unknown-text'}"/>
</BoxContainer>
<ui:HealthAnalyzerControl
Name="HealthAnalyzer"
Access="Public" /> <!-- DeltaV / Shitmed Change - Make Public-->
<PanelContainer StyleClasses="LowDivider"/>
<!-- Begin DeltaV - Medical Records -->
<BoxContainer Name="TriageControls" Orientation="Vertical">
<BoxContainer Name="StatusBox"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0 8 0 0">
</BoxContainer>
<Button Name="ClaimButton"
Text="{Loc 'health-analyzer-window-triage-claim'}"
MinSize="60 0" />
<PanelContainer StyleClasses="LowDivider" Margin="0 8 0 0"/>
</BoxContainer>
<!-- End DeltaV - Medical Records -->
<GridContainer Margin="0 5 0 0"
Columns="2">
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}"/>
<Label Name="StatusLabel"/>
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}"/>
<Label Name="TemperatureLabel"/>
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}"/>
<Label Name="BloodLabel"/>
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}"/>
<Label Name="DamageLabel"/>
</GridContainer>
</BoxContainer>
<PanelContainer Name="AlertsDivider"
Visible="False"
StyleClasses="LowDivider"/>
<BoxContainer Name="AlertsContainer"
Visible="False"
Margin="0 5"
Orientation="Vertical"
HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider"/>
<!-- Shitmed Change End -->
<BoxContainer
Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical">
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</controls:FancyWindow>
@@ -1,20 +1,414 @@
using System.Linq;
using System.Numerics;
using Content.Client._DV.Traits.Assorted; // DeltaV
using Content.Shared._DV.Traits.Assorted; // DeltaV
using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
using Content.Shared._Shitmed.Targeting; // Shitmed
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.MedicalScanner;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.ResourceManagement;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.HealthAnalyzer.UI;
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : FancyWindow
namespace Content.Client.HealthAnalyzer.UI
{
public HealthAnalyzerWindow()
[GenerateTypedNameReferences]
public sealed partial class HealthAnalyzerWindow : FancyWindow
{
RobustXamlLoader.Load(this);
}
private readonly IEntityManager _entityManager;
private readonly SpriteSystem _spriteSystem;
private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache;
private readonly UnborgableSystem _unborgable; // DeltaV
private readonly RedshirtSystem _redshirt; // DeltaV
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
HealthAnalyzer.Populate(msg.State);
// Shitmed Change Start
public event Action<TargetBodyPart?, EntityUid>? OnBodyPartSelected;
private EntityUid _spriteViewEntity;
[ValidatePrototypeId<EntityPrototype>]
private readonly EntProtoId _bodyView = "AlertSpriteView";
private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
private EntityUid? _target;
// Shitmed Change End
// Begin DeltaV - Medical Records
private readonly ButtonGroup _triageStatusGroup = new();
private readonly Dictionary<TriageStatus, Button> _triageControls = new();
public event Action<TriageStatus>? OnTriageStatusChanged;
public event Action? OnClaimPatient;
// End DeltaV - Medical Records
public HealthAnalyzerWindow()
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_entityManager = dependencies.Resolve<IEntityManager>();
_spriteSystem = _entityManager.System<SpriteSystem>();
_prototypes = dependencies.Resolve<IPrototypeManager>();
_cache = dependencies.Resolve<IResourceCache>();
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
// Shitmed Change Start
_bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
{
{ TargetBodyPart.Head, HeadButton },
{ TargetBodyPart.Torso, ChestButton },
{ TargetBodyPart.Groin, GroinButton },
{ TargetBodyPart.LeftArm, LeftArmButton },
{ TargetBodyPart.LeftHand, LeftHandButton },
{ TargetBodyPart.RightArm, RightArmButton },
{ TargetBodyPart.RightHand, RightHandButton },
{ TargetBodyPart.LeftLeg, LeftLegButton },
{ TargetBodyPart.LeftFoot, LeftFootButton },
{ TargetBodyPart.RightLeg, RightLegButton },
{ TargetBodyPart.RightFoot, RightFootButton },
};
foreach (var bodyPartButton in _bodyPartControls)
{
bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key, bodyPartButton.Value);
}
ReturnButton.OnPressed += _ => ResetBodyPart();
// Shitmed Change End
// Begin DeltaV - Medical Records
foreach (var item in Enum.GetValues<TriageStatus>())
{
var btn = new Button();
var ftlKey = item.ToString();
btn.Group = _triageStatusGroup;
btn.Text = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}");
btn.ToolTip = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}.ToolTip");
btn.OnPressed += _ => OnTriageStatusChanged?.Invoke(item);
btn.AddStyleClass("ButtonSquare");
StatusBox.AddChild(btn);
_triageControls[item] = btn;
}
StatusBox.Children.First().AddStyleClass("OpenRight");
StatusBox.Children.First().RemoveStyleClass("ButtonSquare");
StatusBox.Children.Last().AddStyleClass("OpenLeft");
StatusBox.Children.Last().RemoveStyleClass("ButtonSquare");
ClaimButton.OnPressed += _ => OnClaimPatient?.Invoke();
// End DeltaV - Medical Records
}
// Shitmed Change Start
public void SetActiveBodyPart(TargetBodyPart part, TextureButton button)
{
if (_target == null)
return;
// Bit of the ole shitcode until we have Groins in the prototypes.
OnBodyPartSelected?.Invoke(part == TargetBodyPart.Groin ? TargetBodyPart.Torso : part, _target.Value);
}
public void ResetBodyPart()
{
if (_target == null)
return;
OnBodyPartSelected?.Invoke(null, _target.Value);
}
public void SetActiveButtons(bool isHumanoid)
{
foreach (var button in _bodyPartControls)
button.Value.Visible = isHumanoid;
}
// Not all of this function got messed with, but it was spread enough to warrant being covered entirely by a Shitmed Change
public void Populate(HealthAnalyzerScannedUserMessage msg)
{
// Start-Shitmed
_target = _entityManager.GetEntity(msg.TargetEntity);
EntityUid? part = msg.Part != null ? _entityManager.GetEntity(msg.Part.Value) : null;
var isPart = part != null;
if (_target == null
|| !_entityManager.TryGetComponent<DamageableComponent>(isPart ? part : _target, out var damageable))
{
NoPatientDataText.Visible = true;
return;
}
SetActiveButtons(_entityManager.HasComponent<TargetingComponent>(_target.Value));
ReturnButton.Visible = isPart;
PartNameLabel.Visible = isPart;
if (part != null)
PartNameLabel.Text = _entityManager.HasComponent<MetaDataComponent>(part)
? Identity.Name(part.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
NoPatientDataText.Visible = false;
// Scan Mode
ScanModeLabel.Text = msg.ScanMode.HasValue
? msg.ScanMode.Value
? Loc.GetString("health-analyzer-window-scan-mode-active")
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
: Loc.GetString("health-analyzer-window-entity-unknown-text");
ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
// Patient Information
SpriteView.SetEntity(SetupIcon(msg.Body) ?? _target.Value);
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
PartView.Visible = SpriteView.Visible;
NoDataTex.Visible = !SpriteView.Visible;
var name = new FormattedMessage();
name.PushColor(Color.White);
name.AddText(_entityManager.HasComponent<MetaDataComponent>(_target.Value)
? Identity.Name(_target.Value, _entityManager)
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
NameLabel.SetMessage(name);
SpeciesLabel.Text =
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(_target.Value,
out var humanoidAppearanceComponent)
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
// Basic Diagnostic
TemperatureLabel.Text = !float.IsNaN(msg.Temperature)
? $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
? $"{msg.BloodLevel * 100:F1} %"
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
StatusLabel.Text =
_entityManager.TryGetComponent<MobStateComponent>(_target.Value, out var mobStateComponent)
? GetStatus(mobStateComponent.CurrentState)
: Loc.GetString("health-analyzer-window-entity-unknown-text");
// Total Damage
DamageLabel.Text = damageable.TotalDamage.ToString();
// Alerts
var unborgable = _unborgable.IsUnborgable(_target.Value); // DeltaV
var redshirt = _redshirt.IsRedshirt(_target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Redshirt
var showAlerts = msg.Unrevivable == true || msg.Bleeding == true || unborgable || redshirt; // DeltaV - Unborgable/Redshirt
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.RemoveAllChildren();
if (msg.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (msg.Bleeding == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (unborgable) // DeltaV
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unborgable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (redshirt) // DeltaV
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-redshirt-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
// Damage Groups
var damageSortedGroups =
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value);
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
// Begin DeltaV - Medical Records
if (msg.MedicalRecord is not {} records)
{
TriageControls.Visible = false;
return;
}
_triageControls[records.Status].Pressed = true;
// End DeltaV - Medical Records
}
// Shitmed Change End
private static string GetStatus(MobState mobState)
{
return mobState switch
{
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
};
}
private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups,
IReadOnlyDictionary<string, FixedPoint2> damageDict)
{
GroupsContainer.RemoveAllChildren();
foreach (var (damageGroupId, damageAmount) in groups)
{
if (damageAmount == 0)
continue;
var groupTitleText = $"{Loc.GetString(
"health-analyzer-window-damage-group-text",
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
("amount", damageAmount)
)}";
var groupContainer = new BoxContainer
{
Align = BoxContainer.AlignMode.Begin,
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
GroupsContainer.AddChild(groupContainer);
// Show the damage for each type in that group.
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
foreach (var type in group.DamageTypes)
{
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
continue;
var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
}
}
}
private Texture GetTexture(string texture)
{
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
{
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
}
return _spriteSystem.Frame0(rsiSprite);
}
private static Label CreateDiagnosticItemLabel(string text)
{
return new Label
{
Text = text,
};
}
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
{
var rootContainer = new BoxContainer
{
Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom,
Orientation = BoxContainer.LayoutOrientation.Horizontal,
};
rootContainer.AddChild(new TextureRect
{
SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower())
});
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
return rootContainer;
}
// Shitmed Change Start
/// <summary>
/// Sets up the Body Doll using Alert Entity to use in Health Analyzer.
/// </summary>
private EntityUid? SetupIcon(Dictionary<TargetBodyPart, TargetIntegrity>? body)
{
if (body is null)
return null;
if (!_entityManager.Deleted(_spriteViewEntity))
_entityManager.QueueDeleteEntity(_spriteViewEntity);
_spriteViewEntity = _entityManager.Spawn(_bodyView);
if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
return null;
int layer = 0;
foreach (var (bodyPart, integrity) in body)
{
// TODO: PartStatusUIController and make it use layers instead of TextureRects when EE refactors alerts.
string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
int enumValue = (int) integrity;
var rsi = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
// Shitcode with love from Russia :)
if (!_spriteSystem.TryGetLayer(_spriteViewEntity, layer, out _, false))
_spriteSystem.AddTextureLayer(_spriteViewEntity, _spriteSystem.Frame0(rsi));
else
_spriteSystem.LayerSetTexture(_spriteViewEntity, layer, _spriteSystem.Frame0(rsi));
_spriteSystem.LayerSetScale(_spriteViewEntity, layer, new Vector2(3f, 3f));
layer++;
}
return _spriteViewEntity;
}
// Shitmed Change End
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
{
TitleLabel.Text = title;
if (markup)
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()), tagsAllowed: null);
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
else
Content.SetMessage(text);
}
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Content.Client.Info
}
public void SetInfoBlob(string markup)
{
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup), tagsAllowed: null);
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
}
}
}
-2
View File
@@ -38,8 +38,6 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.ZoomIn);
common.AddFunction(ContentKeyFunctions.ResetZoom);
common.AddFunction(ContentKeyFunctions.InspectEntity);
common.AddFunction(ContentKeyFunctions.InspectServerComponent);
common.AddFunction(ContentKeyFunctions.InspectClientComponent);
common.AddFunction(ContentKeyFunctions.ToggleRoundEndSummaryWindow);
// Begin DeltaV Additions
common.AddFunction(ContentKeyFunctions.OpenCHelp);
@@ -1,7 +0,0 @@
using Content.Shared.Kitchen.EntitySystems;
using JetBrains.Annotations;
namespace Content.Client.Kitchen.EntitySystems;
[UsedImplicitly]
public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem;
@@ -20,10 +20,8 @@ namespace Content.Client.Launcher
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IClipboardManager _clipboard = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private LauncherConnectingGui? _control;
private ISawmill _sawmill = default!;
private Page _currentPage;
private string? _connectFailReason;
@@ -63,8 +61,6 @@ namespace Content.Client.Launcher
{
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
_sawmill = _logManager.GetSawmill("launcher-ui");
_userInterfaceManager.StateRoot.AddChild(_control);
_clientNetManager.ConnectFailed += OnConnectFailed;
@@ -119,12 +115,12 @@ namespace Content.Client.Launcher
}
else
{
_sawmill.Info($"Redial not possible, no Ss14Address");
Logger.InfoS("launcher-ui", $"Redial not possible, no Ss14Address");
}
}
catch (Exception ex)
{
_sawmill.Error($"Redial exception: {ex}");
Logger.ErrorS("launcher-ui", $"Redial exception: {ex}");
}
return false;
}
-13
View File
@@ -14,7 +14,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
using Content.Client._Impstation.ReadyManifest; // Impstation - Ready Manifest
namespace Content.Client.Lobby
{
@@ -32,7 +31,6 @@ namespace Content.Client.Lobby
private ClientGameTicker _gameTicker = default!;
private ContentAudioSystem _contentAudioSystem = default!;
private ReadyManifestSystem _readyManifest = default!; // Impstation - Ready Manifest
protected override Type? LinkedScreenType { get; } = typeof(LobbyGui);
public LobbyGui? Lobby;
@@ -50,7 +48,6 @@ namespace Content.Client.Lobby
_gameTicker = _entityManager.System<ClientGameTicker>();
_contentAudioSystem = _entityManager.System<ContentAudioSystem>();
_contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo;
_readyManifest = _entityManager.EntitySysManager.GetEntitySystem<ReadyManifestSystem>(); // Impstation - Ready Manifest
chatController.SetMainChat(true);
@@ -72,7 +69,6 @@ namespace Content.Client.Lobby
Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
Lobby.ReadyButton.OnPressed += OnReadyPressed;
Lobby.ReadyButton.OnToggled += OnReadyToggled;
Lobby.ManifestButton.OnPressed += OnManifestPressed; // Impstation - Ready Manifest
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
@@ -93,7 +89,6 @@ namespace Content.Client.Lobby
Lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed;
Lobby!.ReadyButton.OnPressed -= OnReadyPressed;
Lobby!.ReadyButton.OnToggled -= OnReadyToggled;
Lobby!.ManifestButton.OnPressed -= OnManifestPressed; // Impstation - Ready Manifest
Lobby = null;
}
@@ -125,12 +120,6 @@ namespace Content.Client.Lobby
SetReady(args.Pressed);
}
// Impstation - Ready Manifest
private void OnManifestPressed(BaseButton.ButtonEventArgs args)
{
_readyManifest.RequestReadyManifest();
}
public override void FrameUpdate(FrameEventArgs e)
{
if (_gameTicker.IsGameStarted)
@@ -193,7 +182,6 @@ namespace Content.Client.Lobby
Lobby!.ReadyButton.ToggleMode = false;
Lobby!.ReadyButton.Pressed = false;
Lobby!.ObserveButton.Disabled = false;
Lobby!.ManifestButton.Disabled = true; //imp
}
else
{
@@ -203,7 +191,6 @@ namespace Content.Client.Lobby
Lobby!.ReadyButton.ToggleMode = true;
Lobby!.ReadyButton.Disabled = false;
Lobby!.ObserveButton.Disabled = true;
Lobby!.ManifestButton.Disabled = false; // imp
}
if (_gameTicker.ServerInfoBlob != null)
+4 -4
View File
@@ -123,10 +123,10 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_profileEditor.RefreshSpecies();
}
// if (obj.WasModified<TraitPrototype>()) // DeltaV - Refreshed in TraitsTab
// {
// _profileEditor.RefreshTraits();
// }
if (obj.WasModified<TraitPrototype>())
{
_profileEditor.RefreshTraits();
}
}
}
@@ -1,7 +1,6 @@
using Content.Client.Info;
using Content.Client.Info.PlaytimeStats;
using Content.Client.Resources;
using Content.Shared._DV.Species; // DeltaV - Hidden species
using Content.Shared.CCVar;
using Content.Shared.Preferences;
using Robust.Client.AutoGenerated;
@@ -91,10 +90,6 @@ namespace Content.Client.Lobby.UI
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
{
// DeltaV - Ignore hidden species.
if (character is HumanoidCharacterProfile hcp && SpeciesHiderSystem.IsHidden(hcp.Species.Id))
continue;
// End DeltaV
numberOfFullSlots++;
var characterPickerButton = new CharacterPickerButton(_entManager,
_protomanager,
@@ -3,7 +3,6 @@
xmlns:humanoid="clr-namespace:Content.Client.Humanoid"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ui="clr-namespace:Content.Client.Lobby.UI"
xmlns:traits="clr-namespace:Content.Client._DV.Traits.UI"
HorizontalExpand="True">
<!-- Left side -->
<BoxContainer Orientation="Vertical" Margin="10 10 10 10" HorizontalExpand="True">
@@ -134,15 +133,12 @@
<BoxContainer Name="AntagList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<!-- DV - Traits -->
<!-- <BoxContainer Orientation="Vertical" Margin="10"> -->
<!-- ~1~ Traits @1@ -->
<!-- <ScrollContainer VerticalExpand="True"> -->
<!-- <BoxContainer Name="TraitsList" Orientation="Vertical" /> -->
<!-- </ScrollContainer> -->
<!-- </BoxContainer> -->
<traits:TraitsTab Name="Traits"/>
<!-- End DV -->
<BoxContainer Orientation="Vertical" Margin="10">
<!-- Traits -->
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="TraitsList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Name="MarkingsTab" Orientation="Vertical" Margin="10">
<!-- Markings -->
<ScrollContainer VerticalExpand="True">
@@ -9,7 +9,6 @@ using Content.Client.Players.PlayTimeTracking;
using Content.Client.Stylesheets;
using Content.Client.Sprite;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._DV.Species; // DeltaV - Species hider
using Content.Shared.CCVar;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
@@ -40,7 +39,6 @@ using System.Globalization;
using Content.Client._CD.Records.UI;
using Content.Shared._CD.Records;
// End CD - Character Records
using Content.Shared._DV.Traits; // DV - Traits
namespace Content.Client.Lobby.UI
{
@@ -182,8 +180,6 @@ namespace Content.Client.Lobby.UI
Save?.Invoke();
};
Traits.OnTraitsChanged += OnTraitsSelectionChanged; // DeltaV
#region Left
#region Name
@@ -243,6 +239,7 @@ namespace Content.Client.Lobby.UI
{
SpeciesButton.SelectId(args.Id);
SetSpecies(_species[args.Id].ID);
RefreshTraits(); // DeltaV - Allows for hiding traits
UpdateHairPickers();
OnSkinColorOnValueChanged();
};
@@ -465,7 +462,7 @@ namespace Content.Client.Lobby.UI
TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
// RefreshTraits(); // DeltaV
RefreshTraits();
#region Markings
@@ -518,58 +515,6 @@ namespace Content.Client.Lobby.UI
IsDirty = false;
}
// Begin DeltaV - Traits Integration
/// <summary>
/// Called when trait selection changes in the TraitsTab.
/// Updates the profile with the new trait selection.
/// </summary>
private void OnTraitsSelectionChanged(HashSet<ProtoId<TraitPrototype>> traits)
{
if (Profile is null)
return;
// Remove all existing traits - iterate directly over readonly collection
foreach (var existingTrait in Profile.TraitPreferences)
{
Profile = Profile.WithoutTraitPreference(existingTrait, _prototypeManager);
}
// Add newly selected traits
foreach (var trait in traits)
{
Profile = Profile.WithTraitPreference(trait.Id, _prototypeManager);
}
SetDirty();
}
/// <summary>
/// Updates the traits tab with the current profile's selected traits.
/// </summary>
private void UpdateTraitsSelection()
{
if (Profile is null)
{
Traits.SetSelectedTraits(new HashSet<ProtoId<TraitPrototype>>());
return;
}
// Convert profile's trait preferences (strings) to ProtoId<TraitPrototype>
var selectedTraits = new HashSet<ProtoId<TraitPrototype>>(Profile.TraitPreferences.Count);
foreach (var traitId in Profile.TraitPreferences)
{
// Validate that the trait still exists in prototypes
if (_prototypeManager.HasIndex(traitId))
{
selectedTraits.Add(new ProtoId<TraitPrototype>(traitId));
}
}
Traits.SetSelectedTraits(selectedTraits);
Traits.UpdateConditions(Profile);
}
// End DeltaV - Traits Integration
/// <summary>
/// Refreshes the flavor text editor status.
/// </summary>
@@ -604,122 +549,122 @@ namespace Content.Client.Lobby.UI
/// <summary>
/// Refreshes traits selector
/// </summary>
// public void RefreshTraits()
// {
// TraitsList.RemoveAllChildren();
//
// var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
// TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
//
// if (traits.Count < 1)
// {
// TraitsList.AddChild(new Label
// {
// Text = Loc.GetString("humanoid-profile-editor-no-traits"),
// FontColorOverride = Color.Gray,
// });
// return;
// }
//
// // Setup model
// Dictionary<string, List<string>> traitGroups = new();
// List<string> defaultTraits = new();
// traitGroups.Add(TraitCategoryPrototype.Default, defaultTraits);
//
// foreach (var trait in traits)
// {
// // Begin DeltaV Additions - Species trait exclusion
// if (Profile?.Species is { } selectedSpecies && trait.ExcludedSpecies.Contains(selectedSpecies))
// {
// Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager);
// continue;
// }
// // End DeltaV Additions
//
// if (trait.Category == null)
// {
// defaultTraits.Add(trait.ID);
// continue;
// }
//
// if (!_prototypeManager.HasIndex(trait.Category))
// continue;
//
// var group = traitGroups.GetOrNew(trait.Category);
// group.Add(trait.ID);
// }
//
// // Create UI view from model
// foreach (var (categoryId, categoryTraits) in traitGroups)
// {
// TraitCategoryPrototype? category = null;
//
// if (categoryId != TraitCategoryPrototype.Default)
// {
// category = _prototypeManager.Index<TraitCategoryPrototype>(categoryId);
// // Label
// TraitsList.AddChild(new Label
// {
// Text = Loc.GetString(category.Name),
// Margin = new Thickness(0, 10, 0, 0),
// StyleClasses = { StyleClass.LabelHeading },
// });
// }
//
// List<TraitPreferenceSelector?> selectors = new();
// var selectionCount = 0;
//
// foreach (var traitProto in categoryTraits)
// {
// var trait = _prototypeManager.Index<TraitPrototype>(traitProto);
// var selector = new TraitPreferenceSelector(trait);
//
// selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true;
// if (selector.Preference)
// selectionCount += trait.Cost;
//
// selector.PreferenceChanged += preference =>
// {
// if (preference)
// {
// Profile = Profile?.WithTraitPreference(trait.ID, _prototypeManager);
// }
// else
// {
// Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager);
// }
//
// SetDirty();
// RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
// };
// selectors.Add(selector);
// }
//
// // Selection counter
// if (category is { MaxTraitPoints: >= 0 })
// {
// TraitsList.AddChild(new Label
// {
// Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)),
// FontColorOverride = Color.Gray
// });
// }
//
// foreach (var selector in selectors)
// {
// if (selector == null)
// continue;
//
// if (category is { MaxTraitPoints: >= 0 } &&
// selector.Cost + selectionCount > category.MaxTraitPoints)
// {
// selector.Checkbox.Label.FontColorOverride = Color.Red;
// }
//
// TraitsList.AddChild(selector);
// }
// }
// }
public void RefreshTraits()
{
TraitsList.RemoveAllChildren();
var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
if (traits.Count < 1)
{
TraitsList.AddChild(new Label
{
Text = Loc.GetString("humanoid-profile-editor-no-traits"),
FontColorOverride = Color.Gray,
});
return;
}
// Setup model
Dictionary<string, List<string>> traitGroups = new();
List<string> defaultTraits = new();
traitGroups.Add(TraitCategoryPrototype.Default, defaultTraits);
foreach (var trait in traits)
{
// Begin DeltaV Additions - Species trait exclusion
if (Profile?.Species is { } selectedSpecies && trait.ExcludedSpecies.Contains(selectedSpecies))
{
Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager);
continue;
}
// End DeltaV Additions
if (trait.Category == null)
{
defaultTraits.Add(trait.ID);
continue;
}
if (!_prototypeManager.HasIndex(trait.Category))
continue;
var group = traitGroups.GetOrNew(trait.Category);
group.Add(trait.ID);
}
// Create UI view from model
foreach (var (categoryId, categoryTraits) in traitGroups)
{
TraitCategoryPrototype? category = null;
if (categoryId != TraitCategoryPrototype.Default)
{
category = _prototypeManager.Index<TraitCategoryPrototype>(categoryId);
// Label
TraitsList.AddChild(new Label
{
Text = Loc.GetString(category.Name),
Margin = new Thickness(0, 10, 0, 0),
StyleClasses = { StyleClass.LabelHeading },
});
}
List<TraitPreferenceSelector?> selectors = new();
var selectionCount = 0;
foreach (var traitProto in categoryTraits)
{
var trait = _prototypeManager.Index<TraitPrototype>(traitProto);
var selector = new TraitPreferenceSelector(trait);
selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true;
if (selector.Preference)
selectionCount += trait.Cost;
selector.PreferenceChanged += preference =>
{
if (preference)
{
Profile = Profile?.WithTraitPreference(trait.ID, _prototypeManager);
}
else
{
Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager);
}
SetDirty();
RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
};
selectors.Add(selector);
}
// Selection counter
if (category is { MaxTraitPoints: >= 0 })
{
TraitsList.AddChild(new Label
{
Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)),
FontColorOverride = Color.Gray
});
}
foreach (var selector in selectors)
{
if (selector == null)
continue;
if (category is { MaxTraitPoints: >= 0 } &&
selector.Cost + selectionCount > category.MaxTraitPoints)
{
selector.Checkbox.Label.FontColorOverride = Color.Red;
}
TraitsList.AddChild(selector);
}
}
}
/// <summary>
/// Refreshes the species selector.
@@ -729,7 +674,7 @@ namespace Content.Client.Lobby.UI
SpeciesButton.Clear();
_species.Clear();
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart && !SpeciesHiderSystem.IsHidden(o.ID))); // DeltaV - SpeciesHider
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
_species.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.CurrentCultureIgnoreCase));
var speciesIds = _species.Select(o => o.ID).ToList();
@@ -900,13 +845,11 @@ namespace Content.Client.Lobby.UI
_recordsTab.Update(profile);
// End CD - Character Records
UpdateTraitsSelection(); // DeltaV - Traits
RefreshAntags();
RefreshJobs();
RefreshLoadouts();
RefreshSpecies();
// RefreshTraits(); // DeltaV
RefreshTraits();
RefreshFlavorText();
ReloadPreview();
@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Prometheus;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-4
View File
@@ -36,10 +36,6 @@
Align="Left"
FontColorOverride="{x:Static maths:Color.DarkGray}"
StyleClasses="LabelBig" HorizontalExpand="True" />
<!-- Imp manifest button -->
<Button Name="ManifestButton" Access="Public"
Text="Manifest"
StyleClasses="ButtonBig" MinWidth="137" />
<Button Name="ReadyButton" Access="Public" ToggleMode="True"
Text="{Loc 'ui-lobby-ready-up-button'}"
StyleClasses="ButtonBig" MinWidth="137" />
@@ -19,24 +19,22 @@ public sealed partial class TraitPreferenceSelector : Control
public event Action<bool>? PreferenceChanged;
// DeltaV - This whole control is generally unused but the compiler wouldn't compile if I removed it
// So I'm just gonna comment this part out
// public TraitPreferenceSelector(TraitPrototype trait)
// {
// RobustXamlLoader.Load(this);
//
// var text = trait.Cost != 0 ? $"[{trait.Cost}] " : "";
// text += Loc.GetString(trait.Name);
//
// Cost = trait.Cost;
// Checkbox.Text = text;
// Checkbox.OnToggled += OnCheckBoxToggled;
//
// if (trait.Description is { } desc)
// {
// Checkbox.ToolTip = Loc.GetString(desc);
// }
// }
public TraitPreferenceSelector(TraitPrototype trait)
{
RobustXamlLoader.Load(this);
var text = trait.Cost != 0 ? $"[{trait.Cost}] " : "";
text += Loc.GetString(trait.Name);
Cost = trait.Cost;
Checkbox.Text = text;
Checkbox.OnToggled += OnCheckBoxToggled;
if (trait.Description is { } desc)
{
Checkbox.ToolTip = Loc.GetString(desc);
}
}
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
{
-16
View File
@@ -1,6 +1,4 @@
using System.Text.RegularExpressions;
using Content.Client._DV.UserInterfaces.BuildInfo; // DeltaV - More info in Escape Menu
using Content.Client.Credits; // DeltaV - More info in Escape Menu
using Content.Client.MainMenu.UI;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client;
@@ -50,8 +48,6 @@ namespace Content.Client.MainMenu
_mainMenuControl.DirectConnectButton.OnPressed += DirectConnectButtonPressed;
_mainMenuControl.AddressBox.OnTextEntered += AddressBoxEntered;
_mainMenuControl.ChangelogButton.OnPressed += ChangelogButtonPressed;
_mainMenuControl.BuildInfoButton.OnPressed += BuildInfoButtonPressed; // DeltaV - More info in Escape Menu
_mainMenuControl.CreditsButton.OnPressed += CreditsButtonPressed; // DeltaV - More info in Escape Menu
_client.RunLevelChanged += RunLevelChanged;
}
@@ -70,18 +66,6 @@ namespace Content.Client.MainMenu
_userInterfaceManager.GetUIController<ChangelogUIController>().ToggleWindow();
}
// Begin DeltaV - More info in Escape Menu
private void BuildInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
_userInterfaceManager.GetUIController<BuildInfoUIController>().ToggleWindow();
}
private void CreditsButtonPressed(BaseButton.ButtonEventArgs args)
{
new CreditsWindow().Open();
}
// End DeltaV - More info in Escape Menu
private void OptionsButtonPressed(BaseButton.ButtonEventArgs args)
{
_userInterfaceManager.GetUIController<OptionsUIController>().ToggleWindow();
@@ -48,18 +48,5 @@
<clog:ChangelogButton
Name="ChangelogButton"
Access="Public"/>
<!-- Begin DeltaV - More info in Escape Menu -->
<Button
Name="CreditsButton"
Access="Public"
Text="{Loc server-info-credits-button}"
TextAlign="Center" />
<Button
Name="BuildInfoButton"
Access="Public"
Text="{Loc build-info-button}"
ToolTip="{Loc build-info-tooltip}"
TextAlign="Center" />
<!-- End DeltaV - More info in Escape Menu -->
</BoxContainer>
</Control>
@@ -1,13 +1,12 @@
using Content.Client.Message;
using Content.Client.RichText;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.RichText;
using Content.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
using Content.Shared.MassMedia.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.RichText;
using Robust.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
@@ -21,6 +20,18 @@ public sealed partial class ArticleEditorPanel : Control
private bool _preview;
// DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
private static readonly Type[] AllowedTags =
[
typeof(BoldItalicTag),
typeof(BoldTag),
typeof(BulletTag),
typeof(ColorTag),
typeof(HeadingTag),
typeof(ItalicTag),
typeof(MonoTag),
];
public ArticleEditorPanel()
{
RobustXamlLoader.Load(this);
@@ -85,8 +96,8 @@ public sealed partial class ArticleEditorPanel : Control
TextEditPanel.Visible = !_preview;
PreviewPanel.Visible = _preview;
var articleBody = Rope.Collapse(ContentField.TextRope);
PreviewLabel.SetMessage(FormattedMessage.FromMarkupPermissive(articleBody), UserFormattableTags.BaseAllowedTags);
var articleBody = Rope.Collapse(ContentField.TextRope); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
PreviewLabel.SetMessage(FormattedMessage.FromMarkupPermissive(articleBody), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
}
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
@@ -71,7 +71,7 @@ public sealed partial class MaterialDisplay : PanelContainer
if (!_canEject)
return;
int[] sheetsToEjectArray = { 1, 5, 10, 30 }; // DeltaV - added 30 to array
int[] sheetsToEjectArray = { 1, 5, 10 };
for (var i = 0; i < sheetsToEjectArray.Length; i++)
{
@@ -88,7 +88,7 @@ public sealed partial class MaterialDisplay : PanelContainer
Name = $"{sheetsToEject}",
Access = AccessLevel.Public,
Text = Loc.GetString($"{sheetsToEject}"),
MinWidth = 35, // DeltaV - reduced from 45 to accomodate extra button
MinWidth = 45,
StyleClasses = { styleClass }
};
@@ -1,285 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace Content.Client.Medical.Cryogenics;
public sealed class BeakerBarChart : Control
{
private sealed class Entry
{
public float WidthFraction; // This entry's width as a fraction of the chart's total width (between 0 and 1)
public float TargetAmount;
public string Uid; // This UID is used to track entries between frames, for animation.
public string? Tooltip;
public Color Color;
public Label Label;
public Entry(string uid, Label label)
{
Uid = uid;
Label = label;
}
}
public float Capacity = 50;
public Color NotchColor = new(1, 1, 1, 0.25f);
public Color BackgroundColor = new(0.1f, 0.1f, 0.1f);
public int MediumNotchInterval = 5;
public int BigNotchInterval = 10;
// When we have a very large beaker (i.e. bluespace beaker) we might need to increase the distance between notches.
// The distance between notches is increased by ScaleMultiplier when the distance between notches is less than
// MinSmallNotchScreenDistance in UI units.
public int MinSmallNotchScreenDistance = 2;
public int ScaleMultiplier = 10;
public float SmallNotchHeight = 0.1f;
public float MediumNotchHeight = 0.25f;
public float BigNotchHeight = 1f;
// We don't animate new entries until this control has been drawn at least once.
private bool _hasBeenDrawn = false;
// This is used to keep the segments of the chart in the same order as the SetEntry calls.
// For example: In update 1 we might get cryox, alox, bic (in that order), and in update 2 we get alox, cryox, bic.
// To keep the order of the entries the same as the order of the SetEntry calls, we let the old cryox entry
// disappear and create a new cryox entry behind the alox entry.
private int _nextUpdateableEntry = 0;
private readonly List<Entry> _entries = new();
public BeakerBarChart()
{
MouseFilter = MouseFilterMode.Pass;
TooltipSupplier = SupplyTooltip;
}
public void Clear()
{
foreach (var entry in _entries)
{
entry.TargetAmount = 0;
}
_nextUpdateableEntry = 0;
}
/// <summary>
/// Either adds a new entry to the chart if the UID doesn't appear yet, or updates the amount of an existing entry.
/// </summary>
public void SetEntry(
string uid,
string label,
float amount,
Color color,
Color? textColor = null,
string? tooltip = null)
{
// If we can find an old entry we're allowed to update, update that one.
if (TryFindUpdateableEntry(uid, out var index))
{
_entries[index].TargetAmount = amount;
_entries[index].Tooltip = tooltip;
_entries[index].Label.Text = label;
_nextUpdateableEntry = index + 1;
return;
}
// Otherwise create a new entry.
if (amount <= 0)
return;
// If no text color is provided, use either white or black depending on how dark the background is.
textColor ??= (color.R + color.G + color.B < 1.5f ? Color.White : Color.Black);
var childLabel = new Label
{
Text = label,
ClipText = true,
FontColorOverride = textColor,
Margin = new Thickness(4, 0, 0, 0)
};
AddChild(childLabel);
_entries.Insert(
_nextUpdateableEntry,
new Entry(uid, childLabel)
{
WidthFraction = (_hasBeenDrawn ? 0 : amount / Capacity),
TargetAmount = amount,
Tooltip = tooltip,
Color = color
}
);
_nextUpdateableEntry += 1;
}
private bool TryFindUpdateableEntry(string uid, out int index)
{
for (int i = _nextUpdateableEntry; i < _entries.Count; i++)
{
if (_entries[i].Uid == uid)
{
index = i;
return true;
}
}
index = -1;
return false;
}
private IEnumerable<(Entry, float xMin, float xMax)> EntryRanges(float? pixelWidth = null)
{
float chartWidth = pixelWidth ?? PixelWidth;
var xStart = 0f;
foreach (var entry in _entries)
{
var entryWidth = entry.WidthFraction * chartWidth;
var xEnd = MathF.Min(xStart + entryWidth, chartWidth);
yield return (entry, xStart, xEnd);
xStart = xEnd;
}
}
private bool TryFindEntry(float x, [NotNullWhen(true)] out Entry? entry)
{
foreach (var (currentEntry, xMin, xMax) in EntryRanges())
{
if (xMin <= x && x < xMax)
{
entry = currentEntry;
return true;
}
}
entry = null;
return false;
}
protected override void FrameUpdate(FrameEventArgs args)
{
// Tween the amounts to their target amounts.
const float tweenInverseHalfLife = 8; // Half life of tween is 1/n
var hasChanged = false;
foreach (var entry in _entries)
{
var targetWidthFraction = entry.TargetAmount / Capacity;
if (entry.WidthFraction == targetWidthFraction)
continue;
// Tween with lerp abuse interpolation
entry.WidthFraction = MathHelper.Lerp(
entry.WidthFraction,
targetWidthFraction,
MathHelper.Clamp01(tweenInverseHalfLife * args.DeltaSeconds)
);
hasChanged = true;
if (MathF.Abs(entry.WidthFraction - targetWidthFraction) < 0.0001f)
entry.WidthFraction = targetWidthFraction;
}
if (!hasChanged)
return;
InvalidateArrange();
// Remove old entries whose animations have finished.
foreach (var entry in _entries)
{
if (entry.WidthFraction == 0 && entry.TargetAmount == 0)
RemoveChild(entry.Label);
}
_entries.RemoveAll(entry => entry.WidthFraction == 0 && entry.TargetAmount == 0);
}
protected override void MouseMove(GUIMouseMoveEventArgs args)
{
HideTooltip();
}
protected override void Draw(DrawingHandleScreen handle)
{
handle.DrawRect(PixelSizeBox, BackgroundColor);
// Draw the entry backgrounds
foreach (var (entry, xMin, xMax) in EntryRanges())
{
if (xMin != xMax)
handle.DrawRect(new(xMin, 0, xMax, PixelHeight), entry.Color);
}
// Draw notches
var unitWidth = PixelWidth / Capacity;
var unitsPerNotch = 1;
while (unitWidth < MinSmallNotchScreenDistance)
{
// This is here for 1000u bluespace beakers. If the distance between small notches is so small that it would
// be very ugly, we reduce the amount of notches by ScaleMultiplier (currently a factor of 10).
// (I could use an analytical algorithm here, but it would be more difficult to read with pretty much no
// performance benefit, since it loops zero times normally and one time for the bluespace beaker)
unitWidth *= ScaleMultiplier;
unitsPerNotch *= ScaleMultiplier;
}
for (int i = 0; i <= Capacity / unitsPerNotch; i++)
{
var x = i * unitWidth;
var height = (i % BigNotchInterval == 0 ? BigNotchHeight :
i % MediumNotchInterval == 0 ? MediumNotchHeight :
SmallNotchHeight) * PixelHeight;
var start = new Vector2(x, PixelHeight);
var end = new Vector2(x, PixelHeight - height);
handle.DrawLine(start, end, NotchColor);
}
_hasBeenDrawn = true;
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
foreach (var (entry, xMin, xMax) in EntryRanges(finalSize.X))
{
entry.Label.Arrange(new((int)xMin, 0, (int)xMax, (int)finalSize.Y));
}
return finalSize;
}
private Control? SupplyTooltip(Control sender)
{
var globalMousePos = UserInterfaceManager.MousePositionScaled.Position;
var mousePos = globalMousePos - GlobalPosition;
if (!TryFindEntry(mousePos.X, out var entry) || entry.Tooltip == null)
return null;
var msg = new FormattedMessage();
msg.AddText(entry.Tooltip);
var tooltip = new Tooltip();
tooltip.SetMessage(msg);
return tooltip;
}
}
@@ -1,53 +0,0 @@
using Content.Shared.FixedPoint;
using Content.Shared.Medical.Cryogenics;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Medical.Cryogenics;
[UsedImplicitly]
public sealed class CryoPodBoundUserInterface : BoundUserInterface
{
private CryoPodWindow? _window;
public CryoPodBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = this.CreateWindowCenteredLeft<CryoPodWindow>();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
_window.OnEjectPatientPressed += EjectPatientPressed;
_window.OnEjectBeakerPressed += EjectBeakerPressed;
_window.OnInjectPressed += InjectPressed;
}
private void EjectPatientPressed()
{
var isLocked =
EntMan.TryGetComponent<CryoPodComponent>(Owner, out var cryoComp)
&& cryoComp.Locked;
_window?.SetEjectErrorVisible(isLocked);
SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectPatient));
}
private void EjectBeakerPressed()
{
SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectBeaker));
}
private void InjectPressed(FixedPoint2 transferAmount)
{
SendMessage(new CryoPodInjectUiMessage(transferAmount));
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
if (_window != null && message is CryoPodUserMessage cryoMsg)
{
_window.Populate(cryoMsg);
}
}
}
@@ -6,6 +6,7 @@ namespace Content.Client.Medical.Cryogenics;
public sealed class CryoPodSystem : SharedCryoPodSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
@@ -45,8 +46,8 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
return;
}
if (!Appearance.TryGetData<bool>(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
|| !Appearance.TryGetData<bool>(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
if (!_appearance.TryGetData<bool>(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
|| !_appearance.TryGetData<bool>(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
{
return;
}
@@ -63,11 +64,6 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
_sprite.LayerSetVisible((uid, args.Sprite), CryoPodVisualLayers.Cover, true);
}
}
protected override void UpdateUi(Entity<CryoPodComponent> cryoPod)
{
// Atmos and health scanner aren't predicted currently...
}
}
public enum CryoPodVisualLayers : byte
@@ -1,232 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:health="clr-namespace:Content.Client.HealthAnalyzer.UI"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:cryogenics="clr-namespace:Content.Client.Medical.Cryogenics"
MinSize="250 300"
Resizable="False">
<Label Name="LoadingPlaceHolder"
Text="{Loc 'cryo-pod-window-loading'}"
Align="Center"
HorizontalExpand="True"
VerticalExpand="True"/>
<BoxContainer Name="Sections"
Orientation="Horizontal"
Visible="False"
Margin="10"
SeparationOverride="16">
<BoxContainer Name="CryoSection"
VerticalExpand="True"
Orientation="Vertical"
MinWidth="250"
MaxWidth="250">
<!-- Flavor text -->
<BoxContainer Orientation="Horizontal"
SeparationOverride="10"
Margin="8 0 0 8">
<TextureRect StyleClasses="NTLogoDark"
VerticalExpand="True"
Stretch="KeepAspectCentered"
SetSize="32 32"/>
<BoxContainer Orientation="Vertical"
SeparationOverride="-4">
<Label Text="{Loc 'cryo-pod-window-product-name'}"
StyleClasses="FontLarge"/>
<Label Text="{Loc 'cryo-pod-window-product-subtitle'}"
StyleClasses="LabelSubText"/>
</BoxContainer>
</BoxContainer>
<!-- Atmos info -->
<BoxContainer Orientation="Horizontal"
SeparationOverride="20"
Margin="0 0 0 4">
<!-- Pressure -->
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'gas-analyzer-window-pressure-text'}"
StyleClasses="LabelSubText"/>
<Label Name="Pressure"/>
</BoxContainer>
<!-- Temperature -->
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'gas-analyzer-window-temperature-text'}"
StyleClasses="LabelSubText"/>
<Label Name="Temperature"/>
</BoxContainer>
</BoxContainer>
<!-- Gas mix -->
<Control Margin="0 0 0 22">
<controls:SplitBar Name="GasMixChart"
MinHeight="8"
MaxHeight="8"/>
</Control>
<!-- Warnings & status -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
Align="Center"
Margin="0 0 0 14"
SeparationOverride="20">
<!-- Ejection error (if the pod is locked) -->
<PanelContainer Name="EjectError"
Visible="False"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<Label Text="{Loc 'cryo-pod-window-error-header'}"
FontColorOverride="orange"
Align="Center"/>
<RichTextLabel Text="{Loc 'cryo-pod-window-eject-error'}"/>
</BoxContainer>
</PanelContainer>
<!-- Pressure warning -->
<PanelContainer Name="LowPressureWarning"
Visible="False"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<Label Text="{Loc 'cryo-pod-window-warning-header'}"
FontColorOverride="orange"
Align="Center"/>
<RichTextLabel Text="{Loc 'cryo-pod-window-low-pressure-warning'}"/>
</BoxContainer>
</PanelContainer>
<!-- Temperature warning -->
<PanelContainer Name="HighTemperatureWarning"
Visible="False"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<Label Text="{Loc 'cryo-pod-window-warning-header'}"
FontColorOverride="orange"
Align="Center"/>
<!-- Note: This placeholder text should never be visible. -->
<RichTextLabel Name="HighTemperatureWarningText"
Text="Temperature too high."/>
</BoxContainer>
</PanelContainer>
<!-- Status checklist -->
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal"
SeparationOverride="8">
<Label Text="{Loc 'cryo-pod-window-status'}"/>
<Label Name="StatusLabel"
Text="{Loc 'cryo-pod-window-status-not-ready'}"
FontColorOverride="Orange"/>
</BoxContainer>
<GridContainer Columns="2"
HSeparationOverride="0"
VSeparationOverride="6"
Margin="6 3 0 0">
<Label Text="⋄"
StyleClasses="LabelSubText"/>
<Label Name="PressureCheck"
Text="{Loc 'cryo-pod-window-checklist-pressure'}"
StyleClasses="LabelSubText"/>
<Label Text="⋄"
StyleClasses="LabelSubText"/>
<Label Name="ChemicalsCheck"
Text="{Loc 'cryo-pod-window-checklist-chemicals'}"
StyleClasses="LabelSubText"
FontColorOverride="Orange"/>
<Label Text="⋄"
StyleClasses="LabelSubText"/>
<Label Name="TemperatureCheck"
Text="{Loc 'cryo-pod-window-checklist-temperature'}"
StyleClasses="LabelSubText"/>
</GridContainer>
</BoxContainer>
</BoxContainer>
<!-- Reagents -->
<Control HorizontalExpand="True"
MinHeight="30">
<Label Name="NoBeakerText"
Text="{Loc 'cryo-pod-window-chems-no-beaker'}"
FontColorOverride="Gray"
VerticalExpand="True"
VAlign="Center"/>
<cryogenics:BeakerBarChart Name="ChemicalsChart"
HorizontalExpand="True"
VerticalExpand="True"/>
</Control>
<!-- Buttons -->
<BoxContainer Orientation="Vertical"
Margin="-2 2 -2 0">
<BoxContainer Orientation="Horizontal">
<Button Name="Inject1"
Text="{Loc 'cryo-pod-window-inject-1u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="Inject5"
Text="{Loc 'cryo-pod-window-inject-5u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="Inject10"
Text="{Loc 'cryo-pod-window-inject-10u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="Inject20"
Text="{Loc 'cryo-pod-window-inject-20u'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenBoth"/>
<Button Name="EjectBeakerButton"
Text="{Loc 'cryo-pod-window-eject-beaker'}"
Disabled="True"
StyleClasses="OpenBoth"/>
</BoxContainer>
<Button Name="EjectPatientButton"
Text="{Loc 'cryo-pod-window-eject-patient'}"
Disabled="True"
HorizontalExpand="True"
StyleClasses="OpenRight"/>
</BoxContainer>
</BoxContainer>
<BoxContainer Name="HealthSection"
VerticalExpand="True"
Orientation="Vertical">
<health:HealthAnalyzerControl Name="HealthAnalyzer"/>
<!-- This label is used to deal with a stray hline at the end of the health analyzer UI -->
<Label Name="NoDamageText"
Text="{Loc 'cryo-pod-window-health-no-damage'}"
FontColorOverride="DeepSkyBlue"/>
<Control VerticalExpand="True"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

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