Merge branch 'master' into sec-tickets
Signed-off-by: Theo <rebe83a@gmail.com>
This commit is contained in:
commit
1fcaf44123
|
|
@ -46,13 +46,23 @@ jobs:
|
|||
run: dotnet build --configuration DebugOpt --no-restore /m
|
||||
|
||||
- name: Run Content.Tests
|
||||
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0
|
||||
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"
|
||||
|
||||
- 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
|
||||
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
|
||||
ci-success:
|
||||
name: Build & Test Debug
|
||||
needs:
|
||||
|
|
|
|||
|
|
@ -48,10 +48,11 @@ jobs:
|
|||
cd RobustToolbox/
|
||||
git submodule update --init --recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
# ubuntu-latest has .NET 10
|
||||
# - name: Setup .NET Core
|
||||
# uses: actions/setup-dotnet@v4.1.0
|
||||
# with:
|
||||
# dotnet-version: 10.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
|
@ -60,7 +61,13 @@ jobs:
|
|||
run: dotnet build Content.Packaging --configuration Release --no-restore /m
|
||||
|
||||
- name: Package server
|
||||
run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64
|
||||
run: dotnet run --project Content.Packaging server --log-build --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 --no-wipe-release
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
|
@ -44,7 +45,7 @@ public class ComponentQueryBenchmark
|
|||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
|
||||
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
|
||||
<Nullable>disable</Nullable>
|
||||
<DefineConstants>$(DefineConstants);ALLOW_BAD_PRACTICES</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<Import Project="../MSBuild/Content.props" />
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapdata = await _pair.CreateTestMap();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
|
|
@ -69,7 +70,7 @@ public class DestructibleBenchmark
|
|||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
var server = _pair.Server;
|
||||
|
||||
_entMan = server.ResolveDependency<IEntityManager>();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
|
|
@ -60,7 +61,7 @@ public class DeviceNetworkingBenchmark
|
|||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(DeviceNetworkingBenchmark).Assembly);
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
var server = _pair.Server;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
|
|
@ -51,7 +52,7 @@ public class GasReactionBenchmark
|
|||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
var server = _pair.Server;
|
||||
|
||||
// Create test map and grid
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
|
|
@ -27,7 +28,7 @@ public class HeatCapacityBenchmark
|
|||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
await _pair.Connect();
|
||||
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
|
||||
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
|
@ -29,7 +30,7 @@ public class MapLoadBenchmark
|
|||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
var server = _pair.Server;
|
||||
|
||||
Paths = server.ResolveDependency<IPrototypeManager>()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
|
@ -50,7 +51,7 @@ public class PvsBenchmark
|
|||
#endif
|
||||
PoolManager.Startup();
|
||||
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||
_pair.Server.CfgMan.SetCVar(CVars.NetPVS, true);
|
||||
_pair.Server.CfgMan.SetCVar(CVars.ThreadParallelCount, 0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#nullable enable
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
|
@ -21,7 +22,7 @@ public class RaiseEventBenchmark
|
|||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup(typeof(BenchSystem).Assembly);
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
_pair = PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null)).GetAwaiter().GetResult();
|
||||
var entMan = _pair.Server.EntMan;
|
||||
var fact = _pair.Server.ResolveDependency<IComponentFactory>();
|
||||
var bus = (EntityEventBus)entMan.EventBus;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
|
|
@ -36,7 +37,7 @@ public class SpawnEquipDeleteBenchmark
|
|||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
PoolManager.Startup();
|
||||
_pair = await PoolManager.GetServerClient();
|
||||
_pair = await PoolManager.GetServerClient(testContext: new ExternalTestContext("Benchmark", StreamWriter.Null));
|
||||
var server = _pair.Server;
|
||||
|
||||
var mapData = await _pair.CreateTestMap();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
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,11 +2,9 @@ using System.Linq; // DeltaV
|
|||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
|
||||
|
|
@ -15,21 +13,13 @@ 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()
|
||||
|
|
@ -79,12 +69,6 @@ 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,10 +23,6 @@ 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();
|
||||
|
||||
|
|
@ -46,11 +42,8 @@ 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 <= _maxNameLength;
|
||||
FullNameLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxNameLength);
|
||||
FullNameLineEdit.OnTextChanged += _ =>
|
||||
{
|
||||
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
|
||||
|
|
@ -58,7 +51,7 @@ namespace Content.Client.Access.UI
|
|||
FullNameSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
|
||||
JobTitleLineEdit.IsValid = s => s.Length <= _maxIdJobLength;
|
||||
JobTitleLineEdit.IsValid = s => s.Length <= _cfgManager.GetCVar(CCVars.MaxIdJobLength);
|
||||
JobTitleLineEdit.OnTextChanged += _ =>
|
||||
{
|
||||
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Administration.UI.BanList.Bans;
|
||||
using Content.Client.Administration.UI.BanList.RoleBans;
|
||||
using Content.Client.Eui;
|
||||
|
|
@ -73,7 +74,7 @@ public sealed class BanListEui : BaseEui
|
|||
return date.ToString("MM/dd/yyyy h:mm tt");
|
||||
}
|
||||
|
||||
public static void SetData<T>(IBanListLine<T> line, SharedServerBan ban) where T : SharedServerBan
|
||||
public static void SetData<T>(IBanListLine<T> line, SharedBan ban) where T : SharedBan
|
||||
{
|
||||
line.Reason.Text = ban.Reason;
|
||||
line.BanTime.Text = FormatDate(ban.BanTime);
|
||||
|
|
@ -94,20 +95,20 @@ public sealed class BanListEui : BaseEui
|
|||
line.BanningAdmin.Text = ban.BanningAdminName;
|
||||
}
|
||||
|
||||
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedServerBan
|
||||
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedBan
|
||||
{
|
||||
_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.Address == null
|
||||
var ip = ban.Addresses.Length == 0
|
||||
? string.Empty
|
||||
: 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
|
||||
: 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
|
||||
? string.Empty
|
||||
: Loc.GetString("ban-list-guid", ("guid", ban.UserId.Value.ToString()));
|
||||
: Loc.GetString("ban-list-guid", ("guid", string.Join(',', ban.UserIds)));
|
||||
|
||||
_popup = new BanListIdsPopup(id, ip, hwid, guid);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed partial class BanListControl : Control
|
|||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetBans(List<SharedServerBan> bans)
|
||||
public void SetBans(List<SharedBan> 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<SharedServerBan>
|
||||
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedBan>
|
||||
{
|
||||
public SharedServerBan Ban { get; }
|
||||
public SharedBan Ban { get; }
|
||||
|
||||
public event Action<BanListLine>? IdsClicked;
|
||||
|
||||
public BanListLine(SharedServerBan ban)
|
||||
public BanListLine(SharedBan 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 : SharedServerBan
|
||||
public interface IBanListLine<T> where T : SharedBan
|
||||
{
|
||||
T Ban { get; }
|
||||
Label Reason { get; }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed partial class RoleBanListControl : Control
|
|||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetRoleBans(List<SharedServerRoleBan> bans)
|
||||
public void SetRoleBans(List<SharedBan> 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<SharedServerRoleBan>
|
||||
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedBan>
|
||||
{
|
||||
public SharedServerRoleBan Ban { get; }
|
||||
public SharedBan Ban { get; }
|
||||
|
||||
public event Action<RoleBanListLine>? IdsClicked;
|
||||
|
||||
public RoleBanListLine(SharedServerRoleBan ban)
|
||||
public RoleBanListLine(SharedBan ban)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedS
|
|||
IdsHidden.OnPressed += IdsPressed;
|
||||
|
||||
BanListEui.SetData(this, ban);
|
||||
Role.Text = ban.Role;
|
||||
Role.Text = string.Join(", ", ban.Roles ?? []);
|
||||
}
|
||||
|
||||
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.Round == null ? "Unknown round" : "Round " + Note.Round;
|
||||
RoundLabel.Text = Note.Rounds.Length == 0 ? "Unknown round" : "Round " + string.Join(',', Note.Rounds);
|
||||
AdminLabel.Text = Note.CreatedByName;
|
||||
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
|
||||
|
||||
|
|
@ -143,7 +143,12 @@ public sealed partial class AdminNotesLine : BoxContainer
|
|||
|
||||
private string FormatRoleBanMessage()
|
||||
{
|
||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
|
||||
var 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} ");
|
||||
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.Round == null
|
||||
RoundIdLabel.Text = note.Rounds.Length == 0
|
||||
? Loc.GetString("admin-notes-round-id-unknown")
|
||||
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
|
||||
: Loc.GetString("admin-notes-round-id", ("id", string.Join(',', note.Rounds)));
|
||||
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<HumanoidAppearanceComponent>(ent, out var humanoidAppearance) &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(humanoidAppearance.Species, out var speciesSprite))
|
||||
if (TryComp<HumanoidProfileComponent>(ent, out var humanoid) &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(humanoid.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="Inset"
|
||||
StyleClasses="BackgroundPanelDark"
|
||||
VerticalAlignment="Center">
|
||||
<SpriteView Name="EntityView"
|
||||
SetSize="96 96"
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
using Content.Shared.Atmos.Components;
|
||||
|
||||
namespace Content.Client.Atmos.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponent
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
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;
|
||||
|
|
@ -22,6 +23,7 @@ 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>()
|
||||
{
|
||||
|
|
@ -37,6 +39,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
|||
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_cache = IoCManager.Resolve<IResourceCache>();
|
||||
_atmosphere = _entManager.System<SharedAtmosphereSystem>();
|
||||
|
||||
NetEntity = uid;
|
||||
Coordinates = coordinates;
|
||||
|
|
@ -149,7 +152,7 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer
|
|||
foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
|
||||
{
|
||||
FixedPoint2 gasPercent = percent * 100f;
|
||||
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
|
||||
var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation);
|
||||
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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;
|
||||
|
|
@ -19,12 +20,14 @@ 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;
|
||||
|
||||
|
|
@ -132,7 +135,7 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer
|
|||
var gasPercent = (FixedPoint2)0f;
|
||||
gasPercent = percent * 100f;
|
||||
|
||||
var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation"));
|
||||
var gasAbbreviation = Loc.GetString(_atmosphere.GetGas(gas).Abbreviation);
|
||||
|
||||
var gasLabel = new Label()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ namespace Content.Client.Atmos.EntitySystems
|
|||
[UsedImplicitly]
|
||||
internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
|
||||
{
|
||||
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = new();
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
|
||||
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = [];
|
||||
|
||||
// Configuration set by debug commands and used by AtmosDebugOverlay {
|
||||
/// <summary>Value source for display</summary>
|
||||
|
|
@ -25,6 +27,8 @@ namespace Content.Client.Atmos.EntitySystems
|
|||
public bool CfgCBM = false;
|
||||
// }
|
||||
|
||||
private AtmosDebugOverlay? _overlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -34,10 +38,6 @@ 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,19 +51,25 @@ 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();
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
if (overlayManager.HasOverlay<AtmosDebugOverlay>())
|
||||
overlayManager.RemoveOverlay<AtmosDebugOverlay>();
|
||||
|
||||
RemoveOverlay();
|
||||
}
|
||||
|
||||
public void Reset(RoundRestartCleanupEvent ev)
|
||||
|
|
@ -75,6 +81,15 @@ 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,5 +1,6 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
|
|
@ -13,6 +14,29 @@ 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)
|
||||
{
|
||||
|
|
@ -27,7 +51,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, GasSpecificHeats, tmp);
|
||||
NumericsHelpers.Multiply(moles, GasMolarHeatCapacities, tmp);
|
||||
// Adjust heat capacity by speedup, because this is primarily what
|
||||
// determines how quickly gases heat up/cool.
|
||||
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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,106 +1,85 @@
|
|||
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
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly SpriteSystem _spriteSys = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private GasTileOverlay _overlay = default!;
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
|
||||
public override void Initialize()
|
||||
switch (args.Current)
|
||||
{
|
||||
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)
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
{
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
comp.Chunks[index] = data;
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
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)
|
||||
{
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
|
||||
foreach (var (nent, gridData) in ev.UpdatedChunks)
|
||||
{
|
||||
foreach (var (nent, removedIndicies) in ev.RemovedChunks)
|
||||
var grid = GetEntity(nent);
|
||||
|
||||
if (!TryComp(grid, out GasTileOverlayComponent? comp))
|
||||
continue;
|
||||
|
||||
foreach (var chunkData in gridData)
|
||||
{
|
||||
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;
|
||||
}
|
||||
comp.Chunks[chunkData.Index] = chunkData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
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="Inset" VerticalAlignment="Center" VerticalExpand="True">
|
||||
<PanelContainer Margin="2 0 6 0" StyleClasses="BackgroundPanelDark" VerticalAlignment="Center" VerticalExpand="True">
|
||||
<SpriteView Name="EntityView" OverrideDirection="South" Scale="2 2" SetSize="64 64"/>
|
||||
</PanelContainer>
|
||||
<!-- Center column (pressure, temperature, alarm state) -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,253 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
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,41 +1,33 @@
|
|||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
using Content.Shared.Atmos.Components;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
namespace Content.Client.Atmos.UI;
|
||||
|
||||
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
|
||||
[ViewVariables]
|
||||
private GasAnalyzerWindow? _window;
|
||||
|
||||
public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private GasAnalyzerWindow? _window;
|
||||
}
|
||||
|
||||
public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
if (message is not GasAnalyzerUserMessage cast)
|
||||
return;
|
||||
_window.Populate(cast);
|
||||
}
|
||||
if (message is not GasAnalyzerUserMessage cast)
|
||||
return;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_window?.Dispose();
|
||||
}
|
||||
_window.Populate(cast);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
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;
|
||||
|
|
@ -8,7 +10,6 @@ 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
|
||||
|
|
@ -16,25 +17,17 @@ 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
|
||||
|
|
@ -329,31 +322,31 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
for (var j = 0; j < gasMix.Gases.Length; j++)
|
||||
{
|
||||
var gas = gasMix.Gases[j];
|
||||
var color = Color.FromHex($"#{gas.Color}", Color.White);
|
||||
var gasEntry = gasMix.Gases[j];
|
||||
var gasProto = _atmosphere.GetGas(gasEntry.Gas);
|
||||
// Add to the table
|
||||
tableKey.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString(gas.Name)
|
||||
Text = Loc.GetString(gasProto.Name)
|
||||
});
|
||||
tableVal.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-molarity-text",
|
||||
("mol", $"{gas.Amount:0.00}")),
|
||||
("mol", $"{gasEntry.Amount:0.00}")),
|
||||
Align = Label.AlignMode.Right,
|
||||
});
|
||||
tablePercent.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-percentage-text",
|
||||
("percentage", $"{(gas.Amount / totalGasAmount * 100):0.0}")),
|
||||
("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.0}")),
|
||||
Align = Label.AlignMode.Right
|
||||
});
|
||||
|
||||
// Add to the gas bar //TODO: highlight the currently hover one
|
||||
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.#}")));
|
||||
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.#}")));
|
||||
}
|
||||
|
||||
dataContainer.AddChild(gasBar);
|
||||
|
|
|
|||
|
|
@ -37,10 +37,9 @@ namespace Content.Client.Atmos.UI
|
|||
_window.SelectGasPressed += OnSelectGasPressed;
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
{
|
||||
if (_window is null) return;
|
||||
SendMessage(new GasFilterToggleStatusMessage(_window.FilterStatus));
|
||||
SendMessage(new GasFilterToggleStatusMessage(status));
|
||||
}
|
||||
|
||||
private void OnFilterTransferRatePressed(string value)
|
||||
|
|
|
|||
|
|
@ -1,11 +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="480 400" Title="Filter">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-filter-ui-filter-status}"/>
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-filter-ui-filter-transfer-rate}"/>
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ namespace Content.Client.Atmos.UI
|
|||
{
|
||||
private readonly ButtonGroup _buttonGroup = new();
|
||||
|
||||
public bool FilterStatus = true;
|
||||
public string? SelectedGas;
|
||||
public string? CurrentGasId;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public event Action<string>? FilterTransferRateChanged;
|
||||
public event Action? SelectGasPressed;
|
||||
|
||||
|
|
@ -30,8 +29,7 @@ namespace Content.Client.Atmos.UI
|
|||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnPressed += _ => SetFilterStatus(!FilterStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
|
||||
FilterTransferRateInput.OnTextChanged += _ => SetFilterRate.Disabled = false;
|
||||
SetFilterRate.OnPressed += _ =>
|
||||
|
|
@ -53,15 +51,7 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
public void SetFilterStatus(bool 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");
|
||||
}
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
}
|
||||
|
||||
public void SetGasFiltered(string? id, string name)
|
||||
|
|
|
|||
|
|
@ -33,10 +33,9 @@ namespace Content.Client.Atmos.UI
|
|||
_window.MixerNodePercentageChanged += OnMixerSetPercentagePressed;
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
{
|
||||
if (_window is null) return;
|
||||
SendMessage(new GasMixerToggleStatusMessage(_window.MixerStatus));
|
||||
SendMessage(new GasMixerToggleStatusMessage(status));
|
||||
}
|
||||
|
||||
private void OnMixerOutputPressurePressed(string value)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="200 200" Title="Gas Mixer">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-mixer-ui-mixer-status}"/>
|
||||
<Control MinSize="5 0" />
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-mixer-ui-mixer-output-pressure}"/>
|
||||
<Control MinSize="5 0" />
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ namespace Content.Client.Atmos.UI
|
|||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasMixerWindow : DefaultWindow
|
||||
{
|
||||
public bool MixerStatus = true;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public event Action<string>? MixerOutputPressureChanged;
|
||||
public event Action<string>? MixerNodePercentageChanged;
|
||||
|
||||
|
|
@ -33,8 +31,7 @@ namespace Content.Client.Atmos.UI
|
|||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnPressed += _ => SetMixerStatus(!MixerStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
|
||||
MixerPressureOutputInput.OnTextChanged += _ => SetOutputPressureButton.Disabled = false;
|
||||
SetOutputPressureButton.OnPressed += _ =>
|
||||
|
|
@ -83,15 +80,7 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
public void SetMixerStatus(bool 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");
|
||||
}
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,12 +42,9 @@ public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKe
|
|||
_window.SetOutputPressure(pump.TargetPressure);
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
{
|
||||
if (_window is null)
|
||||
return;
|
||||
|
||||
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
|
||||
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(status));
|
||||
}
|
||||
|
||||
private void OnPumpOutputPressurePressed(float value)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
SetSize="340 110" MinSize="340 110" Title="Pressure Pump">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-pump-ui-pump-status}" Margin="0 0 5 0"/>
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
<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,9 +11,7 @@ namespace Content.Client.Atmos.UI
|
|||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasPressurePumpWindow : FancyWindow
|
||||
{
|
||||
public bool PumpStatus = true;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public event Action<float>? PumpOutputPressureChanged;
|
||||
|
||||
public float MaxPressure
|
||||
|
|
@ -33,8 +31,7 @@ namespace Content.Client.Atmos.UI
|
|||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
|
||||
PumpPressureOutputInput.OnValueChanged += _ => SetOutputPressureButton.Disabled = false;
|
||||
|
||||
|
|
@ -59,15 +56,7 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
public void SetPumpStatus(bool 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");
|
||||
}
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
_window = this.CreateWindow<GasThermomachineWindow>();
|
||||
|
||||
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
|
||||
_window.ToggleStatusButton.OnToggled += _ => OnToggleStatusButtonPressed();
|
||||
_window.TemperatureSpinbox.OnValueChanged += _ => OnTemperatureChanged(_window.TemperatureSpinbox.Value);
|
||||
_window.Entity = Owner;
|
||||
Update();
|
||||
|
|
@ -45,9 +45,6 @@ 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">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-thermomachine-ui-toggle}"/>
|
||||
<Control MinSize="5 0" />
|
||||
<Button Access="Public" Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True"
|
||||
Access="Public" />
|
||||
<BoxContainer Name="SpinboxHBox" Orientation="Horizontal">
|
||||
<Label Text="{Loc comp-gas-thermomachine-ui-temperature}"/>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ public sealed partial class GasThermomachineWindow : FancyWindow
|
|||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
public bool Active = true;
|
||||
|
||||
public FloatSpinBox TemperatureSpinbox;
|
||||
|
||||
public EntityUid Entity;
|
||||
|
|
@ -30,15 +28,7 @@ public sealed partial class GasThermomachineWindow : FancyWindow
|
|||
|
||||
public void SetActive(bool 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");
|
||||
}
|
||||
ToggleStatusButton.Pressed = active;
|
||||
}
|
||||
|
||||
public void SetTemperature(float temperature)
|
||||
|
|
|
|||
|
|
@ -38,11 +38,9 @@ namespace Content.Client.Atmos.UI
|
|||
Update();
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
private void OnToggleStatusButtonPressed(bool status)
|
||||
{
|
||||
if (_window is null) return;
|
||||
|
||||
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
|
||||
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(status));
|
||||
}
|
||||
|
||||
private void OnPumpTransferRatePressed(string value)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@
|
|||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="200 120" Title="Volume Pump">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-pump-ui-pump-status}"/>
|
||||
<Control MinSize="5 0" />
|
||||
<Button Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalAlignment="Left"
|
||||
Pressed="True" />
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Text="{Loc comp-gas-pump-ui-pump-transfer-rate}"/>
|
||||
|
|
|
|||
|
|
@ -19,17 +19,14 @@ namespace Content.Client.Atmos.UI
|
|||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasVolumePumpWindow : FancyWindow
|
||||
{
|
||||
public bool PumpStatus = true;
|
||||
|
||||
public event Action? ToggleStatusButtonPressed;
|
||||
public event Action<bool>? ToggleStatusButtonPressed;
|
||||
public event Action<string>? PumpTransferRateChanged;
|
||||
|
||||
public GasVolumePumpWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
|
||||
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
|
||||
ToggleStatusButton.OnToggled += _ => ToggleStatusButtonPressed?.Invoke(ToggleStatusButton.Pressed);
|
||||
|
||||
PumpTransferRateInput.OnTextChanged += _ => SetTransferRateButton.Disabled = false;
|
||||
SetTransferRateButton.OnPressed += _ =>
|
||||
|
|
@ -52,15 +49,7 @@ namespace Content.Client.Atmos.UI
|
|||
|
||||
public void SetPumpStatus(bool 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");
|
||||
}
|
||||
ToggleStatusButton.Pressed = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
|
|||
|
||||
_window = this.CreateWindow<SpaceHeaterWindow>();
|
||||
|
||||
_window.ToggleStatusButton.OnPressed += _ => OnToggleStatusButtonPressed();
|
||||
_window.ToggleStatusButton.OnToggled += _ => OnToggleStatusButtonPressed();
|
||||
_window.IncreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(_window.TemperatureChangeDelta);
|
||||
_window.DecreaseTempRange.OnPressed += _ => OnTemperatureRangeChanged(-_window.TemperatureChangeDelta);
|
||||
_window.ModeSelector.OnItemSelected += OnModeChanged;
|
||||
|
|
@ -34,7 +34,6 @@ public sealed class SpaceHeaterBoundUserInterface : BoundUserInterface
|
|||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
_window?.SetActive(!_window.Active);
|
||||
SendMessage(new SpaceHeaterToggleMessage());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
<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">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Button Text="{Loc comp-space-heater-ui-status-disabled}" Access="Public" Name="ToggleStatusButton"/>
|
||||
</BoxContainer>
|
||||
<controls:SwitchButton
|
||||
Name="ToggleStatusButton"
|
||||
HorizontalExpand="True"
|
||||
Access="Public" />
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="5">
|
||||
<Label Text="{Loc comp-space-heater-ui-mode}"/>
|
||||
<OptionButton Access="Public" Name="ModeSelector"/>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ 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;
|
||||
|
|
@ -49,17 +48,7 @@ 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,6 +1,7 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.BarSign;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.BarSign.Ui;
|
||||
|
|
@ -16,13 +17,12 @@ 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 = new(sign, allSigns);
|
||||
|
||||
_menu = this.CreateWindow<BarSignMenu>();
|
||||
_menu.LoadSigns(allSigns);
|
||||
|
||||
_menu.OnSignSelected += id =>
|
||||
{
|
||||
|
|
@ -30,16 +30,17 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
|
|||
};
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
_menu.OpenToLeft();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp))
|
||||
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp)
|
||||
|| !_prototype.Resolve(signComp.Current, out var signPrototype))
|
||||
return;
|
||||
|
||||
if (_prototype.Resolve(signComp.Current, out var signPrototype))
|
||||
_menu?.UpdateState(signPrototype);
|
||||
_menu?.UpdateState(signPrototype);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,23 +8,13 @@ namespace Content.Client.BarSign.Ui;
|
|||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BarSignMenu : FancyWindow
|
||||
{
|
||||
private string? _currentId;
|
||||
|
||||
private readonly List<BarSignPrototype> _cachedPrototypes = new();
|
||||
private List<BarSignPrototype> _cachedPrototypes = new();
|
||||
|
||||
public event Action<string>? OnSignSelected;
|
||||
|
||||
public BarSignMenu(BarSignPrototype? currentSign, List<BarSignPrototype> signs)
|
||||
public BarSignMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_currentId = currentSign?.ID;
|
||||
|
||||
_cachedPrototypes.Clear();
|
||||
_cachedPrototypes = signs;
|
||||
foreach (var proto in _cachedPrototypes)
|
||||
{
|
||||
SignOptions.AddItem(Loc.GetString(proto.Name));
|
||||
}
|
||||
|
||||
SignOptions.OnItemSelected += idx =>
|
||||
{
|
||||
|
|
@ -32,18 +22,21 @@ public sealed partial class BarSignMenu : FancyWindow
|
|||
SignOptions.SelectId(idx.Id);
|
||||
};
|
||||
|
||||
if (currentSign != null)
|
||||
}
|
||||
|
||||
public void LoadSigns(List<BarSignPrototype> signs)
|
||||
{
|
||||
_cachedPrototypes.Clear();
|
||||
_cachedPrototypes = signs;
|
||||
|
||||
foreach (var proto in _cachedPrototypes)
|
||||
{
|
||||
var idx = _cachedPrototypes.IndexOf(currentSign);
|
||||
SignOptions.TrySelectId(idx);
|
||||
SignOptions.AddItem(Loc.GetString(proto.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(BarSignPrototype newSign)
|
||||
{
|
||||
if (_currentId != null && newSign.ID == _currentId)
|
||||
return;
|
||||
_currentId = newSign.ID;
|
||||
var idx = _cachedPrototypes.IndexOf(newSign);
|
||||
SignOptions.TrySelectId(idx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
public sealed class BodySystem : SharedBodySystem
|
||||
{
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class MetabolizerSystem : SharedMetabolizerSystem;
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
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 += (args) =>
|
||||
_menu.OnItemSelected += (row) =>
|
||||
{
|
||||
if (args.Button.Parent is not CargoProductRow row)
|
||||
if (row == null)
|
||||
return;
|
||||
|
||||
description.Clear();
|
||||
|
|
@ -175,23 +175,23 @@ namespace Content.Client.Cargo.BUI
|
|||
return true;
|
||||
}
|
||||
|
||||
private void RemoveOrder(ButtonEventArgs args)
|
||||
private void RemoveOrder(CargoOrderData? order)
|
||||
{
|
||||
if (args.Button.Parent?.Parent is not CargoOrderRow row || row.Order == null)
|
||||
if (order == null)
|
||||
return;
|
||||
|
||||
SendMessage(new CargoConsoleRemoveOrderMessage(row.Order.OrderId));
|
||||
SendMessage(new CargoConsoleRemoveOrderMessage(order.OrderId));
|
||||
}
|
||||
|
||||
private void ApproveOrder(ButtonEventArgs args)
|
||||
private void ApproveOrder(CargoOrderData? order)
|
||||
{
|
||||
if (args.Button.Parent?.Parent is not CargoOrderRow row || row.Order == null)
|
||||
if (order == null)
|
||||
return;
|
||||
|
||||
if (OrderCount >= OrderCapacity)
|
||||
return;
|
||||
|
||||
SendMessage(new CargoConsoleApproveOrderMessage(row.Order.OrderId));
|
||||
SendMessage(new CargoConsoleApproveOrderMessage(order.OrderId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,226 @@
|
|||
<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"/>
|
||||
<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">
|
||||
<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>
|
||||
<!-- 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>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
|
@ -29,9 +30,9 @@ namespace Content.Client.Cargo.UI
|
|||
private readonly EntityQuery<CargoOrderConsoleComponent> _orderConsoleQuery;
|
||||
private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
|
||||
|
||||
public event Action<ButtonEventArgs>? OnItemSelected;
|
||||
public event Action<ButtonEventArgs>? OnOrderApproved;
|
||||
public event Action<ButtonEventArgs>? OnOrderCanceled;
|
||||
public event Action<CargoProductRow?>? OnItemSelected;
|
||||
public event Action<CargoOrderData?>? OnOrderApproved;
|
||||
public event Action<CargoOrderData?>? OnOrderCanceled;
|
||||
|
||||
public event Action<ProtoId<CargoAccountPrototype>?, int>? OnAccountAction;
|
||||
|
||||
|
|
@ -164,7 +165,7 @@ namespace Content.Client.Cargo.UI
|
|||
};
|
||||
button.MainButton.OnPressed += args =>
|
||||
{
|
||||
OnItemSelected?.Invoke(args);
|
||||
OnItemSelected?.Invoke(button);
|
||||
};
|
||||
Products.AddChild(button);
|
||||
}
|
||||
|
|
@ -210,38 +211,66 @@ namespace Content.Client.Cargo.UI
|
|||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
if (order.Approved)
|
||||
if (order.Approved || !_protoManager.Resolve(order.Product, out var productProto))
|
||||
continue;
|
||||
|
||||
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
|
||||
var productName = product.Name;
|
||||
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 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",
|
||||
("productName", productName),
|
||||
("orderAmount", order.OrderQuantity),
|
||||
("orderRequester", order.Requester),
|
||||
("orderRequester", requester),
|
||||
("accountColor", account.Color),
|
||||
("account", Loc.GetString(account.Code)))
|
||||
},
|
||||
|
||||
Description =
|
||||
{
|
||||
Text = Loc.GetString("cargo-console-menu-order-reason-description",
|
||||
("reason", order.Reason))
|
||||
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")))
|
||||
}
|
||||
};
|
||||
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
|
||||
|
||||
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(order); };
|
||||
|
||||
// TODO: Disable based on access.
|
||||
row.SetApproveVisible(orderConsole.Mode != CargoOrderConsoleMode.SendToPrimary);
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(order); };
|
||||
Requests.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
|
@ -294,8 +323,7 @@ namespace Content.Client.Cargo.UI
|
|||
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
|
||||
_timing.CurTime < orderConsole.NextAccountActionTime;
|
||||
|
||||
OrdersSpacer.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||
Orders.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||
RightPart.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,53 @@
|
|||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'cargo-console-order-menu-title'}">
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'cargo-console-order-menu-title'}"
|
||||
MinSize="460 261">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<GridContainer Columns="2">
|
||||
<Label Text="{Loc 'cargo-console-order-menu-product-label'}" />
|
||||
<Label Text="{Loc 'cargo-console-order-menu-product-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
|
||||
<Label Name="ProductName"
|
||||
Access="Public" />
|
||||
<Label Text="{Loc 'cargo-console-order-menu-description-label'}" />
|
||||
Access="Public" />
|
||||
|
||||
<Label Text="{Loc 'cargo-console-order-menu-description-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
|
||||
<RichTextLabel Name="Description"
|
||||
Access="Public"
|
||||
VerticalExpand="True"
|
||||
SetWidth="350"/>
|
||||
<Label Text="{Loc 'cargo-console-order-menu-cost-label'}" />
|
||||
Access="Public"
|
||||
HorizontalExpand="True"
|
||||
MaxWidth="460" />
|
||||
|
||||
<Label Text="{Loc 'cargo-console-order-menu-cost-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
|
||||
<Label Name="PointCost"
|
||||
Access="Public" />
|
||||
<Label Text="{Loc 'cargo-console-order-menu-requester-label'}" />
|
||||
Access="Public" />
|
||||
|
||||
<Label Text="{Loc 'cargo-console-order-menu-requester-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
|
||||
<LineEdit Name="Requester"
|
||||
Access="Public" />
|
||||
<Label Text="{Loc 'cargo-console-order-menu-reason-label'}" />
|
||||
|
||||
<Label Text="{Loc 'cargo-console-order-menu-reason-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
|
||||
<LineEdit Name="Reason"
|
||||
Access="Public" />
|
||||
<Label Text="{Loc 'cargo-console-order-menu-amount-label'}" />
|
||||
|
||||
<Label Text="{Loc 'cargo-console-order-menu-amount-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
|
||||
<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'}"
|
||||
TextAlign="Center" />
|
||||
VerticalAlignment="Bottom" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,81 @@
|
|||
<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" />
|
||||
<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"
|
||||
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>
|
||||
<Button Name="Approve"
|
||||
Access="Public"
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,34 @@
|
|||
<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"
|
||||
<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"
|
||||
Access="Public"
|
||||
MinSize="52 32"
|
||||
Align="Right"
|
||||
Margin="0 0 5 0"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
HorizontalExpand="True"
|
||||
ClipText="True" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
<Label Name="PointCost"
|
||||
Access="Public"
|
||||
MinSize="56 32"
|
||||
Align="Right"
|
||||
Margin="0 0 5 0"
|
||||
HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Robust.Client.UserInterface.XAML;
|
|||
namespace Content.Client.Cargo.UI
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CargoProductRow : PanelContainer
|
||||
public sealed partial class CargoProductRow : BoxContainer
|
||||
{
|
||||
public CargoProductPrototype? Product { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,10 @@ namespace Content.Client.Cargo.UI
|
|||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var product = protoManager.Index<EntityPrototype>(order.ProductId);
|
||||
if (!protoManager.Resolve(order.Product, out var productProto))
|
||||
continue;
|
||||
|
||||
var product = protoManager.Index<EntityPrototype>(productProto.Product);
|
||||
var productName = product.Name;
|
||||
var account = protoManager.Index(order.Account);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ 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.Part;
|
||||
using Content.Shared.Body;
|
||||
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<BodyPartComponent>())
|
||||
if (entProto.HasComponent<OrganComponent>())
|
||||
continue;
|
||||
|
||||
//these feel obvious...
|
||||
|
|
@ -116,7 +116,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
|||
}
|
||||
|
||||
|
||||
if (extractableComponent.GrindableSolution is { } grindableSolutionId &&
|
||||
if (extractableComponent.GrindableSolutionName 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="Inset">
|
||||
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundPanelDark">
|
||||
<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="Inset">
|
||||
<PanelContainer VerticalExpand="True" StyleClasses="BackgroundPanelDark">
|
||||
<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<HumanoidAppearanceComponent>(equipee)?.Sex;
|
||||
var equipeeSex = CompOrNull<HumanoidProfileComponent>(equipee)?.Sex;
|
||||
if (equipeeSex != null)
|
||||
{
|
||||
switch (equipeeSex)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Content.Client.Hands.Systems;
|
||||
using Content.Client.NPC.HTN;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.CombatMode;
|
||||
using Robust.Client.Graphics;
|
||||
|
|
@ -59,11 +58,6 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
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,7 +291,6 @@ 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))
|
||||
{
|
||||
|
|
@ -306,20 +305,11 @@ namespace Content.Client.Construction
|
|||
var targetSprite = EnsureComp<SpriteComponent>(dummy);
|
||||
EntityManager.System<AppearanceSystem>().OnChangeData(dummy, targetSprite);
|
||||
|
||||
for (var i = 0; i < targetSprite.AllLayers.Count(); i++)
|
||||
_sprite.CopySprite((dummy, targetSprite), (ghost.Value, sprite));
|
||||
|
||||
for (var i = 0; i < sprite.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);
|
||||
|
|
@ -327,6 +317,8 @@ 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,4 +1,6 @@
|
|||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Damage;
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ public sealed partial class DamageVisualsComponent : Component
|
|||
/// (for example, Brute), and has a value
|
||||
/// of a DamageVisualizerSprite (see below)
|
||||
/// </summary>
|
||||
[DataField("damageOverlayGroups")] public Dictionary<string, DamageVisualizerSprite>? DamageOverlayGroups;
|
||||
[DataField("damageOverlayGroups")] public Dictionary<ProtoId<DamageGroupPrototype>, DamageVisualizerSprite>? DamageOverlayGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Sets if you want sprites to overlay the
|
||||
|
|
@ -84,7 +86,7 @@ public sealed partial class DamageVisualsComponent : Component
|
|||
/// what kind of damage combination
|
||||
/// you would want, on which threshold.
|
||||
/// </remarks>
|
||||
[DataField("damageGroup")] public string? DamageGroup;
|
||||
[DataField("damageGroup")] public ProtoId<DamageGroupPrototype>? DamageGroup;
|
||||
|
||||
/// <summary>
|
||||
/// Set this if you want incoming damage to be
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
|
@ -28,6 +29,7 @@ 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()
|
||||
{
|
||||
|
|
@ -174,7 +176,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))
|
||||
if (!damageContainer.SupportedGroups.Contains(damageVisComp.DamageGroup.Value))
|
||||
{
|
||||
Log.Error($"Damage keys were invalid for entity {entity}.");
|
||||
damageVisComp.Valid = false;
|
||||
|
|
@ -384,7 +386,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
|
|||
if (!AppearanceSystem.TryGetData<DamageVisualizerGroupData>(uid, DamageVisualizerKeys.DamageUpdateGroups,
|
||||
out var data, component))
|
||||
{
|
||||
data = new DamageVisualizerGroupData(Comp<DamageableComponent>(uid).DamagePerGroup.Keys.ToList());
|
||||
data = new DamageVisualizerGroupData(_damageable.GetDamagePerGroup(uid).Keys.ToList());
|
||||
}
|
||||
|
||||
UpdateDamageVisuals(data.GroupList, (uid, damageComponent, spriteComponent, damageVisComp));
|
||||
|
|
@ -486,11 +488,10 @@ 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(damageComponent.TotalDamage, damageVisComp.LastDamageThreshold, damageVisComp, out var threshold))
|
||||
if (!CheckThresholdBoundary(_damageable.GetTotalDamage(entity.AsNullable()), damageVisComp.LastDamageThreshold, damageVisComp, out var threshold))
|
||||
return;
|
||||
|
||||
damageVisComp.LastDamageThreshold = threshold;
|
||||
|
|
@ -513,11 +514,11 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
|
|||
/// according to the list of damage groups
|
||||
/// passed into it.
|
||||
/// </summary>
|
||||
private void UpdateDamageVisuals(List<string> delta, Entity<DamageableComponent, SpriteComponent, DamageVisualsComponent> entity)
|
||||
private void UpdateDamageVisuals(List<ProtoId<DamageGroupPrototype>> 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)
|
||||
{
|
||||
|
|
@ -525,7 +526,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
|
|||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex<DamageGroupPrototype>(damageGroup, out var damageGroupPrototype)
|
||||
|| !damageComponent.Damage.TryGetDamageInGroup(damageGroupPrototype, out var damageTotal))
|
||||
|| !damage.TryGetDamageInGroup(damageGroupPrototype, out var damageTotal))
|
||||
continue;
|
||||
|
||||
if (!damageVisComp.LastThresholdPerGroup.TryGetValue(damageGroup, out var lastThreshold)
|
||||
|
|
@ -590,7 +591,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
|
|||
}
|
||||
else if (damageVisComp.DamageGroup != null)
|
||||
{
|
||||
UpdateDamageVisuals(new List<string>() { damageVisComp.DamageGroup }, entity);
|
||||
UpdateDamageVisuals(new() { damageVisComp.DamageGroup.Value }, entity);
|
||||
}
|
||||
else if (damageVisComp.DamageOverlay != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,19 +11,23 @@ namespace Content.Client.Drunk;
|
|||
|
||||
public sealed class DrunkOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> Shader = "Drunk";
|
||||
private static readonly ProtoId<ShaderPrototype> DrunkShader = "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;
|
||||
|
|
@ -37,12 +41,22 @@ public sealed class DrunkOverlay : Overlay
|
|||
|
||||
private const float BoozePowerScale = 8f;
|
||||
|
||||
private float _visualScale = 0;
|
||||
private float _visualScale = 0f;
|
||||
private float _timeScale = 1f;
|
||||
private float _distortionScale = 1f;
|
||||
|
||||
public DrunkOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_drunkShader = _prototypeManager.Index(Shader).InstanceUnique();
|
||||
_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.
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
|
@ -53,15 +67,14 @@ public sealed class DrunkOverlay : Overlay
|
|||
if (playerEntity == null)
|
||||
return;
|
||||
|
||||
var statusSys = _sysMan.GetEntitySystem<Shared.StatusEffectNew.StatusEffectsSystem>();
|
||||
if (!statusSys.TryGetMaxTime<DrunkStatusEffectComponent>(playerEntity.Value, out var status))
|
||||
if (!_statusEffectsSystem.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)
|
||||
|
|
@ -82,8 +95,12 @@ 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);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Content.Shared.StatusEffectNew;
|
|||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Client.Drunk;
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ 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!;
|
||||
|
||||
|
|
@ -29,7 +31,10 @@ 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)
|
||||
|
|
@ -47,6 +52,7 @@ 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)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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;
|
||||
|
|
@ -24,6 +25,7 @@ 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;
|
||||
|
|
@ -78,6 +80,7 @@ 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()
|
||||
{
|
||||
|
|
@ -180,6 +183,7 @@ namespace Content.Client.Entry
|
|||
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
|
||||
_documentParsingManager.Initialize();
|
||||
_titleWindowManager.Initialize();
|
||||
_feedbackManager.Initialize();
|
||||
|
||||
_baseClient.RunLevelChanged += (_, args) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
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,11 +4,12 @@ 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;
|
||||
|
|
@ -132,17 +133,18 @@ 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()))
|
||||
guideEntryRegistry.GuideEntries.Values.Any(pair => pair.EffectDescriptions.Any() || pair.Metabolites?.Any() == true))
|
||||
{
|
||||
EffectsDescriptionContainer.Children.Clear();
|
||||
foreach (var (group, effect) in guideEntryRegistry.GuideEntries)
|
||||
foreach (var (stage, effect) in guideEntryRegistry.GuideEntries)
|
||||
{
|
||||
if (!effect.EffectDescriptions.Any())
|
||||
var hasMetabolites = effect.Metabolites?.Any() == true;
|
||||
if (!effect.EffectDescriptions.Any() && !hasMetabolites)
|
||||
continue;
|
||||
|
||||
var groupLabel = new RichTextLabel();
|
||||
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
|
||||
("group", _prototype.Index<MetabolismGroupPrototype>(group).LocalizedName), ("rate", effect.MetabolismRate)));
|
||||
groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-stage-rate",
|
||||
("stage", _prototype.Index<MetabolismStagePrototype>(stage).LocalizedName), ("rate", effect.MetabolismRate)));
|
||||
var descriptionLabel = new RichTextLabel
|
||||
{
|
||||
Margin = new Thickness(25, 0, 10, 0)
|
||||
|
|
@ -155,9 +157,20 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
|
|||
{
|
||||
descMsg.AddMarkupOrThrow(effectString);
|
||||
i++;
|
||||
if (i < descriptionsCount)
|
||||
if (i < descriptionsCount || hasMetabolites)
|
||||
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.
|
||||
/// In order to be accessed by this tag, the desired field/property must
|
||||
/// 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
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Numerics;
|
|||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
|
|
@ -35,6 +36,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
|
|||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly IPrototypeManager _prototypes;
|
||||
private readonly IResourceCache _cache;
|
||||
private readonly DamageableSystem _damageable;
|
||||
|
||||
private readonly UnborgableSystem _unborgable; // DeltaV
|
||||
private readonly RedshirtSystem _redshirt; // DeltaV
|
||||
|
|
@ -57,6 +59,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
|
|||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||||
_prototypes = dependencies.Resolve<IPrototypeManager>();
|
||||
_cache = dependencies.Resolve<IResourceCache>();
|
||||
_damageable = _entityManager.System<DamageableSystem>();
|
||||
|
||||
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
|
||||
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
|
||||
|
|
@ -116,9 +119,9 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
|
|||
NameLabel.SetMessage(name);
|
||||
|
||||
SpeciesLabel.Text =
|
||||
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
|
||||
out var humanoidAppearanceComponent)
|
||||
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
|
||||
_entityManager.TryGetComponent<HumanoidProfileComponent>(target.Value,
|
||||
out var humanoidComponent)
|
||||
? Loc.GetString(_prototypes.Index(humanoidComponent.Species).Name)
|
||||
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
|
||||
|
||||
// Basic Diagnostic
|
||||
|
|
@ -138,7 +141,7 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
|
|||
|
||||
// Total Damage
|
||||
|
||||
DamageLabel.Text = damageable.TotalDamage.ToString();
|
||||
DamageLabel.Text = _damageable.GetTotalDamage(target.Value).ToString();
|
||||
|
||||
// Alerts
|
||||
// DeltaV traits - This is going to be horrid if we just keep adding things like this.
|
||||
|
|
@ -198,10 +201,11 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
|
|||
// Damage Groups
|
||||
|
||||
var damageSortedGroups =
|
||||
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
|
||||
_damageable.GetDamagePerGroup(target.Value)
|
||||
.OrderByDescending(damage => damage.Value)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
|
||||
var damagePerType = _damageable.GetAllDamage(target.Value).DamageDict;
|
||||
|
||||
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
|
||||
|
||||
|
|
@ -239,8 +243,8 @@ public sealed partial class HealthAnalyzerControl : BoxContainer
|
|||
}
|
||||
|
||||
private void DrawDiagnosticGroups(
|
||||
Dictionary<string, FixedPoint2> groups,
|
||||
IReadOnlyDictionary<string, FixedPoint2> damageDict)
|
||||
Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> groups,
|
||||
IReadOnlyDictionary<ProtoId<DamageTypePrototype>, FixedPoint2> damageDict)
|
||||
{
|
||||
GroupsContainer.RemoveAllChildren();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HideableHumanoidLayersSystem : SharedHideableHumanoidLayersSystem
|
||||
{
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HideableHumanoidLayersComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<HideableHumanoidLayersComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnComponentInit(Entity<HideableHumanoidLayersComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
UpdateSprite(ent);
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<HideableHumanoidLayersComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateSprite(ent);
|
||||
}
|
||||
|
||||
public override void SetLayerOcclusion(
|
||||
Entity<HideableHumanoidLayersComponent?> ent,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
SlotFlags source)
|
||||
{
|
||||
base.SetLayerOcclusion(ent, layer, visible, source);
|
||||
|
||||
if (Resolve(ent, ref ent.Comp))
|
||||
UpdateSprite((ent, ent.Comp));
|
||||
}
|
||||
|
||||
private void UpdateSprite(Entity<HideableHumanoidLayersComponent> ent)
|
||||
{
|
||||
foreach (var item in ent.Comp.LastHiddenLayers)
|
||||
{
|
||||
if (ent.Comp.HiddenLayers.ContainsKey(item))
|
||||
continue;
|
||||
|
||||
var evt = new HumanoidLayerVisibilityChangedEvent(item, true);
|
||||
RaiseLocalEvent(ent, ref evt);
|
||||
|
||||
if (!_sprite.LayerMapTryGet(ent.Owner, item, out var index, true))
|
||||
continue;
|
||||
|
||||
_sprite.LayerSetVisible(ent.Owner, index, true);
|
||||
}
|
||||
|
||||
foreach (var item in ent.Comp.HiddenLayers.Keys)
|
||||
{
|
||||
if (ent.Comp.LastHiddenLayers.Contains(item))
|
||||
continue;
|
||||
|
||||
var evt = new HumanoidLayerVisibilityChangedEvent(item, false);
|
||||
RaiseLocalEvent(ent, ref evt);
|
||||
|
||||
if (!_sprite.LayerMapTryGet(ent.Owner, item, out var index, true))
|
||||
continue;
|
||||
|
||||
_sprite.LayerSetVisible(ent.Owner, index, false);
|
||||
}
|
||||
|
||||
ent.Comp.LastHiddenLayers.Clear();
|
||||
ent.Comp.LastHiddenLayers.UnionWith(ent.Comp.HiddenLayers.Keys);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,455 +0,0 @@
|
|||
using Content.Client.DisplacementMap;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics; // CD - Character Records
|
||||
using Robust.Client.Console; // CD - Character Records
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
Subs.CVar(_configurationManager, CCVars.AccessibilityClientCensorNudity, OnCvarChanged, true);
|
||||
Subs.CVar(_configurationManager, CCVars.AccessibilityServerCensorNudity, OnCvarChanged, true);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateSprite((uid, component, Comp<SpriteComponent>(uid)));
|
||||
}
|
||||
|
||||
private void OnCvarChanged(bool value)
|
||||
{
|
||||
var humanoidQuery = AllEntityQuery<HumanoidAppearanceComponent, SpriteComponent>();
|
||||
while (humanoidQuery.MoveNext(out var uid, out var humanoidComp, out var spriteComp))
|
||||
{
|
||||
UpdateSprite((uid, humanoidComp, spriteComp));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSprite(Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
|
||||
{
|
||||
UpdateLayers(entity);
|
||||
ApplyMarkingSet(entity);
|
||||
|
||||
// Begin CD - Character Records
|
||||
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(entity.Comp1.Species);
|
||||
var height = Math.Clamp(MathF.Round(entity.Comp1.Height, 2), speciesPrototype.MinHeight, speciesPrototype.MaxHeight); // should NOT be locked, at all
|
||||
|
||||
entity.Comp2.Scale = speciesPrototype.BaseScale * new Vector2(speciesPrototype.ScaleHeight ? height : 1f, height); // DV - CD Character Records shouldn't nuke species heights
|
||||
// End CD - Character Records
|
||||
|
||||
var humanoidAppearance = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
sprite[_sprite.LayerMapReserve((entity.Owner, sprite), HumanoidVisualLayers.Eyes)].Color = humanoidAppearance.EyeColor;
|
||||
}
|
||||
|
||||
private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
|
||||
=> humanoid.HiddenLayers.ContainsKey(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
|
||||
private void UpdateLayers(Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
|
||||
{
|
||||
var component = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
var oldLayers = new HashSet<HumanoidVisualLayers>(component.BaseLayers.Keys);
|
||||
component.BaseLayers.Clear();
|
||||
|
||||
// add default species layers
|
||||
var speciesProto = _prototypeManager.Index(component.Species);
|
||||
var baseSprites = _prototypeManager.Index(speciesProto.SpriteSet);
|
||||
foreach (var (key, id) in baseSprites.Sprites)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
if (!component.CustomBaseLayers.ContainsKey(key))
|
||||
SetLayerData(entity, key, id, sexMorph: true);
|
||||
}
|
||||
|
||||
// add custom layers
|
||||
foreach (var (key, info) in component.CustomBaseLayers)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
SetLayerData(entity, key, info.Id, sexMorph: false, color: info.Color);
|
||||
}
|
||||
|
||||
// hide old layers
|
||||
// TODO maybe just remove them altogether?
|
||||
foreach (var key in oldLayers)
|
||||
{
|
||||
if (_sprite.LayerMapTryGet((entity.Owner, sprite), key, out var index, false))
|
||||
sprite[index].Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLayerData(
|
||||
Entity<HumanoidAppearanceComponent, SpriteComponent> entity,
|
||||
HumanoidVisualLayers key,
|
||||
string? protoId,
|
||||
bool sexMorph = false,
|
||||
Color? color = null)
|
||||
{
|
||||
var component = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
var layerIndex = _sprite.LayerMapReserve((entity.Owner, sprite), key);
|
||||
var layer = sprite[layerIndex];
|
||||
layer.Visible = !IsHidden(component, key);
|
||||
|
||||
if (color != null)
|
||||
layer.Color = color.Value;
|
||||
|
||||
if (protoId == null)
|
||||
return;
|
||||
|
||||
if (sexMorph)
|
||||
protoId = HumanoidVisualLayersExtension.GetSexMorph(key, component.Sex, protoId);
|
||||
|
||||
var proto = _prototypeManager.Index<HumanoidSpeciesSpriteLayer>(protoId);
|
||||
component.BaseLayers[key] = proto;
|
||||
|
||||
if (proto.MatchSkin)
|
||||
layer.Color = component.SkinColor.WithAlpha(proto.LayerAlpha);
|
||||
|
||||
if (proto.BaseSprite != null)
|
||||
_sprite.LayerSetSprite((entity.Owner, sprite), layerIndex, proto.BaseSprite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a profile directly into a humanoid.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid entity's UID</param>
|
||||
/// <param name="profile">The profile to load.</param>
|
||||
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
|
||||
/// <remarks>
|
||||
/// This should not be used if the entity is owned by the server. The server will otherwise
|
||||
/// override this with the appearance data it sends over.
|
||||
/// </remarks>
|
||||
public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profile, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (profile == null)
|
||||
return;
|
||||
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>();
|
||||
|
||||
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
||||
var markings = new MarkingSet(speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
|
||||
|
||||
// Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it.
|
||||
var markingFColored = new Dictionary<Marking, MarkingPrototype>();
|
||||
foreach (var marking in profile.Appearance.Markings)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
if (!prototype.ForcedColoring)
|
||||
{
|
||||
markings.AddBack(prototype.MarkingCategory, marking);
|
||||
}
|
||||
else
|
||||
{
|
||||
markingFColored.Add(marking, prototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// legacy: remove in the future?
|
||||
//markings.RemoveCategory(MarkingCategories.Hair);
|
||||
//markings.RemoveCategory(MarkingCategories.FacialHair);
|
||||
|
||||
// We need to ensure hair before applying it or coloring can try depend on markings that can be invalid
|
||||
var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, out var hairAlpha, _prototypeManager)
|
||||
? profile.Appearance.SkinColor.WithAlpha(hairAlpha)
|
||||
: profile.Appearance.HairColor;
|
||||
var hair = new Marking(profile.Appearance.HairStyleId,
|
||||
new[] { hairColor });
|
||||
|
||||
var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, out var facialHairAlpha, _prototypeManager)
|
||||
? profile.Appearance.SkinColor.WithAlpha(facialHairAlpha)
|
||||
: profile.Appearance.FacialHairColor;
|
||||
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
|
||||
new[] { facialHairColor });
|
||||
|
||||
if (_markingManager.CanBeApplied(profile.Species, profile.Sex, hair, _prototypeManager))
|
||||
{
|
||||
markings.AddBack(MarkingCategories.Hair, hair);
|
||||
}
|
||||
if (_markingManager.CanBeApplied(profile.Species, profile.Sex, facialHair, _prototypeManager))
|
||||
{
|
||||
markings.AddBack(MarkingCategories.FacialHair, facialHair);
|
||||
}
|
||||
|
||||
// Finally adding marking with forced colors
|
||||
foreach (var (marking, prototype) in markingFColored)
|
||||
{
|
||||
var markingColors = MarkingColoring.GetMarkingLayerColors(
|
||||
prototype,
|
||||
profile.Appearance.SkinColor,
|
||||
profile.Appearance.EyeColor,
|
||||
markings
|
||||
);
|
||||
markings.AddBack(prototype.MarkingCategory, new Marking(marking.MarkingId, markingColors));
|
||||
}
|
||||
|
||||
markings.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _prototypeManager);
|
||||
markings.EnsureSexes(profile.Sex, _markingManager);
|
||||
markings.EnsureDefault(
|
||||
profile.Appearance.SkinColor,
|
||||
profile.Appearance.EyeColor,
|
||||
_markingManager);
|
||||
|
||||
DebugTools.Assert(IsClientSide(uid));
|
||||
|
||||
humanoid.MarkingSet = markings;
|
||||
humanoid.PermanentlyHidden = new HashSet<HumanoidVisualLayers>();
|
||||
humanoid.HiddenLayers = new Dictionary<HumanoidVisualLayers, SlotFlags>();
|
||||
humanoid.CustomBaseLayers = customBaseLayers;
|
||||
humanoid.Sex = profile.Sex;
|
||||
humanoid.Gender = profile.Gender;
|
||||
humanoid.Age = profile.Age;
|
||||
humanoid.Species = profile.Species;
|
||||
humanoid.SkinColor = profile.Appearance.SkinColor;
|
||||
humanoid.EyeColor = profile.Appearance.EyeColor;
|
||||
humanoid.Height = profile.Height; // CD - Character Records
|
||||
|
||||
UpdateSprite((uid, humanoid, Comp<SpriteComponent>(uid)));
|
||||
}
|
||||
|
||||
private void ApplyMarkingSet(Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
|
||||
{
|
||||
var humanoid = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
// I am lazy and I CBF resolving the previous mess, so I'm just going to nuke the markings.
|
||||
// Really, markings should probably be a separate component altogether.
|
||||
ClearAllMarkings(entity);
|
||||
|
||||
var censorNudity = _configurationManager.GetCVar(CCVars.AccessibilityClientCensorNudity) ||
|
||||
_configurationManager.GetCVar(CCVars.AccessibilityServerCensorNudity);
|
||||
// The reason we're splitting this up is in case the character already has undergarment equipped in that slot.
|
||||
var applyUndergarmentTop = censorNudity;
|
||||
var applyUndergarmentBottom = censorNudity;
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
{
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, entity);
|
||||
if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
|
||||
applyUndergarmentTop = false;
|
||||
else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
|
||||
applyUndergarmentBottom = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet);
|
||||
|
||||
AddUndergarments(entity, applyUndergarmentTop, applyUndergarmentBottom);
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
|
||||
{
|
||||
var humanoid = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
foreach (var markingList in humanoid.ClientOldMarkings.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
RemoveMarking(marking, (entity, sprite));
|
||||
}
|
||||
}
|
||||
|
||||
humanoid.ClientOldMarkings.Clear();
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
RemoveMarking(marking, (entity, sprite));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMarking(Marking marking, Entity<SpriteComponent> spriteEnt)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var prototype))
|
||||
return;
|
||||
|
||||
foreach (var sprite in prototype.Sprites)
|
||||
{
|
||||
if (sprite is not SpriteSpecifier.Rsi rsi)
|
||||
continue;
|
||||
|
||||
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
|
||||
if (!_sprite.LayerMapTryGet(spriteEnt.AsNullable(), layerId, out var index, false))
|
||||
continue;
|
||||
|
||||
_sprite.LayerMapRemove(spriteEnt.AsNullable(), layerId);
|
||||
_sprite.RemoveLayer(spriteEnt.AsNullable(), index);
|
||||
|
||||
// If this marking is one that can be displaced, we need to remove the displacement as well; otherwise
|
||||
// altering a marking at runtime can lead to the renderer falling over.
|
||||
// The Vulps must be shaved.
|
||||
// (https://github.com/space-wizards/space-station-14/issues/40135).
|
||||
if (prototype.CanBeDisplaced)
|
||||
_displacement.EnsureDisplacementIsNotOnSprite(spriteEnt, layerId);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUndergarments(Entity<HumanoidAppearanceComponent, SpriteComponent> entity, bool undergarmentTop, bool undergarmentBottom)
|
||||
{
|
||||
var humanoid = entity.Comp1;
|
||||
|
||||
if (undergarmentTop && humanoid.UndergarmentTop != null)
|
||||
{
|
||||
var marking = new Marking(humanoid.UndergarmentTop, new List<Color> { new Color() });
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
// Markings are added to ClientOldMarkings because otherwise it causes issues when toggling the feature on/off.
|
||||
humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentTop, new List<Marking> { marking });
|
||||
ApplyMarking(prototype, null, true, entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (undergarmentBottom && humanoid.UndergarmentBottom != null)
|
||||
{
|
||||
var marking = new Marking(humanoid.UndergarmentBottom, new List<Color> { new Color() });
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentBottom, new List<Marking> { marking });
|
||||
ApplyMarking(prototype, null, true, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
Entity<HumanoidAppearanceComponent, SpriteComponent> entity)
|
||||
{
|
||||
var humanoid = entity.Comp1;
|
||||
var sprite = entity.Comp2;
|
||||
|
||||
if (!_sprite.LayerMapTryGet((entity.Owner, sprite), markingPrototype.BodyPart, out var targetLayer, false))
|
||||
return;
|
||||
|
||||
visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
|
||||
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
|
||||
&& setting.AllowsMarkings;
|
||||
|
||||
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
|
||||
{
|
||||
var markingSprite = markingPrototype.Sprites[j];
|
||||
|
||||
if (markingSprite is not SpriteSpecifier.Rsi rsi)
|
||||
return;
|
||||
|
||||
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (!_sprite.LayerMapTryGet((entity.Owner, sprite), layerId, out _, false))
|
||||
{
|
||||
var layer = _sprite.AddLayer((entity.Owner, sprite), markingSprite, targetLayer + j + 1);
|
||||
_sprite.LayerMapSet((entity.Owner, sprite), layerId, layer);
|
||||
_sprite.LayerSetSprite((entity.Owner, sprite), layerId, rsi);
|
||||
}
|
||||
|
||||
_sprite.LayerSetVisible((entity.Owner, sprite), layerId, visible);
|
||||
|
||||
if (!visible || setting == null) // this is kinda implied
|
||||
continue;
|
||||
|
||||
// Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
|
||||
// and we need to check the index is correct.
|
||||
// So if that happens just default to white?
|
||||
if (colors != null && j < colors.Count)
|
||||
_sprite.LayerSetColor((entity.Owner, sprite), layerId, colors[j]);
|
||||
else
|
||||
_sprite.LayerSetColor((entity.Owner, sprite), layerId, Color.White);
|
||||
|
||||
if (humanoid.MarkingsDisplacement.TryGetValue(markingPrototype.BodyPart, out var displacementData) && markingPrototype.CanBeDisplaced)
|
||||
_displacement.TryAddDisplacement(displacementData, (entity.Owner, sprite), targetLayer + j + 1, layerId, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, bool verify = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || humanoid.SkinColor == skinColor)
|
||||
return;
|
||||
|
||||
base.SetSkinColor(uid, skinColor, false, verify, humanoid);
|
||||
|
||||
if (!TryComp(uid, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!spriteInfo.MatchSkin)
|
||||
continue;
|
||||
|
||||
var index = _sprite.LayerMapReserve((uid, sprite), layer);
|
||||
sprite[index].Color = skinColor.WithAlpha(spriteInfo.LayerAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLayerVisibility(
|
||||
Entity<HumanoidAppearanceComponent> ent,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
SlotFlags? slot,
|
||||
ref bool dirty)
|
||||
{
|
||||
base.SetLayerVisibility(ent, layer, visible, slot, ref dirty);
|
||||
|
||||
var sprite = Comp<SpriteComponent>(ent);
|
||||
if (!_sprite.LayerMapTryGet((ent.Owner, sprite), layer, out var index, false))
|
||||
{
|
||||
if (!visible)
|
||||
return;
|
||||
index = _sprite.LayerMapReserve((ent.Owner, sprite), layer);
|
||||
}
|
||||
|
||||
var spriteLayer = sprite[index];
|
||||
if (spriteLayer.Visible == visible)
|
||||
return;
|
||||
|
||||
spriteLayer.Visible = visible;
|
||||
|
||||
// I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
|
||||
// Just a week away...
|
||||
|
||||
foreach (var markingList in ent.Comp.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, (ent, ent.Comp, sprite));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue