Compare commits

..
1 Commits
Author SHA1 Message Date
Vanessa dc7cd70ff6 Allow non-brain nymphs to talk. 2026-06-24 20:24:35 -05:00
3149 changed files with 85876 additions and 71805 deletions
+2 -13
View File
@@ -32,7 +32,6 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
@@ -46,23 +45,13 @@ jobs:
run: dotnet build --configuration DebugOpt --no-restore /m
- name: Run Content.Tests
shell: pwsh
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0 NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0
- name: Run Content.IntegrationTests
shell: pwsh
run: |
$env:DOTNET_gcServer=1
dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed NUnit.TestOutputXml="logs" NUnit.WorkDirectory="$(pwd)/test_results"
- name: Archive NUnit3 test results.
if: always()
uses: actions/upload-artifact@v4
with:
name: nunit3-results-${{ matrix.os }}
path: test_results/*
retention-days: 7
compression-level: 9
dotnet test --no-build --configuration DebugOpt Content.IntegrationTests/Content.IntegrationTests.csproj -- NUnit.ConsoleOut=0 NUnit.MapWarningTo=Failed
ci-success:
name: Build & Test Debug
needs:
-1
View File
@@ -29,7 +29,6 @@ jobs:
run: |
cd RobustToolbox
git fetch --depth=1
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Install dependencies
run: dotnet restore
+6 -13
View File
@@ -48,11 +48,10 @@ jobs:
cd RobustToolbox/
git submodule update --init --recursive
# ubuntu-latest has .NET 10
# - name: Setup .NET Core
# uses: actions/setup-dotnet@v4.1.0
# with:
# dotnet-version: 10.0.x
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore
@@ -61,13 +60,7 @@ jobs:
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --log-build --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --log-build --no-wipe-release
- uses: actions/upload-artifact@v4
with:
name: binlogs
path: release/*.binlog
retention-days: 7
run: dotnet run --project Content.Packaging client --no-wipe-release
-1
View File
@@ -23,7 +23,6 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
rm Robust.Shared.Tests/Networking/NetEncryptionDoSTest.cs # DeltaV - work around bug in the backport
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
+2 -34
View File
@@ -31,7 +31,7 @@ Examples of Pets include, but are not limited to:
:* hand-held "personal AI" devices
== Synthetic ==
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage.
Any Entity consisting primarily of non-organic matter, designed and/or constructed by NanoTrasen or its Employees for the purpose of performing tasks aboard a Space Station. Synthetics are bound by software limitations, typically referred to as Synthetic or Silicon Laws, and physically incorporated into their chassis. Any damage to a Synthetic- including un-authorized modification of their Laws- may leave the offender liable to charges of Sabotage. Irrevocable destruction of a Synthetic may leave the offender liable to the crime of Grand Sabotage.
Examples of Synthetics include, but are not limited to:
:* Crew-constructed Cyborgs
:* The Station AI
@@ -86,7 +86,7 @@ Any individual requiring any above item in the course of their job, or for regul
Should the holder commit crimes through the use of such authorized items, the authorizing party may be found guilty of Accessory to said crimes, in addition to Endangerment and Abuse of Power where applicable. The Commanding Officer is also authorized to supervise the approval and revocation of documentation as they see fit.
== Controlled Substances ==
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a prescription or special dispensation from the Chief Medical Officer. Medical personnel are also permitted to [[Standard Operating Procedure#Prescribing Medication|prescribe medication]] for chronic illnesses or mental health. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
Any individual requiring a Controlled Substance in the course of their job, or for regular function, must request access to a perscription or special dispensation from the Chief Medical Officer. The Psychologist is also permitted to grant prescriptions for psychoactive drugs. Any such document must state the substance(s) being prescribed, the amount(s) where applicable, and the stamp and signature of the authorizing party.
A list of such Substances follow:
@@ -370,38 +370,6 @@ Given good medical reason and with documentation, the Chief Medical Officer of a
In order for a medical evaluation to be considered lawfully applicable, the CMO and relevant head of department must both agree that the specified crewmate is unfit for duty in order to proceed with the demotion. In the case of the Commanding Officer, Central Command should be contacted as soon as possible.
==Prescribing Medication==
In the event that a patient requires stronger medication outside of medbay, Medical personnel have the ability to prescribe medicine to a patient. Prescriptions must be written on valid prescription paper, as well as signed and stamped with a valid medical personnel's stamp and signature. Any such document must state the substance(s) being prescribed as well as the amount(s) where applicable. The chief medical officer also has the right to revoke any prescription with valid medical reasoning. Prescriptions are not valid if they are both intended for and signed and stamped by the same person (e.g. You cannot prescribe something to yourself as a doctor).
=== Prescription Medicine ===
Prescriptions that are valid with any medical personnel's stamp and signature are as follows:
* diphenylmethylamine
* equilibrazine
* tranquinase
* neurozenium
* serenitol
* soretizone
Highly-addictive mental health prescriptions that are only valid with the chief medical officer's or psychologist's stamp and signature are as follows:
* blissifylovene
* happiness
Highly-addictive or potentially-harmful prescriptions that are only valid with the chief medical officer's stamp and signature are as follows:
* agonolexyne
* desoxyephedrine
* nocturine
=== Dangerous Chemical Authorizations ===
Other dangerous chemicals that may be authorized and given out ONLY with valid written permission with the chief medical officer's stamp are as follows:
* mindbreaker, heartbreaker, and mute toxins
* amatoxin and other dangerous poisons
* phlogiston, napalm, and chlorine trifluoride
* vestine and romerol
* Any other [[Standard Operating Procedure#Controlled Substances|controlled substances]] not listed here
The chief medical officer is also required to inform and/or forward authorization of any dangerous chemical to Security.
==Quarantine==
Medical staff are afforded the right to quarantine individuals that currently suffer from infectious or contagious diseases, and may confine them within pre-designated quarantine areas in the course of treatment, until the point that they are no longer infectious or harmful to the rest of the crew.
@@ -1,6 +1,5 @@
#nullable enable
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@@ -45,7 +44,7 @@ public class ComponentQueryBenchmark
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
@@ -7,7 +7,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
<Nullable>disable</Nullable>
<DefineConstants>$(DefineConstants);ALLOW_BAD_PRACTICES</DefineConstants>
</PropertyGroup>
<Import Project="../MSBuild/Content.props" />
<ItemGroup>
+3 -3
View File
@@ -1,11 +1,11 @@
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Analyzers;
@@ -69,7 +69,7 @@ public class DeltaPressureBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
var mapdata = await _pair.CreateTestMap();
+1 -2
View File
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
@@ -70,7 +69,7 @@ public class DestructibleBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
_entMan = server.ResolveDependency<IEntityManager>();
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
@@ -61,7 +60,7 @@ public class DeviceNetworkingBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(DeviceNetworkingBenchmark).Assembly);
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
await server.WaitPost(() =>
+1 -2
View File
@@ -1,4 +1,3 @@
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
@@ -52,7 +51,7 @@ public class GasReactionBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
// Create test map and grid
+2 -3
View File
@@ -1,5 +1,4 @@
using System.IO;
using System.Threading.Tasks;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
@@ -28,7 +27,7 @@ public class HeatCapacityBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
await _pair.Connect();
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
+1 -2
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@@ -30,7 +29,7 @@ public class MapLoadBenchmark
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var server = _pair.Server;
Paths = server.ResolveDependency<IPrototypeManager>()
+1 -2
View File
@@ -1,6 +1,5 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@@ -51,7 +50,7 @@ public class PvsBenchmark
#endif
PoolManager.Startup();
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
_pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
+1 -2
View File
@@ -1,5 +1,4 @@
#nullable enable
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@@ -22,7 +21,7 @@ public class RaiseEventBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(BenchSystem).Assembly);
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var entMan = _pair.Server.EntMan;
var fact = _pair.Server.ResolveDependency<IComponentFactory>();
var bus = (EntityEventBus)entMan.EventBus;
@@ -1,5 +1,4 @@
using System.IO;
using System.Threading.Tasks;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
@@ -37,7 +36,7 @@ public class SpawnEquipDeleteBenchmark
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
var mapData = await _pair.CreateTestMap();
@@ -1,39 +0,0 @@
using Content.Client.Overlays;
using Content.Shared.Access.Systems;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
namespace Content.Client.Access.Systems;
public sealed class JobStatusSystem : SharedJobStatusSystem
{
[Dependency] private readonly ShowJobIconsSystem _showJobIcons = default!;
[Dependency] private readonly ShowCrewIconsSystem _showCrewIcons = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private static readonly ProtoId<SecurityIconPrototype> CrewBorderIcon = "CrewBorderIcon";
private static readonly ProtoId<SecurityIconPrototype> CrewUncertainBorderIcon = "CrewUncertainBorderIcon";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<JobStatusComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
}
// show the status icons if the player has the correponding HUDs
private void OnGetStatusIconsEvent(Entity<JobStatusComponent> ent, ref GetStatusIconsEvent ev)
{
if (_showJobIcons.IsActive && ent.Comp.JobStatusIcon != null)
ev.StatusIcons.Add(_prototype.Index(ent.Comp.JobStatusIcon));
if (_showCrewIcons.IsActive)
{
if (_showCrewIcons.UncertainCrewBorder)
ev.StatusIcons.Add(_prototype.Index(CrewUncertainBorderIcon));
else if (ent.Comp.IsCrew)
ev.StatusIcons.Add(_prototype.Index(CrewBorderIcon));
}
}
}
@@ -2,9 +2,11 @@ using System.Linq; // DeltaV
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.IdCardConsoleComponent;
@@ -13,13 +15,21 @@ namespace Content.Client.Access.UI
public sealed class IdCardConsoleBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
private readonly SharedIdCardConsoleSystem _idCardConsoleSystem = default!;
private IdCardConsoleWindow? _window;
// CCVar.
private int _maxNameLength;
private int _maxIdJobLength;
public IdCardConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_idCardConsoleSystem = EntMan.System<SharedIdCardConsoleSystem>();
_maxNameLength =_cfgManager.GetCVar(CCVars.MaxNameLength);
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
}
protected override void Open()
@@ -69,6 +79,12 @@ namespace Content.Client.Access.UI
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, ProtoId<JobPrototype> newJobPrototype)
{
if (newFullName.Length > _maxNameLength)
newFullName = newFullName[.._maxNameLength];
if (newJobTitle.Length > _maxIdJobLength)
newJobTitle = newJobTitle[.._maxIdJobLength];
SendMessage(new WriteToTargetIdMessage(
newFullName,
newJobTitle,
@@ -23,6 +23,10 @@ namespace Content.Client.Access.UI
private readonly IdCardConsoleBoundUserInterface _owner;
// CCVar.
private int _maxNameLength;
private int _maxIdJobLength;
private AccessLevelControl _accessButtons = new();
private readonly List<string> _jobPrototypeIds = new();
@@ -42,8 +46,11 @@ namespace Content.Client.Access.UI
_owner = owner;
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
_maxIdJobLength = _cfgManager.GetCVar(CCVars.MaxIdJobLength);
FullNameLineEdit.OnTextEntered += _ => SubmitData();
FullNameLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxNameLength);
FullNameLineEdit.IsValid = s => s.Length <= _maxNameLength;
FullNameLineEdit.OnTextChanged += _ =>
{
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
@@ -51,7 +58,7 @@ namespace Content.Client.Access.UI
FullNameSaveButton.OnPressed += _ => SubmitData();
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
JobTitleLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxIdJobLength);
JobTitleLineEdit.IsValid = s => s.Length <= _maxIdJobLength;
JobTitleLineEdit.OnTextChanged += _ =>
{
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
@@ -1,5 +1,4 @@
using System.Linq;
using System.Numerics;
using System.Numerics;
using Content.Client.Administration.UI.BanList.Bans;
using Content.Client.Administration.UI.BanList.RoleBans;
using Content.Client.Eui;
@@ -74,7 +73,7 @@ public sealed class BanListEui : BaseEui
return date.ToString("MM/dd/yyyy h:mm tt");
}
public static void SetData<T>(IBanListLine<T> line, SharedBan ban) where T : SharedBan
public static void SetData<T>(IBanListLine<T> line, SharedServerBan ban) where T : SharedServerBan
{
line.Reason.Text = ban.Reason;
line.BanTime.Text = FormatDate(ban.BanTime);
@@ -95,20 +94,20 @@ public sealed class BanListEui : BaseEui
line.BanningAdmin.Text = ban.BanningAdminName;
}
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedBan
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedServerBan
{
_popup?.Close();
_popup = null;
var ban = line.Ban;
var id = ban.Id == null ? string.Empty : Loc.GetString("ban-list-id", ("id", ban.Id.Value));
var ip = ban.Addresses.Length == 0
var ip = ban.Address == null
? string.Empty
: Loc.GetString("ban-list-ip", ("ip", string.Join(',', ban.Addresses.Select(a => a.address))));
var hwid = ban.HWIds.Length == 0 ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", string.Join(',', ban.HWIds)));
var guid = ban.UserIds.Length == 0
: Loc.GetString("ban-list-ip", ("ip", ban.Address.Value.address));
var hwid = ban.HWId == null ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", ban.HWId));
var guid = ban.UserId == null
? string.Empty
: Loc.GetString("ban-list-guid", ("guid", string.Join(',', ban.UserIds)));
: Loc.GetString("ban-list-guid", ("guid", ban.UserId.Value.ToString()));
_popup = new BanListIdsPopup(id, ip, hwid, guid);
@@ -16,7 +16,7 @@ public sealed partial class BanListControl : Control
RobustXamlLoader.Load(this);
}
public void SetBans(List<SharedBan> bans)
public void SetBans(List<SharedServerBan> bans)
{
for (var i = Bans.ChildCount - 1; i >= 1; i--)
{
@@ -7,13 +7,13 @@ using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.BanList.Bans;
[GenerateTypedNameReferences]
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedBan>
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedServerBan>
{
public SharedBan Ban { get; }
public SharedServerBan Ban { get; }
public event Action<BanListLine>? IdsClicked;
public BanListLine(SharedBan ban)
public BanListLine(SharedServerBan ban)
{
RobustXamlLoader.Load(this);
@@ -3,7 +3,7 @@ using Robust.Client.UserInterface.Controls;
namespace Content.Client.Administration.UI.BanList;
public interface IBanListLine<T> where T : SharedBan
public interface IBanListLine<T> where T : SharedServerBan
{
T Ban { get; }
Label Reason { get; }
@@ -16,7 +16,7 @@ public sealed partial class RoleBanListControl : Control
RobustXamlLoader.Load(this);
}
public void SetRoleBans(List<SharedBan> bans)
public void SetRoleBans(List<SharedServerRoleBan> bans)
{
for (var i = RoleBans.ChildCount - 1; i >= 1; i--)
{
@@ -7,13 +7,13 @@ using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.BanList.RoleBans;
[GenerateTypedNameReferences]
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedBan>
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedServerRoleBan>
{
public SharedBan Ban { get; }
public SharedServerRoleBan Ban { get; }
public event Action<RoleBanListLine>? IdsClicked;
public RoleBanListLine(SharedBan ban)
public RoleBanListLine(SharedServerRoleBan ban)
{
RobustXamlLoader.Load(this);
@@ -21,7 +21,7 @@ public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedB
IdsHidden.OnPressed += IdsPressed;
BanListEui.SetData(this, ban);
Role.Text = string.Join(", ", ban.Roles ?? []);
Role.Text = ban.Role;
}
private void IdsPressed(ButtonEventArgs buttonEventArgs)
@@ -70,7 +70,7 @@ public sealed partial class AdminNotesLine : BoxContainer
TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
ServerLabel.Text = Note.ServerName ?? "Unknown";
RoundLabel.Text = Note.Rounds.Length == 0 ? "Unknown round" : "Round " + string.Join(',', Note.Rounds);
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
AdminLabel.Text = Note.CreatedByName;
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
@@ -143,12 +143,7 @@ public sealed partial class AdminNotesLine : BoxContainer
private string FormatRoleBanMessage()
{
var rolesText = string.Join(
", ",
// Explicit cast here to avoid sandbox violation.
(IEnumerable<BanRoleDef>?)Note.BannedRoles ?? [new BanRoleDef("what", "You should not be seeing this")]);
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {rolesText} ");
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
return FormatBanMessageCommon(banMessage);
}
@@ -32,9 +32,9 @@ public sealed partial class AdminNotesLinePopup : Popup
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
RoundIdLabel.Text = note.Rounds.Length == 0
RoundIdLabel.Text = note.Round == null
? Loc.GetString("admin-notes-round-id-unknown")
: Loc.GetString("admin-notes-round-id", ("id", string.Join(',', note.Rounds)));
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
@@ -25,8 +25,8 @@ public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
var index = _sprite.LayerMapReserve((ent.Owner, sprite), ent.Comp.LayerMap);
if (TryComp<HumanoidProfileComponent>(ent, out var humanoid) &&
ent.Comp.SpeciesSprites.TryGetValue(humanoid.Species, out var speciesSprite))
if (TryComp<HumanoidAppearanceComponent>(ent, out var humanoidAppearance) &&
ent.Comp.SpeciesSprites.TryGetValue(humanoidAppearance.Species, out var speciesSprite))
{
_sprite.LayerSetSprite((ent.Owner, sprite), index, speciesSprite);
}
@@ -1,4 +1,4 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'anomaly-generator-ui-title'}"
@@ -32,7 +32,7 @@
</BoxContainer>
<!--Sprite View-->
<PanelContainer Margin="12 0 0 0"
StyleClasses="BackgroundPanelDark"
StyleClasses="Inset"
VerticalAlignment="Center">
<SpriteView Name="EntityView"
SetSize="96 96"
@@ -0,0 +1,9 @@
using Content.Shared.Atmos.Components;
namespace Content.Client.Atmos.Components;
[RegisterComponent]
public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponent
{
}
@@ -1,7 +1,6 @@
using Content.Client.Stylesheets;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Monitor;
using Content.Shared.FixedPoint;
using Content.Shared.Temperature;
@@ -23,7 +22,6 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
private readonly IEntityManager _entManager;
private readonly IResourceCache _cache;
private readonly SharedAtmosphereSystem _atmosphere;
private Dictionary<AtmosAlarmType, string> _alarmStrings = new Dictionary<AtmosAlarmType, string>()
{
@@ -39,7 +37,6 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
_entManager = IoCManager.Resolve<IEntityManager>();
_cache = IoCManager.Resolve<IResourceCache>();
_atmosphere = _entManager.System<SharedAtmosphereSystem>();
NetEntity = uid;
Coordinates = coordinates;
@@ -152,7 +149,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
{
FixedPoint2 gasPercent = percent * 100f;
var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation);
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
var gasLabel = new Label()
{
@@ -1,7 +1,6 @@
using Content.Client.Stylesheets;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
@@ -20,14 +19,12 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer
private readonly IEntityManager _entManager;
private readonly IResourceCache _cache;
private readonly SharedAtmosphereSystem _atmosphere;
public AtmosMonitoringEntryContainer(AtmosMonitoringConsoleEntry data)
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_cache = IoCManager.Resolve<IResourceCache>();
_atmosphere = _entManager.System<SharedAtmosphereSystem>();
Data = data;
@@ -135,7 +132,7 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer
var gasPercent = (FixedPoint2)0f;
gasPercent = percent * 100f;
var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation);
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
var gasLabel = new Label()
{
@@ -10,9 +10,7 @@ namespace Content.Client.Atmos.EntitySystems
[UsedImplicitly]
internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = [];
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = new();
// Configuration set by debug commands and used by AtmosDebugOverlay {
/// <summary>Value source for display</summary>
@@ -27,8 +25,6 @@ namespace Content.Client.Atmos.EntitySystems
public bool CfgCBM = false;
// }
private AtmosDebugOverlay? _overlay;
public override void Initialize()
{
base.Initialize();
@@ -38,6 +34,10 @@ namespace Content.Client.Atmos.EntitySystems
SubscribeNetworkEvent<AtmosDebugOverlayDisableMessage>(HandleAtmosDebugOverlayDisableMessage);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if(!overlayManager.HasOverlay<AtmosDebugOverlay>())
overlayManager.AddOverlay(new AtmosDebugOverlay(this));
}
private void OnGridRemoved(GridRemovalEvent ev)
@@ -51,25 +51,19 @@ namespace Content.Client.Atmos.EntitySystems
private void HandleAtmosDebugOverlayMessage(AtmosDebugOverlayMessage message)
{
TileData[GetEntity(message.GridId)] = message;
if (_overlay is not null)
return;
_overlay = new AtmosDebugOverlay(this);
_overlayManager.AddOverlay(_overlay);
}
private void HandleAtmosDebugOverlayDisableMessage(AtmosDebugOverlayDisableMessage ev)
{
TileData.Clear();
RemoveOverlay();
}
public override void Shutdown()
{
base.Shutdown();
RemoveOverlay();
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (overlayManager.HasOverlay<AtmosDebugOverlay>())
overlayManager.RemoveOverlay<AtmosDebugOverlay>();
}
public void Reset(RoundRestartCleanupEvent ev)
@@ -81,15 +75,6 @@ namespace Content.Client.Atmos.EntitySystems
{
return TileData.ContainsKey(gridId);
}
private void RemoveOverlay()
{
if (_overlay is null)
return;
_overlayManager.RemoveOverlay(_overlay);
_overlay = null;
}
}
internal enum AtmosDebugOverlayMode : byte
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Reactions;
namespace Content.Client.Atmos.EntitySystems;
@@ -14,29 +13,6 @@ public sealed partial class AtmosphereSystem
implementation.
*/
/// <inheritdoc/>
/// <remarks>No-op on client as reactions aren't entirely in shared.
/// Don't call it. Smile.</remarks>
public override ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
{
// Reactions don't work on client so don't even try.
throw new NotImplementedException();
}
public override bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.Epsilon)
{
var tmp = new float[Atmospherics.AdjustedNumberOfGases];
NumericsHelpers.Multiply(mixture.Moles, GasFuelMask, tmp);
return NumericsHelpers.HorizontalAdd(tmp) > epsilon;
}
public override bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.Epsilon)
{
var tmp = new float[Atmospherics.AdjustedNumberOfGases];
NumericsHelpers.Multiply(mixture.Moles, GasOxidizerMask, tmp);
return NumericsHelpers.HorizontalAdd(tmp) > epsilon;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override float GetHeatCapacityCalculation(float[] moles, bool space)
{
@@ -51,7 +27,7 @@ public sealed partial class AtmosphereSystem
// though this isnt the hottest code path so it should be fine
// the gc can eat a little as a treat
var tmp = new float[moles.Length];
NumericsHelpers.Multiply(moles, GasMolarHeatCapacities, tmp);
NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
// Adjust heat capacity by speedup, because this is primarily what
// determines how quickly gases heat up/cool.
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
@@ -1,30 +0,0 @@
using Content.Client.Atmos.Overlays;
using JetBrains.Annotations;
using Robust.Client.Graphics;
namespace Content.Client.Atmos.EntitySystems;
/// <summary>
/// System responsible for rendering atmos fire animations using <see cref="GasTileFireOverlay"/>.
/// </summary>
[UsedImplicitly]
public sealed class GasTileFireOverlaySystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private GasTileFireOverlay _fireOverlay = default!;
public override void Initialize()
{
base.Initialize();
_fireOverlay = new GasTileFireOverlay();
_overlayMan.AddOverlay(_fireOverlay);
}
public override void Shutdown()
{
base.Shutdown();
_overlayMan.RemoveOverlay<GasTileFireOverlay>();
}
}
@@ -1,30 +0,0 @@
using Content.Client.Atmos.Overlays;
using JetBrains.Annotations;
using Robust.Client.Graphics;
namespace Content.Client.Atmos.EntitySystems;
/// <summary>
/// System responsible for rendering heat distortion using <see cref="GasTileHeatBlurOverlay"/>.
/// </summary>
[UsedImplicitly]
public sealed class GasTileHeatBlurOverlaySystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private GasTileHeatBlurOverlay _gasTileHeatBlurOverlay = default!;
public override void Initialize()
{
base.Initialize();
_gasTileHeatBlurOverlay = new GasTileHeatBlurOverlay();
_overlayMan.AddOverlay(_gasTileHeatBlurOverlay);
}
public override void Shutdown()
{
base.Shutdown();
_overlayMan.RemoveOverlay<GasTileHeatBlurOverlay>();
}
}
@@ -1,85 +1,106 @@
using Content.Client.Atmos.Overlays;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems;
[UsedImplicitly]
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
namespace Content.Client.Atmos.EntitySystems
{
public override void Initialize()
[UsedImplicitly]
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
{
base.Initialize();
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
}
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SpriteSystem _spriteSys = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
{
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
private GasTileOverlay _overlay = default!;
switch (args.Current)
public override void Initialize()
{
// is this a delta or full state?
case GasTileOverlayDeltaState delta:
base.Initialize();
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
_overlayMan.AddOverlay(_overlay);
}
public override void Shutdown()
{
base.Shutdown();
_overlayMan.RemoveOverlay<GasTileOverlay>();
}
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
{
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
switch (args.Current)
{
modifiedChunks = delta.ModifiedChunks;
foreach (var index in comp.Chunks.Keys)
// is this a delta or full state?
case GasTileOverlayDeltaState delta:
{
if (!delta.AllChunks.Contains(index))
comp.Chunks.Remove(index);
}
modifiedChunks = delta.ModifiedChunks;
foreach (var index in comp.Chunks.Keys)
{
if (!delta.AllChunks.Contains(index))
comp.Chunks.Remove(index);
}
break;
}
case GasTileOverlayState state:
{
modifiedChunks = state.Chunks;
foreach (var index in comp.Chunks.Keys)
break;
}
case GasTileOverlayState state:
{
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
modifiedChunks = state.Chunks;
foreach (var index in comp.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
break;
}
break;
default:
return;
}
default:
return;
}
foreach (var (index, data) in modifiedChunks)
{
comp.Chunks[index] = data;
}
}
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
{
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
{
var grid = GetEntity(nent);
if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue;
foreach (var index in removedIndicies)
foreach (var (index, data) in modifiedChunks)
{
comp.Chunks.Remove(index);
comp.Chunks[index] = data;
}
}
foreach (var (nent, gridData) in ev.UpdatedChunks)
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
{
var grid = GetEntity(nent);
if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue;
foreach (var chunkData in gridData)
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
{
comp.Chunks[chunkData.Index] = chunkData;
var grid = GetEntity(nent);
if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue;
foreach (var index in removedIndicies)
{
comp.Chunks.Remove(index);
}
}
foreach (var (nent, gridData) in ev.UpdatedChunks)
{
var grid = GetEntity(nent);
if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue;
foreach (var chunkData in gridData)
{
comp.Chunks[chunkData.Index] = chunkData;
}
}
}
}
@@ -1,31 +0,0 @@
using Content.Client.Atmos.Overlays;
using JetBrains.Annotations;
using Robust.Client.Graphics;
namespace Content.Client.Atmos.EntitySystems;
/// <summary>
/// System responsible for rendering visible atmos gasses (like plasma for example) using <see cref="GasTileVisibleGasOverlay"/>.
/// </summary>
[UsedImplicitly]
public sealed class GasTileVisibleGasOverlaySystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private GasTileVisibleGasOverlay _visibleGasOverlay = default!;
public override void Initialize()
{
base.Initialize();
_visibleGasOverlay = new GasTileVisibleGasOverlay();
_overlayMan.AddOverlay(_visibleGasOverlay);
}
public override void Shutdown()
{
base.Shutdown();
_overlayMan.RemoveOverlay<GasTileVisibleGasOverlay>();
}
}
@@ -7,7 +7,7 @@
<!-- Status (pressure, temperature, alarm state, device total, address, etc) -->
<BoxContainer Orientation="Horizontal" Margin="0 0 0 2">
<!-- Left column (view of entity) -->
<PanelContainer Margin="2 0 6 0" StyleClasses="BackgroundPanelDark" VerticalAlignment="Center" VerticalExpand="True">
<PanelContainer Margin="2 0 6 0" StyleClasses="Inset" VerticalAlignment="Center" VerticalExpand="True">
<SpriteView Name="EntityView" OverrideDirection="South" Scale="2 2" SetSize="64 64"/>
</PanelContainer>
<!-- Center column (pressure, temperature, alarm state) -->
@@ -1,253 +0,0 @@
using Content.Client.Atmos.EntitySystems;
using Content.Client.Graphics;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using System.Numerics;
namespace Content.Client.Atmos.Overlays;
/// <summary>
/// Renders a thermal heatmap overlay for gas tiles, used for equipment like thermal glasses.
/// /// </summary>
public sealed class GasTileDangerousTemperatureOverlay : Overlay
{
public override bool RequestScreenTexture { get; set; } = false;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
private GasTileOverlaySystem? _gasTileOverlay;
private readonly SharedTransformSystem _xformSys;
private EntityQuery<GasTileOverlayComponent> _overlayQuery;
private readonly OverlayResourceCache<CachedResources> _resources = new();
private List<Entity<MapGridComponent>> _grids = new();
// Cache used to transform ThermalByte into Color for overlay
private readonly Color[] _colorCache = new Color[256];
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public GasTileDangerousTemperatureOverlay()
{
IoCManager.InjectDependencies(this);
_xformSys = _entManager.System<SharedTransformSystem>();
_overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
for (byte i = 0; i <= ThermalByte.TempResolution; i++)
{
_colorCache[i] = PreCalculateColor(i);
}
_colorCache[ThermalByte.StateVacuum] = Color.Teal;
_colorCache[ThermalByte.StateVacuum].A = 0.6f;
_colorCache[ThermalByte.AtmosImpossible] = Color.Transparent;
#if DEBUG // This shouldn't happend so tell me if you see this LimeGreen on the screen
_colorCache[ThermalByte.ReservedFuture0] = Color.LimeGreen;
_colorCache[ThermalByte.ReservedFuture1] = Color.LimeGreen;
_colorCache[ThermalByte.ReservedFuture2] = Color.LimeGreen;
#else
_colorCache[ThermalByte.ReservedFuture0] = Color.Transparent;
_colorCache[ThermalByte.ReservedFuture1] = Color.Transparent;
_colorCache[ThermalByte.ReservedFuture2] = Color.Transparent;
#endif
}
/// <summary>
/// Used for Calculating onscreen color from ThermalByte core value
/// /// </summary>
private static Color PreCalculateColor(byte byteTemp)
{
// Color Thresholds in Kelvin
// -150 C
const float deepFreezeK = 123.15f;
// -50 C
const float freezeStartK = 223.15f;
// 0 C
const float waterFreezeK = 273.15f;
// 50 C
const float heatStartK = 323.15f;
// 100 C
const float waterBoilK = 373.15f;
// 300 C
const float superHeatK = 573.15f;
var tempK = byteTemp * ThermalByte.TempDegreeResolution;
// Neutral Zone Check (0C to 50C)
// If between 273.15K and 323.15K, it's transparent.
if (tempK >= waterFreezeK && tempK < heatStartK)
{
return Color.Transparent;
}
Color resultingColor;
switch (tempK)
{
case < deepFreezeK:
resultingColor = Color.FromHex("#330066");
resultingColor.A = 0.7f;
break;
case < freezeStartK:
// Interpolate Deep Purple -> Blue
// Range: 123.15 to 223.15 (Span: 100)
resultingColor = Color.InterpolateBetween(
Color.FromHex("#330066"),
Color.Blue,
(tempK - deepFreezeK) * 0.01f);
resultingColor.A = 0.6f;
break;
case < waterFreezeK:
// Interpolate Blue -> Transparent
// Range: 223.15 to 273.15 (Span: 50)
resultingColor = Color.InterpolateBetween(
new Color(Color.Blue.R, Color.Blue.G, Color.Blue.B, 0.6f),
new Color(Color.Blue.R, Color.Blue.G, Color.Blue.B, 0.2f),
(tempK - freezeStartK) * 0.02f);
break;
case < waterBoilK:
// Interpolate Transparent -> Yellow
// Range: 323.15 to 373.15 (Span: 50)
resultingColor = Color.InterpolateBetween(
new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 0.2f),
new Color(Color.Yellow.R, Color.Yellow.G, Color.Yellow.B, 0.6f),
(tempK - heatStartK) * 0.02f);
break;
case < superHeatK:
// Interpolate Yellow -> Red
// Range: 373.15 to 573.15 (Span: 200)
resultingColor = Color.InterpolateBetween(
Color.Yellow,
Color.Red,
(tempK - waterBoilK) * 0.005f);
resultingColor.A = 0.6f;
break;
default:
resultingColor = Color.DarkRed;
resultingColor.A = 0.7f;
break;
}
return resultingColor;
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return false;
_gasTileOverlay ??= _entManager.System<GasTileOverlaySystem>();
if (_gasTileOverlay == null)
return false;
var target = args.Viewport.RenderTarget;
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
if (res.TemperatureTarget is null || res.TemperatureTarget.Texture.Size != target.Size)
{
res.TemperatureTarget?.Dispose();
res.TemperatureTarget = _clyde.CreateRenderTarget(
target.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
name: nameof(GasTileDangerousTemperatureOverlay));
}
var drawHandle = args.WorldHandle;
var worldBounds = args.WorldBounds;
var worldAABB = args.WorldAABB;
var mapId = args.MapId;
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
drawHandle.RenderInRenderTarget(res.TemperatureTarget,
() =>
{
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _grids);
foreach (var grid in _grids)
{
if (!_overlayQuery.TryGetComponent(grid.Owner, out var comp))
continue;
var gridTileSizeVec = grid.Comp.TileSizeVector;
var gridTileCenterVec = grid.Comp.TileSizeHalfVector;
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
drawHandle.SetTransform(gridEntToViewportLocal);
var worldToGridLocal = _xformSys.GetInvWorldMatrix(grid.Owner);
var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
var localBounds = new Box2i(
(int)MathF.Floor(floatBounds.Left),
(int)MathF.Floor(floatBounds.Bottom),
(int)MathF.Ceiling(floatBounds.Right),
(int)MathF.Ceiling(floatBounds.Top));
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var tileGas))
{
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(tilePosition))
continue;
var gasColor = _colorCache[tileGas.ByteGasTemperature.Value];
if (gasColor.A <= 0f)
continue;
drawHandle.DrawRect(
Box2.CenteredAround(tilePosition + gridTileCenterVec, gridTileSizeVec),
gasColor
);
}
}
}
},
new Color(0, 0, 0, 0));
drawHandle.SetTransform(Matrix3x2.Identity);
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
if (res.TemperatureTarget != null)
args.WorldHandle.DrawTextureRect(res.TemperatureTarget.Texture, args.WorldBounds);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
protected override void DisposeBehavior()
{
_resources.Dispose();
base.DisposeBehavior();
}
private sealed class CachedResources : IDisposable
{
public IRenderTexture? TemperatureTarget;
public void Dispose()
{
TemperatureTarget?.Dispose();
}
}
}
@@ -1,172 +0,0 @@
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Species;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Numerics;
namespace Content.Client.Atmos.Overlays;
/// <summary>
/// Overlay responsible for rendering atmos fire animation.
/// </summary>
public sealed class GasTileFireOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
private readonly SharedTransformSystem _xformSys;
private readonly SharedMapSystem _mapSystem = default!;
private readonly ShaderInstance _shader;
private readonly float[] _timer;
private readonly float[][] _frameDelays;
private readonly int[] _frameCounter;
// TODO combine textures into a single texture atlas.
private readonly Texture[][] _frames;
private const int FireStates = 3;
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
public const int GasOverlayZIndex = (int)Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
public GasTileFireOverlay()
{
IoCManager.InjectDependencies(this);
_xformSys = _entManager.System<SharedTransformSystem>();
_mapSystem = _entManager.System<SharedMapSystem>();
_shader = _protoMan.Index(UnshadedShader).Instance();
ZIndex = GasOverlayZIndex;
_timer = new float[FireStates];
_frameDelays = new float[FireStates][];
_frameCounter = new int[FireStates];
_frames = new Texture[FireStates][];
var fire = _resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
for (var i = 0; i < FireStates; i++)
{
if (!fire.TryGetState((i + 1).ToString(), out var state))
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
_frames[i] = state.GetFrames(RsiDirection.South);
_frameDelays[i] = state.GetDelays();
_frameCounter[i] = 0;
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
for (var i = 0; i < FireStates; i++)
{
var delays = _frameDelays[i];
if (delays.Length == 0)
continue;
var frameCount = _frameCounter[i];
_timer[i] += args.DeltaSeconds;
var time = delays[frameCount];
if (_timer[i] < time) continue;
_timer[i] -= time;
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
}
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return;
var drawHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
var gridState = (args.WorldBounds,
args.WorldHandle,
_frames,
_frameCounter,
_shader,
overlayQuery,
xformQuery,
_xformSys);
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
if (args.Space != OverlaySpace.WorldSpaceEntities)
return;
// TODO: WorldBounds callback.
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
static (EntityUid uid, MapGridComponent grid,
ref (Box2Rotated WorldBounds,
DrawingHandleWorld drawHandle,
Texture[][] frames,
int[] frameCounter,
ShaderInstance shader,
EntityQuery<GasTileOverlayComponent> overlayQuery,
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSys) state) =>
{
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
{
return true;
}
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i(
(int)MathF.Floor(floatBounds.Left),
(int)MathF.Floor(floatBounds.Bottom),
(int)MathF.Ceiling(floatBounds.Right),
(int)MathF.Ceiling(floatBounds.Top));
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
// by chunk, even though its currently slower.
state.drawHandle.UseShader(state.shader);
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var gas))
{
if (gas.FireState == 0)
continue;
var index = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(index))
continue;
var fireState = gas.FireState - 1;
var texture = state.frames[fireState][state.frameCounter[fireState]];
state.drawHandle.DrawTexture(texture, index);
}
}
return true;
});
drawHandle.UseShader(null);
drawHandle.SetTransform(Matrix3x2.Identity);
}
}
@@ -1,263 +0,0 @@
using Content.Client.Atmos.EntitySystems;
using Content.Client.Graphics;
using Content.Client.Resources;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using System.Numerics;
using Color = Robust.Shared.Maths.Color;
using Texture = Robust.Client.Graphics.Texture;
namespace Content.Client.Atmos.Overlays;
/// <summary>
/// Overlay responsible for rendering heat distortion shader.
/// </summary>
public sealed class GasTileHeatBlurOverlay : Overlay
{
public override bool RequestScreenTexture { get; set; } = true;
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
private static readonly ProtoId<ShaderPrototype> HeatOverlayShader = "HeatBlur";
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
private readonly SharedTransformSystem _xformSys;
private readonly ShaderInstance _shader;
private readonly Texture _noiseTexture;
private readonly Texture _heatGradientTexture;
private List<Entity<MapGridComponent>> _intersectingGrids = new();
private readonly OverlayResourceCache<CachedResources> _resources = new();
// Overlay settings
private const float
ShaderSpilling = 2.5f; // for example 4f - spills shader one tile from hotspot, 2.5f - spills it half tile
private const float ShaderStrength = 0.04f; // Makes waves stronger
private const float ShaderScale = 1f; // Makes more waves
private const float ShaderSpeed = 0.4f; // Makes waves run faster
// Overlay settings for reduced motion setting
private const float ShaderStrengthForReducedMotion = 0.01f;
private const float ShaderScaleReducedMotion = 0.5f;
private const float ShaderSpeedReducedMotion = 0.25f;
private const int MinDistortionTemp = 300; // Distortion starts to show up at this temperature in Kelvins
private const int MaxDistortionTemp = 2000; // Maximum distortion strength at this temperature in Kelvins
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public GasTileHeatBlurOverlay()
{
IoCManager.InjectDependencies(this);
_xformSys = _entManager.System<SharedTransformSystem>();
_noiseTexture = _resourceCache.GetTexture("/Textures/Effects/HeatBlur/perlin_noise.png");
_heatGradientTexture = _resourceCache.GetTexture("/Textures/Effects/HeatBlur/soft_circle.png");
_shader = _proto.Index(HeatOverlayShader).InstanceUnique();
_configManager.OnValueChanged(CCVars.ReducedMotion, SetReducedMotion, invokeImmediately: true);
}
private void SetReducedMotion(bool reducedMotion)
{
_shader.SetParameter("strength_scale", reducedMotion ? ShaderStrengthForReducedMotion : ShaderStrength);
_shader.SetParameter("spatial_scale", reducedMotion ? ShaderScaleReducedMotion : ShaderScale);
_shader.SetParameter("speed_scale", reducedMotion ? ShaderSpeedReducedMotion : ShaderSpeed);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return false;
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
var target = args.Viewport.RenderTarget;
// Probably the resolution of the game window changed, remake the textures.
if (res.HeatTarget?.Texture.Size != target.Size)
{
res.HeatTarget?.Dispose();
res.HeatTarget = _clyde.CreateRenderTarget(
target.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
name: nameof(GasTileHeatBlurOverlaySystem));
}
if (res.HeatBlurTarget?.Texture.Size != target.Size)
{
res.HeatBlurTarget?.Dispose();
res.HeatBlurTarget = _clyde.CreateRenderTarget(
target.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
name: $"{nameof(GasTileHeatBlurOverlaySystem)}-blur");
}
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
args.WorldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
var mapId = args.MapId;
var worldAABB = args.WorldAABB;
var worldBounds = args.WorldBounds;
var worldHandle = args.WorldHandle;
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
// If there is no distortion after checking all visible tiles, we can bail early
var anyDistortion = false;
// We're rendering in the context of the heat target texture, which will encode data as to where and how strong
// the heat distortion will be
args.WorldHandle.RenderInRenderTarget(res.HeatTarget,
() =>
{
_intersectingGrids.Clear();
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _intersectingGrids);
foreach (var grid in _intersectingGrids)
{
if (!overlayQuery.TryGetComponent(grid.Owner, out var comp))
continue;
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
if (!Matrix3x2.Invert(gridEntToViewportLocal, out var viewportLocalToGridEnt))
continue;
var uvToUi = Matrix3Helpers.CreateScale(res.HeatTarget.Size.X, -res.HeatTarget.Size.Y);
var uvToGridEnt = uvToUi * viewportLocalToGridEnt;
// Because we want the actual distortion to be calculated based on the grid coordinates*, we need
// to pass a matrix transformation to go from the viewport coordinates to grid coordinates.
// * (why? because otherwise the effect would shimmer like crazy as you moved around, think
// moving a piece of warped glass above a picture instead of placing the warped glass on the
// paper and moving them together)
_shader.SetParameter("grid_ent_from_viewport_local", uvToGridEnt);
// Draw commands (like DrawRect) will be using grid coordinates from here
worldHandle.SetTransform(gridEntToViewportLocal);
// We only care about tiles that fit in these bounds
var worldToGridLocal = _xformSys.GetInvWorldMatrix(grid.Owner);
var floatBounds = worldToGridLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
var localBounds = new Box2i(
(int)MathF.Floor(floatBounds.Left),
(int)MathF.Floor(floatBounds.Bottom),
(int)MathF.Ceiling(floatBounds.Right),
(int)MathF.Ceiling(floatBounds.Top));
// for each tile and its gas --->
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var tileGas))
{
// Check and make sure the tile is within the viewport/screen
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(tilePosition))
continue;
// Get the distortion strength from the temperature and bail if it's not hot enough
var strength = GetHeatDistortionStrength(tileGas.ByteGasTemperature);
if (strength <= 0f)
continue;
anyDistortion = true;
// Encode the strength in the red channel
// alpha set to 1 as tile is active
worldHandle.DrawTextureRect(
_heatGradientTexture,
Box2.CenteredAround(tilePosition + grid.Comp.TileSizeHalfVector,
grid.Comp.TileSizeVector * ShaderSpilling),
new Color(strength, 0f, 0f));
}
}
}
},
// This clears the buffer to all zero first...
new Color(0, 0, 0, 0));
// no distortion, no need to render
if (!anyDistortion)
{
args.WorldHandle.UseShader(null);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
return false;
}
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
var res = _resources.GetForViewport(args.Viewport, static _ => new CachedResources());
if (ScreenTexture is null || res.HeatTarget is null || res.HeatBlurTarget is null)
return;
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_shader.SetParameter("NOISE_TEXTURE", _noiseTexture);
args.WorldHandle.UseShader(_shader);
args.WorldHandle.DrawTextureRect(res.HeatTarget.Texture, args.WorldBounds);
args.WorldHandle.UseShader(null);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
protected override void DisposeBehavior()
{
_resources.Dispose();
_configManager.UnsubValueChanged(CCVars.ReducedMotion, SetReducedMotion);
base.DisposeBehavior();
}
/// <summary>
/// Gets the strength of the heat distortion effect based on the temperature of the tile.
/// The strength is a value between 0 and 1, where 0 means no distortion and 1 means maximum distortion.
/// </summary>
/// <param name="temp">The temperature of the tile.</param>
/// <returns>The strength of the heat distortion effect.</returns>
/// <seealso cref="ThermalByte"/>
private static float GetHeatDistortionStrength(ThermalByte temp)
{
if (!temp.TryGetTemperature(out var kelvinTemp))
{
return 0f;
}
var strength = (kelvinTemp - MinDistortionTemp) / (MaxDistortionTemp - MinDistortionTemp);
return MathHelper.Clamp01(strength);
}
internal sealed class CachedResources : IDisposable
{
public IRenderTexture? HeatTarget;
public IRenderTexture? HeatBlurTarget;
public void Dispose()
{
HeatTarget?.Dispose();
HeatBlurTarget?.Dispose();
}
}
}
@@ -0,0 +1,302 @@
using System.Numerics;
using Content.Client.Atmos.Components;
using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Atmos.Overlays
{
public sealed class GasTileOverlay : Overlay
{
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager;
private readonly SharedAtmosphereSystem _atmosphereSystem;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _xformSys;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader;
// Gas overlays
private readonly float[] _timer;
private readonly float[][] _frameDelays;
private readonly int[] _frameCounter;
// TODO combine textures into a single texture atlas.
private readonly Texture[][] _frames;
// Fire overlays
private const int FireStates = 3;
private const string FireRsiPath = "/Textures/Effects/fire.rsi";
private readonly float[] _fireTimer = new float[FireStates];
private readonly float[][] _fireFrameDelays = new float[FireStates][];
private readonly int[] _fireFrameCounter = new int[FireStates];
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
private int _gasCount;
public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
{
_entManager = entManager;
_mapManager = IoCManager.Resolve<IMapManager>();
_atmosphereSystem = entManager.System<SharedAtmosphereSystem>();
_mapSystem = entManager.System<SharedMapSystem>();
_xformSys = xformSys;
_shader = protoMan.Index(UnshadedShader).Instance();
ZIndex = GasOverlayZIndex;
_gasCount = system.VisibleGasId.Length;
_timer = new float[_gasCount];
_frameDelays = new float[_gasCount][];
_frameCounter = new int[_gasCount];
_frames = new Texture[_gasCount][];
for (var i = 0; i < _gasCount; i++)
{
var gasPrototype = _atmosphereSystem.GetGas(system.VisibleGasId[i]);
SpriteSpecifier overlay;
if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
overlay = new SpriteSpecifier.Rsi(new (gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
overlay = new SpriteSpecifier.Texture(new (gasPrototype.GasOverlayTexture));
else
continue;
switch (overlay)
{
case SpriteSpecifier.Rsi animated:
var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
var stateId = animated.RsiState;
if (!rsi.TryGetState(stateId, out var state))
continue;
_frames[i] = state.GetFrames(RsiDirection.South);
_frameDelays[i] = state.GetDelays();
_frameCounter[i] = 0;
break;
case SpriteSpecifier.Texture texture:
_frames[i] = new[] { spriteSys.Frame0(texture) };
_frameDelays[i] = Array.Empty<float>();
break;
}
}
var fire = resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
for (var i = 0; i < FireStates; i++)
{
if (!fire.TryGetState((i + 1).ToString(), out var state))
throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
_fireFrames[i] = state.GetFrames(RsiDirection.South);
_fireFrameDelays[i] = state.GetDelays();
_fireFrameCounter[i] = 0;
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
for (var i = 0; i < _gasCount; i++)
{
var delays = _frameDelays[i];
if (delays.Length == 0)
continue;
var frameCount = _frameCounter[i];
_timer[i] += args.DeltaSeconds;
var time = delays[frameCount];
if (_timer[i] < time)
continue;
_timer[i] -= time;
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
}
for (var i = 0; i < FireStates; i++)
{
var delays = _fireFrameDelays[i];
if (delays.Length == 0)
continue;
var frameCount = _fireFrameCounter[i];
_fireTimer[i] += args.DeltaSeconds;
var time = delays[frameCount];
if (_fireTimer[i] < time) continue;
_fireTimer[i] -= time;
_fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length;
}
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return;
var drawHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
var gridState = (args.WorldBounds,
args.WorldHandle,
_gasCount,
_frames,
_frameCounter,
_fireFrames,
_fireFrameCounter,
_shader,
overlayQuery,
xformQuery,
_xformSys);
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
DrawMapOverlay(drawHandle, args, mapUid, atmos);
if (args.Space != OverlaySpace.WorldSpaceEntities)
return;
// TODO: WorldBounds callback.
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
static (EntityUid uid, MapGridComponent grid,
ref (Box2Rotated WorldBounds,
DrawingHandleWorld drawHandle,
int gasCount,
Texture[][] frames,
int[] frameCounter,
Texture[][] fireFrames,
int[] fireFrameCounter,
ShaderInstance shader,
EntityQuery<GasTileOverlayComponent> overlayQuery,
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSys) state) =>
{
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
{
return true;
}
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i(
(int) MathF.Floor(floatBounds.Left),
(int) MathF.Floor(floatBounds.Bottom),
(int) MathF.Ceiling(floatBounds.Right),
(int) MathF.Ceiling(floatBounds.Top));
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
// by chunk, even though its currently slower.
state.drawHandle.UseShader(null);
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var gas))
{
if (gas.Opacity == null!)
continue;
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(tilePosition))
continue;
for (var i = 0; i < state.gasCount; i++)
{
var opacity = gas.Opacity[i];
if (opacity > 0)
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
}
}
// And again for fire, with the unshaded shader
state.drawHandle.UseShader(state.shader);
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var gas))
{
if (gas.FireState == 0)
continue;
var index = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(index))
continue;
var fireState = gas.FireState - 1;
var texture = state.fireFrames[fireState][state.fireFrameCounter[fireState]];
state.drawHandle.DrawTexture(texture, index);
}
}
return true;
});
drawHandle.UseShader(null);
drawHandle.SetTransform(Matrix3x2.Identity);
}
private void DrawMapOverlay(
DrawingHandleWorld handle,
OverlayDrawArgs args,
EntityUid map,
MapAtmosphereComponent atmos)
{
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
// map-grid atmospheres get drawn above grids
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
return;
// Normal map atmospheres get drawn below grids
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
return;
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
var topRight = args.WorldAABB.TopRight.Ceiled();
for (var x = bottomLeft.X; x <= topRight.X; x++)
{
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
{
var tilePosition = new Vector2(x, y);
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
{
var opacity = atmos.OverlayData.Opacity[i];
if (opacity > 0)
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
}
}
}
}
}
@@ -1,248 +0,0 @@
using Content.Client.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Numerics;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
namespace Content.Client.Atmos.Overlays;
/// <summary>
/// Overlay responsible for rendering visible atmos gasses (like plasma for example) usin.
/// </summary>
public sealed class GasTileVisibleGasOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
private readonly SharedAtmosphereSystem _atmosphereSystem;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _xformSys;
private readonly SharedGasTileOverlaySystem _gasTileOverlaySystem;
private readonly SpriteSystem _spriteSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader;
// Gas overlays
private readonly float[] _timer;
private readonly float[][] _frameDelays;
private readonly int[] _frameCounter;
// TODO combine textures into a single texture atlas.
private readonly Texture[][] _frames;
private readonly int _gasCount;
public const int GasOverlayZIndex = (int)DrawDepth.Gasses; // Under ghosts and fire, above mostly everything else
public GasTileVisibleGasOverlay()
{
IoCManager.InjectDependencies(this);
_atmosphereSystem = _entManager.System<SharedAtmosphereSystem>();
_mapSystem = _entManager.System<SharedMapSystem>();
_xformSys = _entManager.System<SharedTransformSystem>();
_gasTileOverlaySystem = _entManager.System<SharedGasTileOverlaySystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
_shader = _protoManager.Index(UnshadedShader).Instance();
ZIndex = GasOverlayZIndex;
_gasCount = _gasTileOverlaySystem.VisibleGasId.Length;
_timer = new float[_gasCount];
_frameDelays = new float[_gasCount][];
_frameCounter = new int[_gasCount];
_frames = new Texture[_gasCount][];
for (var i = 0; i < _gasCount; i++)
{
var gasPrototype = _atmosphereSystem.GetGas(_gasTileOverlaySystem.VisibleGasId[i]);
switch (gasPrototype.GasOverlaySprite)
{
case SpriteSpecifier.Rsi animated:
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
var stateId = animated.RsiState;
if (!rsi.TryGetState(stateId, out var state))
continue;
_frames[i] = state.GetFrames(RsiDirection.South);
_frameDelays[i] = state.GetDelays();
_frameCounter[i] = 0;
break;
case SpriteSpecifier.Texture texture:
_frames[i] = new[] { _spriteSystem.Frame0(texture) };
_frameDelays[i] = Array.Empty<float>();
break;
}
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
for (var i = 0; i < _gasCount; i++)
{
var delays = _frameDelays[i];
if (delays.Length == 0)
continue;
var frameCount = _frameCounter[i];
_timer[i] += args.DeltaSeconds;
var time = delays[frameCount];
if (_timer[i] < time)
continue;
_timer[i] -= time;
_frameCounter[i] = (frameCount + 1) % _frames[i].Length;
}
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.MapId == MapId.Nullspace)
return;
var drawHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
var gridState = (args.WorldBounds,
args.WorldHandle,
_gasCount,
_frames,
_frameCounter,
_shader,
overlayQuery,
xformQuery,
_xformSys);
var mapUid = _mapSystem.GetMapOrInvalid(args.MapId);
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
DrawMapOverlay(drawHandle, args, mapUid, atmos);
if (args.Space != OverlaySpace.WorldSpaceEntities)
return;
// TODO: WorldBounds callback.
_mapManager.FindGridsIntersecting(args.MapId,
args.WorldAABB,
ref gridState,
static (EntityUid uid,
MapGridComponent grid,
ref (Box2Rotated WorldBounds,
DrawingHandleWorld drawHandle,
int gasCount,
Texture[][] frames,
int[] frameCounter,
ShaderInstance shader,
EntityQuery<GasTileOverlayComponent> overlayQuery,
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSys) state) =>
{
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
{
return true;
}
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i(
(int)MathF.Floor(floatBounds.Left),
(int)MathF.Floor(floatBounds.Bottom),
(int)MathF.Ceiling(floatBounds.Right),
(int)MathF.Ceiling(floatBounds.Top));
// Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
// ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
// by chunk, even though its currently slower.
state.drawHandle.UseShader(null);
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
while (enumerator.MoveNext(out var gas))
{
if (gas.Opacity == null!)
continue;
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
if (!localBounds.Contains(tilePosition))
continue;
for (var i = 0; i < state.gasCount; i++)
{
var opacity = gas.Opacity[i];
if (opacity > 0)
{
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]],
tilePosition,
Color.White.WithAlpha(opacity));
}
}
}
}
return true;
});
drawHandle.UseShader(null);
drawHandle.SetTransform(Matrix3x2.Identity);
}
private void DrawMapOverlay(
DrawingHandleWorld handle,
OverlayDrawArgs args,
EntityUid map,
MapAtmosphereComponent atmos)
{
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
// map-grid atmospheres get drawn above grids
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
return;
// Normal map atmospheres get drawn below grids
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
return;
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
var topRight = args.WorldAABB.TopRight.Ceiled();
for (var x = bottomLeft.X; x <= topRight.X; x++)
{
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
{
var tilePosition = new Vector2(x, y);
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
{
var opacity = atmos.OverlayData.Opacity[i];
if (opacity > 0)
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
}
}
}
}
@@ -1,33 +1,41 @@
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Content.Shared.Atmos.Components;
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
namespace Content.Client.Atmos.UI;
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
namespace Content.Client.Atmos.UI
{
[ViewVariables]
private GasAnalyzerWindow? _window;
public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
{
}
[ViewVariables]
private GasAnalyzerWindow? _window;
protected override void Open()
{
base.Open();
public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
_window.OnClose += Close;
}
protected override void Open()
{
base.Open();
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
if (_window == null)
return;
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
_window.OnClose += Close;
}
if (message is not GasAnalyzerUserMessage cast)
return;
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
if (_window == null)
return;
if (message is not GasAnalyzerUserMessage cast)
return;
_window.Populate(cast);
}
_window.Populate(cast);
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
_window?.Dispose();
}
}
}
@@ -1,8 +1,6 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Temperature;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
@@ -10,6 +8,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Atmos.UI
@@ -17,17 +16,25 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences]
public sealed partial class GasAnalyzerWindow : DefaultWindow
{
private readonly SharedAtmosphereSystem _atmosphere;
private NetEntity _currentEntity = NetEntity.Invalid;
public GasAnalyzerWindow()
{
RobustXamlLoader.Load(this);
_atmosphere = IoCManager.Resolve<IEntityManager>().System<SharedAtmosphereSystem>();
}
public void Populate(GasAnalyzerUserMessage msg)
{
if (msg.Error != null)
{
CTopBox.AddChild(new Label
{
Text = Loc.GetString("gas-analyzer-window-error-text", ("errorText", msg.Error)),
FontColorOverride = Color.Red
});
return;
}
if (msg.NodeGasMixes.Length == 0)
{
CTopBox.AddChild(new Label
@@ -322,31 +329,31 @@ namespace Content.Client.Atmos.UI
for (var j = 0; j < gasMix.Gases.Length; j++)
{
var gasEntry = gasMix.Gases[j];
var gasProto = _atmosphere.GetGas(gasEntry.Gas);
var gas = gasMix.Gases[j];
var color = Color.FromHex($"#{gas.Color}", Color.White);
// Add to the table
tableKey.AddChild(new Label
{
Text = Loc.GetString(gasProto.Name)
Text = Loc.GetString(gas.Name)
});
tableVal.AddChild(new Label
{
Text = Loc.GetString("gas-analyzer-window-molarity-text",
("mol", $"{gasEntry.Amount:0.00}")),
("mol", $"{gas.Amount:0.00}")),
Align = Label.AlignMode.Right,
});
tablePercent.AddChild(new Label
{
Text = Loc.GetString("gas-analyzer-window-percentage-text",
("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.0}")),
("percentage", $"{(gas.Amount / totalGasAmount * 100):0.0}")),
Align = Label.AlignMode.Right
});
// Add to the gas bar //TODO: highlight the currently hover one
gasBar.AddEntry(gasEntry.Amount, gasProto.Color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text",
("gasName", Loc.GetString(gasProto.Name)),
("amount", $"{gasEntry.Amount:0.##}"),
("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.#}")));
gasBar.AddEntry(gas.Amount, color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text",
("gasName", gas.Name),
("amount", $"{gas.Amount:0.##}"),
("percentage", $"{(gas.Amount / totalGasAmount * 100):0.#}")));
}
dataContainer.AddChild(gasBar);
@@ -37,9 +37,10 @@ namespace Content.Client.Atmos.UI
_window.SelectGasPressed += OnSelectGasPressed;
}
private void OnToggleStatusButtonPressed(bool status)
private void OnToggleStatusButtonPressed()
{
SendMessage(new GasFilterToggleStatusMessage(status));
if (_window is null) return;
SendMessage(new GasFilterToggleStatusMessage(_window.FilterStatus));
}
private void OnFilterTransferRatePressed(string value)
+4 -5
View File
@@ -1,12 +1,11 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="480 400" Title="Filter">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<controls:SwitchButton
Name="ToggleStatusButton"
HorizontalAlignment="Left"
Pressed="True" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-filter-ui-filter-status}"/>
<Button Name="ToggleStatusButton"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-filter-ui-filter-transfer-rate}"/>
@@ -18,10 +18,11 @@ namespace Content.Client.Atmos.UI
{
private readonly ButtonGroup _buttonGroup = new();
public bool FilterStatus = true;
public string? SelectedGas;
public string? CurrentGasId;
public event Action<bool>? ToggleStatusButtonPressed;
public event Action? ToggleStatusButtonPressed;
public event Action<string>? FilterTransferRateChanged;
public event Action? SelectGasPressed;
@@ -29,7 +30,8 @@ namespace Content.Client.Atmos.UI
{
RobustXamlLoader.Load(this);
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
ToggleStatusButton.OnPressed += _ => SetFilterStatus(!FilterStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
FilterTransferRateInput.OnTextChanged += _ => SetFilterRate.Disabled = false;
SetFilterRate.OnPressed += _ =>
@@ -51,7 +53,15 @@ namespace Content.Client.Atmos.UI
public void SetFilterStatus(bool enabled)
{
ToggleStatusButton.Pressed = enabled;
FilterStatus = enabled;
if (enabled)
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-filter-ui-status-enabled");
}
else
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-filter-ui-status-disabled");
}
}
public void SetGasFiltered(string? id, string name)
@@ -33,9 +33,10 @@ namespace Content.Client.Atmos.UI
_window.MixerNodePercentageChanged += OnMixerSetPercentagePressed;
}
private void OnToggleStatusButtonPressed(bool status)
private void OnToggleStatusButtonPressed()
{
SendMessage(new GasMixerToggleStatusMessage(status));
if (_window is null) return;
SendMessage(new GasMixerToggleStatusMessage(_window.MixerStatus));
}
private void OnMixerOutputPressurePressed(string value)
+5 -5
View File
@@ -1,12 +1,12 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="200 200" Title="Gas Mixer">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<controls:SwitchButton
Name="ToggleStatusButton"
HorizontalAlignment="Left"
Pressed="True" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-mixer-ui-mixer-status}"/>
<Control MinSize="5 0" />
<Button Name="ToggleStatusButton"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-mixer-ui-mixer-output-pressure}"/>
<Control MinSize="5 0" />
+14 -3
View File
@@ -21,7 +21,9 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences]
public sealed partial class GasMixerWindow : DefaultWindow
{
public event Action<bool>? ToggleStatusButtonPressed;
public bool MixerStatus = true;
public event Action? ToggleStatusButtonPressed;
public event Action<string>? MixerOutputPressureChanged;
public event Action<string>? MixerNodePercentageChanged;
@@ -31,7 +33,8 @@ namespace Content.Client.Atmos.UI
{
RobustXamlLoader.Load(this);
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
ToggleStatusButton.OnPressed += _ => SetMixerStatus(!MixerStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
MixerPressureOutputInput.OnTextChanged += _ => SetOutputPressureButton.Disabled = false;
SetOutputPressureButton.OnPressed += _ =>
@@ -80,7 +83,15 @@ namespace Content.Client.Atmos.UI
public void SetMixerStatus(bool enabled)
{
ToggleStatusButton.Pressed = enabled;
MixerStatus = enabled;
if (enabled)
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-mixer-ui-status-enabled");
}
else
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-mixer-ui-status-disabled");
}
}
}
}
@@ -42,9 +42,12 @@ public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKe
_window.SetOutputPressure(pump.TargetPressure);
}
private void OnToggleStatusButtonPressed(bool status)
private void OnToggleStatusButtonPressed()
{
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(status));
if (_window is null)
return;
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
}
private void OnPumpOutputPressurePressed(float value)
@@ -4,10 +4,8 @@
SetSize="340 110" MinSize="340 110" Title="Pressure Pump">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<controls:SwitchButton
Name="ToggleStatusButton"
HorizontalAlignment="Left"
Pressed="True" />
<Label Text="{Loc comp-gas-pump-ui-pump-status}" Margin="0 0 5 0"/>
<Button Name="ToggleStatusButton"/>
<Control HorizontalExpand="True"/>
<Button HorizontalAlignment="Right" Name="SetOutputPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" Disabled="True" Margin="0 0 5 0"/>
<Button Name="SetMaxPressureButton" Text="{Loc comp-gas-pump-ui-pump-set-max}" />
@@ -11,7 +11,9 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences]
public sealed partial class GasPressurePumpWindow : FancyWindow
{
public event Action<bool>? ToggleStatusButtonPressed;
public bool PumpStatus = true;
public event Action? ToggleStatusButtonPressed;
public event Action<float>? PumpOutputPressureChanged;
public float MaxPressure
@@ -31,7 +33,8 @@ namespace Content.Client.Atmos.UI
{
RobustXamlLoader.Load(this);
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
PumpPressureOutputInput.OnValueChanged += _ => SetOutputPressureButton.Disabled = false;
@@ -56,7 +59,15 @@ namespace Content.Client.Atmos.UI
public void SetPumpStatus(bool enabled)
{
ToggleStatusButton.Pressed = enabled;
PumpStatus = enabled;
if (enabled)
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-enabled");
}
else
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-disabled");
}
}
}
}
@@ -37,7 +37,7 @@ namespace Content.Client.Atmos.UI
_window = this.CreateWindow<GasThermomachineWindow>();
_window.ToggleStatusButton.OnToggled += _ => OnToggleStatusButtonPressed();
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
_window.TemperatureSpinbox.OnValueChanged += _ => OnTemperatureChanged(_window.TemperatureSpinbox.Value);
_window.Entity = Owner;
Update();
@@ -45,6 +45,9 @@ namespace Content.Client.Atmos.UI
private void OnToggleStatusButtonPressed()
{
if (_window is null) return;
_window.SetActive(!_window.Active);
SendPredictedMessage(new GasThermomachineToggleMessage());
}
@@ -3,11 +3,11 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="300 120" Title="{Loc comp-gas-thermomachine-ui-title-freezer}">
<BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<controls:SwitchButton
Name="ToggleStatusButton"
HorizontalAlignment="Left"
Pressed="True"
Access="Public" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-thermomachine-ui-toggle}"/>
<Control MinSize="5 0" />
<Button Access="Public" Name="ToggleStatusButton"/>
</BoxContainer>
<BoxContainer Name="SpinboxHBox" Orientation="Horizontal">
<Label Text="{Loc comp-gas-thermomachine-ui-temperature}"/>
</BoxContainer>
@@ -12,6 +12,8 @@ public sealed partial class GasThermomachineWindow : FancyWindow
{
[Dependency] private readonly IEntityManager _entManager = default!;
public bool Active = true;
public FloatSpinBox TemperatureSpinbox;
public EntityUid Entity;
@@ -28,7 +30,15 @@ public sealed partial class GasThermomachineWindow : FancyWindow
public void SetActive(bool active)
{
ToggleStatusButton.Pressed = active;
Active = active;
if (active)
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-thermomachine-ui-status-enabled");
}
else
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-thermomachine-ui-status-disabled");
}
}
public void SetTemperature(float temperature)
@@ -38,9 +38,11 @@ namespace Content.Client.Atmos.UI
Update();
}
private void OnToggleStatusButtonPressed(bool status)
private void OnToggleStatusButtonPressed()
{
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(status));
if (_window is null) return;
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
}
private void OnPumpTransferRatePressed(string value)
@@ -3,10 +3,11 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="200 120" Title="Volume Pump">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<controls:SwitchButton
Name="ToggleStatusButton"
HorizontalAlignment="Left"
Pressed="True" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-status}"/>
<Control MinSize="5 0" />
<Button Name="ToggleStatusButton"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc comp-gas-pump-ui-pump-transfer-rate}"/>
@@ -19,14 +19,17 @@ namespace Content.Client.Atmos.UI
[GenerateTypedNameReferences]
public sealed partial class GasVolumePumpWindow : FancyWindow
{
public event Action<bool>? ToggleStatusButtonPressed;
public bool PumpStatus = true;
public event Action? ToggleStatusButtonPressed;
public event Action<string>? PumpTransferRateChanged;
public GasVolumePumpWindow()
{
RobustXamlLoader.Load(this);
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
PumpTransferRateInput.OnTextChanged += _ => SetTransferRateButton.Disabled = false;
SetTransferRateButton.OnPressed += _ =>
@@ -49,7 +52,15 @@ namespace Content.Client.Atmos.UI
public void SetPumpStatus(bool enabled)
{
ToggleStatusButton.Pressed = enabled;
PumpStatus = enabled;
if (enabled)
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-enabled");
}
else
{
ToggleStatusButton.Text = Loc.GetString("comp-gas-pump-ui-status-disabled");
}
}
}
}
@@ -24,7 +24,7 @@ public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
_window = this.CreateWindow<SpaceHeaterWindow>();
_window.ToggleStatusButton.OnToggled += _ => OnToggleStatusButtonPressed();
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
_window.IncreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(_window.TemperatureChangeDelta);
_window.DecreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(-_window.TemperatureChangeDelta);
_window.ModeSelector.OnItemSelected += OnModeChanged;
@@ -34,6 +34,7 @@ public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
private void OnToggleStatusButtonPressed()
{
_window?.SetActive(!_window.Active);
SendMessage(new SpaceHeaterToggleMessage());
}
@@ -1,15 +1,13 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="280 160" Title="{Loc comp-space-heater-ui-title}">
<BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<controls:SwitchButton
Name="ToggleStatusButton"
HorizontalExpand="True"
Access="Public" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Button Text="{Loc comp-space-heater-ui-status-disabled}" Access="Public" Name="ToggleStatusButton"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" SeparationOverride="5">
<Label Text="{Loc comp-space-heater-ui-mode}"/>
<OptionButton Access="Public" Name="ModeSelector"/>
@@ -15,6 +15,7 @@ public sealed partial class SpaceHeaterWindow : DefaultWindow
{
// To account for a minimum delta temperature for atmos equalization to trigger we use a fixed step for target temperature increment/decrement
public int TemperatureChangeDelta = 5;
public bool Active;
// Temperatures range bounds in Kelvin (K)
public float MinTemp;
@@ -48,7 +49,17 @@ public sealed partial class SpaceHeaterWindow : DefaultWindow
public void SetActive(bool active)
{
Active = active;
ToggleStatusButton.Pressed = active;
if (active)
{
ToggleStatusButton.Text = Loc.GetString("comp-space-heater-ui-status-enabled");
}
else
{
ToggleStatusButton.Text = Loc.GetString("comp-space-heater-ui-status-disabled");
}
}
public void SetTemperature(float targetTemperature)
@@ -1,7 +1,6 @@
using System.Linq;
using Content.Shared.BarSign;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.BarSign.Ui;
@@ -17,12 +16,13 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
{
base.Open();
var sign = EntMan.GetComponentOrNull<BarSignComponent>(Owner)?.Current is { } current
? _prototype.Index(current)
: null;
var allSigns = BarSignSystem.GetAllBarSigns(_prototype)
.OrderBy(p => Loc.GetString(p.Name))
.ToList();
_menu = this.CreateWindow<BarSignMenu>();
_menu.LoadSigns(allSigns);
_menu = new(sign, allSigns);
_menu.OnSignSelected += id =>
{
@@ -30,17 +30,16 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
};
_menu.OnClose += Close;
_menu.OpenToLeft();
_menu.OpenCentered();
}
public override void Update()
{
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp)
|| !_prototype.Resolve(signComp.Current, out var signPrototype))
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp))
return;
_menu?.UpdateState(signPrototype);
if (_prototype.Resolve(signComp.Current, out var signPrototype))
_menu?.UpdateState(signPrototype);
}
}
+18 -11
View File
@@ -8,13 +8,23 @@ namespace Content.Client.BarSign.Ui;
[GenerateTypedNameReferences]
public sealed partial class BarSignMenu : FancyWindow
{
private List<BarSignPrototype> _cachedPrototypes = new();
private string? _currentId;
private readonly List<BarSignPrototype> _cachedPrototypes = new();
public event Action<string>? OnSignSelected;
public BarSignMenu()
public BarSignMenu(BarSignPrototype? currentSign, List<BarSignPrototype> signs)
{
RobustXamlLoader.Load(this);
_currentId = currentSign?.ID;
_cachedPrototypes.Clear();
_cachedPrototypes = signs;
foreach (var proto in _cachedPrototypes)
{
SignOptions.AddItem(Loc.GetString(proto.Name));
}
SignOptions.OnItemSelected += idx =>
{
@@ -22,21 +32,18 @@ public sealed partial class BarSignMenu : FancyWindow
SignOptions.SelectId(idx.Id);
};
}
public void LoadSigns(List<BarSignPrototype> signs)
{
_cachedPrototypes.Clear();
_cachedPrototypes = signs;
foreach (var proto in _cachedPrototypes)
if (currentSign != null)
{
SignOptions.AddItem(Loc.GetString(proto.Name));
var idx = _cachedPrototypes.IndexOf(currentSign);
SignOptions.TrySelectId(idx);
}
}
public void UpdateState(BarSignPrototype newSign)
{
if (_currentId != null && newSign.ID == _currentId)
return;
_currentId = newSign.ID;
var idx = _cachedPrototypes.IndexOf(newSign);
SignOptions.TrySelectId(idx);
}
@@ -0,0 +1,7 @@
using Content.Shared.Body.Systems;
namespace Content.Client.Body.Systems;
public sealed class BodySystem : SharedBodySystem
{
}
@@ -0,0 +1,6 @@
using Content.Shared.Body.Systems;
namespace Content.Client.Body.Systems;
/// <inheritdoc/>
public sealed class MetabolizerSystem : SharedMetabolizerSystem;
-264
View File
@@ -1,264 +0,0 @@
using System.Linq;
using Content.Shared.Body;
using Content.Shared.CCVar;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Body;
public sealed class VisualBodySystem : SharedVisualBodySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly MarkingManager _marking = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VisualOrganComponent, OrganGotInsertedEvent>(OnOrganGotInserted);
SubscribeLocalEvent<VisualOrganComponent, OrganGotRemovedEvent>(OnOrganGotRemoved);
SubscribeLocalEvent<VisualOrganComponent, AfterAutoHandleStateEvent>(OnOrganState);
SubscribeLocalEvent<VisualOrganMarkingsComponent, OrganGotInsertedEvent>(OnMarkingsGotInserted);
SubscribeLocalEvent<VisualOrganMarkingsComponent, OrganGotRemovedEvent>(OnMarkingsGotRemoved);
SubscribeLocalEvent<VisualOrganMarkingsComponent, AfterAutoHandleStateEvent>(OnMarkingsState);
SubscribeLocalEvent<VisualOrganMarkingsComponent, BodyRelayedEvent<HumanoidLayerVisibilityChangedEvent>>(OnMarkingsChangedVisibility);
Subs.CVar(_cfg, CCVars.AccessibilityClientCensorNudity, OnCensorshipChanged, true);
Subs.CVar(_cfg, CCVars.AccessibilityServerCensorNudity, OnCensorshipChanged, true);
}
private void OnCensorshipChanged(bool value)
{
var query = AllEntityQuery<OrganComponent, VisualOrganMarkingsComponent>();
while (query.MoveNext(out var ent, out var organComp, out var markingsComp))
{
if (organComp.Body is not { } body)
continue;
RemoveMarkings((ent, markingsComp), body);
ApplyMarkings((ent, markingsComp), body);
}
}
private void OnOrganGotInserted(Entity<VisualOrganComponent> ent, ref OrganGotInsertedEvent args)
{
ApplyVisual(ent, args.Target);
}
private void OnOrganGotRemoved(Entity<VisualOrganComponent> ent, ref OrganGotRemovedEvent args)
{
RemoveVisual(ent, args.Target);
}
private void OnOrganState(Entity<VisualOrganComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
ApplyVisual(ent, body);
}
private void ApplyVisual(Entity<VisualOrganComponent> ent, EntityUid target)
{
if (!_sprite.LayerMapTryGet(target, ent.Comp.Layer, out var index, true))
return;
_sprite.LayerSetData(target, index, ent.Comp.Data);
}
private void RemoveVisual(Entity<VisualOrganComponent> ent, EntityUid target)
{
if (!_sprite.LayerMapTryGet(target, ent.Comp.Layer, out var index, true))
return;
_sprite.LayerSetRsiState(target, index, RSI.StateId.Invalid);
}
private void OnMarkingsGotInserted(Entity<VisualOrganMarkingsComponent> ent, ref OrganGotInsertedEvent args)
{
ApplyMarkings(ent, args.Target);
}
private void OnMarkingsGotRemoved(Entity<VisualOrganMarkingsComponent> ent, ref OrganGotRemovedEvent args)
{
RemoveMarkings(ent, args.Target);
}
private void OnMarkingsState(Entity<VisualOrganMarkingsComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
RemoveMarkings(ent, body);
ApplyMarkings(ent, body);
}
protected override void SetOrganColor(Entity<VisualOrganComponent> ent, Color color)
{
base.SetOrganColor(ent, color);
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
ApplyVisual(ent, body);
}
protected override void SetOrganMarkings(Entity<VisualOrganMarkingsComponent> ent, Dictionary<HumanoidVisualLayers, List<Marking>> markings)
{
base.SetOrganMarkings(ent, markings);
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
RemoveMarkings(ent, body);
ApplyMarkings(ent, body);
}
protected override void SetOrganAppearance(Entity<VisualOrganComponent> ent, PrototypeLayerData data)
{
base.SetOrganAppearance(ent, data);
if (Comp<OrganComponent>(ent).Body is not { } body)
return;
ApplyVisual(ent, body);
}
private IEnumerable<Marking> AllMarkings(Entity<VisualOrganMarkingsComponent> ent)
{
foreach (var markings in ent.Comp.Markings.Values)
{
foreach (var marking in markings)
{
yield return marking;
}
}
var censorNudity = _cfg.GetCVar(CCVars.AccessibilityClientCensorNudity) || _cfg.GetCVar(CCVars.AccessibilityServerCensorNudity);
if (!censorNudity)
yield break;
var group = _prototype.Index(ent.Comp.MarkingData.Group);
foreach (var layer in ent.Comp.MarkingData.Layers)
{
if (!group.Limits.TryGetValue(layer, out var layerLimits))
continue;
if (layerLimits.NudityDefault.Count < 1)
continue;
var markings = ent.Comp.Markings.GetValueOrDefault(layer) ?? [];
if (markings.Any(marking => _marking.TryGetMarking(marking, out var proto) && proto.BodyPart == layer))
continue;
foreach (var marking in layerLimits.NudityDefault)
{
yield return new(marking, 1);
}
}
}
private void ApplyMarkings(Entity<VisualOrganMarkingsComponent> ent, EntityUid target)
{
var applied = new List<Marking>();
foreach (var marking in AllMarkings(ent))
{
if (!_marking.TryGetMarking(marking, out var proto))
continue;
if (!_sprite.LayerMapTryGet(target, proto.BodyPart, out var index, true))
continue;
for (var i = 0; i < proto.Sprites.Count; i++)
{
var sprite = proto.Sprites[i];
DebugTools.Assert(sprite is SpriteSpecifier.Rsi);
if (sprite is not SpriteSpecifier.Rsi rsi)
continue;
var layerId = $"{proto.ID}-{rsi.RsiState}";
if (!_sprite.LayerMapTryGet(target, layerId, out _, false))
{
var layer = _sprite.AddLayer(target, sprite, index + i + 1);
_sprite.LayerMapSet(target, layerId, layer);
_sprite.LayerSetSprite(target, layerId, rsi);
}
if (marking.MarkingColors is not null && i < marking.MarkingColors.Count)
_sprite.LayerSetColor(target, layerId, marking.MarkingColors[i]);
else
_sprite.LayerSetColor(target, layerId, Color.White);
}
applied.Add(marking);
}
ent.Comp.AppliedMarkings = applied;
}
private void RemoveMarkings(Entity<VisualOrganMarkingsComponent> ent, EntityUid target)
{
foreach (var marking in ent.Comp.AppliedMarkings)
{
if (!_marking.TryGetMarking(marking, out var proto))
continue;
foreach (var sprite in proto.Sprites)
{
DebugTools.Assert(sprite is SpriteSpecifier.Rsi);
if (sprite is not SpriteSpecifier.Rsi rsi)
continue;
var layerId = $"{proto.ID}-{rsi.RsiState}";
if (!_sprite.LayerMapTryGet(target, layerId, out var index, false))
continue;
_sprite.LayerMapRemove(target, layerId);
_sprite.RemoveLayer(target, index);
}
}
}
private void OnMarkingsChangedVisibility(Entity<VisualOrganMarkingsComponent> ent, ref BodyRelayedEvent<HumanoidLayerVisibilityChangedEvent> args)
{
if (!ent.Comp.HideableLayers.Contains(args.Args.Layer))
return;
foreach (var markings in ent.Comp.Markings.Values)
{
foreach (var marking in markings)
{
if (!_marking.TryGetMarking(marking, out var proto))
continue;
if (proto.BodyPart != args.Args.Layer && !(ent.Comp.DependentHidingLayers.TryGetValue(args.Args.Layer, out var dependent) && dependent.Contains(proto.BodyPart)))
continue;
foreach (var sprite in proto.Sprites)
{
DebugTools.Assert(sprite is SpriteSpecifier.Rsi);
if (sprite is not SpriteSpecifier.Rsi rsi)
continue;
var layerId = $"{proto.ID}-{rsi.RsiState}";
if (!_sprite.LayerMapTryGet(args.Body.Owner, layerId, out var index, true))
continue;
_sprite.LayerSetVisible(args.Body.Owner, index, args.Args.Visible);
}
}
}
}
}
@@ -70,9 +70,9 @@ namespace Content.Client.Cargo.BUI
_menu.OnClose += Close;
_menu.OnItemSelected += (row) =>
_menu.OnItemSelected += (args) =>
{
if (row == null)
if (args.Button.Parent is not CargoProductRow row)
return;
description.Clear();
@@ -175,23 +175,23 @@ namespace Content.Client.Cargo.BUI
return true;
}
private void RemoveOrder(CargoOrderData? order)
private void RemoveOrder(ButtonEventArgs args)
{
if (order == null)
if (args.Button.Parent?.Parent is not CargoOrderRow row || row.Order == null)
return;
SendMessage(new CargoConsoleRemoveOrderMessage(order.OrderId));
SendMessage(new CargoConsoleRemoveOrderMessage(row.Order.OrderId));
}
private void ApproveOrder(CargoOrderData? order)
private void ApproveOrder(ButtonEventArgs args)
{
if (order == null)
if (args.Button.Parent?.Parent is not CargoOrderRow row || row.Order == null)
return;
if (OrderCount >= OrderCapacity)
return;
SendMessage(new CargoConsoleApproveOrderMessage(order.OrderId));
SendMessage(new CargoConsoleApproveOrderMessage(row.Order.OrderId));
}
}
}
+83 -223
View File
@@ -1,226 +1,86 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="540 390"
SetSize="995 600">
<!-- Main Container -->
<BoxContainer Orientation="Vertical"
VerticalExpand="True">
<TabContainer Name="TabContainer" VerticalExpand="True">
<!-- Sub-Main Container -->
<BoxContainer Orientation="Horizontal"
VerticalExpand="True"
Margin="8 4 8 6">
<!-- Left Part -->
<BoxContainer Orientation="Vertical"
SeparationOverride="4"
Margin="0 0 8 0"
HorizontalExpand="True">
<!-- Info -->
<BoxContainer Orientation="Vertical">
<GridContainer Columns="3">
<!-- Account -->
<Label Text="{Loc 'cargo-console-menu-account-name-label'}"
StyleClasses="LabelKeyText" />
<PanelContainer StyleClasses="LowDivider" Margin="0 -2"/>
<RichTextLabel Name="AccountNameLabel"
Text="{Loc 'cargo-console-menu-account-name-none-text'}"
Margin="4 0"/>
<!-- Balance -->
<Label Text="{Loc 'cargo-console-menu-points-label'}"
StyleClasses="LabelKeyText"/>
<PanelContainer StyleClasses="LowDivider" Margin="0 -2"/>
<RichTextLabel Name="PointsLabel"
Text="$0"
Margin="4 0" />
<!-- Orders Count/Capacity -->
<Label Text="{Loc 'cargo-console-menu-order-capacity-label'}"
StyleClasses="LabelKeyText" />
<PanelContainer StyleClasses="LowDivider" Margin="0 -2 0 -1"/>
<Label Name="ShuttleCapacityLabel"
Text="0/20"
Margin="4 0"/>
</GridContainer>
<PanelContainer StyleClasses="LowDivider" Margin="0 4.5 -8 0"/>
</BoxContainer>
<!-- Search -->
<BoxContainer Orientation="Horizontal"
Margin="0 2 0 0">
<LineEdit Name="SearchBar"
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
HorizontalExpand="True" />
<OptionButton Name="Categories"
Prefix="{Loc 'cargo-console-menu-categories-label'}"
StyleClasses="OpenLeft"/>
</BoxContainer>
<!-- Product list -->
<ScrollContainer
HorizontalExpand="False"
VerticalExpand="True"
HScrollEnabled="False">
<BoxContainer Name="Products"
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<!-- Products get added here by code -->
</BoxContainer>
</ScrollContainer>
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" Margin="0 -8"/>
<!-- Right Part -->
<BoxContainer Orientation="Vertical"
SizeFlagsStretchRatio="0.8"
HorizontalExpand="True"
Name="RightPart">
<!-- Requests Part -->
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
SizeFlagsStretchRatio="2">
<!-- Title -->
<controls:StripeBack>
<Label Text="{Loc 'cargo-console-menu-requests-label'}"
HorizontalAlignment="Center"
Margin="4"/>
</controls:StripeBack>
<PanelContainer VerticalExpand="True"
Margin="0 -4 0 0">
<!-- Background -->
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#040404" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="Requests"
Orientation="Vertical"
StyleClasses="transparentItemList"
VerticalExpand="True"
SeparationOverride="8"
Margin="8">
<!-- Requests are added here by code -->
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
<!-- Orders Part -->
<BoxContainer Orientation="Vertical"
VerticalExpand="True">
<!-- Title -->
<controls:StripeBack>
<Label Text="{Loc 'cargo-console-menu-orders-label'}"
HorizontalAlignment="Center"
Margin="4"/>
</controls:StripeBack>
<PanelContainer VerticalExpand="True"
Margin="0 -4 0 0">
<!-- Background -->
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#040404" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical"
Margin="6">
<ScrollContainer VerticalExpand="True">
<BoxContainer Orientation="Vertical"
StyleClasses="transparentItemList"
VerticalExpand="True"
SeparationOverride="6">
<!-- Orders are added here by code -->
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Funds tab -->
<BoxContainer Orientation="Vertical" Margin="15">
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="TransferLimitLabel" Margin="0 0 15 0"/>
<RichTextLabel Name="UnlimitedNotifier" Text="{Loc 'cargo-console-menu-account-action-transfer-limit-unlimited-notifier'}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Text="{Loc 'cargo-console-menu-account-action-select'}" Margin="0 0 10 0"/>
<OptionButton Name="ActionOptions"/>
</BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="AmountText" Text="{ Loc 'cargo-console-menu-account-action-amount'}"/>
<SpinBox Name="TransferSpinBox" MinWidth="100" Value="10"/>
</BoxContainer>
<Control MinHeight="15"/>
<BoxContainer HorizontalAlignment="Center">
<Button Name="AccountActionButton" Text="{ Loc 'cargo-console-menu-account-action-button'}" MinHeight="45" MinWidth="120"/>
</BoxContainer>
<Control VerticalExpand="True"/>
<BoxContainer VerticalAlignment="Bottom" HorizontalAlignment="Center">
<Button Name="AccountLimitToggleButton" Text="{ Loc 'cargo-console-menu-toggle-account-lock-button'}" MinHeight="45" MinWidth="120"/>
</BoxContainer>
</BoxContainer>
</TabContainer>
<!-- Footer -->
<!-- TODO: Create customControls element -->
<BoxContainer Orientation="Vertical"
VerticalAlignment="Bottom">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal"
Margin="12 0 6 2"
VerticalAlignment="Bottom">
<!-- Footer title -->
<Label Text="{Loc 'cargo-console-menu-flavor-left'}"
StyleClasses="WindowFooterText" />
<!-- Version -->
<Label Text="{Loc 'cargo-console-menu-flavor-right'}"
StyleClasses="WindowFooterText"
HorizontalAlignment="Right"
HorizontalExpand="True"
Margin="0 0 4 0" />
<TextureRect StyleClasses="NTLogoDark"
Stretch="KeepAspectCentered"
VerticalAlignment="Center"
HorizontalAlignment="Right"
SetSize="19 19"/>
</BoxContainer>
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="600 600"
MinSize="600 600">
<BoxContainer Orientation="Vertical" Margin="15 5 15 10">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cargo-console-menu-account-name-label'}"
StyleClasses="LabelKeyText" />
<RichTextLabel Name="AccountNameLabel"
Text="{Loc 'cargo-console-menu-account-name-none-text'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cargo-console-menu-points-label'}"
StyleClasses="LabelKeyText" />
<RichTextLabel Name="PointsLabel"
Text="$0" />
</BoxContainer>
<Control MinHeight="10"/>
<TabContainer Name="TabContainer" VerticalExpand="True">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<BoxContainer Orientation="Horizontal">
<OptionButton Name="Categories"
Prefix="{Loc 'cargo-console-menu-categories-label'}"
HorizontalExpand="True" />
<LineEdit Name="SearchBar"
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
HorizontalExpand="True" />
</BoxContainer>
<Control MinHeight="5"/>
<ScrollContainer HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="2">
<BoxContainer Name="Products"
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<!-- Products get added here by code -->
</BoxContainer>
</ScrollContainer>
<Control MinHeight="5" Name="OrdersSpacer"/>
<PanelContainer VerticalExpand="True"
SizeFlagsStretchRatio="1"
Name="Orders">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#000000" />
</PanelContainer.PanelOverride>
<ScrollContainer VerticalExpand="True">
<BoxContainer Orientation="Vertical" Margin="5">
<Label Text="{Loc 'cargo-console-menu-requests-label'}" />
<BoxContainer Name="Requests"
Orientation="Vertical"
VerticalExpand="True">
<!-- Requests are added here by code -->
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
<!-- Funds tab -->
<BoxContainer Orientation="Vertical" Margin="15">
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="TransferLimitLabel" Margin="0 0 15 0"/>
<RichTextLabel Name="UnlimitedNotifier" Text="{Loc 'cargo-console-menu-account-action-transfer-limit-unlimited-notifier'}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Text="{Loc 'cargo-console-menu-account-action-select'}" Margin="0 0 10 0"/>
<OptionButton Name="ActionOptions"/>
</BoxContainer>
<Control MinHeight="5"/>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="AmountText" Text="{ Loc 'cargo-console-menu-account-action-amount'}"/>
<SpinBox Name="TransferSpinBox" MinWidth="100" Value="10"/>
</BoxContainer>
<Control MinHeight="15"/>
<BoxContainer HorizontalAlignment="Center">
<Button Name="AccountActionButton" Text="{ Loc 'cargo-console-menu-account-action-button'}" MinHeight="45" MinWidth="120"/>
</BoxContainer>
<Control VerticalExpand="True"/>
<BoxContainer VerticalAlignment="Bottom" HorizontalAlignment="Center">
<Button Name="AccountLimitToggleButton" Text="{ Loc 'cargo-console-menu-toggle-account-lock-button'}" MinHeight="45" MinWidth="120"/>
</BoxContainer>
</BoxContainer>
</TabContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -6,7 +6,6 @@ using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
@@ -30,9 +29,9 @@ namespace Content.Client.Cargo.UI
private readonly EntityQuery<CargoOrderConsoleComponent> _orderConsoleQuery;
private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
public event Action<CargoProductRow?>? OnItemSelected;
public event Action<CargoOrderData?>? OnOrderApproved;
public event Action<CargoOrderData?>? OnOrderCanceled;
public event Action<ButtonEventArgs>? OnItemSelected;
public event Action<ButtonEventArgs>? OnOrderApproved;
public event Action<ButtonEventArgs>? OnOrderCanceled;
public event Action<ProtoId<CargoAccountPrototype>?, int>? OnAccountAction;
@@ -165,7 +164,7 @@ namespace Content.Client.Cargo.UI
};
button.MainButton.OnPressed += args =>
{
OnItemSelected?.Invoke(button);
OnItemSelected?.Invoke(args);
};
Products.AddChild(button);
}
@@ -211,66 +210,38 @@ namespace Content.Client.Cargo.UI
foreach (var order in orders)
{
if (order.Approved || !_protoManager.Resolve(order.Product, out var productProto))
if (order.Approved)
continue;
var product = _protoManager.Index<EntityPrototype>(productProto.Product);
var productName = productProto.Name;
var requester = !string.IsNullOrEmpty(order.Requester) ?
order.Requester : Loc.GetString("cargo-console-menu-order-row-alerts-requester-unknown");
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
var productName = product.Name;
var account = _protoManager.Index(order.Account);
var row = new CargoOrderRow
{
Order = order,
Title =
{
Text = Loc.GetString(
"cargo-console-menu-order-row-title",
("productName", productName),
("orderAmount", order.OrderQuantity),
("orderPrice", productProto.Cost)),
},
Stride =
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = account.Color,
ContentMarginBottomOverride = 2,
},
},
Icon = { Texture = _spriteSystem.Frame0(product) },
ProductName =
{
Text = Loc.GetString(
"cargo-console-menu-populate-orders-cargo-order-row-product-name-text",
("orderRequester", requester),
("productName", productName),
("orderAmount", order.OrderQuantity),
("orderRequester", order.Requester),
("accountColor", account.Color),
("account", Loc.GetString(account.Code)))
},
Description =
{
Text = !string.IsNullOrEmpty(order.Reason) ?
Loc.GetString(
"cargo-console-menu-order-row-product-description",
("orderReason", order.Reason))
:
Loc.GetString(
"cargo-console-menu-order-row-product-description",
("orderReason", Loc.GetString("cargo-console-menu-order-row-alerts-reason-absent")))
Text = Loc.GetString("cargo-console-menu-order-reason-description",
("reason", order.Reason))
}
};
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(order); };
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
// TODO: Disable based on access.
row.SetApproveVisible(orderConsole.Mode != CargoOrderConsoleMode.SendToPrimary);
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(order); };
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
Requests.AddChild(row);
}
}
@@ -323,7 +294,8 @@ namespace Content.Client.Cargo.UI
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
_timing.CurTime < orderConsole.NextAccountActionTime;
RightPart.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
OrdersSpacer.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
Orders.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
}
}
}
@@ -1,53 +1,33 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'cargo-console-order-menu-title'}"
MinSize="460 261">
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'cargo-console-order-menu-title'}">
<BoxContainer Orientation="Vertical">
<GridContainer Columns="2">
<Label Text="{Loc 'cargo-console-order-menu-product-label'}"
StyleClasses="LabelKeyText" />
<Label Text="{Loc 'cargo-console-order-menu-product-label'}" />
<Label Name="ProductName"
Access="Public" />
<Label Text="{Loc 'cargo-console-order-menu-description-label'}"
StyleClasses="LabelKeyText" />
Access="Public" />
<Label Text="{Loc 'cargo-console-order-menu-description-label'}" />
<RichTextLabel Name="Description"
Access="Public"
HorizontalExpand="True"
MaxWidth="460" />
<Label Text="{Loc 'cargo-console-order-menu-cost-label'}"
StyleClasses="LabelKeyText" />
Access="Public"
VerticalExpand="True"
SetWidth="350"/>
<Label Text="{Loc 'cargo-console-order-menu-cost-label'}" />
<Label Name="PointCost"
Access="Public" />
<Label Text="{Loc 'cargo-console-order-menu-requester-label'}"
StyleClasses="LabelKeyText" />
Access="Public" />
<Label Text="{Loc 'cargo-console-order-menu-requester-label'}" />
<LineEdit Name="Requester"
Access="Public" />
<Label Text="{Loc 'cargo-console-order-menu-reason-label'}"
StyleClasses="LabelKeyText" />
<Label Text="{Loc 'cargo-console-order-menu-reason-label'}" />
<LineEdit Name="Reason"
Access="Public" />
<Label Text="{Loc 'cargo-console-order-menu-amount-label'}"
StyleClasses="LabelKeyText" />
<Label Text="{Loc 'cargo-console-order-menu-amount-label'}" />
<SpinBox Name="Amount"
Access="Public"
HorizontalExpand="True"
Value="1" />
</GridContainer>
<Control VerticalExpand="True"/>
<PanelContainer StyleClasses="LowDivider" Margin="0 6 0 2"/>
<Button Name="SubmitButton"
Access="Public"
Text="{Loc 'cargo-console-order-menu-submit-button'}"
VerticalAlignment="Bottom" />
TextAlign="Center" />
</BoxContainer>
</DefaultWindow>
+30 -78
View File
@@ -1,81 +1,33 @@
<PanelContainer
xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
HorizontalExpand="True"
StyleClasses="BackgroundPanel">
<!-- Main Container -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
SeparationOverride="6"
Margin="-14 -2">
<BoxContainer Orientation="Vertical">
<Control>
<PanelContainer StyleClasses="WindowHeadingBackground" />
<BoxContainer Margin="6">
<Label Name="Title"
Access="Public"
MaxHeight="28"
StyleClasses="LabelKeyText"/>
</BoxContainer>
</Control>
<PanelContainer Name="Stride"
<PanelContainer xmlns="https://spacestation14.io"
HorizontalExpand="True"
Margin="0 1">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True">
<TextureRect Name="Icon"
Access="Public"
MinSize="32 32"
RectClipContent="True" />
<Control MinWidth="5"/>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<RichTextLabel Name="ProductName"
Access="Public"
HorizontalExpand="True"
StyleClasses="LabelSubText" />
<Label Name="Description"
Access="Public"
HorizontalExpand="True"
StyleClasses="LabelSubText"
ClipText="True" />
</BoxContainer>
<Button Name="Approve"
Access="Public"
StyleClasses="LowDivider" />
</BoxContainer>
<!-- Info -->
<BoxContainer>
<TextureRect Name="Icon"
Access="Public"
MinSize="32 32"
Margin="4"
Stretch="KeepAspectCentered"
RectClipContent="True"
VerticalAlignment="Center"/>
<PanelContainer StyleClasses="LowDivider" Margin="4 0"/>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<RichTextLabel Name="ProductName"
Access="Public"
HorizontalExpand="True"
VerticalExpand="True"
StyleClasses="LabelSubText" />
<Label Name="Description"
Access="Public"
HorizontalExpand="True"
VerticalExpand="True"
StyleClasses="LabelSubText"
ClipText="True" />
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<!-- Buttons -->
<!-- Btn's position hardcoded (args.Button.Parent?.Parent?.Parent type) in CargoConsoleBUI 158 & 166 line -->
<BoxContainer Margin="6">
<Button Name="Approve"
Access="Public"
Text="{Loc 'cargo-console-menu-order-row-button-approve'}"
StyleClasses="OpenRight"
HorizontalExpand="True"/>
<Button Name="Cancel"
Access="Public"
Text="{Loc 'cargo-console-menu-order-row-button-cancel'}"
StyleClasses="OpenLeft"
HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
Text="{Loc 'cargo-console-menu-cargo-order-row-approve-button'}"
StyleClasses="OpenRight" />
<Button Name="Cancel"
Access="Public"
Text="{Loc 'cargo-console-menu-cargo-order-row-cancel-button'}"
StyleClasses="OpenLeft" />
</BoxContainer>
</PanelContainer>
+25 -33
View File
@@ -1,34 +1,26 @@
<BoxContainer xmlns="https://spacestation14.io"
HorizontalExpand="True">
<PanelContainer HorizontalExpand="True">
<!-- Btn position hardcoded (args.Button.Parent?.Parent type) in CargoConsoleBUI 71 line -->
<Button Name="MainButton"
ToolTip=""
Access="Public"
VerticalExpand="False"
StyleClasses="OpenBoth" />
<!-- Icon & Name -->
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
Margin="4 0">
<TextureRect Name="Icon"
Access="Public"
MinSize="32 32"
RectClipContent="True" />
<Label Name="ProductName"
<PanelContainer xmlns="https://spacestation14.io"
HorizontalExpand="True">
<Button Name="MainButton"
ToolTip=""
Access="Public"
HorizontalExpand="True"
VerticalExpand="True"
StyleClasses="OpenBoth"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True">
<TextureRect Name="Icon"
Access="Public"
MinSize="32 32"
RectClipContent="True" />
<Label Name="ProductName"
Access="Public"
HorizontalExpand="True" />
<PanelContainer StyleClasses="BackgroundDark">
<Label Name="PointCost"
Access="Public"
HorizontalExpand="True"
ClipText="True" />
</BoxContainer>
</PanelContainer>
<Label Name="PointCost"
Access="Public"
MinSize="56 32"
Align="Right"
Margin="0 0 5 0"
HorizontalAlignment="Right"/>
</BoxContainer>
MinSize="52 32"
Align="Right"
Margin="0 0 5 0"/>
</PanelContainer>
</BoxContainer>
</PanelContainer>
@@ -7,7 +7,7 @@ using Robust.Client.UserInterface.XAML;
namespace Content.Client.Cargo.UI
{
[GenerateTypedNameReferences]
public sealed partial class CargoProductRow : BoxContainer
public sealed partial class CargoProductRow : PanelContainer
{
public CargoProductPrototype? Product { get; set; }
@@ -34,10 +34,7 @@ namespace Content.Client.Cargo.UI
foreach (var order in orders)
{
if (!protoManager.Resolve(order.Product, out var productProto))
continue;
var product = protoManager.Index<EntityPrototype>(productProto.Product);
var product = protoManager.Index<EntityPrototype>(order.ProductId);
var productName = product.Name;
var account = protoManager.Index(order.Account);
@@ -9,6 +9,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args)
{
@@ -1,7 +1,7 @@
using System.Linq;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Atmos.Prototypes;
using Content.Shared.Body;
using Content.Shared.Body.Part;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
@@ -94,7 +94,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
continue;
//these bloat the hell out of blood/fat
if (entProto.HasComponent<OrganComponent>())
if (entProto.HasComponent<BodyPartComponent>())
continue;
//these feel obvious...
@@ -116,7 +116,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
}
if (extractableComponent.GrindableSolutionName is { } grindableSolutionId &&
if (extractableComponent.GrindableSolution is { } grindableSolutionId &&
entProto.TryGetComponent<SolutionContainerManagerComponent>(out var manager, EntityManager.ComponentFactory) &&
_solutionContainer.TryGetSolution(manager, grindableSolutionId, out var grindableSolution))
{
@@ -8,7 +8,7 @@
VerticalExpand="True"
HorizontalExpand="True"
MinSize="100 150">
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundPanelDark">
<PanelContainer VerticalExpand="True" StyleClasses="Inset">
<BoxContainer Name="GeneticScannerContents" Margin="5 5 5 5" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<Label HorizontalAlignment="Center" Text="{Loc 'cloning-console-window-scanner-details-label'}" />
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
@@ -35,7 +35,7 @@
</BoxContainer>
</PanelContainer>
<Control MinSize="50 5" />
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundPanelDark">
<PanelContainer VerticalExpand="True" StyleClasses="Inset">
<BoxContainer Name="CloningPodContents" Margin="5 5 5 5" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<Label HorizontalAlignment="Center" Text="{Loc 'cloning-console-window-pod-details-label'}" />
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
@@ -273,7 +273,7 @@ public sealed class ClientClothingSystem : ClothingSystem
// Select displacement maps
var displacementData = inventory.Displacements.GetValueOrDefault(slot); //Default unsexed map
var equipeeSex = CompOrNull<HumanoidProfileComponent>(equipee)?.Sex;
var equipeeSex = CompOrNull<HumanoidAppearanceComponent>(equipee)?.Sex;
if (equipeeSex != null)
{
switch (equipeeSex)
@@ -1,4 +1,5 @@
using Content.Client.Hands.Systems;
using Content.Client.NPC.HTN;
using Content.Shared.CCVar;
using Content.Shared.CombatMode;
using Robust.Client.Graphics;
@@ -58,6 +59,11 @@ public sealed class CombatModeSystem : SharedCombatModeSystem
UpdateHud(entity);
}
protected override bool IsNpc(EntityUid uid)
{
return HasComp<HTNComponent>(uid);
}
private void UpdateHud(EntityUid entity)
{
if (entity != _playerManager.LocalEntity || !Timing.IsFirstTimePredicted)
@@ -0,0 +1,36 @@
using Content.Shared.Body.Organ;
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.Containers;
namespace Content.Client.Commands;
public sealed class HideMechanismsCommand : LocalizedEntityCommands
{
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
public override string Command => "hidemechanisms";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out _, out var sprite))
{
_spriteSystem.SetContainerOccluded((uid, sprite), false);
var tempParent = uid;
while (_containerSystem.TryGetContainingContainer((tempParent, null, null), out var container))
{
if (!container.ShowContents)
{
_spriteSystem.SetContainerOccluded((uid, sprite), true);
break;
}
tempParent = container.Owner;
}
}
}
}
@@ -0,0 +1,22 @@
using Content.Shared.Body.Organ;
using Robust.Client.GameObjects;
using Robust.Shared.Console;
namespace Content.Client.Commands;
public sealed class ShowMechanismsCommand : LocalizedEntityCommands
{
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
public override string Command => "showmechanisms";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out _, out var sprite))
{
_spriteSystem.SetContainerOccluded((uid, sprite), false);
}
}
}
@@ -291,6 +291,7 @@ namespace Content.Client.Construction
_ghosts.Add(comp.GhostId, ghost.Value);
var sprite = Comp<SpriteComponent>(ghost.Value);
_sprite.SetColor((ghost.Value, sprite), new Color(48, 255, 48, 128));
if (targetProto.TryGetComponent(out IconComponent? icon, EntityManager.ComponentFactory))
{
@@ -305,11 +306,20 @@ namespace Content.Client.Construction
var targetSprite = EnsureComp<SpriteComponent>(dummy);
EntityManager.System<AppearanceSystem>().OnChangeData(dummy, targetSprite);
_sprite.CopySprite((dummy, targetSprite), (ghost.Value, sprite));
for (var i = 0; i < sprite.AllLayers.Count(); i++)
for (var i = 0; i < targetSprite.AllLayers.Count(); i++)
{
if (!targetSprite[i].Visible || !targetSprite[i].RsiState.IsValid)
continue;
var rsi = targetSprite[i].Rsi ?? targetSprite.BaseRSI;
if (rsi is null || !rsi.TryGetState(targetSprite[i].RsiState, out var state) ||
state.StateId.Name is null)
continue;
_sprite.AddBlankLayer((ghost.Value, sprite), i);
_sprite.LayerSetSprite((ghost.Value, sprite), i, new SpriteSpecifier.Rsi(rsi.Path, state.StateId.Name));
sprite.LayerSetShader(i, "unshaded");
_sprite.LayerSetVisible((ghost.Value, sprite), i, true);
}
Del(dummy);
@@ -317,8 +327,6 @@ namespace Content.Client.Construction
else
return false;
_sprite.SetColor((ghost.Value, sprite), new Color(48, 255, 48, 128));
if (prototype.CanBuildInImpassable)
EnsureComp<WallMountComponent>(ghost.Value).Arc = new(Math.Tau);
@@ -1,6 +1,4 @@
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Client.Damage;
@@ -57,7 +55,7 @@ public sealed partial class DamageVisualsComponent : Component
/// (for example, Brute), and has a value
/// of a DamageVisualizerSprite (see below)
/// </summary>
[DataField("damageOverlayGroups")] public Dictionary<ProtoId<DamageGroupPrototype>, DamageVisualizerSprite>? DamageOverlayGroups;
[DataField("damageOverlayGroups")] public Dictionary<string, DamageVisualizerSprite>? DamageOverlayGroups;
/// <summary>
/// Sets if you want sprites to overlay the
@@ -86,7 +84,7 @@ public sealed partial class DamageVisualsComponent : Component
/// what kind of damage combination
/// you would want, on which threshold.
/// </remarks>
[DataField("damageGroup")] public ProtoId<DamageGroupPrototype>? DamageGroup;
[DataField("damageGroup")] public string? DamageGroup;
/// <summary>
/// Set this if you want incoming damage to be
+8 -9
View File
@@ -2,7 +2,6 @@ using System.Linq;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Damage.Systems;
using Content.Shared.FixedPoint;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
@@ -29,7 +28,6 @@ namespace Content.Client.Damage;
public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
public override void Initialize()
{
@@ -176,7 +174,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
// See if that group is in our entity's damage container.
else if (!damageVisComp.Overlay && damageVisComp.DamageGroup != null)
{
if (!damageContainer.SupportedGroups.Contains(damageVisComp.DamageGroup.Value))
if (!damageContainer.SupportedGroups.Contains(damageVisComp.DamageGroup))
{
Log.Error($"Damage keys were invalid for entity {entity}.");
damageVisComp.Valid = false;
@@ -386,7 +384,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
if (!AppearanceSystem.TryGetData<DamageVisualizerGroupData>(uid, DamageVisualizerKeys.DamageUpdateGroups,
out var data, component))
{
data = new DamageVisualizerGroupData(_damageable.GetDamagePerGroup(uid).Keys.ToList());
data = new DamageVisualizerGroupData(Comp<DamageableComponent>(uid).DamagePerGroup.Keys.ToList());
}
UpdateDamageVisuals(data.GroupList, (uid, damageComponent, spriteComponent, damageVisComp));
@@ -488,10 +486,11 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
/// </summary>
private void UpdateDamageVisuals(Entity<DamageableComponent, SpriteComponent, DamageVisualsComponent> entity)
{
var damageComponent = entity.Comp1;
var spriteComponent = entity.Comp2;
var damageVisComp = entity.Comp3;
if (!CheckThresholdBoundary(_damageable.GetTotalDamage(entity.AsNullable()), damageVisComp.LastDamageThreshold, damageVisComp, out var threshold))
if (!CheckThresholdBoundary(damageComponent.TotalDamage, damageVisComp.LastDamageThreshold, damageVisComp, out var threshold))
return;
damageVisComp.LastDamageThreshold = threshold;
@@ -514,11 +513,11 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
/// according to the list of damage groups
/// passed into it.
/// </summary>
private void UpdateDamageVisuals(List<ProtoId<DamageGroupPrototype>> delta, Entity<DamageableComponent, SpriteComponent, DamageVisualsComponent> entity)
private void UpdateDamageVisuals(List<string> delta, Entity<DamageableComponent, SpriteComponent, DamageVisualsComponent> entity)
{
var damageComponent = entity.Comp1;
var spriteComponent = entity.Comp2;
var damageVisComp = entity.Comp3;
var damage = _damageable.GetAllDamage((entity.Owner, entity.Comp1));
foreach (var damageGroup in delta)
{
@@ -526,7 +525,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
continue;
if (!_prototypeManager.TryIndex<DamageGroupPrototype>(damageGroup, out var damageGroupPrototype)
|| !damage.TryGetDamageInGroup(damageGroupPrototype, out var damageTotal))
|| !damageComponent.Damage.TryGetDamageInGroup(damageGroupPrototype, out var damageTotal))
continue;
if (!damageVisComp.LastThresholdPerGroup.TryGetValue(damageGroup, out var lastThreshold)
@@ -591,7 +590,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
}
else if (damageVisComp.DamageGroup != null)
{
UpdateDamageVisuals(new() { damageVisComp.DamageGroup.Value }, entity);
UpdateDamageVisuals(new List<string>() { damageVisComp.DamageGroup }, entity);
}
else if (damageVisComp.DamageOverlay != null)
{
+10 -27
View File
@@ -1,8 +1,8 @@
using Content.Shared.CCVar;
using Content.Shared.Drunk;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -11,23 +11,19 @@ namespace Content.Client.Drunk;
public sealed class DrunkOverlay : Overlay
{
private static readonly ProtoId<ShaderPrototype> DrunkShader = "Drunk";
private static readonly ProtoId<ShaderPrototype> Shader = "Drunk";
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
private readonly Shared.StatusEffectNew.StatusEffectsSystem _statusEffectsSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
private readonly ShaderInstance _drunkShader;
public float CurrentBoozePower = 0.0f;
// Starting phase for the rotation effect.
// Needed so it doesn't always look the same for 0 motion.
public float Phase = 0f;
private const float VisualThreshold = 10.0f;
private const float PowerDivisor = 250.0f;
@@ -41,22 +37,12 @@ public sealed class DrunkOverlay : Overlay
private const float BoozePowerScale = 8f;
private float _visualScale = 0f;
private float _timeScale = 1f;
private float _distortionScale = 1f;
private float _visualScale = 0;
public DrunkOverlay()
{
IoCManager.InjectDependencies(this);
_statusEffectsSystem = _entityManager.System<Shared.StatusEffectNew.StatusEffectsSystem>();
_drunkShader = _prototypeManager.Index(DrunkShader).InstanceUnique();
_configManager.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true);
}
private void OnReducedMotionChanged(bool reducedMotion)
{
_timeScale = reducedMotion ? 0.0f : 1.0f;
_distortionScale = reducedMotion ? 4.0f : 1.0f; // Make the offset stronger to compensate the lack of motion.
_drunkShader = _prototypeManager.Index(Shader).InstanceUnique();
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -67,14 +53,15 @@ public sealed class DrunkOverlay : Overlay
if (playerEntity == null)
return;
if (!_statusEffectsSystem.TryGetMaxTime<DrunkStatusEffectComponent>(playerEntity.Value, out var status))
var statusSys = _sysMan.GetEntitySystem<Shared.StatusEffectNew.StatusEffectsSystem>();
if (!statusSys.TryGetMaxTime<DrunkStatusEffectComponent>(playerEntity.Value, out var status))
return;
var time = status.Item2;
var power = time == null ? MaxBoozePower : (float)Math.Min((time - _timing.CurTime).Value.TotalSeconds, MaxBoozePower);
var power = time == null ? MaxBoozePower : (float) Math.Min((time - _timing.CurTime).Value.TotalSeconds, MaxBoozePower);
CurrentBoozePower += BoozePowerScale * (power - CurrentBoozePower) * args.DeltaSeconds / (power + 1);
CurrentBoozePower += BoozePowerScale * (power - CurrentBoozePower) * args.DeltaSeconds / (power+1);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
@@ -95,12 +82,8 @@ public sealed class DrunkOverlay : Overlay
return;
var handle = args.WorldHandle;
_drunkShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
_drunkShader.SetParameter("boozePower", _visualScale);
_drunkShader.SetParameter("timeScale", _timeScale);
_drunkShader.SetParameter("distortionScale", _distortionScale);
_drunkShader.SetParameter("phase", Phase);
handle.UseShader(_drunkShader);
handle.DrawRect(args.WorldBounds, Color.White);
handle.UseShader(null);
-6
View File
@@ -3,7 +3,6 @@ using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Client.Drunk;
@@ -11,7 +10,6 @@ public sealed class DrunkSystem : SharedDrunkSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private DrunkOverlay _overlay = default!;
@@ -31,10 +29,7 @@ public sealed class DrunkSystem : SharedDrunkSystem
private void OnStatusApplied(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
{
if (!_overlayMan.HasOverlay<DrunkOverlay>())
{
_overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect
_overlayMan.AddOverlay(_overlay);
}
}
private void OnStatusRemoved(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
@@ -52,7 +47,6 @@ public sealed class DrunkSystem : SharedDrunkSystem
private void OnPlayerAttached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerAttachedEvent> args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
-4
View File
@@ -3,7 +3,6 @@ using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.DebugMon;
using Content.Client.Eui;
using Content.Client.FeedbackPopup;
using Content.Client.Fullscreen;
using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick;
@@ -25,7 +24,6 @@ using Content.Client.UserInterface;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Shared.Ame.Components;
using Content.Shared.FeedbackSystem;
using Content.Shared.Gravity;
using Content.Shared.Localizations;
using Robust.Client;
@@ -80,7 +78,6 @@ namespace Content.Client.Entry
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ClientsidePlaytimeTrackingManager _clientsidePlaytimeManager = default!;
[Dependency] private readonly ClientFeedbackManager _feedbackManager = null!;
public override void PreInit()
{
@@ -183,7 +180,6 @@ namespace Content.Client.Entry
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize();
_titleWindowManager.Initialize();
_feedbackManager.Initialize();
_baseClient.RunLevelChanged += (_, args) =>
{
+3 -3
View File
@@ -25,13 +25,13 @@ public sealed partial class FaxWindow : DefaultWindow
PaperButtonPressed += OnPaperButtonPressed;
FileButton.OnPressed += _ => FileButtonPressed?.Invoke();
PaperButton.OnPressed += _ => PaperButtonPressed?.Invoke();
FileButton.OnPressed += _ => FileButtonPressed?.Invoke();
PaperButton.OnPressed += _ => PaperButtonPressed?.Invoke();
CopyButton.OnPressed += _ => CopyButtonPressed?.Invoke();
SendButton.OnPressed += _ => SendButtonPressed?.Invoke();
RefreshButton.OnPressed += _ => RefreshButtonPressed?.Invoke();
PeerSelector.OnItemSelected += args =>
PeerSelected?.Invoke((string)args.Button.GetItemMetadata(args.Id)!);
PeerSelected?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
}
public void UpdateState(FaxUiState state)
@@ -1,70 +0,0 @@
using Content.Shared.FeedbackSystem;
using Robust.Shared.Prototypes;
namespace Content.Client.FeedbackPopup;
/// <inheritdoc />
public sealed class ClientFeedbackManager : SharedFeedbackManager
{
/// <summary>
/// A read-only set representing the currently displayed feedback popups.
/// </summary>
public override IReadOnlySet<ProtoId<FeedbackPopupPrototype>> DisplayedPopups => _displayedPopups;
private readonly HashSet<ProtoId<FeedbackPopupPrototype>> _displayedPopups = [];
public override void Initialize()
{
base.Initialize();
NetManager.RegisterNetMessage<FeedbackPopupMessage>(ReceivedPopupMessage);
NetManager.RegisterNetMessage<OpenFeedbackPopupMessage>(_ => Open());
}
/// <summary>
/// Opens the feedback popup window.
/// </summary>
public void Open()
{
InvokeDisplayedPopupsChanged(true);
}
/// <inheritdoc />
public override void Display(List<ProtoId<FeedbackPopupPrototype>>? prototypes)
{
if (prototypes == null || !NetManager.IsClient)
return;
var count = _displayedPopups.Count;
_displayedPopups.UnionWith(prototypes);
InvokeDisplayedPopupsChanged(_displayedPopups.Count > count);
}
/// <inheritdoc />
public override void Remove(List<ProtoId<FeedbackPopupPrototype>>? prototypes)
{
if (!NetManager.IsClient)
return;
if (prototypes == null)
{
_displayedPopups.Clear();
}
else
{
_displayedPopups.ExceptWith(prototypes);
}
InvokeDisplayedPopupsChanged(false);
}
private void ReceivedPopupMessage(FeedbackPopupMessage message)
{
if (message.Remove)
{
Remove(message.FeedbackPrototypes);
return;
}
Display(message.FeedbackPrototypes);
}
}
@@ -1,24 +0,0 @@
<Control xmlns="https://spacestation14.io"
MinHeight="100">
<PanelContainer StyleClasses="BackgroundPanel" ModulateSelfOverride="#2b2b31"/>
<BoxContainer Orientation="Vertical">
<!-- Title -->
<PanelContainer StyleIdentifier="FeedbackBorderThinBottom">
<RichTextLabel Name="TitleLabel" Margin="12 6 6 6" />
</PanelContainer>
<!-- Description -->
<RichTextLabel Name="DescriptionLabel" StyleClasses="LabelLight" Margin="12 4 12 8" VerticalExpand="True"/>
<!-- Footer -->
<PanelContainer StyleIdentifier="FeedbackBorderThinTop">
<BoxContainer>
<Label FontColorOverride="#b1b1b2" StyleClasses="LabelSmall" Name="TypeLabel" Margin="14 6 6 6" />
<Button Name="LinkButton" Text="{Loc feedbackpopup-control-button-text}" MinWidth="80"
Margin="8 6 14 6" HorizontalExpand="True" HorizontalAlignment="Right" />
</BoxContainer>
</PanelContainer>
</BoxContainer>
</Control>
@@ -1,54 +0,0 @@
using Content.Shared.FeedbackSystem;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.FeedbackPopup;
[GenerateTypedNameReferences]
public sealed partial class FeedbackEntry : Control
{
private readonly IUriOpener _uri;
private readonly FeedbackPopupPrototype? _prototype;
public FeedbackEntry(ProtoId<FeedbackPopupPrototype> popupProto, IPrototypeManager proto, IUriOpener uri)
{
RobustXamlLoader.Load(this);
_uri = uri;
_prototype = proto.Index(popupProto);
// Title
TitleLabel.Text = _prototype.Title;
DescriptionLabel.Text = _prototype.Description;
TypeLabel.Text = _prototype.ResponseType;
LinkButton.Visible = !string.IsNullOrEmpty(_prototype.ResponseLink);
// link button
if (!string.IsNullOrEmpty(_prototype.ResponseLink))
{
LinkButton.OnPressed += OnButtonPressed;
}
}
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
{
if (!string.IsNullOrWhiteSpace(_prototype?.ResponseLink))
_uri.OpenUri(_prototype.ResponseLink);
}
protected override void Resized()
{
base.Resized();
// magic
TitleLabel.SetWidth = Width - TitleLabel.Margin.SumHorizontal;
TitleLabel.InvalidateArrange();
DescriptionLabel.SetWidth = Width - DescriptionLabel.Margin.SumHorizontal;
DescriptionLabel.InvalidateArrange();
}
}
@@ -1,36 +0,0 @@
using Content.Client.Stylesheets;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using static Content.Client.Stylesheets.StylesheetHelpers;
namespace Content.Client.FeedbackPopup;
[CommonSheetlet]
public sealed class FeedbackPopupSheetlet : Sheetlet<PalettedStylesheet>
{
public override StyleRule[] GetRules(PalettedStylesheet sheet, object config)
{
var borderTop = new StyleBoxFlat()
{
BorderColor = sheet.SecondaryPalette.Base,
BorderThickness = new Thickness(0, 1, 0, 0),
};
var borderBottom = new StyleBoxFlat()
{
BorderColor = sheet.SecondaryPalette.Base,
BorderThickness = new Thickness(0, 0, 0, 1),
};
return
[
E<PanelContainer>()
.Identifier("FeedbackBorderThinTop")
.Prop(PanelContainer.StylePropertyPanel, borderTop),
E<PanelContainer>()
.Identifier("FeedbackBorderThinBottom")
.Prop(PanelContainer.StylePropertyPanel, borderBottom),
];
}
}
@@ -1,75 +0,0 @@
using Content.Shared.FeedbackSystem;
using Content.Shared.GameTicking;
using Robust.Client.UserInterface.Controllers;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.FeedbackPopup;
/// <summary>
/// This handles getting feedback popup messages from the server and making a popup in the client.
/// </summary>
[UsedImplicitly]
public sealed class FeedbackPopupUIController : UIController
{
[Dependency] private readonly ClientFeedbackManager _feedbackManager = null!;
[Dependency] private readonly IPrototypeManager _proto = null!;
[Dependency] private readonly IUriOpener _uri = null!;
private FeedbackPopupWindow _window = null!;
public override void Initialize()
{
_window = new FeedbackPopupWindow(_proto, _uri);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
SubscribeNetworkEvent<RoundEndMessageEvent>(OnRoundEnd);
_feedbackManager.DisplayedPopupsChanged += OnPopupsChanged;
}
public void ToggleWindow()
{
if (_window.IsOpen)
{
_window.Close();
}
else
{
_window.OpenCentered();
}
}
private void OnRoundEnd(RoundEndMessageEvent ev, EntitySessionEventArgs args)
{
// Add round end prototypes.
var roundEndPrototypes = _feedbackManager.GetOriginFeedbackPrototypes(true);
if (roundEndPrototypes.Count == 0)
return;
_feedbackManager.Display(roundEndPrototypes);
// Even if no new prototypes were added, we still want to open the window.
if (!_window.IsOpen)
_window.OpenCentered();
}
private void OnPopupsChanged(bool newPopups)
{
UpdateWindow(_feedbackManager.DisplayedPopups);
if (newPopups && !_window.IsOpen)
_window.OpenCentered();
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev)
{
UpdateWindow(_feedbackManager.DisplayedPopups);
}
private void UpdateWindow(IReadOnlyCollection<ProtoId<FeedbackPopupPrototype>> prototypes)
{
_window.Update(prototypes);
}
}
@@ -1,24 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc feedbackpopup-window-name}" MinSize="510 460" RectClipContent="True">
<BoxContainer Orientation="Vertical">
<!-- main box area -->
<BoxContainer Margin="12 12 12 5" VerticalExpand="True">
<PanelContainer HorizontalExpand="True" StyleClasses="PanelDark">
<ScrollContainer HorizontalExpand="True" HScrollEnabled="False">
<BoxContainer Name="NotificationContainer" HorizontalExpand="True" Orientation="Vertical" Margin="10" SeparationOverride="10" />
</ScrollContainer>
</PanelContainer>
</BoxContainer>
<!-- Footer -->
<BoxContainer Orientation="Vertical" SetHeight="30" Margin="2 0 0 0">
<BoxContainer SetHeight="33" Margin="10 0 10 5">
<Label Text="{Loc feedbackpopup-control-ui-footer}" Margin="6 0" StyleClasses="PdaContentFooterText"/>
<Label Name="NumNotifications" Margin="6 0" HorizontalExpand="True" HorizontalAlignment="Right"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -1,49 +0,0 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.FeedbackSystem;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.FeedbackPopup;
[GenerateTypedNameReferences]
public sealed partial class FeedbackPopupWindow : FancyWindow
{
private readonly IPrototypeManager _proto;
private readonly IUriOpener _uri;
public FeedbackPopupWindow(IPrototypeManager proto, IUriOpener uri)
{
_proto = proto;
_uri = uri;
RobustXamlLoader.Load(this);
DisplayNoEntryLabel();
}
public void Update(IReadOnlyCollection<ProtoId<FeedbackPopupPrototype>> prototypes)
{
NotificationContainer.RemoveAllChildren();
if (prototypes.Count == 0)
DisplayNoEntryLabel();
foreach (var proto in prototypes)
{
NotificationContainer.AddChild(new FeedbackEntry(proto, _proto, _uri));
}
NumNotifications.Text = Loc.GetString("feedbackpopup-control-total-surveys", ("num", prototypes.Count));
}
private void DisplayNoEntryLabel()
{
NotificationContainer.AddChild(new Label()
{
Text = Loc.GetString("feedbackpopup-control-no-entries"),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
});
}
}
@@ -4,12 +4,11 @@ using Content.Client.Chemistry.EntitySystems;
using Content.Client.Guidebook.Richtext;
using Content.Client.Message;
using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Body.Prototypes;
using Content.Shared.CCVar;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Contraband;
using Content.Shared.Localizations;
using Content.Shared.Metabolism;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
@@ -133,18 +132,17 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
#region Effects
if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistry) &&
guideEntryRegistry.GuideEntries != null &&
guideEntryRegistry.GuideEntries.Values.Any(pair => pair.EffectDescriptions.Any() || pair.Metabolites?.Any() == true))
guideEntryRegistry.GuideEntries.Values.Any(pair => pair.EffectDescriptions.Any()))
{
EffectsDescriptionContainer.Children.Clear();
foreach (var (stage, effect) in guideEntryRegistry.GuideEntries)
foreach (var (group, effect) in guideEntryRegistry.GuideEntries)
{
var hasMetabolites = effect.Metabolites?.Any() == true;
if (!effect.EffectDescriptions.Any() && !hasMetabolites)
if (!effect.EffectDescriptions.Any())
continue;
var groupLabel = new RichTextLabel();
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-stage-rate",
("stage", _prototype.Index<MetabolismStagePrototype>(stage).LocalizedName), ("rate", effect.MetabolismRate)));
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
("group", _prototype.Index<MetabolismGroupPrototype>(group).LocalizedName), ("rate", effect.MetabolismRate)));
var descriptionLabel = new RichTextLabel
{
Margin = new Thickness(25, 0, 10, 0)
@@ -157,20 +155,9 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
{
descMsg.AddMarkupOrThrow(effectString);
i++;
if (i < descriptionsCount || hasMetabolites)
if (i < descriptionsCount)
descMsg.PushNewline();
}
if (hasMetabolites)
{
var metabolites = new List<string>();
foreach (var (metabolite, ratio) in effect.Metabolites!)
{
metabolites.Add(Loc.GetString("guidebook-reagent-effects-metabolite-item", ("rate", (double)ratio), ("reagent", _prototype.Index(metabolite).LocalizedName)));
}
metabolites.Sort();
descMsg.AddMarkupOrThrow(Loc.GetString("guidebook-reagent-effects-metabolites", ("items", ContentLocalizationManager.FormatList(metabolites))));
}
descriptionLabel.SetMessage(descMsg);
EffectsDescriptionContainer.AddChild(groupLabel);
@@ -6,7 +6,7 @@ namespace Content.Client.Guidebook.RichText;
/// <summary>
/// RichText tag that can display values extracted from entity prototypes.
/// To be accessed by this tag, the desired field/property must
/// In order to be accessed by this tag, the desired field/property must
/// be tagged with <see cref="Shared.Guidebook.GuidebookDataAttribute"/>.
/// </summary>
public sealed class ProtodataTag : IMarkupTagHandler

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