Compare commits

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

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

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

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

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

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

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

(cherry picked from commit eedb378b9f33033faf4e9dd139f4b2366dcf0632)
2025-12-10 17:50:02 +01:00
4695 changed files with 61911 additions and 323815 deletions
+3
View File
@@ -9,6 +9,9 @@
# Server config files
/Resources/ConfigPresets/ @Toby222
# Sprites - Direction in lack of art director
/Resources/Textures/ @DeltaV-Station/direction
# Lobby art and music - automatically direction issues since its immediately visible to players
/Resources/Audio/Lobby/ @DeltaV-Station/direction
/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction
+2 -2
View File
@@ -7,7 +7,7 @@ concurrency:
on:
workflow_dispatch:
schedule:
- cron: '45 16 * * *'
- cron: '0 10 * * *'
jobs:
build:
@@ -37,7 +37,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
+6 -11
View File
@@ -20,16 +20,11 @@ jobs:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
exempt-issue-labels: 'Priority: 1-Urgent,ignore-stale'
days-before-stale: 90
stale-issue-message: 'This issue will close in 2 days due to inactivity'
days-before-close: 2
stale-pr-message: 'This Pull Request will close in 2 days due to inactivity'
stale-issue-label: 'stale'
stale-pr-label: 'stale'
remove-stale-when-updated: true
stale-issue-message: 'This issue has not been triaged for 90 days'
only-issue-labels: 'S: Untriaged'
stale-issue-label: 'Stale'
days-before-issue-close: -1
stale-pr-message: 'This Pull Request has not seen activity in 90 days, and will be closed in 2 days without activity'
exempt-pr-labels: 'Ignore Stale'
stale-pr-label: 'Stale'
days-before-pr-close: 2
+1 -1
View File
@@ -60,7 +60,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
+14 -14
View File
@@ -6,15 +6,15 @@ It is highly recommended to read the '''[[Standard Operating Procedure|Standard
* '''[[Standard Operating Procedure#Controlled Items|Controlled Items]]:''' If a member of station crew requires access to a Controlled Item for use in their job, or for regular function, they must obtain the signed and stamped approval of a relevant member of Station Command or the CO. The latter is able to override the decisions of Station Command in this regard, however.
* '''[[Standard Operating Procedure#Entity Definitions|Entity]]:''' Any thing with distinct, independent existence.
* '''[[Standard Operating Procedure#Entity Definitions|Employee]]:''' An entity who has an employment contract with a Company.
* '''[[Standard Operating Procedure#Entity Definitions|Pet]]:''' Any Entity that is provided to the Company's Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support.
* '''[[Standard Operating Procedure#Entity Definitions|Pet]]:''' Any Entity that is provided to Company's Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support.
* '''[[Standard Operating Procedure#Security Jurisdiction|Security Jurisdiction]]:''' Station law enforcement officers are bound by Space Law; they cannot operate outside of standard accessible areas, or perform searches, unless such action is allowed by either Alert Procedure or a valid warrant.
* '''[[Standard Operating Procedure#Arrest|Arrest and Sentencing]]:''' Any entity may be arrested, during which they are restrained, informed of their charges, and taken to the brig for search and processing under '''[[Standard Operating Procedure#Brig Procedure|Brig Procedure]]'''.
* '''[[Standard Operating Procedure#Sentencing|Arrest and Sentencing]]:''' Any member of law enforcement is allowed to pass sentence on Misdemeanor, Felony, and Grand Felony charges. The Chief Justice, or in their absence, the Head of Security are allowed to pass sentence on all Charges.
* '''[[Standard Operating Procedure#Treatment of Prisoners|Treatment of Prisoners]]:''' Prisoners must be provided with food, water, clothing, free movement within the brig, adequate medical care, access to the Common and Prison radio channels, and moral, spiritual, or legal counseling if requested and available. If the brig becomes uninhabitable, prisoners must be securely and safely relocated to another area until the brig is functional once more. Prisoners sentenced to Permanent Confinement have additional rights and firmer restrictions that must be upheld by law enforcement officials.
* '''[[Standard Operating Procedure#Paroles and Pardons|Paroles & Pardons]]:''' Parole may be offered under the discretion of the Warden, Head of Security, or CO. Pardons may be issued by the arresting officer, the Judge of a trial, by the Warden (to those within the permanent brig), or by the CO, when deemed in the best interest of the crew or vessel, or when the circumstances of the crime warrant suspension of sentence.
* '''[[Standard Operating Procedure#Arrest|Arrest]]:'''Any entity may be arrested, during which they are restrained, informed of their charges, and taken to the brig for search and processing under '''[[Standard Operating Procedure#Brig Procedure|Brig Procedure]]'''.
* '''[[Standard Operating Procedure#Sentencing|Sentencing]]:''' Any member of law enforcement is allowed to pass sentence on Misdemeanor, Felony, and Grand Felony charges. The Chief Justice, or in their absence, the Head of Security are allowed to pass sentence on all Charges.
* '''[[Standard Operating Procedure#Treatment of Prisoners|Treatment of Prisoners]]:''' Prisoners must be provided with food, water, clothing, free movement within the brig, adequate medical care, access to the Common and Prison radio channels, and moral, spiritual, or legal counseling if requested and available. If the brig becomes uninhabitable, prisoners must be securely and safely relocated to another area until the brig is functional once more. Prisoners sentenced to Extended Confinement have additional rights and firmer restrictions that must be upheld by law enforcement officials.
* '''[[Standard Operating Procedure#Paroles and Pardons|Paroles & Pardons]]:''' Parole may be offered under the discretion of the Warden, Head of Security, or CO. A parole hearing requires only a Judge, as discussed within Trial Procedure. Pardons may be issued by the arresting officer, the Judge of a trial, by the Warden (to those within the permanent brig), or by the CO, when deemed in the best interest of the crew or vessel, or when the circumstances of the crime warrant suspension of sentence.
* '''[[Standard Operating Procedure#Pressing Charges|Pressing Charges]]:''' Any Employee may press charges against any other entity. Individuals may only be charged with a crime if it can be proven beyond reasonable doubt that they have committed the crime.
* '''[[Standard Operating Procedure#Hearing Procedure|Hearing Procedure]]:''' A criminal hearing’s Judge must be, in sequential order: The Chief Justice, the Court Clerk, an impartial member of Justice or Station Command, or the Warden. The Prosecution should ideally be the Prosecutor, or an available member of Station Command or Station Security in that order. The Defense should ideally be a member of the Justice department assigned to the defendant, though they are allowed to represent themselves if they wish.
* '''[[Alert Procedure]]:''' Codes Blue, Red, Gamma, and Delta authorize Security to carry progressively more advanced equipment, and loosen '''[[Standard Operating Procedure#Rules of Engagement|Rules of Engagement]]''' regarding their use. The former two codes discourage openly carrying firearms by personnel; the latter two suspend many rights to privacy, and grant Security the power to do whatever neccessary to retake or scuttle the station.
* '''[[Standard Operating Procedure#Trial Procedure|Trial Procedure]]:''' A criminal trial’s Judge must be, in sequential order: The Chief Justice, the Court Clerk, an impartial member of Justice or Station Command, or the Warden. The Prosecution should ideally be the Prosecutor, or an available member of Station Command or Station Security in that order. The Defense should ideally be a member of the Justice department assigned to the defendant, though they are allowed to represent themselves if they wish. If insufficient crew are available, the Judge is fully authorized and strongly encouraged to pass a sentence of Extended Confinement until a proper trial can be held.
* '''[[Alert Procedure]]:''' Codes Blue, Red, Gamma, and Delta authorize Security to carry progressively more advanced equipment, and loosen '''[[Standard Operating Procedure#Rules of Engagement|Rules of Engagement]]''' regarding their use. The former two codes disallow openly carrying firearms by non-Security personnel; the latter two suspend many rights to privacy, and grant Security the power to do whatever neccessary to retake or scuttle the station.
==Procedural Defense==
If a defendant is accused of a crime, they are granted the privilege to challenge the legitimacy of charges of claims brought against them. Any of the following may be invoked:
@@ -165,7 +165,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| style="border: 1px solid black;" | [[File:SL_Terrorism.png]]
! style="border: 1px solid black;" | '''Terrorism'''
| style="border: 1px solid black;" | Capital
| style="border: 1px solid black;" | To maliciously commit an act or take hostages with reckless indifference to life in order to provoke a state of terror in, intimidate, or otherwise compel an Employee to act or abstain from acting.
| style="border: 1px solid black;" | To maliciously commit an act or take hostages with reckless indifference to life in order to provoke a state of terror in, intimidate, or otherwise compel an entity to act or abstain from acting.
|-
| style="border: 1px solid black;" | 403
| style="border: 1px solid black;" | [[File:SL_GrandSabotage.png]]
@@ -211,7 +211,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|style= "border:1px solid black;"| [[File:SL_Kidnapping.png]]
|style= "border:1px solid black;"| Kidnapping
|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;"| To unlawfully confine or restrict the free movement of an Employee against its will.
|-
| style="border:1px solid black;" | 303
| style="border:1px solid black;" | WIP
@@ -235,13 +235,13 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| style="border:1px solid black;" | [[File:SL_AbuseOfPower.png]]
| style="border:1px solid black;" | Abuse of Power
| 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;" | To intentionally misuse or wrongfully exercise one’s own authority, influence, or control, resulting in harm, unjust treatment, or demonstratable loss to another entity.
|-
| style="border:1px solid black;" | 307
| style="border:1px solid black;" | [[File:SL_GrandTheft.png]]
| style="border:1px solid black;" | Grand Larceny
| style="border:1px solid black;" | 12 minutes
| style="border:1px solid black;" | To deprive an Employee of any Controlled Item in their lawful possession.
| style="border:1px solid black;" | To deprive an entity of any Controlled Item in their lawful possession.
|-
| style="border:1px solid black;" | 308
| style="border:1px solid black;" | [[File:SL_BlackMarketeering.png]]
@@ -336,7 +336,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|style= "border:1px solid black;"| WIP
!style= "border:1px solid black;"| Harassment
|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;"| To verbally demean, humiliate, or harass an entity with malice; or maliciously spread false information amongst a population.
|-
|style= "border:1px solid black;"| 103
|style= "border:1px solid black;"| WIP
@@ -348,7 +348,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| style="border: 1px solid black;" | [[File:SL_Theft.png]]
! style="border: 1px solid black;" | {{anchor|Theft}}Petty Larceny
| 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;" | To deprive an entity of an item in their lawful property without consent.
|-
| style="border: 1px solid black;" | 105
| style="border: 1px solid black;" | [[File:SL_Trespass.png]]
@@ -360,7 +360,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
| style="border: 1px solid black;" | [[File:SL_Vandalism.png]]
! style="border: 1px solid black;" | {{anchor|Vandalism}}Vandalism
| 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;" | To deface or superficially damage public property, or property belonging to another person.
|-
| style="border: 1px solid black;" | 107
| style="border: 1px solid black;" | [[File:SL_Hooliganism.png]]
@@ -19,7 +19,7 @@ All crew operating within their designated job locations are given the right to
The following definitions are provided to clarify the legal protections- and obligations- that Standard Operating Procedures and Space Law provide to any given entity. Unless explicitly covered by a more distinctive term, all Entities are to be treated as such until clarification is provided.
== Entity ==
Any individual with a distinct existence. Entities may also encompass a plurality of identities contained within a single physical form. NanoTrasen does not require Entities be offered the protections of Standard Operating Procedure, Space Law, or Rules of Engagement unless further defined below. Security Officers are encouraged to be reasonable with their use of force regarding circumstances, especially if the Entity's termination would leave lawsuits or tarnish reputation for NanoTrasen.
Any individual with a distinct existence. Entities may also encompass a plurality of identities contained within a single physical form. NanoTrasen does not require Entities be offered the protections of Standard Operating Procedure, Space Law, or Rules of Engagement unless further defined below. Security Officers are encouraged to be reasonable with their use of force regarding circumstances, especially if Entity's termination would leave lawsuits or tarnish reputation for NanoTrasen.
== Pet ==
Any Entity that is provided to NanoTrasen Space Stations - either through the station's Logistics Department, or brought on board by the crew - for the purposes of emotional support. Any harm against such a Pet may render the offender liable to the crime of Animal Cruelty.
Examples of Pets include, but are not limited to:
-160
View File
@@ -1,160 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Destructible;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
using Content.Shared.Maps;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Benchmarks;
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class DestructibleBenchmark
{
/// <summary>
/// Number of destructible entities per prototype to spawn with a <see cref="DestructibleComponent"/>.
/// </summary>
[Params(1, 10, 100, 1000, 5000)]
public int EntityCount;
/// <summary>
/// Amount of blunt damage we do to each entity.
/// </summary>
[Params(10000)]
public FixedPoint2 DamageAmount;
[Params("Blunt")]
public ProtoId<DamageTypePrototype> DamageType;
private static readonly EntProtoId WindowProtoId = "Window";
private static readonly EntProtoId WallProtoId = "WallReinforced";
private static readonly EntProtoId HumanProtoId = "MobHuman";
private static readonly ProtoId<ContentTileDefinition> TileRef = "Plating";
private readonly EntProtoId[] _prototypes = [WindowProtoId, WallProtoId, HumanProtoId];
private readonly List<Entity<DamageableComponent>> _damageables = new();
private readonly List<Entity<DamageableComponent, DestructibleComponent>> _destructbiles = new();
private DamageSpecifier _damage;
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private IPrototypeManager _protoMan = default!;
private IRobustRandom _random = default!;
private ITileDefinitionManager _tileDefMan = default!;
private DamageableSystem _damageable = default!;
private DestructibleSystem _destructible = default!;
private SharedMapSystem _map = default!;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
var mapdata = await _pair.CreateTestMap();
_entMan = server.ResolveDependency<IEntityManager>();
_protoMan = server.ResolveDependency<IPrototypeManager>();
_random = server.ResolveDependency<IRobustRandom>();
_tileDefMan = server.ResolveDependency<ITileDefinitionManager>();
_damageable = _entMan.System<DamageableSystem>();
_destructible = _entMan.System<DestructibleSystem>();
_map = _entMan.System<SharedMapSystem>();
if (!_protoMan.Resolve(DamageType, out var type))
return;
_damage = new DamageSpecifier(type, DamageAmount);
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
var plating = _tileDefMan[TileRef].TileId;
// 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.
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(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
}
}
for (var x = 0; x < EntityCount; x++)
{
var y = 0;
foreach (var protoId in _prototypes)
{
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
_entMan.SpawnEntity(protoId, coords);
y++;
}
}
var query = _entMan.EntityQueryEnumerator<DamageableComponent, DestructibleComponent>();
while (query.MoveNext(out var uid, out var damageable, out var destructible))
{
_damageables.Add((uid, damageable));
_destructbiles.Add((uid, damageable, destructible));
}
});
}
[Benchmark]
public async Task PerformDealDamage()
{
await _pair.Server.WaitPost(() =>
{
_damageable.ApplyDamageToAllEntities(_damageables, _damage);
});
}
[Benchmark]
public async Task PerformTestTriggers()
{
await _pair.Server.WaitPost(() =>
{
_destructible.TestAllTriggers(_destructbiles);
});
}
[Benchmark]
public async Task PerformTestBehaviors()
{
await _pair.Server.WaitPost(() =>
{
_destructible.TestAllBehaviors(_destructbiles);
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
-253
View File
@@ -1,253 +0,0 @@
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Benchmarks;
/// <summary>
/// Benchmarks the performance of different gas reactions.
/// Tests each reaction type with realistic gas mixtures to measure computational cost.
/// </summary>
[Virtual]
[GcServer(true)]
[MemoryDiagnoser]
public class GasReactionBenchmark
{
private const int Iterations = 1000;
private TestPair _pair = default!;
private AtmosphereSystem _atmosphereSystem = default!;
// Grid and tile for reactions that need a holder
private EntityUid _testGrid = default!;
private TileAtmosphere _testTile = default!;
// Reaction instances
private PlasmaFireReaction _plasmaFireReaction = default!;
private TritiumFireReaction _tritiumFireReaction = default!;
private FrezonProductionReaction _frezonProductionReaction = default!;
private FrezonCoolantReaction _frezonCoolantReaction = default!;
private AmmoniaOxygenReaction _ammoniaOxygenReaction = default!;
private N2ODecompositionReaction _n2oDecompositionReaction = default!;
private WaterVaporReaction _waterVaporReaction = default!;
// Gas mixtures for each reaction type
private GasMixture _plasmaFireMixture = default!;
private GasMixture _tritiumFireMixture = default!;
private GasMixture _frezonProductionMixture = default!;
private GasMixture _frezonCoolantMixture = default!;
private GasMixture _ammoniaOxygenMixture = default!;
private GasMixture _n2oDecompositionMixture = default!;
private GasMixture _waterVaporMixture = default!;
[GlobalSetup]
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
// Create test map and grid
var mapData = await _pair.CreateTestMap();
_testGrid = mapData.Grid;
await server.WaitPost(() =>
{
var entMan = server.ResolveDependency<IEntityManager>();
_atmosphereSystem = entMan.System<AtmosphereSystem>();
_plasmaFireReaction = new PlasmaFireReaction();
_tritiumFireReaction = new TritiumFireReaction();
_frezonProductionReaction = new FrezonProductionReaction();
_frezonCoolantReaction = new FrezonCoolantReaction();
_ammoniaOxygenReaction = new AmmoniaOxygenReaction();
_n2oDecompositionReaction = new N2ODecompositionReaction();
_waterVaporReaction = new WaterVaporReaction();
SetupGasMixtures();
SetupTile();
});
}
private void SetupGasMixtures()
{
// Plasma Fire: Plasma + Oxygen at high temperature
// Temperature must be > PlasmaMinimumBurnTemperature for reaction to occur
_plasmaFireMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.PlasmaMinimumBurnTemperature + 100f // ~673K
};
_plasmaFireMixture.AdjustMoles(Gas.Plasma, 20f);
_plasmaFireMixture.AdjustMoles(Gas.Oxygen, 100f);
// Tritium Fire: Tritium + Oxygen at high temperature
// Temperature must be > FireMinimumTemperatureToExist for reaction to occur
_tritiumFireMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.FireMinimumTemperatureToExist + 100f // ~473K
};
_tritiumFireMixture.AdjustMoles(Gas.Tritium, 20f);
_tritiumFireMixture.AdjustMoles(Gas.Oxygen, 100f);
// Frezon Production: Oxygen + Tritium + Nitrogen catalyst
// Optimal temperature for efficiency (80% of max efficiency temp)
_frezonProductionMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.FrezonProductionMaxEfficiencyTemperature * 0.8f // ~48K
};
_frezonProductionMixture.AdjustMoles(Gas.Oxygen, 50f);
_frezonProductionMixture.AdjustMoles(Gas.Tritium, 50f);
_frezonProductionMixture.AdjustMoles(Gas.Nitrogen, 10f);
// Frezon Coolant: Frezon + Nitrogen
// Temperature must be > FrezonCoolLowerTemperature (23.15K) for reaction to occur
_frezonCoolantMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C + 50f // ~343K
};
_frezonCoolantMixture.AdjustMoles(Gas.Frezon, 30f);
_frezonCoolantMixture.AdjustMoles(Gas.Nitrogen, 100f);
// Ammonia + Oxygen reaction (concentration-dependent, no temp requirement)
_ammoniaOxygenMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C + 100f // ~393K
};
_ammoniaOxygenMixture.AdjustMoles(Gas.Ammonia, 40f);
_ammoniaOxygenMixture.AdjustMoles(Gas.Oxygen, 40f);
// N2O Decomposition (no temperature requirement, just needs N2O moles)
_n2oDecompositionMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C + 100f // ~393K
};
_n2oDecompositionMixture.AdjustMoles(Gas.NitrousOxide, 100f);
// Water Vapor - needs water vapor to condense
_waterVaporMixture = new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C
};
_waterVaporMixture.AdjustMoles(Gas.WaterVapor, 50f);
}
private void SetupTile()
{
// Create a tile atmosphere to use as holder for all reactions
var testIndices = new Vector2i(0, 0);
_testTile = new TileAtmosphere(_testGrid, testIndices, new GasMixture(Atmospherics.CellVolume)
{
Temperature = Atmospherics.T20C
});
}
private static GasMixture CloneMixture(GasMixture original)
{
return new GasMixture(original);
}
[Benchmark]
public async Task PlasmaFireReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_plasmaFireMixture);
_plasmaFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task TritiumFireReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_tritiumFireMixture);
_tritiumFireReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task FrezonProductionReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_frezonProductionMixture);
_frezonProductionReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task FrezonCoolantReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_frezonCoolantMixture);
_frezonCoolantReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task AmmoniaOxygenReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_ammoniaOxygenMixture);
_ammoniaOxygenReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task N2ODecompositionReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_n2oDecompositionMixture);
_n2oDecompositionReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[Benchmark]
public async Task WaterVaporReaction()
{
await _pair.Server.WaitPost(() =>
{
for (var i = 0; i < Iterations; i++)
{
var mixture = CloneMixture(_waterVaporMixture);
_waterVaporReaction.React(mixture, _testTile, _atmosphereSystem, 1f);
}
});
}
[GlobalCleanup]
public async Task CleanupAsync()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
}
-3
View File
@@ -1,3 +0,0 @@
// Global usings for Content.Benchmarks
global using Robust.UnitTesting.Pool;
@@ -1,4 +1,3 @@
using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -51,8 +50,8 @@ namespace Content.Client.Access.UI
if (EntMan.TryGetComponent<AccessOverriderComponent>(Owner, out var accessOverrider))
{
// DeltaV - Sort Better
accessLevels = accessOverrider.AccessLevels.OrderBy(x => _prototypeManager.TryIndex<AccessLevelPrototype>(x.Id, out var access) ? access.GetAccessLevelName() : x.Id).ToList();
accessLevels = accessOverrider.AccessLevels;
accessLevels.Sort();
}
else
{
@@ -25,11 +25,11 @@ namespace Content.Client.Access.UI
public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessLevelPrototype>> accessLevels)
{
_accessButtons.Clear();
AccessLevelGrid.RemoveAllChildren();
AccessLevelGrid.DisposeAllChildren();
foreach (var access in accessLevels)
{
if (!protoManager.Resolve(access, out var accessLevel))
if (!protoManager.TryIndex(access, out var accessLevel))
{
continue;
}
@@ -76,7 +76,7 @@ namespace Content.Client.Access.UI
public void SetAllowedIcons(string currentJobIconId)
{
IconGrid.RemoveAllChildren();
IconGrid.DisposeAllChildren();
var jobIconButtonGroup = new ButtonGroup();
var i = 0;
@@ -84,12 +84,12 @@ namespace Content.Client.Access.UI
icons.Sort((x, y) => string.Compare(x.LocalizedJobName, y.LocalizedJobName, StringComparison.CurrentCulture));
foreach (var jobIcon in icons)
{
String styleBase = StyleClass.ButtonOpenBoth;
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
if (modulo == 0)
styleBase = StyleClass.ButtonOpenRight;
styleBase = StyleBase.ButtonOpenRight;
else if (modulo == JobIconColumnCount - 1)
styleBase = StyleClass.ButtonOpenLeft;
styleBase = StyleBase.ButtonOpenLeft;
// Generate buttons
var jobIconButton = new Button
@@ -57,7 +57,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
foreach (var accessGroup in _accessGroups)
{
if (!_protoManager.Resolve(accessGroup, out var accessGroupProto))
if (!_protoManager.TryIndex(accessGroup, out var accessGroupProto))
continue;
_groupedAccessLevels.Add(accessGroupProto, new());
@@ -65,13 +65,13 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
// Ensure that the 'general' access group is added to handle
// misc. access levels that aren't associated with any group
if (_protoManager.Resolve(GeneralAccessGroup, out var generalAccessProto))
if (_protoManager.TryIndex(GeneralAccessGroup, out var generalAccessProto))
_groupedAccessLevels.TryAdd(generalAccessProto, new());
// Assign known access levels with their associated groups
foreach (var accessLevel in _accessLevels)
{
if (!_protoManager.Resolve(accessLevel, out var accessLevelProto))
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto))
continue;
var assigned = false;
@@ -99,8 +99,8 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
private bool TryRebuildAccessGroupControls()
{
AccessGroupList.RemoveAllChildren();
AccessLevelChecklist.RemoveAllChildren();
AccessGroupList.DisposeAllChildren();
AccessLevelChecklist.DisposeAllChildren();
// No access level prototypes were assigned to any of the access level groups.
// Either the turret controller has no assigned access levels or their names were invalid.
@@ -119,11 +119,11 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
if (_groupedAccessLevels.Count > 1)
{
if (AccessGroupList.ChildCount == 0)
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenLeft);
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenLeft);
else if (_groupedAccessLevels.Count > 1 && AccessGroupList.ChildCount == (_groupedAccessLevels.Count - 1))
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenRight);
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenRight);
else
accessGroupButton.AddStyleClass(StyleClass.ButtonOpenBoth);
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenBoth);
}
accessGroupButton.Pressed = _accessGroupTabIndex == orderedAccessGroups.IndexOf(accessGroup);
@@ -165,7 +165,7 @@ public sealed partial class GroupedAccessLevelChecklist : BoxContainer
/// </summary>
public void RebuildAccessLevelsControls()
{
AccessLevelChecklist.RemoveAllChildren();
AccessLevelChecklist.DisposeAllChildren();
_accessLevelEntries.Clear();
// No access level prototypes were assigned to any of the access level groups
@@ -1,11 +1,9 @@
using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.IdCardConsoleComponent;
@@ -39,8 +37,7 @@ namespace Content.Client.Access.UI
if (EntMan.TryGetComponent<IdCardConsoleComponent>(Owner, out var idCard))
{
// DeltaV - Sort the access list
accessLevels = idCard.AccessLevels.OrderBy(x => _prototypeManager.TryIndex<AccessLevelPrototype>(x.Id, out var access) ? access.GetAccessLevelName() : x.Id).ToList();
accessLevels = idCard.AccessLevels;
}
else
{
@@ -56,6 +53,7 @@ namespace Content.Client.Access.UI
_window.CrewManifestButton.OnPressed += _ => SendMessage(new CrewManifestOpenUiMessage());
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
_window.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(TargetIdCardSlotId));
_window.OnToggleAccess += id => SendMessage(new IdCardConsoleToggleMessage(id)); // DeltaV
_window.OnClose += Close;
_window.OpenCentered();
@@ -77,7 +75,7 @@ namespace Content.Client.Access.UI
_window?.UpdateState(castState);
}
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, string newJobPrototype)
{
if (newFullName.Length > _maxNameLength)
newFullName = newFullName[.._maxNameLength];
@@ -1,7 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
MinSize="650 290">
<BoxContainer Orientation="Vertical">
<!-- Privileged and target IDs, crew manifest button. -->
<GridContainer Columns="2">
<GridContainer Columns="3" HorizontalExpand="True">
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
@@ -17,7 +16,6 @@
</BoxContainer>
</GridContainer>
<Control MinSize="0 8" />
<!-- Full name and job title editing. -->
<GridContainer Columns="3" HSeparationOverride="4">
<Label Name="FullNameLabel" Text="{Loc 'id-card-console-window-full-name-label'}" />
<LineEdit Name="FullNameLineEdit" HorizontalExpand="True" />
@@ -28,19 +26,10 @@
<Button Name="JobTitleSaveButton" Text="{Loc 'id-card-console-window-save-button'}" Disabled="True" />
</GridContainer>
<Control MinSize="0 8" />
<!-- Job preset selection, grant/revoke all access buttons. -->
<BoxContainer Margin="0 8 0 4">
<BoxContainer>
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" />
</BoxContainer>
<Control HorizontalExpand="True"/>
<BoxContainer>
<Button Name="SelectAllButton" Text="{Loc 'id-card-console-window-select-all-button'}" />
<Button Name="DeselectAllButton" Text="{Loc 'id-card-console-window-deselect-all-button'}" />
</BoxContainer>
</BoxContainer>
<!-- Individual access buttons -->
<GridContainer Columns="2">
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" />
</GridContainer>
<Control Name="AccessLevelControlContainer" />
</BoxContainer>
</DefaultWindow>
@@ -37,6 +37,8 @@ namespace Content.Client.Access.UI
// The job that will be picked if the ID doesn't have a job on the station.
private static ProtoId<JobPrototype> _defaultJob = "Passenger";
public event Action<ProtoId<AccessLevelPrototype>>? OnToggleAccess; // DeltaV
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
{
@@ -79,37 +81,19 @@ namespace Content.Client.Access.UI
JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobPrototypeIds.Count - 1);
}
SelectAllButton.OnPressed += _ =>
{
SetAllAccess(true);
SubmitData();
};
DeselectAllButton.OnPressed += _ =>
{
SetAllAccess(false);
SubmitData();
};
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
_accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons);
foreach (var (id, button) in _accessButtons.ButtonsList)
{
button.OnPressed += _ => SubmitData();
var copied = id; // DeltaV
button.OnPressed += _ => OnToggleAccess?.Invoke(id);
}
}
/// <param name="enabled">If true, every individual access button will be pressed. If false, each will be depressed.</param>
private void SetAllAccess(bool enabled)
{
foreach (var button in _accessButtons.ButtonsList.Values)
{
if (!button.Disabled && button.Pressed != enabled)
button.Pressed = enabled;
}
}
// DeltaV - removed as part of job preset access fix
// private void ClearAllAccess()
private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
{
@@ -121,34 +105,28 @@ namespace Content.Client.Access.UI
JobTitleLineEdit.Text = Loc.GetString(job.Name);
args.Button.SelectId(args.Id);
SetAllAccess(false);
// this is a sussy way to do this
foreach (var access in job.Access)
{
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
}
// DeltaV - start of job preset access fix
SubmitData();
var targetAccesses = job.Access.ToHashSet();
foreach (var group in job.AccessGroups)
{
if (!_prototypeManager.Resolve(group, out AccessGroupPrototype? groupPrototype))
if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype))
{
continue;
}
foreach (var access in groupPrototype.Tags)
{
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
}
targetAccesses.UnionWith(groupPrototype.Tags);
}
SubmitData();
// this is a sussy way to do this
foreach (var (id, button) in _accessButtons.ButtonsList)
{
if (!button.Disabled && button.Pressed != targetAccesses.Contains(id))
{
OnToggleAccess?.Invoke(id);
}
}
// DeltaV - end of job preset access fix
}
public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
@@ -222,7 +200,7 @@ namespace Content.Client.Access.UI
FullNameLineEdit.Text,
JobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
[], // DeltaV - don't send list of accesses
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
}
}
+3 -23
View File
@@ -33,7 +33,6 @@ namespace Content.Client.Actions
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceManager _resources = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
public event Action<EntityUid>? OnActionAdded;
public event Action<EntityUid>? OnActionRemoved;
@@ -288,27 +287,8 @@ namespace Content.Client.Actions
continue;
}
if (assignmentNode is SequenceDataNode sequenceAssignments)
{
try
{
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(sequenceAssignments, notNullableOverride: true);
foreach (var index in nodeAssignments)
{
assignments.Add(new SlotAssignment(index.Hotbar, index.Slot, actionId));
}
}
catch (Exception ex)
{
Log.Error($"Failed to parse action assignments: {ex}");
}
}
AddActionDirect((user, actions), actionId);
}
AssignSlot?.Invoke(assignments);
}
// DeltaV - begin changes
@@ -421,10 +401,10 @@ namespace Content.Client.Actions
// this is the actual entity-world targeting magic
EntityUid? targetEnt = null;
if (TryComp<EntityTargetActionComponent>(ent, out var entity) &&
args.Input.EntityUid is { Valid: true } entityUid &&
ValidateEntityTarget(user, entityUid, (uid, entity)))
args.Input.EntityUid != null &&
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
{
targetEnt = entityUid;
targetEnt = args.Input.EntityUid;
}
if (action.ClientExclusive)
@@ -23,10 +23,9 @@ namespace Content.Client.Actions.UI
public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null)
{
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSystem;
_gameTiming = IoCManager.Resolve<IGameTiming>();
SetOnlyStyleClass(StyleClass.TooltipPanel);
SetOnlyStyleClass(StyleNano.StyleClassTooltipPanel);
BoxContainer vbox;
AddChild(vbox = new BoxContainer
@@ -37,7 +36,7 @@ namespace Content.Client.Actions.UI
var nameLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipTitle }
StyleClasses = {StyleNano.StyleClassTooltipActionTitle}
};
nameLabel.SetMessage(name);
vbox.AddChild(nameLabel);
@@ -47,7 +46,7 @@ namespace Content.Client.Actions.UI
var description = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc }
StyleClasses = {StyleNano.StyleClassTooltipActionDescription}
};
description.SetMessage(desc);
vbox.AddChild(description);
@@ -56,7 +55,7 @@ namespace Content.Client.Actions.UI
vbox.AddChild(_cooldownLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc },
StyleClasses = {StyleNano.StyleClassTooltipActionCooldown},
Visible = false
});
@@ -65,7 +64,7 @@ namespace Content.Client.Actions.UI
var requiresLabel = new RichTextLabel
{
MaxWidth = TooltipTextMaxWidth,
StyleClasses = { StyleClass.TooltipDesc }
StyleClasses = {StyleNano.StyleClassTooltipActionRequirements}
};
if (!FormattedMessage.TryFromMarkup("[color=#635c5c]" + requires + "[/color]", out var markup))
@@ -1,5 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc admin-camera-window-title-placeholder}"
SetSize="425 550"
MinSize="200 225">
MinSize="200 225"
Name="Window">
</DefaultWindow>
@@ -7,7 +7,7 @@
</PanelContainer.PanelOverride>
<Control HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="600">
<PanelContainer StyleClasses="BackgroundPanel" />
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical" Margin="4">
<RichTextLabel Name="Description" />
@@ -12,19 +12,17 @@ namespace Content.Client.Administration.UI.AdminRemarks;
[GenerateTypedNameReferences]
public sealed partial class AdminMessagePopupWindow : Control
{
[Dependency] private readonly IStylesheetManager _styleMan = default!;
private float _timer = float.MaxValue;
public event Action? OnDismissPressed;
public event Action? OnAcceptPressed;
public AdminMessagePopupWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Stylesheet = _styleMan.SheetSystem;
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
AcceptButton.OnPressed += OnAcceptButtonPressed;
DismissButton.OnPressed += OnDismissButtonPressed;
@@ -51,8 +49,7 @@ public sealed partial class AdminMessagePopupWindow : Control
MessageContainer.AddChild(new AdminMessagePopupMessage(message));
}
Description.SetMessage(
FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
Description.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
}
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
@@ -24,7 +24,7 @@ namespace Content.Client.Administration.UI.BanPanel;
[GenerateTypedNameReferences]
public sealed partial class BanPanel : DefaultWindow
{
public event Action<Ban>? BanSubmitted;
public event Action<string?, (IPAddress, int)?, bool, ImmutableTypedHwid?, bool, uint, string, NoteSeverity, string[]?, bool>? BanSubmitted;
public event Action<string>? PlayerChanged;
private string? PlayerUsername { get; set; }
private (IPAddress, int)? IpAddress { get; set; }
@@ -37,8 +37,8 @@ public sealed partial class BanPanel : DefaultWindow
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated.
// Role group name -> the role buttons themselves.
private readonly Dictionary<string, List<(Button, IPrototype)>> _roleCheckboxes = new();
private readonly ISawmill _banPanelSawmill;
private readonly Dictionary<string, List<Button>> _roleCheckboxes = new();
private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -79,7 +79,7 @@ public sealed partial class BanPanel : DefaultWindow
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_banPanelSawmill = _logManager.GetSawmill("admin.banpanel");
_banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
PlayerCheckbox.OnPressed += _ =>
@@ -110,7 +110,7 @@ public sealed partial class BanPanel : DefaultWindow
TypeOption.SelectId(args.Id);
OnTypeChanged();
};
LastConnCheckbox.OnPressed += _ =>
LastConnCheckbox.OnPressed += args =>
{
IpLine.ModulateSelfOverride = null;
HwidLine.ModulateSelfOverride = null;
@@ -164,7 +164,7 @@ public sealed partial class BanPanel : DefaultWindow
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
.OrderBy(x => x.ID);
CreateRoleGroup(AntagPrototype.GroupName, AntagPrototype.GroupColor, antagRoles);
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
}
/// <summary>
@@ -226,7 +226,7 @@ public sealed partial class BanPanel : DefaultWindow
var roleGroupCheckbox = new Button
{
Name = $"{groupName}GroupCheckbox",
Text = Loc.GetString("role-bans-ban-group"),
Text = "Ban all",
Margin = new Thickness(0, 0, 5, 0),
ToggleMode = true,
};
@@ -236,14 +236,14 @@ public sealed partial class BanPanel : DefaultWindow
{
foreach (var role in _roleCheckboxes[groupName])
{
role.Item1.Pressed = args.Pressed;
role.Pressed = args.Pressed;
}
if (args.Pressed)
{
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banPanelSawmill
_banpanelSawmill
.Warning("Departmental role ban severity could not be parsed from config!");
return;
}
@@ -255,14 +255,14 @@ public sealed partial class BanPanel : DefaultWindow
{
foreach (var button in roleButtons)
{
if (button.Item1.Pressed)
if (button.Pressed)
return;
}
}
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banPanelSawmill
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
return;
}
@@ -294,44 +294,30 @@ public sealed partial class BanPanel : DefaultWindow
}
/// <summary>
/// Adds a toggle button specifically for one "role" in a "group"
/// Adds a checkbutton specifically for one "role" in a "group"
/// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
/// </summary>
private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
{
var roleCheckboxContainer = new BoxContainer();
var roleToggleButton = new Button
var roleCheckButton = new Button
{
Name = role,
Name = $"{role}RoleCheckbox",
Text = role,
ToggleMode = true,
};
roleToggleButton.OnToggled += args =>
roleCheckButton.OnToggled += args =>
{
// Checks the role group checkbox if all the children are pressed
if (args.Pressed && _roleCheckboxes[group].All(e => e.Item1.Pressed))
if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
roleGroupCheckbox.Pressed = args.Pressed;
else
roleGroupCheckbox.Pressed = false;
};
IPrototype rolePrototype;
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype))
rolePrototype = jobPrototype;
else if (_protoMan.TryIndex<AntagPrototype>(role, out var antagPrototype))
rolePrototype = antagPrototype;
else
{
_banPanelSawmill.Error($"Adding a role checkbox for role {role}: role is not a JobPrototype or AntagPrototype.");
return;
}
// This is adding the icon before the role name
// TODO: This should not be using raw strings for prototypes as it means it won't be validated at all.
// // I know the ban manager is doing the same thing, but that should not leak into UI code.
if (jobPrototype is not null && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
// Yeah, this is sus, but having to split the functions up and stuff is worse imo.
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
{
var jobIconTexture = new TextureRect
{
@@ -343,12 +329,12 @@ public sealed partial class BanPanel : DefaultWindow
roleCheckboxContainer.AddChild(jobIconTexture);
}
roleCheckboxContainer.AddChild(roleToggleButton);
roleCheckboxContainer.AddChild(roleCheckButton);
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
_roleCheckboxes.TryAdd(group, []);
_roleCheckboxes[group].Add((roleToggleButton, rolePrototype));
_roleCheckboxes[group].Add(roleCheckButton);
}
public void UpdateBanFlag(bool newFlag)
@@ -391,7 +377,7 @@ public sealed partial class BanPanel : DefaultWindow
TimeLine.Text = args.Text;
if (!double.TryParse(args.Text, out var result))
{
ExpiresLabel.Text = Loc.GetString("ban-panel-expiry-error");
ExpiresLabel.Text = "err";
ErrorLevel |= ErrorLevelEnum.Minutes;
TimeLine.ModulateSelfOverride = Color.Red;
UpdateSubmitEnabled();
@@ -501,7 +487,7 @@ public sealed partial class BanPanel : DefaultWindow
newSeverity = serverSeverity;
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Server ban severity could not be parsed from config!");
}
@@ -514,7 +500,7 @@ public sealed partial class BanPanel : DefaultWindow
}
else
{
_banPanelSawmill
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
}
break;
@@ -559,51 +545,34 @@ public sealed partial class BanPanel : DefaultWindow
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
ProtoId<JobPrototype>[]? jobs = null;
ProtoId<AntagPrototype>[]? antags = null;
string[]? roles = null;
if (TypeOption.SelectedId == (int) Types.Role)
{
var jobList = new List<ProtoId<JobPrototype>>();
var antagList = new List<ProtoId<AntagPrototype>>();
var rolesList = new List<string>();
if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty");
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
{
if (button.Item1 is { Pressed: true, Name: not null })
if (button is { Pressed: true, Text: not null })
{
switch (button.Item2)
{
case JobPrototype:
jobList.Add(button.Item2.ID);
break;
case AntagPrototype:
antagList.Add(button.Item2.ID);
break;
}
rolesList.Add(button.Text);
}
}
if (jobList.Count + antagList.Count == 0)
if (rolesList.Count == 0)
{
Tabs.CurrentTab = (int) TabNumbers.Roles;
return;
}
jobs = jobList.ToArray();
antags = antagList.ToArray();
roles = rolesList.ToArray();
}
if (TypeOption.SelectedId == (int) Types.None)
{
TypeOption.ModulateSelfOverride = Color.Red;
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
return;
}
@@ -615,7 +584,6 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.GrabKeyboardFocus();
ReasonTextEdit.ModulateSelfOverride = Color.Red;
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
return;
}
@@ -624,7 +592,6 @@ public sealed partial class BanPanel : DefaultWindow
ButtonResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
SubmitButton.ModulateSelfOverride = Color.Red;
SubmitButton.Text = Loc.GetString("ban-panel-confirm");
return;
}
@@ -633,22 +600,7 @@ public sealed partial class BanPanel : DefaultWindow
var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null;
var severity = (NoteSeverity) SeverityOption.SelectedId;
var erase = EraseCheckbox.Pressed;
var ban = new Ban(
player,
IpAddress,
useLastIp,
Hwid,
useLastHwid,
(uint)(TimeEntered * Multiplier),
reason,
severity,
jobs,
antags,
erase
);
BanSubmitted?.Invoke(ban);
BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles, erase);
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -14,7 +14,8 @@ public sealed class BanPanelEui : BaseEui
{
BanPanel = new BanPanel();
BanPanel.OnClose += () => SendMessage(new CloseEuiMessage());
BanPanel.BanSubmitted += ban => SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(ban));
BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase)
=> SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase));
BanPanel.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player));
}
@@ -18,7 +18,7 @@
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
<Control HorizontalExpand="True" />
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
<Button Visible="False" Access="Public" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
<controls:ConfirmButton Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
<controls:ConfirmButton Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
@@ -3,16 +3,12 @@ using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.CustomControls;
[GenerateTypedNameReferences]
public sealed partial class PlayerListEntry : BoxContainer
{
private readonly ResPath _pinnedResPath = new("/Textures/Interface/Bwoink/pinned.png");
private readonly ResPath _unPinnedResPath = new("/Textures/Interface/Bwoink/un_pinned.png");
public PlayerListEntry()
{
RobustXamlLoader.Load(this);
@@ -46,6 +42,15 @@ public sealed partial class PlayerListEntry : BoxContainer
private void UpdatePinButtonTexture(bool isPinned)
{
PlayerEntryPinButton.TexturePath = isPinned ? _pinnedResPath.CanonPath : _unPinnedResPath.CanonPath;
if (isPinned)
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned);
}
else
{
PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned);
PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned);
}
}
}
@@ -88,7 +88,7 @@ public sealed class AdminLogsEui : BaseEui
ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters
{
Maximized = false,
Title = Loc.GetString("admin-logs-title"),
Title = "Admin Logs",
Monitor = monitor,
Width = 1100,
Height = 400
@@ -67,7 +67,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// </summary>
public void UpdateReagents()
{
ReagentList.RemoveAllChildren();
ReagentList.DisposeAllChildren();
if (_selectedSolution == null || _solutions == null)
return;
@@ -92,7 +92,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// <param name="solution">The selected solution.</param>
private void UpdateVolumeBox(Solution solution)
{
VolumeBox.RemoveAllChildren();
VolumeBox.DisposeAllChildren();
var volumeLabel = new Label();
volumeLabel.HorizontalExpand = true;
@@ -131,7 +131,7 @@ namespace Content.Client.Administration.UI.ManageSolutions
/// <param name="solution">The selected solution.</param>
private void UpdateThermalBox(Solution solution)
{
ThermalBox.RemoveAllChildren();
ThermalBox.DisposeAllChildren();
var heatCap = solution.GetHeatCapacity(null);
var specificHeatLabel = new Label();
specificHeatLabel.HorizontalExpand = true;
@@ -5,7 +5,7 @@
<BoxContainer Orientation="Vertical" Name="Notes" Access="Public" VerticalExpand="True"/>
</ScrollContainer>
<Button Name="ShowMoreButton" Text="{Loc admin-notes-show-more}" Visible="False" HorizontalAlignment="Center" />
<Button Name="NewNoteButton" Access="Public" Text="{Loc admin-notes-new-note}" Disabled="True" />
<Button Name="NewNoteButton" Text="{Loc admin-notes-new-note}" Disabled="True" />
</BoxContainer>
</PanelContainer>
</Control>
@@ -82,11 +82,7 @@ public sealed partial class AdminNotesLine : BoxContainer
if (Note.UnbannedTime is not null)
{
ExtraLabel.Text = Loc.GetString(
"admin-notes-unbanned",
("admin", Note.UnbannedByName ?? "[error]"),
("date", Note.UnbannedTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))
);
ExtraLabel.Text = Loc.GetString("admin-notes-unbanned", ("admin", Note.UnbannedByName ?? "[error]"), ("date", Note.UnbannedTime));
ExtraLabel.Visible = true;
}
else if (Note.ExpiryTime is not null)
@@ -143,7 +139,7 @@ public sealed partial class AdminNotesLine : BoxContainer
private string FormatRoleBanMessage()
{
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new []{"unknown"})} ");
return FormatBanMessageCommon(banMessage);
}
@@ -3,7 +3,7 @@
Title="Loading..."
MinSize="400 200">
<BoxContainer Orientation="Vertical" Margin="4">
<TextEdit Name="NoteTextEdit" Access="Public" HorizontalExpand="True" VerticalExpand="True" />
<TextEdit Name="NoteTextEdit" HorizontalExpand="True" VerticalExpand="True" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
@@ -17,7 +17,7 @@
ToolTip="{Loc admin-note-editor-secret-tooltip}" />
<CheckBox Name="PermanentCheckBox" Pressed="True" Text="{Loc admin-note-editor-expiry-checkbox}"
ToolTip="{Loc admin-note-editor-expiry-checkbox-tooltip}" />
<Button Name="SubmitButton" Access="Public" Text="{Loc admin-note-editor-submit}" HorizontalAlignment="Right" />
<Button Name="SubmitButton" Text="{Loc admin-note-editor-submit}" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -133,7 +133,7 @@ public sealed partial class NoteEdit : FancyWindow
private bool IsSecret { get; set; }
private NoteType NoteType { get; set; }
public NoteSeverity? NoteSeverity
private NoteSeverity? NoteSeverity
{
get => _noteSeverity;
set
@@ -27,7 +27,7 @@ namespace Content.Client.Administration.UI
[Dependency] private readonly IClientAdminManager _adminManager = default!;
private readonly Menu _menu;
private readonly List<BaseWindow> _subWindows = new();
private readonly List<DefaultWindow> _subWindows = new();
private Dictionary<int, PermissionsEuiState.AdminRankData> _ranks =
new();
@@ -216,7 +216,7 @@ namespace Content.Client.Administration.UI
var titleControl = new Label { Text = admin.Title ?? Loc.GetString("permissions-eui-edit-admin-title-control-text").ToLowerInvariant() };
if (admin.Title == null) // none
{
titleControl.StyleClasses.Add(StyleClass.Italic);
titleControl.StyleClasses.Add(StyleBase.StyleClassItalic);
}
al.AddChild(titleControl);
@@ -240,7 +240,7 @@ namespace Content.Client.Administration.UI
var rankControl = new Label { Text = rank };
if (italic)
{
rankControl.StyleClasses.Add(StyleClass.Italic);
rankControl.StyleClasses.Add(StyleBase.StyleClassItalic);
}
al.AddChild(rankControl);
@@ -340,9 +340,10 @@ namespace Content.Client.Administration.UI
tab.AddChild(adminVBox);
tab.AddChild(rankVBox);
ContentsContainer.AddChild(tab);
ContentsContainer.MinSize = new(600, 400);
Contents.AddChild(tab);
}
protected override Vector2 ContentsMinimumSize => new Vector2(600, 400);
}
private sealed class EditAdminWindow : DefaultWindow
@@ -418,21 +419,21 @@ namespace Content.Client.Administration.UI
var inherit = new Button
{
Text = "I",
StyleClasses = { StyleClass.ButtonOpenRight },
StyleClasses = { StyleBase.ButtonOpenRight },
Disabled = disable,
Group = group,
};
var sub = new Button
{
Text = "-",
StyleClasses = { StyleClass.ButtonOpenBoth },
StyleClasses = { StyleBase.ButtonOpenBoth },
Disabled = disable,
Group = group
};
var plus = new Button
{
Text = "+",
StyleClasses = { StyleClass.ButtonOpenLeft },
StyleClasses = { StyleBase.ButtonOpenLeft },
Disabled = disable,
Group = group
};
@@ -478,7 +479,7 @@ namespace Content.Client.Administration.UI
bottomButtons.AddChild(SaveButton);
ContentsContainer.AddChild(new BoxContainer
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
@@ -604,7 +605,7 @@ namespace Content.Client.Administration.UI
bottomButtons.AddChild(SaveButton);
ContentsContainer.AddChild(new BoxContainer
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
@@ -8,8 +8,10 @@
MinSize="50 50">
<BoxContainer Orientation="Vertical">
<GridContainer Columns="3">
<cc:UICommandButton Command="kick" Text="{Loc admin-player-actions-window-title}" WindowType="{x:Type at:PlayerActionsWindow}" />
<cc:CommandButton Command="banpanel" Text="{Loc admin-player-actions-window-ban}" />
<cc:CommandButton Command="aghost" Text="{Loc admin-player-actions-window-admin-ghost}" />
<cc:UICommandButton Command="tpto" Text="{Loc admin-player-actions-window-teleport}" WindowType="{x:Type at:TeleportWindow}" />
<cc:CommandButton Command="permissions" Text="{Loc admin-player-actions-window-permissions}" />
<cc:CommandButton Command="announceui" Text="{Loc admin-player-actions-window-announce}"/>
<cc:UICommandButton Command="callshuttle" Text="{Loc admin-player-actions-window-shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
@@ -0,0 +1,19 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc admin-player-actions-reason}" MinWidth="100" />
<Control MinWidth="50" />
<LineEdit Name="ReasonLine" MinWidth="100" HorizontalExpand="True" />
</BoxContainer>
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
<BoxContainer Orientation="Horizontal">
<controls:ConfirmButton Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
<Button Name="SubmitAHelpButton" Text="{Loc admin-player-actions-ahelp}" Disabled="True"/>
<controls:ConfirmButton Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
</BoxContainer>
</BoxContainer>
</DefaultWindow>
@@ -0,0 +1,64 @@
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Tabs.AdminTab
{
[GenerateTypedNameReferences]
[UsedImplicitly]
public sealed partial class PlayerActionsWindow : DefaultWindow
{
private PlayerInfo? _selectedPlayer;
public PlayerActionsWindow()
{
RobustXamlLoader.Load(this);
SubmitKickButton.OnPressed += SubmitKickButtonOnPressed;
SubmitAHelpButton.OnPressed += SubmitAhelpButtonOnPressed;
SubmitRespawnButton.OnPressed += SubmitRespawnButtonOnPressed;
PlayerList.OnSelectionChanged += OnListOnOnSelectionChanged;
}
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
{
_selectedPlayer = obj;
var disableButtons = _selectedPlayer == null;
SubmitKickButton.Disabled = disableButtons;
SubmitAHelpButton.Disabled = disableButtons;
SubmitRespawnButton.Disabled = disableButtons;
}
private void SubmitKickButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"kick \"{_selectedPlayer.Username}\" \"{CommandParsing.Escape(ReasonLine.Text)}\"");
}
private void SubmitAhelpButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"openahelp \"{_selectedPlayer.SessionId}\"");
}
private void SubmitRespawnButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"respawn \"{_selectedPlayer.Username}\"");
}
}
}
@@ -0,0 +1,9 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc admin-ui-teleport}" MinSize="425 230">
<BoxContainer Orientation="Vertical">
<cc:PlayerListControl Name="PlayerList" />
<Button Name="SubmitButton" Text="{Loc admin-ui-teleport}" />
</BoxContainer>
</DefaultWindow>
@@ -0,0 +1,38 @@
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
namespace Content.Client.Administration.UI.Tabs.AdminTab
{
[GenerateTypedNameReferences]
[UsedImplicitly]
public sealed partial class TeleportWindow : DefaultWindow
{
private PlayerInfo? _selectedPlayer;
protected override void EnteredTree()
{
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
PlayerList.OnSelectionChanged += OnListOnOnSelectionChanged;
}
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
{
_selectedPlayer = obj;
SubmitButton.Disabled = _selectedPlayer == null;
}
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_selectedPlayer == null)
return;
// Execute command
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"tpto \"{_selectedPlayer.Username}\"");
}
}
}
@@ -1,40 +0,0 @@
using Robust.Client.Graphics;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Anomaly;
/// <summary>
/// This component creates and handles the drawing of a ScreenTexture to be used on the Anomaly Scanner
/// for an indicator of Anomaly Severity.
/// </summary>
/// <remarks>
/// In the future I would like to make this a more generic "DynamicTextureComponent" that can contain a dictionary
/// of texture components like "Bar(offset, size, minimumValue, maximumValue, AppearanceKey, LayerMapKey)" that can
/// just draw a bar or other basic drawn element that will show up on a texture layer.
/// </remarks>
[RegisterComponent]
[Access(typeof(AnomalyScannerSystem))]
public sealed partial class AnomalyScannerScreenComponent : Component
{
/// <summary>
/// This is the texture drawn as a layer on the Anomaly Scanner device.
/// </summary>
public OwnedTexture? ScreenTexture;
/// <summary>
/// A small buffer that we can reuse to draw the severity bar.
/// </summary>
public Rgba32[]? BarBuf;
/// <summary>
/// The position of the top-left of the severity bar in pixels.
/// </summary>
[DataField(readOnly: true)]
public Vector2i Offset = new Vector2i(12, 17);
/// <summary>
/// The width and height of the severity bar in pixels.
/// </summary>
[DataField(readOnly: true)]
public Vector2i Size = new Vector2i(10, 3);
}
@@ -1,110 +0,0 @@
using System.Numerics;
using Content.Shared.Anomaly;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Anomaly;
/// <inheritdoc cref="SharedAnomalyScannerSystem"/>
public sealed class AnomalyScannerSystem : SharedAnomalyScannerSystem
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
private const float MaxHueDegrees = 360f;
private const float GreenHueDegrees = 110f;
private const float RedHueDegrees = 0f;
private const float GreenHue = GreenHueDegrees / MaxHueDegrees;
private const float RedHue = RedHueDegrees / MaxHueDegrees;
// Just an array to initialize the pixels of a new OwnedTexture
private static readonly Rgba32[] EmptyTexture = new Rgba32[32*32];
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnomalyScannerScreenComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<AnomalyScannerScreenComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<AnomalyScannerScreenComponent, AppearanceChangeEvent>(OnScannerAppearanceChanged);
}
private void OnComponentInit(Entity<AnomalyScannerScreenComponent> ent, ref ComponentInit args)
{
if(!_sprite.TryGetLayer(ent.Owner, AnomalyScannerVisualLayers.Base, out var layer, true))
return;
// Allocate the OwnedTexture
ent.Comp.ScreenTexture = _clyde.CreateBlankTexture<Rgba32>(layer.PixelSize);
if (layer.PixelSize.X < ent.Comp.Offset.X + ent.Comp.Size.X ||
layer.PixelSize.Y < ent.Comp.Offset.Y + ent.Comp.Size.Y)
{
// If the bar doesn't fit, just bail here, ScreenTexture and BarBuf will remain null, and appearance updates
// will do nothing.
DebugTools.Assert(false, "AnomalyScannerScreenComponent: Bar does not fit within sprite");
return;
}
// Initialize the texture
ent.Comp.ScreenTexture.SetSubImage((0, 0), layer.PixelSize, new ReadOnlySpan<Rgba32>(EmptyTexture));
// Initialize bar drawing buffer
ent.Comp.BarBuf = new Rgba32[ent.Comp.Size.X * ent.Comp.Size.Y];
}
private void OnComponentStartup(Entity<AnomalyScannerScreenComponent> ent, ref ComponentStartup args)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
_sprite.LayerSetTexture((ent, sprite), AnomalyScannerVisualLayers.Screen, ent.Comp.ScreenTexture);
}
private void OnScannerAppearanceChanged(Entity<AnomalyScannerScreenComponent> ent, ref AppearanceChangeEvent args)
{
if (args.Sprite is null || ent.Comp.ScreenTexture is null || ent.Comp.BarBuf is null)
return;
args.AppearanceData.TryGetValue(AnomalyScannerVisuals.AnomalySeverity, out var severityObj);
if (severityObj is not float severity)
severity = 0;
// Get the bar length
var barLength = (int)(severity * ent.Comp.Size.X);
// Calculate the bar color
// Hue "angle" of two colors to interpolate between depending on severity
// Just a lerp from Green hue at severity = 0.5 to Red hue at 1.0
var hue = Math.Clamp(2*GreenHue * (1 - severity), RedHue, GreenHue);
var color = new Rgba32(Color.FromHsv(new Vector4(hue, 1f, 1f, 1f)).RGBA);
var transparent = new Rgba32(0, 0, 0, 255);
for(var y = 0; y < ent.Comp.Size.Y; y++)
{
for (var x = 0; x < ent.Comp.Size.X; x++)
{
ent.Comp.BarBuf[y*ent.Comp.Size.X + x] = x < barLength ? color : transparent;
}
}
// Copy the buffer to the texture
try
{
ent.Comp.ScreenTexture.SetSubImage(
ent.Comp.Offset,
ent.Comp.Size,
new ReadOnlySpan<Rgba32>(ent.Comp.BarBuf)
);
}
catch (IndexOutOfRangeException)
{
Log.Warning($"Bar dimensions out of bounds with the texture on entity {ent.Owner}");
}
}
}
+1 -2
View File
@@ -7,7 +7,7 @@ using Robust.Shared.Timing;
namespace Content.Client.Anomaly;
public sealed partial class AnomalySystem : SharedAnomalySystem
public sealed class AnomalySystem : SharedAnomalySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly FloatingVisualizerSystem _floating = default!;
@@ -24,7 +24,6 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
}
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
{
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
@@ -14,7 +14,7 @@
VerticalExpand="True"
Margin="0 0 0 0"
VerticalAlignment="Center">
<Label Text="{Loc 'anomaly-generator-fuel-display'}" StyleClasses="highlight" />
<Label Text="{Loc 'anomaly-generator-fuel-display'}" StyleClasses="StatusFieldTitle" />
<ProgressBar Name="FuelBar"
HorizontalExpand="True"
MaxValue="1"
@@ -27,8 +27,8 @@
Text="0 %" />
</ProgressBar>
</BoxContainer>
<RichTextLabel Name="CooldownLabel" StyleClasses="highlight" />
<RichTextLabel Name="ReadyLabel" StyleClasses="highlight" />
<RichTextLabel Name="CooldownLabel" StyleClasses="StatusFieldTitle" />
<RichTextLabel Name="ReadyLabel" StyleClasses="StatusFieldTitle" />
</BoxContainer>
<!--Sprite View-->
<PanelContainer Margin="12 0 0 0"
+1 -1
View File
@@ -338,7 +338,7 @@ namespace Content.Client.Arcade
menuInnerPanel.AddChild(menuContainer);
#endregion
ContentsContainer.AddChild(_mainPanel);
Contents.AddChild(_mainPanel);
CanKeyboardFocus = true;
}
@@ -86,7 +86,7 @@ namespace Content.Client.Arcade
newGame.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
grid.AddChild(newGame);
ContentsContainer.AddChild(grid);
Contents.AddChild(grid);
}
private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage message)
-1
View File
@@ -1,4 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Content.Tests")]
[assembly: InternalsVisibleTo("Content.IntegrationTests")]
+1 -1
View File
@@ -134,7 +134,7 @@ public sealed class AlignAtmosPipeLayers : SnapgridCenter
var newProtoId = altPrototypes[(int)layer];
if (!_protoManager.Resolve(newProtoId, out var newProto))
if (!_protoManager.TryIndex(newProtoId, out var newProto))
return;
if (newProto.Type != ConstructionType.Structure)
@@ -1,5 +0,0 @@
using Content.Shared.Atmos.EntitySystems;
namespace Content.Client.Atmos.EntitySystems;
public sealed class DeltaPressureSystem : SharedDeltaPressureSystem;
@@ -1,6 +1,6 @@
using Content.Client.Atmos.Monitor.UI.Widgets;
using Content.Client.Message;
using Content.Client.Stylesheets.Palette;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
@@ -17,7 +17,7 @@ namespace Content.Client.Atmos.Monitor.UI;
public sealed partial class AirAlarmWindow : FancyWindow
{
public event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
public event Action<IAtmosDeviceData>? AtmosDeviceDataCopied;
public event Action<IAtmosDeviceData>? AtmosDeviceDataCopied;
public event Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
public event Action<AirAlarmMode>? AirAlarmModeChanged;
public event Action<bool>? AutoModeChanged;
@@ -131,7 +131,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
case GasVentPumpData pump:
if (!_pumps.TryGetValue(addr, out var pumpControl))
{
var control = new PumpControl(pump, addr);
var control= new PumpControl(pump, addr);
control.PumpDataChanged += AtmosDeviceDataChanged;
control.PumpDataCopied += AtmosDeviceDataCopied;
_pumps.Add(addr, control);
@@ -186,9 +186,11 @@ public sealed partial class AirAlarmWindow : FancyWindow
{
return curAlarm switch
{
AtmosAlarmType.Danger => Palettes.Status.Critical,
AtmosAlarmType.Warning => Palettes.Status.Warning,
_ => Palettes.Status.Good,
AtmosAlarmType.Danger => StyleNano.DangerousRedFore,
AtmosAlarmType.Warning => StyleNano.ConcerningOrangeFore,
_ => StyleNano.GoodGreenFore,
};
}
}
@@ -1,4 +1,3 @@
using Content.Client.Stylesheets;
using Content.Shared.Atmos.Monitor;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@@ -31,10 +30,7 @@ public sealed partial class ThresholdBoundControl : BoxContainer
public void SetValue(float value)
{
_value = value;
if (!CSpinner.HasKeyboardFocus())
{
CSpinner.Value = ScaledValue;
}
CSpinner.Value = ScaledValue;
}
public void SetEnabled(bool enabled)
@@ -43,11 +39,11 @@ public sealed partial class ThresholdBoundControl : BoxContainer
if (enabled)
{
CBoundLabel.RemoveStyleClass(StyleClass.LabelWeak);
CBoundLabel.RemoveStyleClass("Disabled");
}
else
{
CBoundLabel.SetOnlyStyleClass(StyleClass.LabelWeak);
CBoundLabel.SetOnlyStyleClass("Disabled");
}
}
@@ -4,7 +4,7 @@
MinSize="480 400" Title="Canister">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc comp-gas-canister-ui-canister-status}" StyleClasses="LabelHeading"/>
<Label Text="{Loc comp-gas-canister-ui-canister-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc comp-gas-canister-ui-canister-pressure}"/>
<Label Name="CanisterPressureLabel" Align="Center" HorizontalExpand="True"/>
@@ -16,7 +16,7 @@
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc comp-gas-canister-ui-holding-tank-status}" StyleClasses="LabelHeading"/>
<Label Text="{Loc comp-gas-canister-ui-holding-tank-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc comp-gas-canister-ui-holding-tank-label}"/>
<Label Name="TankLabelLabel" Text="{Loc comp-gas-canister-ui-holding-tank-label-empty}" Align="Center" HorizontalExpand="True"/>
@@ -29,7 +29,7 @@
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc comp-gas-canister-ui-release-valve-status}" StyleClasses="LabelHeading"/>
<Label Text="{Loc comp-gas-canister-ui-release-valve-status}" FontColorOverride="{x:Static s:StyleNano.NanoGold}" StyleClasses="LabelBig"/>
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical">
<Label Text="{Loc comp-gas-canister-ui-release-pressure}"/>
@@ -58,7 +58,7 @@ public sealed class JukeboxBoundUserInterface : BoundUserInterface
_menu.SetAudioStream(jukebox.AudioStream);
if (_protoManager.Resolve(jukebox.SelectedSongId, out var songProto))
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto))
{
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString());
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);
@@ -1,4 +1,3 @@
using System.Linq; // DeltaV
using Content.Shared.Audio.Jukebox;
using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
@@ -89,7 +88,7 @@ public sealed partial class JukeboxMenu : FancyWindow
{
MusicList.Clear();
foreach (var entry in jukeboxProtos.OrderBy(x => x.Name)) // DeltaV - Sort the jukebox list
foreach (var entry in jukeboxProtos)
{
MusicList.AddItem(entry.Name, metadata: entry.ID);
}
+1 -1
View File
@@ -39,7 +39,7 @@ public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
if (powered
&& sign.Current != null
&& _prototypeManager.Resolve(sign.Current, out var proto))
&& _prototypeManager.TryIndex(sign.Current, out var proto))
{
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
sprite.LayerSetShader(0, "unshaded");
@@ -35,7 +35,7 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
public void Update(ProtoId<BarSignPrototype>? sign)
{
if (_prototype.Resolve(sign, out var signPrototype))
if (_prototype.TryIndex(sign, out var signPrototype))
_menu?.UpdateState(signPrototype);
}
@@ -7,7 +7,10 @@
Orientation="Vertical"
HorizontalExpand="True"
Margin="0 0 0 5">
<PanelContainer StyleClasses="PanelDark">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="{xNamespace:Static style:StyleNano.ButtonColorDisabled}" />
</PanelContainer.PanelOverride>
<Collapsible Name="Collapsible">
<CollapsibleHeading Name="Heading" MinHeight="35"/>
<CollapsibleBody Name="Body">
@@ -14,8 +14,10 @@
<PanelContainer
VerticalExpand="True"
HorizontalExpand="True"
Margin="15"
StyleClasses="PanelDark">
Margin="15">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="{xNamespace:Static style:StyleNano.PanelDark}" />
</PanelContainer.PanelOverride>
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
<Control>
<Label Text="{Loc 'cryostorage-ui-label-no-bodies'}" Name="EmptyLabel" HorizontalAlignment="Center" VerticalAlignment="Center"/>
@@ -1,7 +1,7 @@
<DefaultWindow
xmlns="https://spacestation14.io"
Title="{Loc 'ui-bql-results-title'}">
<BoxContainer Orientation="Vertical" MinSize="500 700">
<BoxContainer Orientation="Vertical">
<Label Name="StatusLabel" />
<ScrollContainer VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ItemList" VerticalExpand="True" />
@@ -21,6 +21,8 @@ internal sealed partial class ToolshedVisualizeWindow : DefaultWindow
RobustXamlLoader.Load(this);
}
protected override Vector2 ContentsMinimumSize => new(500, 700);
public void Update((string name, NetEntity entity)[] entities)
{
StatusLabel.Text = _loc.GetString("ui-bql-results-status", ("count", entities.Length));
+1 -1
View File
@@ -3,7 +3,7 @@
Margin="10 10 10 0"
HorizontalExpand="True"
Visible="True">
<PanelContainer StyleClasses="BackgroundPanel" HorizontalExpand="True">
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True">
<BoxContainer Orientation="Horizontal">
+1 -1
View File
@@ -29,7 +29,7 @@ public sealed partial class BountyEntry : BoxContainer
UntilNextSkip = untilNextSkip;
if (!_prototype.Resolve<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
return;
var items = new List<string>();
@@ -19,7 +19,7 @@ public sealed partial class BountyHistoryEntry : BoxContainer
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
if (!_prototype.Resolve(bounty.Bounty, out var bountyPrototype))
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
return;
var items = new List<string>();
@@ -206,7 +206,7 @@ namespace Content.Client.Cargo.UI
if (!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
return;
Requests.RemoveAllChildren();
Requests.DisposeAllChildren();
foreach (var order in orders)
{
@@ -30,7 +30,7 @@ namespace Content.Client.Cargo.UI
public void SetOrders(SpriteSystem sprites, IPrototypeManager protoManager, List<CargoOrderData> orders)
{
Orders.RemoveAllChildren();
Orders.DisposeAllChildren();
foreach (var order in orders)
{
@@ -59,6 +59,9 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface
SendCartridgeUiReadyEvent(_activeProgram.Value);
}
if (_activeCartridgeUI is IDisposable disposableUi) // Funky: Handle cleanup for popups and such.
disposableUi.Dispose();
_activeCartridgeUI = ui;
_activeUiFragment?.Dispose();
_activeUiFragment = control;
@@ -121,8 +124,13 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface
{
base.Dispose(disposing);
if (disposing)
if (disposing) // Funky: Handle cleanup for popups and such.
{
if (_activeCartridgeUI is IDisposable disposableUi)
disposableUi.Dispose();
_activeUiFragment?.Dispose();
}
}
protected CartridgeComponent? RetrieveCartridgeComponent(EntityUid? cartridgeUid)
@@ -1,4 +1,4 @@
using Content.Client.CrewManifest.UI;
using Content.Client.CrewManifest.UI;
using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@@ -21,6 +21,7 @@ public sealed partial class CrewManifestUiFragment : BoxContainer
public void UpdateState(string stationName, CrewManifestEntries? entries)
{
CrewManifestListing.DisposeAllChildren();
CrewManifestListing.RemoveAllChildren();
StationNameContainer.Visible = entries != null;
@@ -1,7 +1,4 @@
using Content.Client.Stylesheets.Palette;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using Content.Shared.Changeling.Systems;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
@@ -10,58 +7,28 @@ namespace Content.Client.Changeling.UI;
[UsedImplicitly]
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
private SimpleRadialMenu? _menu;
private static readonly Color SelectedOptionBackground = Palettes.Green.Element.WithAlpha(128);
private static readonly Color SelectedOptionHoverBackground = Palettes.Green.HoveredElement.WithAlpha(128);
private ChangelingTransformMenu? _window;
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<SimpleRadialMenu>();
Update();
_menu.OpenOverMouseScreenPosition();
}
_window = this.CreateWindow<ChangelingTransformMenu>();
_window.OnIdentitySelect += SendIdentitySelect;
_window.Update(Owner);
}
public override void Update()
{
if (_menu == null)
if (_window == null)
return;
if (!EntMan.TryGetComponent<ChangelingIdentityComponent>(Owner, out var lingIdentity))
return;
var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity?.CurrentIdentity);
_menu.SetButtons(models);
_window.Update(Owner);
}
private IEnumerable<RadialMenuOptionBase> ConvertToButtons(
IEnumerable<EntityUid> identities,
EntityUid? currentIdentity
)
{
var buttons = new List<RadialMenuOptionBase>();
foreach (var identity in identities)
{
if (!EntMan.TryGetComponent<MetaDataComponent>(identity, out var metadata))
continue;
var option = new RadialMenuActionOption<NetEntity>(SendIdentitySelect, EntMan.GetNetEntity(identity))
{
IconSpecifier = RadialMenuIconSpecifier.With(identity),
ToolTip = metadata.EntityName,
BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null,
HoverBackgroundColor = (currentIdentity == identity) ? SelectedOptionHoverBackground : null
};
buttons.Add(option);
}
return buttons;
}
private void SendIdentitySelect(NetEntity identityId)
public void SendIdentitySelect(NetEntity identityId)
{
SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId));
}
@@ -0,0 +1,8 @@
<ui:RadialMenu
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True">
<ui:RadialContainer Name="Main">
</ui:RadialContainer>
</ui:RadialMenu>
@@ -0,0 +1,62 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Changeling.UI;
[GenerateTypedNameReferences]
public sealed partial class ChangelingTransformMenu : RadialMenu
{
[Dependency] private readonly IEntityManager _entity = default!;
public event Action<NetEntity>? OnIdentitySelect;
public ChangelingTransformMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void Update(EntityUid uid)
{
Main.DisposeAllChildren();
if (!_entity.TryGetComponent<ChangelingIdentityComponent>(uid, out var identityComp))
return;
foreach (var identityUid in identityComp.ConsumedIdentities)
{
if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata))
continue;
var identityName = metadata.EntityName;
var button = new ChangelingTransformMenuButton()
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64, 64),
ToolTip = identityName,
};
var entView = new SpriteView()
{
SetSize = new Vector2(48, 48),
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Stretch = SpriteView.StretchMode.Fill,
};
entView.SetEntity(identityUid);
button.OnButtonUp += _ =>
{
OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
Close();
};
button.AddChild(entView);
Main.AddChild(button);
}
}
}
public sealed class ChangelingTransformMenuButton : RadialMenuTextureButtonWithSector;
+2 -2
View File
@@ -36,12 +36,12 @@ namespace Content.Client.Changelog
if (_changelogManager.NewChangelogEntries)
{
Text = Loc.GetString("changelog-button-new-entries");
StyleClasses.Add(StyleClass.Negative);
StyleClasses.Add(StyleBase.ButtonCaution);
}
else
{
Text = Loc.GetString("changelog-button");
StyleClasses.Remove(StyleClass.Negative);
StyleClasses.Remove(StyleBase.ButtonCaution);
}
}
}
@@ -52,7 +52,6 @@ namespace Content.Client.Changelog
// Open changelog purely to compare to the last viewed date.
var changelogs = await LoadChangelog();
UpdateChangelogs(changelogs);
_configManager.OnValueChanged(CCVars.ServerId, OnServerIdCVarChanged);
}
private void UpdateChangelogs(List<Changelog> changelogs)
@@ -82,11 +81,6 @@ namespace Content.Client.Changelog
MaxId = changelog.Entries.Max(c => c.Id);
CheckLastSeenEntry();
}
private void CheckLastSeenEntry()
{
var path = new ResPath($"/changelog_last_seen_{_configManager.GetCVar(CCVars.ServerId)}");
if (_resource.UserData.TryReadAllText(path, out var lastReadIdText))
{
@@ -98,11 +92,6 @@ namespace Content.Client.Changelog
NewChangelogEntriesChanged?.Invoke();
}
private void OnServerIdCVarChanged(string newValue)
{
CheckLastSeenEntry();
}
public Task<List<Changelog>> LoadChangelog()
{
return Task.Run(() =>
@@ -58,7 +58,7 @@ public sealed partial class ChangelogTab : Control
ChangelogBody.AddChild(new Label
{
Text = dayNice,
StyleClasses = { StyleClass.LabelHeading },
StyleClasses = { StyleBase.StyleClassLabelHeading },
Margin = new Thickness(4, 6, 0, 0)
});
@@ -117,7 +117,7 @@ public sealed partial class ChangelogTab : Control
};
readDivider.AddChild(hBox);
readDivider.AddChild(new PanelContainer { StyleClasses = { StyleClass.LowDivider } });
readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
ChangelogBody.AddChild(readDivider);
if (first)
@@ -2,9 +2,8 @@
xmlns:ui="clr-namespace:Content.Client.Changelog"
Title="{Loc 'changelog-window-title'}"
MinSize="500 400"
SetSize="500 400"
Stylesheet="Interface">
<PanelContainer StyleClasses="BackgroundPanel" />
SetSize="500 400">
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" Access="Public" HorizontalExpand="True" VerticalExpand="True" />
<PanelContainer StyleClasses="LowDivider" />
@@ -21,7 +21,8 @@ namespace Content.Client.Changelog
public ChangelogWindow()
{
RobustXamlLoader.Load(this);
WindowTitle.AddStyleClass(StyleClass.LabelHeading);
WindowTitle.AddStyleClass(StyleBase.StyleClassLabelHeading);
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
}
protected override void Opened()
@@ -54,7 +55,7 @@ namespace Content.Client.Changelog
// Changelog is not kept in memory so load it again.
var changelogs = await _changelog.LoadChangelog();
Tabs.RemoveAllChildren();
Tabs.DisposeAllChildren();
var i = 0;
foreach (var changelog in changelogs)
@@ -32,11 +32,6 @@ internal sealed class ChatManager : IChatManager
// See server-side manager. This just exists for shared code.
}
public void SendAdminAlertNoFormatOrEscape(string message)
{
// See server-side manager. This just exists for shared code.
}
public void SendMessage(string text, ChatSelectChannel channel)
{
var str = text.ToString();
@@ -35,7 +35,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
}
// End DeltaV Additions
if (!_prototypeManager.Resolve(currentTypingIndicator, out var proto))
if (!_prototypeManager.TryIndex(currentTypingIndicator, out var proto))
{
Log.Error($"Unknown typing indicator id: {component.TypingIndicatorPrototype}");
return;
@@ -45,7 +45,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
if (!layerExists)
layer = SpriteSystem.LayerMapReserve((uid, args.Sprite), TypingIndicatorLayers.Base);
SpriteSystem.LayerSetRsi((uid, args.Sprite), layer, proto.SpritePath, proto.TypingState);
SpriteSystem.LayerSetRsi((uid, args.Sprite), layer, proto.SpritePath, proto.TypingState); // DeltaV - Combine path and state calls to avoid missing state error on alternate indicators
args.Sprite.LayerSetShader(layer, proto.Shader);
SpriteSystem.LayerSetOffset((uid, args.Sprite), layer, proto.Offset);
@@ -0,0 +1,16 @@
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class HyposprayStatusControlSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<HyposprayComponent>(ent => new HyposprayStatusControl(ent, _solutionContainers));
}
}
@@ -1,20 +0,0 @@
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class InjectorStatusControlSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<InjectorComponent>(injector => new InjectorStatusControl(injector, _solutionContainers, _prototypeManager));
}
}
@@ -0,0 +1,16 @@
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class InjectorSystem : SharedInjectorSystem
{
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<InjectorComponent>(ent => new InjectorStatusControl(ent, SolutionContainer));
}
}
@@ -33,10 +33,10 @@
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'chem-master-window-buffer-text'}" />
<Control HorizontalExpand="True" />
<Button MinSize="80 0" Margin="0 0 10 0" Name="BufferSortButton" Access="Public" Text="{Loc 'chem-master-window-sort-type-none'}" />
<!-- DeltaV - Removed discarding
<Button MinSize="80 0" Name="BufferTransferButton" Access="Public" Text="{Loc 'chem-master-window-transfer-button'}" ToggleMode="True" StyleClasses="OpenRight" />
-->
<Button MinSize="80 0" Name="BufferSortButton" Access="Public" Text="{Loc 'chem-master-window-sort-type-none'}" StyleClasses="OpenBoth" />
<!-- DeltaV - Removed discarding
<Button MinSize="80 0" Name="BufferDiscardButton" Access="Public" Text="{Loc 'chem-master-window-discard-button'}" ToggleMode="True" StyleClasses="OpenLeft" />
-->
@@ -86,7 +86,7 @@
<Label Text="{Loc 'chem-master-window-packaging-text'}" />
<Control HorizontalExpand="True"/>
<Label Text="{Loc 'chem-master-window-container-label'}" /> <!-- DeltaV-->
<Label Name="BufferCurrentVolume" StyleClasses="LabelWeak" />
<Label Name="BufferCurrentVolume" StyleClasses="LabelSecondaryColor" />
</BoxContainer>
<!-- Wrap the packaging info-->
@@ -116,9 +116,9 @@
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'chem-master-window-pills-label'}" />
<Control HorizontalExpand="True" MinSize="50 0" />
<Label Text="{Loc 'chem-master-window-pills-number-label'}" Margin="5 0 0 0" StyleClasses="LabelWeak" />
<Label Text="{Loc 'chem-master-window-pills-number-label'}" Margin="5 0 0 0" StyleClasses="LabelSecondaryColor" />
<SpinBox MinSize="100 0" Name="PillNumber" Access="Public" Value="0" />
<Label Text="{Loc 'chem-master-window-dose-label'}" Margin="5 0 0 0" StyleClasses="LabelWeak" />
<Label Text="{Loc 'chem-master-window-dose-label'}" Margin="5 0 0 0" StyleClasses="LabelSecondaryColor" />
<SpinBox MinSize="100 0" Name="PillDosage" Access="Public" Value="1" />
<Button MinSize="80 0" Name="CreatePillButton" Access="Public" Text="{Loc 'chem-master-window-create-button'}" />
</BoxContainer>
@@ -126,7 +126,7 @@
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'chem-master-window-bottles-label'}" />
<Control HorizontalExpand="True" MinSize="50 0" />
<Label Text="{Loc 'chem-master-window-dose-label'}" Margin="5 0 0 0" StyleClasses="LabelWeak" />
<Label Text="{Loc 'chem-master-window-dose-label'}" Margin="5 0 0 0" StyleClasses="LabelSecondaryColor" />
<SpinBox MinSize="100 0" Name="BottleDosage" Access="Public" Value="0" />
<Button MinSize="80 0" Name="CreateBottleButton" Access="Public" Text="{Loc 'chem-master-window-create-button'}" />
</BoxContainer>
@@ -53,14 +53,14 @@ namespace Content.Client.Chemistry.UI
{
// For every button decide which stylebase to have
// Every row has 10 buttons
String styleBase = StyleClass.ButtonOpenBoth;
String styleBase = StyleBase.ButtonOpenBoth;
uint modulo = i % 10;
if (i > 0 && modulo == 0)
styleBase = StyleClass.ButtonOpenRight;
styleBase = StyleBase.ButtonOpenRight;
else if (i > 0 && modulo == 9)
styleBase = StyleClass.ButtonOpenLeft;
styleBase = StyleBase.ButtonOpenLeft;
else if (i == 0)
styleBase = StyleClass.ButtonOpenRight;
styleBase = StyleBase.ButtonOpenRight;
// Generate buttons
PillTypeButtons[i] = new Button
@@ -113,16 +113,13 @@ namespace Content.Client.Chemistry.UI
var buttonConfigs = new (string text, ChemMasterReagentAmount amount, string styleClass)[]
{
("1", ChemMasterReagentAmount.U1, StyleClass.ButtonOpenBoth),
("5", ChemMasterReagentAmount.U5, StyleClass.ButtonOpenBoth),
("10", ChemMasterReagentAmount.U10, StyleClass.ButtonOpenBoth),
("15", ChemMasterReagentAmount.U15, StyleClass.ButtonOpenBoth),
("20", ChemMasterReagentAmount.U20, StyleClass.ButtonOpenBoth),
("25", ChemMasterReagentAmount.U25, StyleClass.ButtonOpenBoth),
("30", ChemMasterReagentAmount.U30, StyleClass.ButtonOpenBoth),
("50", ChemMasterReagentAmount.U50, StyleClass.ButtonOpenBoth),
("100", ChemMasterReagentAmount.U100, StyleClass.ButtonOpenBoth),
(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleClass.ButtonOpenLeft),
("1", ChemMasterReagentAmount.U1, StyleBase.ButtonOpenBoth),
("5", ChemMasterReagentAmount.U5, StyleBase.ButtonOpenBoth),
("10", ChemMasterReagentAmount.U10, StyleBase.ButtonOpenBoth),
("25", ChemMasterReagentAmount.U25, StyleBase.ButtonOpenBoth),
("50", ChemMasterReagentAmount.U50, StyleBase.ButtonOpenBoth),
("100", ChemMasterReagentAmount.U100, StyleBase.ButtonOpenBoth),
(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleBase.ButtonOpenLeft),
};
var buttons = new List<ReagentButton>();
@@ -249,7 +246,7 @@ namespace Content.Client.Chemistry.UI
var bufferVol = new Label
{
Text = $"{state.BufferCurrentVolume}u",
StyleClasses = { StyleClass.LabelWeak }
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
};
bufferHBox.AddChild(bufferVol);
@@ -318,7 +315,7 @@ namespace Content.Client.Chemistry.UI
new Label
{
Text = $"{info.CurrentVolume}/{info.MaxVolume}",
StyleClasses = { StyleClass.LabelWeak }
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
}
}
});
@@ -373,7 +370,7 @@ namespace Content.Client.Chemistry.UI
new Label
{
Text = $"{quantity}u",
StyleClasses = { StyleClass.LabelWeak }
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
},
// Padding
@@ -0,0 +1,58 @@
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
namespace Content.Client.Chemistry.UI;
public sealed class HyposprayStatusControl : Control
{
private readonly Entity<HyposprayComponent> _parent;
private readonly RichTextLabel _label;
private readonly SharedSolutionContainerSystem _solutionContainers;
private FixedPoint2 PrevVolume;
private FixedPoint2 PrevMaxVolume;
private bool PrevOnlyAffectsMobs;
public HyposprayStatusControl(Entity<HyposprayComponent> parent, SharedSolutionContainerSystem solutionContainers)
{
_parent = parent;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
return;
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
&& PrevOnlyAffectsMobs == _parent.Comp.OnlyAffectsMobs)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
var modeStringLocalized = Loc.GetString((_parent.Comp.OnlyAffectsMobs && _parent.Comp.CanContainerDraw) switch
{
false => "hypospray-all-mode-text",
true => "hypospray-mobs-only-mode-text",
});
_label.SetMarkup(Loc.GetString("hypospray-volume-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", modeStringLocalized)));
}
}
@@ -2,35 +2,29 @@ using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Chemistry.UI;
public sealed class InjectorStatusControl : Control
{
private readonly IPrototypeManager _prototypeManager;
private readonly Entity<InjectorComponent> _parent;
private readonly SharedSolutionContainerSystem _solutionContainers;
private readonly RichTextLabel _label;
private FixedPoint2 _prevVolume;
private FixedPoint2 _prevMaxVolume;
private FixedPoint2? _prevTransferAmount;
private InjectorBehavior _prevBehavior;
private FixedPoint2 PrevVolume;
private FixedPoint2 PrevMaxVolume;
private FixedPoint2 PrevTransferAmount;
private InjectorToggleMode PrevToggleState;
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers, IPrototypeManager prototypeManager)
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers)
{
_prototypeManager = prototypeManager;
_parent = parent;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
}
@@ -38,38 +32,33 @@ public sealed class InjectorStatusControl : Control
{
base.FrameUpdate(args);
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution)
|| !_prototypeManager.Resolve(_parent.Comp.ActiveModeProtoId, out var activeMode))
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
return;
// only updates the UI if any of the details are different than they previously were
if (_prevVolume == solution.Volume
&& _prevMaxVolume == solution.MaxVolume
&& _prevTransferAmount == _parent.Comp.CurrentTransferAmount
&& _prevBehavior == activeMode.Behavior)
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
&& PrevTransferAmount == _parent.Comp.CurrentTransferAmount
&& PrevToggleState == _parent.Comp.ToggleState)
return;
_prevVolume = solution.Volume;
_prevMaxVolume = solution.MaxVolume;
_prevTransferAmount = _parent.Comp.CurrentTransferAmount;
_prevBehavior = activeMode.Behavior;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
PrevTransferAmount = _parent.Comp.CurrentTransferAmount;
PrevToggleState = _parent.Comp.ToggleState;
// Update current volume and injector state
// Seeing transfer volume is only important for injectors that can change it.
if (activeMode.TransferAmounts.Count > 1 && _parent.Comp.CurrentTransferAmount.HasValue)
var modeStringLocalized = Loc.GetString(_parent.Comp.ToggleState switch
{
_label.SetMarkup(Loc.GetString("injector-volume-transfer-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", Loc.GetString(activeMode.Name)),
("transferVolume", _parent.Comp.CurrentTransferAmount.Value)));
}
else
{
_label.SetMarkup(Loc.GetString("injector-volume-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", Loc.GetString(activeMode.Name))));
}
InjectorToggleMode.Draw => "injector-draw-text",
InjectorToggleMode.Inject => "injector-inject-text",
_ => "injector-invalid-injector-toggle-mode"
});
_label.SetMarkup(Loc.GetString("injector-volume-label",
("currentVolume", solution.Volume),
("totalVolume", solution.MaxVolume),
("modeString", modeStringLocalized),
("transferVolume", _parent.Comp.CurrentTransferAmount)));
}
}
@@ -107,7 +107,7 @@ namespace Content.Client.Chemistry.UI
var quantityLabel = new Label
{
Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", quantity)),
StyleClasses = { StyleClass.LabelWeak },
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor },
};
ContainerInfo.Children.Add(new BoxContainer
@@ -29,7 +29,7 @@ public sealed class SolutionStatusControl : PollingItemStatusControl<SolutionSta
_parent = parent;
_entityManager = entityManager;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
}
@@ -17,7 +17,7 @@ namespace Content.Client.Cloning.UI
Title = Loc.GetString("accept-cloning-window-title");
ContentsContainer.AddChild(new BoxContainer
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
@@ -45,7 +45,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
var newTargets = new List<EntProtoId>();
foreach (var target in targets)
{
if (string.IsNullOrEmpty(target) || !_proto.Resolve(target, out EntityPrototype? proto))
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))
continue;
if (!proto.TryGetComponent(out TagComponent? tag, EntMan.ComponentFactory) || !_tag.HasTag(tag, st.RequiredTag))
@@ -54,7 +54,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
foreach (var id in _possibleIds)
{
if (!_prototypeManager.Resolve(id, out EntityPrototype? proto))
if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto))
continue;
var lowId = id.Id.ToLowerInvariant();
@@ -67,7 +67,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
MinSize = new Vector2(48, 48),
HorizontalExpand = true,
Group = group,
StyleClasses = {StyleClass.ButtonSquare},
StyleClasses = {StyleBase.ButtonSquare},
ToggleMode = true,
Pressed = _selectedId == id,
ToolTip = proto.Name
@@ -68,7 +68,7 @@ namespace Content.Client.Configurable.UI
outerColumn.AddChild(Column);
baseContainer.AddChild(outerColumn);
baseContainer.AddChild(confirmButton);
ContentsContainer.AddChild(baseContainer);
Contents.AddChild(baseContainer);
}
private void OnConfirm(ButtonEventArgs args)
@@ -80,7 +80,7 @@ namespace Content.Client.Construction
{
foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{
if (!PrototypeManager.Resolve(constructionProto.Graph, out var graphProto))
if (!PrototypeManager.TryIndex(constructionProto.Graph, out var graphProto))
continue;
if (constructionProto.TargetNode is not { } targetNodeId)
@@ -121,14 +121,17 @@ namespace Content.Client.Construction
// If we got the id of the prototype, we exit the “recursion” by clearing the stack.
stack.Clear();
if (!PrototypeManager.Resolve(entityId, out var proto))
if (!PrototypeManager.TryIndex(constructionProto.ID, out ConstructionPrototype? recipe))
continue;
var name = constructionProto.SetName.HasValue ? Loc.GetString(constructionProto.SetName) : proto.Name;
var desc = constructionProto.SetDescription.HasValue ? Loc.GetString(constructionProto.SetDescription) : proto.Description;
if (!PrototypeManager.TryIndex(entityId, out var proto))
continue;
constructionProto.Name = name;
constructionProto.Description = desc;
var name = recipe.SetName.HasValue ? Loc.GetString(recipe.SetName) : proto.Name;
var desc = recipe.SetDescription.HasValue ? Loc.GetString(recipe.SetDescription) : proto.Description;
recipe.Name = name;
recipe.Description = desc;
_recipesMetadataCache.Add(constructionProto.ID, entityId);
} while (stack.Count > 0);
@@ -169,7 +172,7 @@ namespace Content.Client.Construction
"construction-ghost-examine-message",
("name", component.Prototype.Name)));
if (!PrototypeManager.Resolve(component.Prototype.Graph, out var graph))
if (!PrototypeManager.TryIndex(component.Prototype.Graph, out var graph))
return;
var startNode = graph.Nodes[component.Prototype.StartNode];
@@ -510,7 +510,7 @@ namespace Content.Client.Construction.UI
foreach (var id in favorites)
{
if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe))
if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe, logError: false))
_favoritedRecipes.Add(recipe);
}
@@ -13,8 +13,7 @@
Name="IconLabel"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
StyleClasses="LabelSubText"
Align="Right"
StyleClasses="contextMenuIconLabel"
Visible="false"/>
</SpriteView>
<RichTextLabel
@@ -16,6 +16,7 @@ namespace Content.Client.ContextMenu.UI
{
public const string StyleClassContextMenuButton = "contextMenuButton";
public const string StyleClassContextMenuExpansionTexture = "contextMenuExpansionTexture";
public const string StyleClassEntityMenuIconLabel = "contextMenuIconLabel";
public const float ElementMargin = 2;
public const float ElementHeight = 32;
@@ -95,7 +95,7 @@ namespace Content.Client.ContextMenu.UI
/// </summary>
public void Close()
{
RootMenu.MenuBody.RemoveAllChildren();
RootMenu.MenuBody.DisposeAllChildren();
CancelOpen?.Cancel();
CancelClose?.Cancel();
OnContextClosed?.Invoke();
@@ -293,7 +293,7 @@ namespace Content.Client.ContextMenu.UI
var element = new EntityMenuElement(entity);
element.SubMenu = new ContextMenuPopup(_context, element);
element.SubMenu.OnPopupOpen += () => _verb.OpenVerbMenu(entity, popup: element.SubMenu);
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.RemoveAllChildren;
element.SubMenu.OnPopupHide += element.SubMenu.MenuBody.DisposeAllChildren;
_context.AddElement(menu, element);
Elements.TryAdd(entity, element);
}
+14
View File
@@ -0,0 +1,14 @@
using Content.Shared.Crayon;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Client.Crayon
{
[RegisterComponent]
public sealed partial class CrayonComponent : SharedCrayonComponent
{
[ViewVariables(VVAccess.ReadWrite)] public bool UIUpdateNeeded;
[ViewVariables] public int Charges { get; set; }
[ViewVariables] public int Capacity { get; set; }
}
}
+37 -21
View File
@@ -1,62 +1,78 @@
using Content.Client.Items;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Crayon;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
namespace Content.Client.Crayon;
public sealed class CrayonSystem : SharedCrayonSystem
{
[Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
// Didn't do in shared because I don't think most of the server stuff can be predicted.
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CrayonComponent, ComponentHandleState>(OnCrayonHandleState);
Subs.ItemStatus<CrayonComponent>(ent => new StatusControl(ent));
}
Subs.ItemStatus<CrayonComponent>(ent => new StatusControl(ent, _charges, _entityManager));
private static void OnCrayonHandleState(EntityUid uid, CrayonComponent component, ref ComponentHandleState args)
{
if (args.Current is not CrayonComponentState state) return;
component.Color = state.Color;
component.SelectedState = state.State;
component.Charges = state.Charges;
component.Capacity = state.Capacity;
component.UIUpdateNeeded = true;
}
private sealed class StatusControl : Control
{
private readonly Entity<CrayonComponent> _crayon;
private readonly SharedChargesSystem _charges;
private readonly CrayonComponent _parent;
private readonly RichTextLabel _label;
private readonly int _capacity;
public StatusControl(Entity<CrayonComponent> crayon, SharedChargesSystem charges, EntityManager entityManage)
public StatusControl(CrayonComponent parent)
{
_crayon = crayon;
_charges = charges;
_capacity = entityManage.GetComponent<LimitedChargesComponent>(_crayon.Owner).MaxCharges;
_label = new RichTextLabel { StyleClasses = { StyleClass.ItemStatus } };
_parent = parent;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
parent.UIUpdateNeeded = true;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (!_parent.UIUpdateNeeded)
{
return;
}
_parent.UIUpdateNeeded = false;
// Frontier: unlimited crayon, Delta V Port
if (_charges.GetCurrentCharges(_crayon.Owner) == int.MaxValue)
if (_parent.Capacity == int.MaxValue)
{
_label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label-unlimited",
("color", _crayon.Comp.Color),
("state", _crayon.Comp.SelectedState)));
("color", _parent.Color),
("state", _parent.SelectedState)));
return;
}
// End Frontier, Delta V Port
_label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label",
("color",_crayon.Comp.Color),
("state",_crayon.Comp.SelectedState),
("charges", _charges.GetCurrentCharges(_crayon.Owner)),
("capacity", _capacity)));
("color",_parent.Color),
("state",_parent.SelectedState),
("charges", _parent.Charges),
("capacity",_parent.Capacity)));
}
}
}
@@ -53,7 +53,7 @@ namespace Content.Client.Crayon.UI
private void RefreshList()
{
// Clear
Grids.RemoveAllChildren();
Grids.DisposeAllChildren();
if (_decals == null || _allDecals == null)
return;
@@ -137,8 +137,7 @@ namespace Content.Client.Crayon.UI
private void ButtonOnPressed(ButtonEventArgs obj)
{
if (obj.Button.Name == null)
return;
if (obj.Button.Name == null) return;
_selected = obj.Button.Name;
_autoSelected = null;
+5 -5
View File
@@ -103,11 +103,11 @@ public sealed partial class CreditsWindow : DefaultWindow
var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal };
var previousPageButton = new Button { Text = Loc.GetString("credits-window-previous-page-button") };
var previousPageButton = new Button { Text = "Previous Page" };
previousPageButton.OnPressed +=
_ => PopulateAttributions(attributionsContainer, count - AttributionsSourcesPerPage);
var nextPageButton = new Button { Text = Loc.GetString("credits-window-next-page-button") };
var nextPageButton = new Button { Text = "Next Page" };
nextPageButton.OnPressed +=
_ => PopulateAttributions(attributionsContainer, count + AttributionsSourcesPerPage);
@@ -261,7 +261,7 @@ public sealed partial class CreditsWindow : DefaultWindow
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
{
licensesContainer.AddChild(new Label
{ StyleClasses = { StyleClass.LabelHeading }, Text = entry.Name });
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = entry.Name });
// We split these line by line because otherwise
// the LGPL causes Clyde to go out of bounds in the rendering code.
@@ -302,7 +302,7 @@ public sealed partial class CreditsWindow : DefaultWindow
first = false;
patronsContainer.AddChild(new Label
{ StyleClasses = { StyleClass.LabelHeading }, Text = $"{tier.Key}" });
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = $"{tier.Key}" });
var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
@@ -350,7 +350,7 @@ public sealed partial class CreditsWindow : DefaultWindow
first = false;
ss14ContributorsContainer.AddChild(new Label
{ StyleClasses = { StyleClass.LabelHeading }, Text = title });
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = title });
var label = new RichTextLabel();
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
@@ -18,6 +18,7 @@ public sealed partial class CrewManifestUi : DefaultWindow
public void Populate(string name, CrewManifestEntries? entries)
{
CrewManifestListing.DisposeAllChildren();
CrewManifestListing.RemoveAllChildren();
StationNameContainer.Visible = entries != null;
@@ -58,7 +58,7 @@
StyleClasses="LabelBig" />
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<Label Text="{Loc 'crew-monitoring-ui-job-label'}"
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
FontColorOverride="DarkGray" />
<TextureRect Name="PersonJobIcon"
TextureScale="2 2"
@@ -276,7 +276,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private void SetStatus(SecurityStatus status)
{
if (status == SecurityStatus.Wanted || status == SecurityStatus.Suspected || status == SecurityStatus.Hostile)
if (status == SecurityStatus.Wanted || status == SecurityStatus.Suspected)
{
GetReason(status);
return;
@@ -322,8 +322,6 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
SecurityStatus.Detained => "hud_incarcerated",
SecurityStatus.Discharged => "hud_discharged",
SecurityStatus.Suspected => "hud_suspected",
SecurityStatus.Hostile => "hud_hostile",
SecurityStatus.Eliminated => "hud_eliminated",
_ => "SecurityIconNone"
};
}

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