Merge pull request #5316 from BarryNorfolk/merge_dec_jan
Upstream Merge (December 2025 - Jan 16th 2026)
This commit is contained in:
commit
8aceccfb9d
|
|
@ -351,7 +351,7 @@ resharper_csharp_qualified_using_at_nested_scope = false
|
||||||
resharper_csharp_prefer_qualified_reference = false
|
resharper_csharp_prefer_qualified_reference = false
|
||||||
resharper_csharp_allow_alias = false
|
resharper_csharp_allow_alias = false
|
||||||
|
|
||||||
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
|
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,slnx}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[nuget.config]
|
[nuget.config]
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 9.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 9.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 9.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 9.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Get Engine Tag
|
- name: Get Engine Tag
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ jobs:
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 9.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: 9.0.x
|
dotnet-version: 10.0.x
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
# MSbuild binlog files
|
||||||
|
*.binlog
|
||||||
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
|
SOLUTION_PATH = Path("..") / "SpaceStation14.slnx"
|
||||||
# If this doesn't match the saved version we overwrite them all.
|
# If this doesn't match the saved version we overwrite them all.
|
||||||
CURRENT_HOOKS_VERSION = "4"
|
CURRENT_HOOKS_VERSION = "4"
|
||||||
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
|
QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
|
||||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
|
||||||
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
|
<OutputPath>..\bin\Content.Benchmarks\</OutputPath>
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>12</LangVersion>
|
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<Import Project="../MSBuild/Content.props" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" />
|
<PackageReference Include="BenchmarkDotNet" />
|
||||||
|
|
||||||
|
<!-- pin transitive deps -->
|
||||||
|
<PackageReference Include="System.Management" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
|
<ProjectReference Include="..\Content.Client\Content.Client.csproj" />
|
||||||
|
|
@ -19,10 +22,12 @@
|
||||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||||
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
|
<ProjectReference Include="..\Content.Tests\Content.Tests.csproj" />
|
||||||
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
|
<ProjectReference Include="..\Content.IntegrationTests\Content.IntegrationTests.csproj" />
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Server.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Benchmarks.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Testing.props" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ public class DestructibleBenchmark
|
||||||
private readonly List<Entity<DamageableComponent>> _damageables = new();
|
private readonly List<Entity<DamageableComponent>> _damageables = new();
|
||||||
private readonly List<Entity<DamageableComponent, DestructibleComponent>> _destructbiles = new();
|
private readonly List<Entity<DamageableComponent, DestructibleComponent>> _destructbiles = new();
|
||||||
|
|
||||||
|
private TestMapData _currentMapData = default!;
|
||||||
|
|
||||||
private DamageSpecifier _damage;
|
private DamageSpecifier _damage;
|
||||||
|
|
||||||
private TestPair _pair = default!;
|
private TestPair _pair = default!;
|
||||||
|
|
@ -70,8 +72,6 @@ public class DestructibleBenchmark
|
||||||
_pair = await PoolManager.GetServerClient();
|
_pair = await PoolManager.GetServerClient();
|
||||||
var server = _pair.Server;
|
var server = _pair.Server;
|
||||||
|
|
||||||
var mapdata = await _pair.CreateTestMap();
|
|
||||||
|
|
||||||
_entMan = server.ResolveDependency<IEntityManager>();
|
_entMan = server.ResolveDependency<IEntityManager>();
|
||||||
_protoMan = server.ResolveDependency<IPrototypeManager>();
|
_protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||||
_random = server.ResolveDependency<IRobustRandom>();
|
_random = server.ResolveDependency<IRobustRandom>();
|
||||||
|
|
@ -86,19 +86,25 @@ public class DestructibleBenchmark
|
||||||
_damage = new DamageSpecifier(type, DamageAmount);
|
_damage = new DamageSpecifier(type, DamageAmount);
|
||||||
|
|
||||||
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
|
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
|
||||||
|
}
|
||||||
|
|
||||||
|
[IterationSetup]
|
||||||
|
public void IterationSetup()
|
||||||
|
{
|
||||||
var plating = _tileDefMan[TileRef].TileId;
|
var plating = _tileDefMan[TileRef].TileId;
|
||||||
|
var server = _pair.Server;
|
||||||
|
_currentMapData = _pair.CreateTestMap().GetAwaiter().GetResult();
|
||||||
|
|
||||||
// We make a rectangular grid of destructible entities, and then damage them all simultaneously to stress test the system.
|
// We make a rectangular grid of destructible entities, and then damage them all simultaneously to stress test the system.
|
||||||
// Needed for managing the performance of destructive effects and damage application.
|
// Needed for managing the performance of destructive effects and damage application.
|
||||||
await server.WaitPost(() =>
|
server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
// Set up a thin line of tiles to place our objects on. They should be anchored for a "realistic" scenario...
|
// Set up a thin line of tiles to place our objects on. They should be anchored for a "realistic" scenario...
|
||||||
for (var x = 0; x < EntityCount; x++)
|
for (var x = 0; x < EntityCount; x++)
|
||||||
{
|
{
|
||||||
for (var y = 0; y < _prototypes.Length; y++)
|
for (var y = 0; y < _prototypes.Length; y++)
|
||||||
{
|
{
|
||||||
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
|
_map.SetTile(_currentMapData.Grid, _currentMapData.Grid, new Vector2i(x, y), new Tile(plating));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +113,7 @@ public class DestructibleBenchmark
|
||||||
var y = 0;
|
var y = 0;
|
||||||
foreach (var protoId in _prototypes)
|
foreach (var protoId in _prototypes)
|
||||||
{
|
{
|
||||||
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
|
var coords = new EntityCoordinates(_currentMapData.Grid, x + 0.5f, y + 0.5f);
|
||||||
_entMan.SpawnEntity(protoId, coords);
|
_entMan.SpawnEntity(protoId, coords);
|
||||||
y++;
|
y++;
|
||||||
}
|
}
|
||||||
|
|
@ -115,12 +121,17 @@ public class DestructibleBenchmark
|
||||||
|
|
||||||
var query = _entMan.EntityQueryEnumerator<DamageableComponent, DestructibleComponent>();
|
var query = _entMan.EntityQueryEnumerator<DamageableComponent, DestructibleComponent>();
|
||||||
|
|
||||||
|
_destructbiles.EnsureCapacity(EntityCount);
|
||||||
|
_damageables.EnsureCapacity(EntityCount);
|
||||||
|
|
||||||
while (query.MoveNext(out var uid, out var damageable, out var destructible))
|
while (query.MoveNext(out var uid, out var damageable, out var destructible))
|
||||||
{
|
{
|
||||||
_damageables.Add((uid, damageable));
|
_damageables.Add((uid, damageable));
|
||||||
_destructbiles.Add((uid, damageable, destructible));
|
_destructbiles.Add((uid, damageable, destructible));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
|
|
@ -150,6 +161,26 @@ public class DestructibleBenchmark
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[IterationCleanup]
|
||||||
|
public void IterationCleanupAsync()
|
||||||
|
{
|
||||||
|
// We need to nuke the entire map and respawn everything as some destructible effects
|
||||||
|
// spawn entities and whatnot.
|
||||||
|
_pair.Server.WaitPost(() =>
|
||||||
|
{
|
||||||
|
_map.QueueDeleteMap(_currentMapData.MapId);
|
||||||
|
})
|
||||||
|
.Wait();
|
||||||
|
|
||||||
|
// Deletion of entities is often queued (QueueDel) which must be processed by running ticks
|
||||||
|
// or else it will grow infinitely and leak memory.
|
||||||
|
_pair.Server.WaitRunTicks(2)
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
|
|
||||||
|
_destructbiles.Clear();
|
||||||
|
_damageables.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public async Task CleanupAsync()
|
public async Task CleanupAsync()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Content.IntegrationTests;
|
||||||
|
using Content.IntegrationTests.Pair;
|
||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Robust.Shared;
|
||||||
|
using Robust.Shared.Analyzers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Benchmarks;
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
[GcServer(true)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class HeatCapacityBenchmark
|
||||||
|
{
|
||||||
|
private TestPair _pair = default!;
|
||||||
|
private IEntityManager _sEntMan = default!;
|
||||||
|
private IEntityManager _cEntMan = default!;
|
||||||
|
private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
|
||||||
|
private AtmosphereSystem _sAtmos = default!;
|
||||||
|
private GasMixture _mix;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public async Task SetupAsync()
|
||||||
|
{
|
||||||
|
ProgramShared.PathOffset = "../../../../";
|
||||||
|
PoolManager.Startup();
|
||||||
|
_pair = await PoolManager.GetServerClient();
|
||||||
|
await _pair.Connect();
|
||||||
|
_cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
|
||||||
|
_sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
|
||||||
|
_cAtmos = _cEntMan.System<Client.Atmos.EntitySystems.AtmosphereSystem>();
|
||||||
|
_sAtmos = _sEntMan.System<AtmosphereSystem>();
|
||||||
|
|
||||||
|
const float volume = 2500f;
|
||||||
|
const float temperature = 293.15f;
|
||||||
|
|
||||||
|
const float o2 = 12.3f;
|
||||||
|
const float n2 = 45.6f;
|
||||||
|
const float co2 = 0.42f;
|
||||||
|
const float plasma = 0.05f;
|
||||||
|
|
||||||
|
_mix = new GasMixture(volume) { Temperature = temperature };
|
||||||
|
|
||||||
|
_mix.AdjustMoles(Gas.Oxygen, o2);
|
||||||
|
_mix.AdjustMoles(Gas.Nitrogen, n2);
|
||||||
|
_mix.AdjustMoles(Gas.CarbonDioxide, co2);
|
||||||
|
_mix.AdjustMoles(Gas.Plasma, plasma);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task ClientHeatCapacityBenchmark()
|
||||||
|
{
|
||||||
|
await _pair.Client.WaitPost(delegate
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
_cAtmos.GetHeatCapacity(_mix, applyScaling: true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task ServerHeatCapacityBenchmark()
|
||||||
|
{
|
||||||
|
await _pair.Server.WaitPost(delegate
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
_sAtmos.GetHeatCapacity(_mix, applyScaling: true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public async Task CleanupAsync()
|
||||||
|
{
|
||||||
|
await _pair.DisposeAsync();
|
||||||
|
PoolManager.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using Content.IntegrationTests;
|
using Content.IntegrationTests;
|
||||||
using Content.IntegrationTests.Pair;
|
using Content.IntegrationTests.Pair;
|
||||||
using Content.Server.Maps;
|
using Content.Shared.Maps;
|
||||||
using Robust.Shared;
|
using Robust.Shared;
|
||||||
using Robust.Shared.Analyzers;
|
using Robust.Shared.Analyzers;
|
||||||
using Robust.Shared.EntitySerialization.Systems;
|
using Robust.Shared.EntitySerialization.Systems;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<GridContainer xmlns="https://spacestation14.io"
|
<GridContainer xmlns="https://spacestation14.io"
|
||||||
Columns="5"
|
Columns="4"
|
||||||
HorizontalAlignment="Center">
|
HorizontalAlignment="Center">
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
MinSize="650 290">
|
MinSize="650 290">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
<GridContainer Columns="2">
|
<GridContainer Columns="2">
|
||||||
<GridContainer Columns="3" HorizontalExpand="True">
|
<GridContainer Name="PrivilegedIdGrid" Columns="3" HorizontalExpand="True">
|
||||||
<Label Text="{Loc 'access-overrider-window-privileged-id'}" />
|
<Label Text="{Loc 'access-overrider-window-privileged-id'}" />
|
||||||
<Button Name="PrivilegedIdButton" Access="Public"/>
|
<Button Name="PrivilegedIdButton" Access="Public"/>
|
||||||
<Label Name="PrivilegedIdLabel" />
|
<Label Name="PrivilegedIdLabel" />
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ namespace Content.Client.Access.UI
|
||||||
|
|
||||||
public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUserInterfaceState state)
|
public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
|
PrivilegedIdGrid.Visible = state.ShowPrivilegedIdGrid;
|
||||||
|
|
||||||
PrivilegedIdLabel.Text = state.PrivilegedIdName;
|
PrivilegedIdLabel.Text = state.PrivilegedIdName;
|
||||||
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
|
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
|
||||||
? Loc.GetString("access-overrider-window-eject-button")
|
? Loc.GetString("access-overrider-window-eject-button")
|
||||||
|
|
@ -77,7 +79,9 @@ namespace Content.Client.Access.UI
|
||||||
missingPrivileges.Add(privilege);
|
missingPrivileges.Add(privilege);
|
||||||
}
|
}
|
||||||
|
|
||||||
MissingPrivilegesLabel.Text = Loc.GetString("access-overrider-window-missing-privileges");
|
MissingPrivilegesLabel.Text = state.ShowPrivilegedIdGrid ?
|
||||||
|
Loc.GetString("access-overrider-window-missing-privileges") :
|
||||||
|
Loc.GetString("access-overrider-window-missing-privileges-no-id");
|
||||||
MissingPrivilegesText.Text = string.Join(", ", missingPrivileges);
|
MissingPrivilegesText.Text = string.Join(", ", missingPrivileges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||||
? null
|
? null
|
||||||
: _prototypeManager.Index(playerInfo.RoleProto.Value);
|
: _prototypeManager.Index(playerInfo.RoleProto.Value);
|
||||||
|
|
||||||
var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
|
var roleName = rolePrototype?.Name ?? RoleTypePrototype.FallbackName;
|
||||||
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
|
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
|
||||||
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
|
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
|
||||||
|
|
||||||
|
|
@ -227,7 +227,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||||
{
|
{
|
||||||
color = Color.GreenYellow;
|
color = Color.GreenYellow;
|
||||||
color.A = alpha;
|
color.A = alpha;
|
||||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? color : colorDisconnected);
|
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.StartingJob, uiScale, playerInfo.Connected ? color : colorDisconnected);
|
||||||
currentOffset += lineoffset;
|
currentOffset += lineoffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +255,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||||
color = roleColor;
|
color = roleColor;
|
||||||
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
|
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||||
text = IsFiltered(playerInfo.RoleProto)
|
text = IsFiltered(playerInfo.RoleProto)
|
||||||
? roleName.ToUpper()
|
? Loc.GetString(roleName).ToUpper()
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
break;
|
break;
|
||||||
case AdminOverlayAntagFormat.Subtype:
|
case AdminOverlayAntagFormat.Subtype:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
using Content.Shared.Administration.Components;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
|
|
||||||
namespace Content.Client.Administration.Components;
|
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class KillSignComponent : SharedKillSignComponent;
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
using Content.Shared.Administration.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.Systems;
|
||||||
|
|
||||||
|
public sealed class BufferingSystem : SharedBufferingSystem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -1,46 +1,77 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Client.Administration.Components;
|
using Content.Shared.Administration.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Client.Player;
|
||||||
|
|
||||||
namespace Content.Client.Administration.Systems;
|
namespace Content.Client.Administration.Systems;
|
||||||
|
|
||||||
public sealed class KillSignSystem : EntitySystem
|
public sealed class KillSignSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<KillSignComponent, ComponentStartup>(KillSignAdded);
|
SubscribeLocalEvent<KillSignComponent, ComponentStartup>(KillSignAdded);
|
||||||
SubscribeLocalEvent<KillSignComponent, ComponentShutdown>(KillSignRemoved);
|
SubscribeLocalEvent<KillSignComponent, ComponentShutdown>(KillSignRemoved);
|
||||||
|
SubscribeLocalEvent<KillSignComponent, AfterAutoHandleStateEvent>(AfterAutoHandleState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KillSignRemoved(EntityUid uid, KillSignComponent component, ComponentShutdown args)
|
private void KillSignRemoved(Entity<KillSignComponent> ent, ref ComponentShutdown args)
|
||||||
{
|
{
|
||||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
RemoveKillsign(ent);
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var layer, false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_sprite.RemoveLayer((uid, sprite), layer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KillSignAdded(EntityUid uid, KillSignComponent component, ComponentStartup args)
|
private void KillSignAdded(Entity<KillSignComponent> ent, ref ComponentStartup args)
|
||||||
{
|
{
|
||||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
AddKillsign(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AfterAutoHandleState(Entity<KillSignComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||||
|
{
|
||||||
|
// After receiving a new state for the component, we remove the old killsign and build a new one.
|
||||||
|
// This is so changes to the sprite can be displayed live and allowing them to be edited via ViewVariables.
|
||||||
|
// This could just update an existing sprite, but this is both easier and runs rarely anyway.
|
||||||
|
RemoveKillsign(ent);
|
||||||
|
AddKillsign(ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddKillsign(Entity<KillSignComponent> ent)
|
||||||
|
{
|
||||||
|
// If we hide from owner and we ARE the owner, don't add a killsign.
|
||||||
|
// This could use session specific networking to FULLY hide it, but I am too lazy right now.
|
||||||
|
if (ent.Comp.HideFromOwner && _player.LocalEntity == ent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var _, false))
|
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var adj = _sprite.GetLocalBounds((uid, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
|
if (_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var _, false))
|
||||||
|
return;
|
||||||
|
|
||||||
var layer = _sprite.AddLayer((uid, sprite), new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
|
if (ent.Comp.Sprite == null)
|
||||||
_sprite.LayerMapSet((uid, sprite), KillSignKey.Key, layer);
|
return;
|
||||||
|
|
||||||
_sprite.LayerSetOffset((uid, sprite), layer, new Vector2(0.0f, adj));
|
var adj = _sprite.GetLocalBounds((ent, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
|
||||||
sprite.LayerSetShader(layer, "unshaded");
|
|
||||||
|
var layer = _sprite.AddLayer((ent, sprite), ent.Comp.Sprite);
|
||||||
|
_sprite.LayerMapSet((ent, sprite), KillSignKey.Key, layer);
|
||||||
|
_sprite.LayerSetScale((ent, sprite), layer, ent.Comp.Scale);
|
||||||
|
_sprite.LayerSetOffset((ent, sprite), layer, ent.Comp.DoOffset ? new Vector2(0.0f, adj) : new Vector2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
if (ent.Comp.ForceUnshaded)
|
||||||
|
sprite.LayerSetShader(layer, "unshaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveKillsign(Entity<KillSignComponent> ent)
|
||||||
|
{
|
||||||
|
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var layer, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_sprite.RemoveLayer((ent, sprite), layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum KillSignKey
|
private enum KillSignKey
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.CustomControls;
|
||||||
|
|
||||||
|
public sealed class AdminLogLabel : RichTextLabel
|
||||||
|
{
|
||||||
|
public AdminLogLabel(ref SharedAdminLog log, HSeparator separator)
|
||||||
|
{
|
||||||
|
Log = log;
|
||||||
|
Separator = separator;
|
||||||
|
|
||||||
|
SetMessage($"{log.Date:HH:mm:ss}: {log.Message}");
|
||||||
|
OnVisibilityChanged += VisibilityChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public new SharedAdminLog Log { get; }
|
||||||
|
|
||||||
|
public HSeparator Separator { get; }
|
||||||
|
|
||||||
|
private void VisibilityChanged(Control control)
|
||||||
|
{
|
||||||
|
Separator.Visible = Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
OnVisibilityChanged -= VisibilityChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Content.Client.Administration.Systems;
|
using Content.Client.Administration.Systems;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Verbs.UI;
|
using Content.Client.Verbs.UI;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Input;
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.CustomControls;
|
namespace Content.Client.Administration.UI.CustomControls;
|
||||||
|
|
||||||
|
|
@ -95,26 +96,13 @@ public sealed partial class PlayerListControl : BoxContainer
|
||||||
private void FilterList()
|
private void FilterList()
|
||||||
{
|
{
|
||||||
_sortedPlayerList.Clear();
|
_sortedPlayerList.Clear();
|
||||||
|
|
||||||
Regex filterRegex;
|
|
||||||
// There is no neat way to handle invalid regex being submitted other than
|
|
||||||
// catching and ignoring the exception which gets thrown when it's invalid.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
filterRegex = new Regex(FilterLineEdit.Text, RegexOptions.IgnoreCase);
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var info in _playerList)
|
foreach (var info in _playerList)
|
||||||
{
|
{
|
||||||
var displayName = $"{info.CharacterName} ({info.Username})";
|
var displayName = $"{info.CharacterName} ({info.Username})";
|
||||||
if (info.IdentityName != info.CharacterName)
|
if (info.IdentityName != info.CharacterName)
|
||||||
displayName += $" [{info.IdentityName}]";
|
displayName += $" [{info.IdentityName}]";
|
||||||
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
|
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
|
||||||
&& !filterRegex.IsMatch(displayName))
|
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
|
||||||
continue;
|
continue;
|
||||||
_sortedPlayerList.Add(info);
|
_sortedPlayerList.Add(info);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<Control xmlns="https://spacestation14.io"
|
<Control xmlns="https://spacestation14.io"
|
||||||
xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
xmlns:aui="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
|
||||||
<PanelContainer StyleClasses="BackgroundDark">
|
<PanelContainer StyleClasses="BackgroundDark">
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
|
|
@ -55,15 +54,9 @@
|
||||||
<Control HorizontalExpand="True"/>
|
<Control HorizontalExpand="True"/>
|
||||||
<Label Name="Count" Access="Public"/>
|
<Label Name="Count" Access="Public"/>
|
||||||
<Control HorizontalExpand="True"/>
|
<Control HorizontalExpand="True"/>
|
||||||
|
<Button Name="ExportLogs" Access="Public" Text="{Loc admin-logs-export}"/>
|
||||||
<Button Name="PopOutButton" Access="Public" Text="{Loc admin-logs-pop-out}"/>
|
<Button Name="PopOutButton" Access="Public" Text="{Loc admin-logs-pop-out}"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<BoxContainer HorizontalExpand="True">
|
|
||||||
<Button Name="RenderRichTextButton" Access="Public" Text="{Loc admin-logs-render-rich-text}"
|
|
||||||
StyleClasses="OpenRight" ToggleMode="True"/>
|
|
||||||
<Button Name="RemoveMarkupButton" Access="Public" Text="{Loc admin-logs-remove-markup}"
|
|
||||||
StyleClasses="OpenLeft" ToggleMode="True"/>
|
|
||||||
<Control HorizontalExpand="True"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<LineEdit Name="LogSearch" Access="Public" StyleClasses="actionSearchBox"
|
<LineEdit Name="LogSearch" Access="Public" StyleClasses="actionSearchBox"
|
||||||
HorizontalExpand="true" PlaceHolder="{Loc admin-logs-search-logs-placeholder}"/>
|
HorizontalExpand="true" PlaceHolder="{Loc admin-logs-search-logs-placeholder}"/>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Content.Client.Administration.UI.CustomControls;
|
using Content.Client.Administration.UI.CustomControls;
|
||||||
using Content.Client.Administration.UI.Logs.Entries;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
|
@ -40,9 +38,6 @@ public sealed partial class AdminLogsControl : Control
|
||||||
SelectAllPlayersButton.OnPressed += SelectAllPlayers;
|
SelectAllPlayersButton.OnPressed += SelectAllPlayers;
|
||||||
SelectNoPlayersButton.OnPressed += SelectNoPlayers;
|
SelectNoPlayersButton.OnPressed += SelectNoPlayers;
|
||||||
|
|
||||||
RenderRichTextButton.OnPressed += RenderRichTextChanged;
|
|
||||||
RemoveMarkupButton.OnPressed += RemoveMarkupChanged;
|
|
||||||
|
|
||||||
RoundSpinBox.IsValid = i => i > 0 && i <= CurrentRound;
|
RoundSpinBox.IsValid = i => i > 0 && i <= CurrentRound;
|
||||||
RoundSpinBox.ValueChanged += RoundSpinBoxChanged;
|
RoundSpinBox.ValueChanged += RoundSpinBoxChanged;
|
||||||
RoundSpinBox.InitDefaultButtons();
|
RoundSpinBox.InitDefaultButtons();
|
||||||
|
|
@ -63,8 +58,6 @@ public sealed partial class AdminLogsControl : Control
|
||||||
|
|
||||||
private int CurrentRound { get; set; }
|
private int CurrentRound { get; set; }
|
||||||
|
|
||||||
private Regex LogSearchRegex { get; set; } = new("");
|
|
||||||
|
|
||||||
public int SelectedRoundId => RoundSpinBox.Value;
|
public int SelectedRoundId => RoundSpinBox.Value;
|
||||||
public string Search => LogSearch.Text;
|
public string Search => LogSearch.Text;
|
||||||
private int ShownLogs { get; set; }
|
private int ShownLogs { get; set; }
|
||||||
|
|
@ -84,8 +77,6 @@ public sealed partial class AdminLogsControl : Control
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<SharedAdminLog> RawAdminLogs { get; set; } = new();
|
private List<SharedAdminLog> RawAdminLogs { get; set; } = new();
|
||||||
|
|
||||||
private bool RenderRichText { get; set; }
|
|
||||||
private bool RemoveMarkup { get; set; }
|
|
||||||
public HashSet<LogType> SelectedTypes { get; } = new();
|
public HashSet<LogType> SelectedTypes { get; } = new();
|
||||||
|
|
||||||
public HashSet<Guid> SelectedPlayers { get; } = new();
|
public HashSet<Guid> SelectedPlayers { get; } = new();
|
||||||
|
|
@ -132,19 +123,6 @@ public sealed partial class AdminLogsControl : Control
|
||||||
|
|
||||||
private void LogSearchChanged(LineEditEventArgs args)
|
private void LogSearchChanged(LineEditEventArgs args)
|
||||||
{
|
{
|
||||||
// This exception is thrown if the regex is invalid, which happens often, so we ignore it.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LogSearchRegex = new Regex(
|
|
||||||
"(" + LogSearch.Text + ")",
|
|
||||||
RegexOptions.IgnoreCase,
|
|
||||||
TimeSpan.FromSeconds(1));
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateLogs();
|
UpdateLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,26 +204,6 @@ public sealed partial class AdminLogsControl : Control
|
||||||
UpdateLogs();
|
UpdateLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderRichTextChanged(ButtonEventArgs args)
|
|
||||||
{
|
|
||||||
RenderRichText = args.Button.Pressed;
|
|
||||||
|
|
||||||
RemoveMarkup = RemoveMarkup && !RenderRichText;
|
|
||||||
RemoveMarkupButton.Pressed = RemoveMarkup;
|
|
||||||
|
|
||||||
UpdateLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveMarkupChanged(ButtonEventArgs args)
|
|
||||||
{
|
|
||||||
RemoveMarkup = args.Button.Pressed;
|
|
||||||
|
|
||||||
RenderRichText = !RemoveMarkup && RenderRichText;
|
|
||||||
RenderRichTextButton.Pressed = RenderRichText;
|
|
||||||
|
|
||||||
UpdateLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTypesSelection(HashSet<LogType> selectedTypes, bool invert = false)
|
public void SetTypesSelection(HashSet<LogType> selectedTypes, bool invert = false)
|
||||||
{
|
{
|
||||||
SelectedTypes.Clear();
|
SelectedTypes.Clear();
|
||||||
|
|
@ -304,15 +262,16 @@ public sealed partial class AdminLogsControl : Control
|
||||||
|
|
||||||
foreach (var child in LogsContainer.Children)
|
foreach (var child in LogsContainer.Children)
|
||||||
{
|
{
|
||||||
if (child is not AdminLogEntry log)
|
if (child is not AdminLogLabel log)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
child.Visible = ShouldShowLog(log);
|
child.Visible = ShouldShowLog(log);
|
||||||
if (!child.Visible)
|
if (child.Visible)
|
||||||
continue;
|
{
|
||||||
|
ShownLogs++;
|
||||||
log.RenderResults(LogSearchRegex, RenderRichText, RemoveMarkup);
|
}
|
||||||
ShownLogs++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCount();
|
UpdateCount();
|
||||||
|
|
@ -330,30 +289,30 @@ public sealed partial class AdminLogsControl : Control
|
||||||
button.Text.Contains(PlayerSearch.Text, StringComparison.OrdinalIgnoreCase);
|
button.Text.Contains(PlayerSearch.Text, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool LogMatchesPlayerFilter(AdminLogEntry entry)
|
private bool LogMatchesPlayerFilter(AdminLogLabel label)
|
||||||
{
|
{
|
||||||
if (entry.Log.Players.Length == 0)
|
if (label.Log.Players.Length == 0)
|
||||||
return SelectedPlayers.Count == 0 || IncludeNonPlayerLogs;
|
return SelectedPlayers.Count == 0 || IncludeNonPlayerLogs;
|
||||||
|
|
||||||
return SelectedPlayers.Overlaps(entry.Log.Players);
|
return SelectedPlayers.Overlaps(label.Log.Players);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldShowLog(AdminLogEntry entry)
|
private bool ShouldShowLog(AdminLogLabel label)
|
||||||
{
|
{
|
||||||
// Check log type
|
// Check log type
|
||||||
if (!SelectedTypes.Contains(entry.Log.Type))
|
if (!SelectedTypes.Contains(label.Log.Type))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check players
|
// Check players
|
||||||
if (!LogMatchesPlayerFilter(entry))
|
if (!LogMatchesPlayerFilter(label))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check impact
|
// Check impact
|
||||||
if (!SelectedImpacts.Contains(entry.Log.Impact))
|
if (!SelectedImpacts.Contains(label.Log.Impact))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check search
|
// Check search
|
||||||
if (!LogSearchRegex.IsMatch(entry.Log.Message))
|
if (!label.Log.Message.Contains(LogSearch.Text, StringComparison.OrdinalIgnoreCase))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -427,16 +386,15 @@ public sealed partial class AdminLogsControl : Control
|
||||||
{
|
{
|
||||||
var log = tempLogs[i];
|
var log = tempLogs[i];
|
||||||
ref var logRef = ref log; // It didn't like me doing this as one line lmao
|
ref var logRef = ref log; // It didn't like me doing this as one line lmao
|
||||||
var entry = new AdminLogEntry(ref log);
|
|
||||||
|
|
||||||
//var label = new AdminLogLabel(ref logRef, separator);
|
var separator = new HSeparator();
|
||||||
//label.Visible = ShouldShowLog(label);
|
var label = new AdminLogLabel(ref logRef, separator);
|
||||||
|
label.Visible = ShouldShowLog(label);
|
||||||
|
|
||||||
TotalLogs++;
|
if (label.Visible) ShownLogs++;
|
||||||
//if (label.Visible) ShownLogs++;
|
|
||||||
|
|
||||||
//LogsContainer.AddChild(label);
|
LogsContainer.AddChild(label);
|
||||||
LogsContainer.AddChild(entry);
|
LogsContainer.AddChild(separator);
|
||||||
}
|
}
|
||||||
UpdateLogs();
|
UpdateLogs();
|
||||||
}
|
}
|
||||||
|
|
@ -636,7 +594,6 @@ public sealed partial class AdminLogsControl : Control
|
||||||
SelectAllTypesButton.OnPressed -= SelectAllTypes;
|
SelectAllTypesButton.OnPressed -= SelectAllTypes;
|
||||||
SelectNoTypesButton.OnPressed -= SelectNoTypes;
|
SelectNoTypesButton.OnPressed -= SelectNoTypes;
|
||||||
|
|
||||||
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
|
|
||||||
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
|
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
|
||||||
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
|
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
|
||||||
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;
|
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Administration.UI.Logs.Entries;
|
using Content.Client.Administration.UI.CustomControls;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
|
|
@ -17,6 +17,16 @@ public sealed class AdminLogsEui : BaseEui
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IClyde _clyde = default!;
|
[Dependency] private readonly IClyde _clyde = default!;
|
||||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||||
|
[Dependency] private readonly IFileDialogManager _dialogManager = default!;
|
||||||
|
[Dependency] private readonly ILogManager _log = default!;
|
||||||
|
|
||||||
|
private const char CsvSeparator = ',';
|
||||||
|
private const string CsvQuote = "\"";
|
||||||
|
private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message";
|
||||||
|
|
||||||
|
private ISawmill _sawmill;
|
||||||
|
|
||||||
|
private bool _currentlyExportingLogs = false;
|
||||||
|
|
||||||
public AdminLogsEui()
|
public AdminLogsEui()
|
||||||
{
|
{
|
||||||
|
|
@ -28,6 +38,9 @@ public sealed class AdminLogsEui : BaseEui
|
||||||
LogsControl.RefreshButton.OnPressed += _ => RequestLogs();
|
LogsControl.RefreshButton.OnPressed += _ => RequestLogs();
|
||||||
LogsControl.NextButton.OnPressed += _ => NextLogs();
|
LogsControl.NextButton.OnPressed += _ => NextLogs();
|
||||||
LogsControl.PopOutButton.OnPressed += _ => PopOut();
|
LogsControl.PopOutButton.OnPressed += _ => PopOut();
|
||||||
|
LogsControl.ExportLogs.OnPressed += _ => ExportLogs();
|
||||||
|
|
||||||
|
_sawmill = _log.GetSawmill("admin.logs.ui");
|
||||||
}
|
}
|
||||||
|
|
||||||
private WindowRoot? Root { get; set; }
|
private WindowRoot? Root { get; set; }
|
||||||
|
|
@ -76,6 +89,71 @@ public sealed class AdminLogsEui : BaseEui
|
||||||
SendMessage(request);
|
SendMessage(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void ExportLogs()
|
||||||
|
{
|
||||||
|
if (_currentlyExportingLogs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_currentlyExportingLogs = true;
|
||||||
|
LogsControl.ExportLogs.Disabled = true;
|
||||||
|
|
||||||
|
var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("csv")));
|
||||||
|
|
||||||
|
if (file == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Buffer is set to 4KB for performance reasons. As the average export of 1000 logs is ~200KB
|
||||||
|
await using var writer = new StreamWriter(file.Value.fileStream, bufferSize: 4096);
|
||||||
|
await writer.WriteLineAsync(CsvHeader);
|
||||||
|
foreach (var child in LogsControl.LogsContainer.Children)
|
||||||
|
{
|
||||||
|
if (child is not AdminLogLabel logLabel || !child.Visible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var log = logLabel.Log;
|
||||||
|
|
||||||
|
// Date
|
||||||
|
// I swear to god if someone adds ,s or "s to the other fields...
|
||||||
|
await writer.WriteAsync(log.Date.ToString("s", System.Globalization.CultureInfo.InvariantCulture));
|
||||||
|
await writer.WriteAsync(CsvSeparator);
|
||||||
|
// ID
|
||||||
|
await writer.WriteAsync(log.Id.ToString());
|
||||||
|
await writer.WriteAsync(CsvSeparator);
|
||||||
|
// PlayerID
|
||||||
|
var players = log.Players;
|
||||||
|
for (var i = 0; i < players.Length; i++)
|
||||||
|
{
|
||||||
|
await writer.WriteAsync(players[i] + (i == players.Length - 1 ? "" : " "));
|
||||||
|
}
|
||||||
|
await writer.WriteAsync(CsvSeparator);
|
||||||
|
// Severity
|
||||||
|
await writer.WriteAsync(log.Impact.ToString());
|
||||||
|
await writer.WriteAsync(CsvSeparator);
|
||||||
|
// Type
|
||||||
|
await writer.WriteAsync(log.Type.ToString());
|
||||||
|
await writer.WriteAsync(CsvSeparator);
|
||||||
|
// Message
|
||||||
|
await writer.WriteAsync(CsvQuote);
|
||||||
|
await writer.WriteAsync(log.Message.Replace(CsvQuote, CsvQuote + CsvQuote));
|
||||||
|
await writer.WriteAsync(CsvQuote);
|
||||||
|
|
||||||
|
await writer.WriteLineAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exc)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Error when exporting admin log:\n{exc.StackTrace}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await file.Value.fileStream.DisposeAsync();
|
||||||
|
_currentlyExportingLogs = false;
|
||||||
|
LogsControl.ExportLogs.Disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void PopOut()
|
private void PopOut()
|
||||||
{
|
{
|
||||||
if (LogsWindow == null)
|
if (LogsWindow == null)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
<BoxContainer xmlns="https://spacestation14.io"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
|
||||||
Orientation="Vertical">
|
|
||||||
<BoxContainer Margin="2">
|
|
||||||
<Collapsible>
|
|
||||||
<CollapsibleHeading Name="DetailsHeading" Access="Public">
|
|
||||||
<RichTextLabel Margin="20 0 0 0" Name="Message" MinSize="50 10" VerticalExpand="True" Access="Public" />
|
|
||||||
</CollapsibleHeading>
|
|
||||||
<CollapsibleBody Name="DetailsBody" Access="Public" />
|
|
||||||
</Collapsible>
|
|
||||||
</BoxContainer>
|
|
||||||
<cc:HSeparator/>
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Content.Client.Message;
|
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.Logs.Entries;
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class AdminLogEntry : BoxContainer
|
|
||||||
{
|
|
||||||
private readonly IConfigurationManager _cfgManager;
|
|
||||||
|
|
||||||
public SharedAdminLog Log { get; }
|
|
||||||
|
|
||||||
private readonly string _rawMessage;
|
|
||||||
|
|
||||||
public AdminLogEntry(ref SharedAdminLog log)
|
|
||||||
{
|
|
||||||
_cfgManager = IoCManager.Resolve<IConfigurationManager>();
|
|
||||||
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
|
|
||||||
Log = log;
|
|
||||||
|
|
||||||
_rawMessage = $"{log.Date:HH:mm:ss}: {log.Message}";
|
|
||||||
Message.SetMessage(_rawMessage);
|
|
||||||
|
|
||||||
DetailsHeading.OnToggled += DetailsToggled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets text to be highlighted from a search result, and renders rich text, or removes all rich text markup.
|
|
||||||
/// </summary>
|
|
||||||
public void RenderResults(Regex highlightRegex, bool renderRichText, bool removeMarkup)
|
|
||||||
{
|
|
||||||
var color = _cfgManager.GetCVar(CCVars.AdminLogsHighlightColor);
|
|
||||||
var formattedMessage = renderRichText
|
|
||||||
? _rawMessage
|
|
||||||
: removeMarkup
|
|
||||||
? FormattedMessage.RemoveMarkupPermissive(_rawMessage)
|
|
||||||
: FormattedMessage.EscapeText(_rawMessage);
|
|
||||||
|
|
||||||
// Want to avoid highlighting smaller strings
|
|
||||||
if (highlightRegex.ToString().Length > 4)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
formattedMessage = highlightRegex.Replace(formattedMessage, $"[color={color}]$1[/color]", 3);
|
|
||||||
}
|
|
||||||
catch (RegexMatchTimeoutException)
|
|
||||||
{
|
|
||||||
// if we time out then don't bother highlighting results
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FormattedMessage.TryFromMarkup(formattedMessage, out var outputMessage))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Message.SetMessage(outputMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// We perform some extra calculations in the dropdown, so we want to render that only when
|
|
||||||
/// the dropdown is actually opened.
|
|
||||||
/// This also removes itself from the event listener so it doesn't trigger again.
|
|
||||||
/// </summary>
|
|
||||||
private void DetailsToggled(BaseButton.ButtonToggledEventArgs args)
|
|
||||||
{
|
|
||||||
if (!args.Pressed || DetailsBody.ChildCount > 0)
|
|
||||||
return;
|
|
||||||
DetailsBody.AddChild(new AdminLogEntryDetails(Log));
|
|
||||||
DetailsHeading.OnToggled -= DetailsToggled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
<BoxContainer xmlns="https://spacestation14.io"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
|
||||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
|
||||||
StyleClasses="BackgroundDark">
|
|
||||||
<BoxContainer Orientation="Vertical" Margin="4">
|
|
||||||
<BoxContainer Orientation="Vertical">
|
|
||||||
<Label Text="{Loc admin-logs-field-type}" Margin="0 0 4 0"/>
|
|
||||||
<Label Name="Type" Text="None" StyleClasses="LabelSecondaryColor" Access="Public"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<cc:HSeparator/>
|
|
||||||
<BoxContainer Orientation="Vertical">
|
|
||||||
<Label Text="{Loc admin-logs-field-impact}" Margin="0 0 4 0"/>
|
|
||||||
<Label Name="Impact" Text="None" StyleClasses="LabelSecondaryColor" Access="Public"/>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
<cc:VSeparator/>
|
|
||||||
<BoxContainer Orientation="Vertical" Margin="4">
|
|
||||||
<Label Text="{Loc admin-logs-field-time-header}"/>
|
|
||||||
<cc:HSeparator/>
|
|
||||||
<BoxContainer>
|
|
||||||
<Label Text="{Loc admin-logs-field-time-local}" Margin="0 0 4 0" HorizontalExpand="True"/>
|
|
||||||
<Label Name="LocalTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<cc:HSeparator/>
|
|
||||||
<BoxContainer Orientation="Horizontal">
|
|
||||||
<Label Text="{Loc admin-logs-field-time-utc}" Margin="0 0 4 0" HorizontalExpand="True"/>
|
|
||||||
<Label Name="UTCTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<cc:HSeparator/>
|
|
||||||
<BoxContainer Orientation="Horizontal">
|
|
||||||
<Label Text="{Loc admin-logs-field-time-round}" Margin="0 0 4 0" HorizontalExpand="True"/>
|
|
||||||
<Label Name="CurTime" Text="None" StyleClasses="LabelSecondaryColor" Access="Public" HorizontalExpand="True"/>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
<cc:VSeparator/>
|
|
||||||
<BoxContainer Orientation="Vertical" Margin="4" HorizontalExpand="True">
|
|
||||||
<Label Text="{Loc admin-logs-field-players-header}"/>
|
|
||||||
<cc:HSeparator/>
|
|
||||||
<BoxContainer Orientation="Horizontal" Margin="4" VerticalExpand="True">
|
|
||||||
<controls:ListContainer Name="PlayerListContainer" Access="Public" HorizontalExpand="True"/>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using Content.Client.Administration.Systems;
|
|
||||||
using Content.Client.Administration.UI.CustomControls;
|
|
||||||
using Content.Client.UserInterface.Controls;
|
|
||||||
using Content.Client.Verbs.UI;
|
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Input;
|
|
||||||
using Robust.Shared.Network;
|
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.Logs.Entries;
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class AdminLogEntryDetails : BoxContainer
|
|
||||||
{
|
|
||||||
private readonly AdminSystem _adminSystem;
|
|
||||||
private readonly IUserInterfaceManager _uiManager;
|
|
||||||
private readonly IEntityManager _entManager;
|
|
||||||
|
|
||||||
public AdminLogEntryDetails(SharedAdminLog log)
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
_uiManager = IoCManager.Resolve<IUserInterfaceManager>();
|
|
||||||
_adminSystem = _entManager.System<AdminSystem>();
|
|
||||||
|
|
||||||
Type.Text = log.Type.ToString();
|
|
||||||
Impact.Text = log.Impact.ToString();
|
|
||||||
|
|
||||||
LocalTime.Text = $"{log.Date.ToLocalTime():HH:mm:ss}";
|
|
||||||
UTCTime.Text = $"{log.Date:HH:mm:ss}";
|
|
||||||
// TimeSpan and DateTime use different formatting string conventions for some completely logical reason
|
|
||||||
// that mere mortals such as myself will never be able to understand.
|
|
||||||
CurTime.Text = new TimeSpan(log.CurTime).ToString(@"hh\:mm\:ss");
|
|
||||||
|
|
||||||
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
|
|
||||||
PlayerListContainer.GenerateItem += GenerateButton;
|
|
||||||
PopulateList(log.Players);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateList(Guid[] players)
|
|
||||||
{
|
|
||||||
if (players.Length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_adminSystem.PlayerList is not { } allPlayers || allPlayers.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var listData = new List<PlayerListData>();
|
|
||||||
foreach (var playerGuid in players)
|
|
||||||
{
|
|
||||||
var netUserId = new NetUserId(playerGuid);
|
|
||||||
|
|
||||||
// Linq here is fine since this runs in response to admin input in the UI and
|
|
||||||
// this loop only tends to go through 1-4 iterations.
|
|
||||||
if (allPlayers.FirstOrDefault(player => player.SessionId == netUserId) is not { } playerInfo)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
listData.Add(new PlayerListData(playerInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listData.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
PlayerListContainer.PopulateList(listData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data)
|
|
||||||
{
|
|
||||||
if (args == null || data is not PlayerListData { Info: var selectedPlayer })
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!(args.Function == EngineKeyFunctions.UIRightClick
|
|
||||||
|| args.Function == EngineKeyFunctions.UIClick)
|
|
||||||
|| selectedPlayer.NetEntity == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_uiManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(selectedPlayer.NetEntity.Value, true);
|
|
||||||
args.Handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GenerateButton(ListData data, ListContainerButton button)
|
|
||||||
{
|
|
||||||
if (data is not PlayerListData { Info: var info })
|
|
||||||
return;
|
|
||||||
|
|
||||||
var entryLabel = new Label();
|
|
||||||
entryLabel.Text = $"{info.CharacterName} ({info.Username})";
|
|
||||||
var entry = new BoxContainer();
|
|
||||||
entry.AddChild(entryLabel);
|
|
||||||
|
|
||||||
button.AddChild(entry);
|
|
||||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -56,6 +56,7 @@ public sealed partial class PlayerTab : Control
|
||||||
_config.OnValueChanged(CCVars.AdminPlayerTabMarkGhosted, MarkGhostedChanged, true); // DeltaV
|
_config.OnValueChanged(CCVars.AdminPlayerTabMarkGhosted, MarkGhostedChanged, true); // DeltaV
|
||||||
_config.OnValueChanged(CCVars.AdminPlayerTabMarkWatchlisted, MarkWatchlistedChanged, true); // DeltaV
|
_config.OnValueChanged(CCVars.AdminPlayerTabMarkWatchlisted, MarkWatchlistedChanged, true); // DeltaV
|
||||||
|
|
||||||
|
|
||||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||||
|
|
||||||
|
|
@ -68,6 +69,7 @@ public sealed partial class PlayerTab : Control
|
||||||
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
||||||
|
|
||||||
RefreshPlayerList(_adminSystem.PlayerList);
|
RefreshPlayerList(_adminSystem.PlayerList);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeltaV - mark ghosted, watchlisted START
|
// DeltaV - mark ghosted, watchlisted START
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Client.Atmos.EntitySystems;
|
||||||
|
|
||||||
|
public sealed partial class AtmosphereSystem
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Partial class for operations involving GasMixtures.
|
||||||
|
|
||||||
|
Any method that is overridden here is usually because the server-sided implementation contains
|
||||||
|
code that would escape sandbox. As such these methods are overridden here with a safe
|
||||||
|
implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
protected override float GetHeatCapacityCalculation(float[] moles, bool space)
|
||||||
|
{
|
||||||
|
// Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
|
||||||
|
if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
|
||||||
|
{
|
||||||
|
return Atmospherics.SpaceHeatCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicit stackalloc call is banned on client tragically.
|
||||||
|
// the JIT does not stackalloc this during runtime,
|
||||||
|
// though this isnt the hottest code path so it should be fine
|
||||||
|
// the gc can eat a little as a treat
|
||||||
|
var tmp = new float[moles.Length];
|
||||||
|
NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
|
||||||
|
// Adjust heat capacity by speedup, because this is primarily what
|
||||||
|
// determines how quickly gases heat up/cool.
|
||||||
|
return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Client.Atmos.EntitySystems;
|
namespace Content.Client.Atmos.EntitySystems;
|
||||||
|
|
||||||
public sealed class AtmosphereSystem : SharedAtmosphereSystem
|
public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
using Content.Client.BarSign.Ui;
|
|
||||||
using Content.Shared.BarSign;
|
|
||||||
using Content.Shared.Power;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Client.BarSign;
|
|
||||||
|
|
||||||
public sealed class BarSignSystem : VisualizerSystem<BarSignComponent>
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<BarSignComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAfterAutoHandleState(EntityUid uid, BarSignComponent component, ref AfterAutoHandleStateEvent args)
|
|
||||||
{
|
|
||||||
if (_ui.TryGetOpenUi<BarSignBoundUserInterface>(uid, BarSignUiKey.Key, out var bui))
|
|
||||||
bui.Update(component.Current);
|
|
||||||
|
|
||||||
UpdateAppearance(uid, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
UpdateAppearance(uid, component, args.Component, args.Sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateAppearance(EntityUid id, BarSignComponent sign, AppearanceComponent? appearance = null, SpriteComponent? sprite = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(id, ref appearance, ref sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
AppearanceSystem.TryGetData<bool>(id, PowerDeviceVisuals.Powered, out var powered, appearance);
|
|
||||||
|
|
||||||
if (powered
|
|
||||||
&& sign.Current != null
|
|
||||||
&& _prototypeManager.Resolve(sign.Current, out var proto))
|
|
||||||
{
|
|
||||||
SpriteSystem.LayerSetSprite((id, sprite), 0, proto.Icon);
|
|
||||||
sprite.LayerSetShader(0, "unshaded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SpriteSystem.LayerSetRsiState((id, sprite), 0, "empty");
|
|
||||||
sprite.LayerSetShader(0, null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
using Content.Shared.BarSign;
|
||||||
|
using Content.Shared.Power;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.BarSign;
|
||||||
|
|
||||||
|
public sealed class BarSignVisualizerSystem : VisualizerSystem<BarSignComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, BarSignComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
AppearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component);
|
||||||
|
AppearanceSystem.TryGetData<string>(uid, BarSignVisuals.BarSignPrototype, out var currentSign, args.Component);
|
||||||
|
|
||||||
|
if (powered
|
||||||
|
&& currentSign != null
|
||||||
|
&& _prototypeManager.Resolve<BarSignPrototype>(currentSign, out var proto))
|
||||||
|
{
|
||||||
|
SpriteSystem.LayerSetSprite((uid, args.Sprite), 0, proto.Icon);
|
||||||
|
args.Sprite?.LayerSetShader(0, "unshaded");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SpriteSystem.LayerSetRsiState((uid, args.Sprite), 0, "empty");
|
||||||
|
args.Sprite?.LayerSetShader(0, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,32 +19,27 @@ public sealed class BarSignBoundUserInterface(EntityUid owner, Enum uiKey) : Bou
|
||||||
var sign = EntMan.GetComponentOrNull<BarSignComponent>(Owner)?.Current is { } current
|
var sign = EntMan.GetComponentOrNull<BarSignComponent>(Owner)?.Current is { } current
|
||||||
? _prototype.Index(current)
|
? _prototype.Index(current)
|
||||||
: null;
|
: null;
|
||||||
var allSigns = Shared.BarSign.BarSignSystem.GetAllBarSigns(_prototype)
|
var allSigns = BarSignSystem.GetAllBarSigns(_prototype)
|
||||||
.OrderBy(p => Loc.GetString(p.Name))
|
.OrderBy(p => Loc.GetString(p.Name))
|
||||||
.ToList();
|
.ToList();
|
||||||
_menu = new(sign, allSigns);
|
_menu = new(sign, allSigns);
|
||||||
|
|
||||||
_menu.OnSignSelected += id =>
|
_menu.OnSignSelected += id =>
|
||||||
{
|
{
|
||||||
SendMessage(new SetBarSignMessage(id));
|
SendPredictedMessage(new SetBarSignMessage(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
_menu.OnClose += Close;
|
_menu.OnClose += Close;
|
||||||
_menu.OpenCentered();
|
_menu.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(ProtoId<BarSignPrototype>? sign)
|
public override void Update()
|
||||||
{
|
{
|
||||||
if (_prototype.Resolve(sign, out var signPrototype))
|
if (!EntMan.TryGetComponent<BarSignComponent>(Owner, out var signComp))
|
||||||
_menu?.UpdateState(signPrototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
if (!disposing)
|
|
||||||
return;
|
return;
|
||||||
_menu?.Dispose();
|
|
||||||
|
if (_prototype.Resolve(signComp.Current, out var signPrototype))
|
||||||
|
_menu?.UpdateState(signPrototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using Content.Shared.Bed;
|
|
||||||
|
|
||||||
namespace Content.Client.Bed;
|
|
||||||
|
|
||||||
public sealed class BedSystem : SharedBedSystem
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,6 @@ using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Content.Client.Cargo.UI;
|
namespace Content.Client.Cargo.UI;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
using Content.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
using Content.Client.RichText;
|
||||||
|
using Content.Client.UserInterface.RichText;
|
||||||
using Content.Shared.MassMedia.Systems;
|
using Content.Shared.MassMedia.Systems;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
using Robust.Client.UserInterface.RichText;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Utility; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
|
@ -17,18 +18,6 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
|
||||||
|
|
||||||
public event Action? OnNotificationSwithPressed;
|
public event Action? OnNotificationSwithPressed;
|
||||||
|
|
||||||
// DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
|
||||||
private static readonly Type[] AllowedTags =
|
|
||||||
[
|
|
||||||
typeof(BoldItalicTag),
|
|
||||||
typeof(BoldTag),
|
|
||||||
typeof(BulletTag),
|
|
||||||
typeof(ColorTag),
|
|
||||||
typeof(HeadingTag),
|
|
||||||
typeof(ItalicTag),
|
|
||||||
typeof(MonoTag),
|
|
||||||
];
|
|
||||||
|
|
||||||
public NewsReaderUiFragment()
|
public NewsReaderUiFragment()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
@ -46,7 +35,7 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
|
||||||
Author.Visible = true;
|
Author.Visible = true;
|
||||||
|
|
||||||
PageName.Text = article.Title;
|
PageName.Text = article.Title;
|
||||||
PageText.SetMessage(FormattedMessage.FromMarkupPermissive(article.Content), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
PageText.SetMessage(FormattedMessage.FromMarkupPermissive(article.Content), UserFormattableTags.BaseAllowedTags);
|
||||||
|
|
||||||
PageNum.Text = $"{targetNum}/{totalNum}";
|
PageNum.Text = $"{targetNum}/{totalNum}";
|
||||||
|
|
||||||
|
|
@ -55,8 +44,8 @@ public sealed partial class NewsReaderUiFragment : BoxContainer
|
||||||
var shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
|
var shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
|
||||||
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
|
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
|
||||||
|
|
||||||
var author = Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author ?? Loc.GetString("news-read-ui-no-author")); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
var author = Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author ?? Loc.GetString("news-read-ui-no-author"));
|
||||||
Author.SetMessage(FormattedMessage.FromMarkupPermissive(author), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
Author.SetMessage(FormattedMessage.FromMarkupPermissive(author), UserFormattableTags.BaseAllowedTags);
|
||||||
|
|
||||||
Prev.Disabled = targetNum <= 1;
|
Prev.Disabled = targetNum <= 1;
|
||||||
Next.Disabled = targetNum >= totalNum;
|
Next.Disabled = targetNum >= totalNum;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ namespace Content.Client.Chemistry.UI
|
||||||
(uint) _window.BottleDosage.Value, _window.LabelLine));
|
(uint) _window.BottleDosage.Value, _window.LabelLine));
|
||||||
_window.BufferSortButton.OnPressed += _ => SendMessage(
|
_window.BufferSortButton.OnPressed += _ => SendMessage(
|
||||||
new ChemMasterSortingTypeCycleMessage());
|
new ChemMasterSortingTypeCycleMessage());
|
||||||
|
_window.OutputBufferDraw.OnPressed += _ => SendMessage(
|
||||||
|
new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.Internal));
|
||||||
|
_window.OutputBeakerDraw.OnPressed += _ => SendMessage(
|
||||||
|
new ChemMasterOutputDrawSourceMessage(ChemMasterDrawSource.External));
|
||||||
|
|
||||||
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
|
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,13 @@
|
||||||
|
|
||||||
<!-- Packaging -->
|
<!-- Packaging -->
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<Label Text="{Loc 'chem-master-window-packaging-text'}" />
|
<Label Text="{Loc 'chem-master-output-source'}" StyleClasses="LabelSecondaryColor" Margin="0 0 5 0"/>
|
||||||
|
<Button MinSize="80 0" Name="OutputBufferDraw" Access="Public" Text="{Loc 'chem-master-output-buffer-draw'}" ToggleMode="True" StyleClasses="OpenRight" />
|
||||||
|
<Button MinSize="80 0" Name="OutputBeakerDraw" Access="Public" Text="{Loc 'chem-master-output-beaker-draw'}" ToggleMode="True" StyleClasses="OpenLeft" />
|
||||||
<Control HorizontalExpand="True"/>
|
<Control HorizontalExpand="True"/>
|
||||||
<Label Text="{Loc 'chem-master-window-container-label'}" /> <!-- DeltaV-->
|
<!-- Output Draw Source -->
|
||||||
<Label Name="BufferCurrentVolume" StyleClasses="LabelWeak" />
|
<Label Name="DrawSource"/>
|
||||||
|
<Label Name="BufferCurrentVolume" StyleClasses="LabelSecondaryColor" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|
||||||
<!-- Wrap the packaging info-->
|
<!-- Wrap the packaging info-->
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,17 @@ namespace Content.Client.Chemistry.UI
|
||||||
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
|
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
|
||||||
UpdatePanelInfo(castState);
|
UpdatePanelInfo(castState);
|
||||||
|
|
||||||
BufferCurrentVolume.Text = $" {castState.InputContainerInfo?.CurrentVolume.Int() ?? 0}u"; // DeltaV
|
switch (castState.DrawSource)
|
||||||
|
{
|
||||||
|
case ChemMasterDrawSource.Internal:
|
||||||
|
SetBufferText(castState.BufferCurrentVolume, "chem-master-output-buffer-draw");
|
||||||
|
break;
|
||||||
|
case ChemMasterDrawSource.External:
|
||||||
|
SetBufferText(castState.InputContainerInfo?.CurrentVolume, "chem-master-output-beaker-draw");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new($"Chemmaster {castState.OutputContainerInfo} draw source is not set");
|
||||||
|
}
|
||||||
|
|
||||||
InputEjectButton.Disabled = castState.InputContainerInfo is null;
|
InputEjectButton.Disabled = castState.InputContainerInfo is null;
|
||||||
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
|
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
|
||||||
|
|
@ -168,9 +178,14 @@ namespace Content.Client.Chemistry.UI
|
||||||
var holdsReagents = output?.Reagents != null;
|
var holdsReagents = output?.Reagents != null;
|
||||||
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
|
var pillNumberMax = holdsReagents ? 0 : remainingCapacity;
|
||||||
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
|
var bottleAmountMax = holdsReagents ? remainingCapacity : 0;
|
||||||
var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
|
var outputVolume = castState.DrawSource switch
|
||||||
|
{
|
||||||
|
ChemMasterDrawSource.Internal => castState.BufferCurrentVolume?.Int() ?? 0,
|
||||||
|
ChemMasterDrawSource.External => castState.InputContainerInfo?.CurrentVolume.Int() ?? 0,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
|
PillDosage.Value = (int)Math.Min(outputVolume, castState.PillDosageLimit);
|
||||||
|
|
||||||
PillTypeButtons[castState.SelectedPillType].Pressed = true;
|
PillTypeButtons[castState.SelectedPillType].Pressed = true;
|
||||||
|
|
||||||
|
|
@ -186,25 +201,35 @@ namespace Content.Client.Chemistry.UI
|
||||||
// Avoid division by zero
|
// Avoid division by zero
|
||||||
if (PillDosage.Value > 0)
|
if (PillDosage.Value > 0)
|
||||||
{
|
{
|
||||||
PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax);
|
PillNumber.Value = Math.Min(outputVolume / PillDosage.Value, pillNumberMax);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PillNumber.Value = 0;
|
PillNumber.Value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume);
|
BottleDosage.Value = Math.Min(bottleAmountMax, outputVolume);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a product label based on reagents in the buffer.
|
/// Generate a product label based on reagents in the buffer or beaker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">State data sent by the server.</param>
|
/// <param name="state">State data sent by the server.</param>
|
||||||
private string GenerateLabel(ChemMasterBoundUserInterfaceState state)
|
private string GenerateLabel(ChemMasterBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
if (state.BufferCurrentVolume == 0)
|
if (
|
||||||
|
state.BufferCurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.Internal ||
|
||||||
|
state.InputContainerInfo?.CurrentVolume == 0 && state.DrawSource == ChemMasterDrawSource.External ||
|
||||||
|
state.InputContainerInfo?.Reagents == null
|
||||||
|
)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
var reagent = state.BufferReagents.OrderBy(r => r.Quantity).First().Reagent;
|
var reagent = (state.DrawSource switch
|
||||||
|
{
|
||||||
|
ChemMasterDrawSource.Internal => state.BufferReagents,
|
||||||
|
ChemMasterDrawSource.External => state.InputContainerInfo.Reagents ?? [],
|
||||||
|
_ => throw new($"Chemmaster {state.OutputContainerInfo} draw source is not set"),
|
||||||
|
}).MinBy(r => r.Quantity)
|
||||||
|
.Reagent;
|
||||||
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
|
_prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto);
|
||||||
return proto?.LocalizedName ?? "";
|
return proto?.LocalizedName ?? "";
|
||||||
}
|
}
|
||||||
|
|
@ -230,6 +255,8 @@ namespace Content.Client.Chemistry.UI
|
||||||
_ => Loc.GetString("chem-master-window-sort-type-none")
|
_ => Loc.GetString("chem-master-window-sort-type-none")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
OutputBufferDraw.Pressed = state.DrawSource == ChemMasterDrawSource.Internal;
|
||||||
|
OutputBeakerDraw.Pressed = state.DrawSource == ChemMasterDrawSource.External;
|
||||||
|
|
||||||
if (!state.BufferReagents.Any())
|
if (!state.BufferReagents.Any())
|
||||||
{
|
{
|
||||||
|
|
@ -411,6 +438,12 @@ namespace Content.Client.Chemistry.UI
|
||||||
get => LabelLineEdit.Text;
|
get => LabelLineEdit.Text;
|
||||||
set => LabelLineEdit.Text = value;
|
set => LabelLineEdit.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetBufferText(FixedPoint2? volume, string text)
|
||||||
|
{
|
||||||
|
BufferCurrentVolume.Text = $" {volume ?? FixedPoint2.Zero}u";
|
||||||
|
DrawSource.Text = Loc.GetString(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ReagentButton : Button
|
public sealed class ReagentButton : Button
|
||||||
|
|
|
||||||
|
|
@ -2,41 +2,31 @@ using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
namespace Content.Client.Chemistry.UI
|
namespace Content.Client.Chemistry.UI;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[ViewVariables]
|
||||||
public sealed class TransferAmountBoundUserInterface : BoundUserInterface
|
private TransferAmountWindow? _window;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
{
|
{
|
||||||
private IEntityManager _entManager;
|
base.Open();
|
||||||
private EntityUid _owner;
|
_window = this.CreateWindow<TransferAmountWindow>();
|
||||||
[ViewVariables]
|
|
||||||
private TransferAmountWindow? _window;
|
|
||||||
|
|
||||||
public TransferAmountBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
if (EntMan.TryGetComponent<SolutionTransferComponent>(Owner, out var comp))
|
||||||
|
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
|
||||||
|
|
||||||
|
_window.ApplyButton.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
_owner = owner;
|
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
|
||||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
|
||||||
{
|
|
||||||
base.Open();
|
|
||||||
_window = this.CreateWindow<TransferAmountWindow>();
|
|
||||||
|
|
||||||
if (_entManager.TryGetComponent<SolutionTransferComponent>(_owner, out var comp))
|
|
||||||
_window.SetBounds(comp.MinimumTransferAmount.Int(), comp.MaximumTransferAmount.Int());
|
|
||||||
|
|
||||||
_window.ApplyButton.OnPressed += _ =>
|
|
||||||
{
|
{
|
||||||
if (int.TryParse(_window.AmountLineEdit.Text, out var i))
|
SendPredictedMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
|
||||||
{
|
_window.Close();
|
||||||
SendMessage(new TransferAmountSetValueMessage(FixedPoint2.New(i)));
|
}
|
||||||
_window.Close();
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,33 @@ using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
namespace Content.Client.Chemistry.UI
|
namespace Content.Client.Chemistry.UI;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class TransferAmountWindow : DefaultWindow
|
||||||
{
|
{
|
||||||
[GenerateTypedNameReferences]
|
private int _max = Int32.MaxValue;
|
||||||
public sealed partial class TransferAmountWindow : DefaultWindow
|
private int _min = 1;
|
||||||
|
|
||||||
|
public TransferAmountWindow()
|
||||||
{
|
{
|
||||||
private int _max = Int32.MaxValue;
|
RobustXamlLoader.Load(this);
|
||||||
private int _min = 1;
|
AmountLineEdit.OnTextChanged += OnValueChanged;
|
||||||
|
}
|
||||||
|
|
||||||
public TransferAmountWindow()
|
public void SetBounds(int min, int max)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
_min = min;
|
||||||
AmountLineEdit.OnTextChanged += OnValueChanged;
|
_max = max;
|
||||||
}
|
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
|
||||||
|
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
|
||||||
|
}
|
||||||
|
|
||||||
public void SetBounds(int min, int max)
|
private void OnValueChanged(LineEdit.LineEditEventArgs args)
|
||||||
{
|
{
|
||||||
_min = min;
|
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
|
||||||
_max = max;
|
ApplyButton.Disabled = true;
|
||||||
MinimumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-min", ("amount", _min));
|
else
|
||||||
MaximumAmount.Text = Loc.GetString("comp-solution-transfer-set-amount-max", ("amount", _max));
|
ApplyButton.Disabled = false;
|
||||||
}
|
|
||||||
|
|
||||||
private void OnValueChanged(LineEdit.LineEditEventArgs args)
|
|
||||||
{
|
|
||||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount > _max || amount < _min)
|
|
||||||
ApplyButton.Disabled = true;
|
|
||||||
else
|
|
||||||
ApplyButton.Disabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@ using Content.Shared.Weapons.Ranged.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Input;
|
using Robust.Client.Input;
|
||||||
using Robust.Client.Serialization;
|
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Graphics;
|
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.CombatMode;
|
namespace Content.Client.CombatMode;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
using Content.Client.Wall;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
|
namespace Content.Client.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows the area in which entities with <see cref="Content.Shared.Wall.WallMountComponent" /> can be interacted from.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ShowWallmountsCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||||
|
|
||||||
|
public override string Command => "showwallmounts";
|
||||||
|
|
||||||
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
var existing = _overlay.RemoveOverlay<WallmountDebugOverlay>();
|
||||||
|
if (!existing)
|
||||||
|
_overlay.AddOverlay(new WallmountDebugOverlay());
|
||||||
|
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-showwallmounts-status", ("status", !existing)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,10 @@ namespace Content.Client.Construction.UI
|
||||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||||
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
|
||||||
private readonly SpriteSystem _spriteSystem;
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
private readonly ISawmill _sawmill;
|
||||||
|
|
||||||
private readonly IConstructionMenuView _constructionView;
|
private readonly IConstructionMenuView _constructionView;
|
||||||
private readonly EntityWhitelistSystem _whitelistSystem;
|
private readonly EntityWhitelistSystem _whitelistSystem;
|
||||||
|
|
@ -90,6 +93,7 @@ namespace Content.Client.Construction.UI
|
||||||
_constructionView = new ConstructionMenu();
|
_constructionView = new ConstructionMenu();
|
||||||
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
||||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||||
|
_sawmill = _logManager.GetSawmill("construction.ui");
|
||||||
|
|
||||||
// This is required so that if we load after the system is initialized, we can bind to it immediately
|
// This is required so that if we load after the system is initialized, we can bind to it immediately
|
||||||
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
|
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
|
||||||
|
|
@ -284,7 +288,7 @@ namespace Content.Client.Construction.UI
|
||||||
|
|
||||||
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
|
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
|
||||||
{
|
{
|
||||||
Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
|
_sawmill.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
|
||||||
recipe.ID,
|
recipe.ID,
|
||||||
nameof(ConstructionPrototype));
|
nameof(ConstructionPrototype));
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
|
||||||
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
|
<EntityPrototypeView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
|
||||||
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
|
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<Control MinHeight="10"/>
|
<Control MinHeight="10"/>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Materials;
|
using Content.Client.Materials;
|
||||||
using Content.Client.Materials.UI;
|
|
||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Construction.Components;
|
using Content.Shared.Construction.Components;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Materials;
|
using Content.Shared.Materials;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
@ -24,14 +22,12 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||||
private readonly ItemSlotsSystem _itemSlots;
|
private readonly ItemSlotsSystem _itemSlots;
|
||||||
private readonly FlatpackSystem _flatpack;
|
private readonly FlatpackSystem _flatpack;
|
||||||
private readonly MaterialStorageSystem _materialStorage;
|
private readonly MaterialStorageSystem _materialStorage;
|
||||||
private readonly SpriteSystem _spriteSystem;
|
|
||||||
|
|
||||||
private EntityUid _owner;
|
private EntityUid _owner;
|
||||||
|
|
||||||
public static readonly EntProtoId NoBoardEffectId = "FlatpackerNoBoardEffect";
|
public static readonly EntProtoId NoBoardEffectId = "FlatpackerNoBoardEffect";
|
||||||
|
|
||||||
private EntityUid? _currentBoard = EntityUid.Invalid;
|
private EntityUid? _currentBoard = EntityUid.Invalid;
|
||||||
private EntityUid? _machinePreview;
|
|
||||||
|
|
||||||
public event Action? PackButtonPressed;
|
public event Action? PackButtonPressed;
|
||||||
|
|
||||||
|
|
@ -43,7 +39,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||||
_itemSlots = _entityManager.System<ItemSlotsSystem>();
|
_itemSlots = _entityManager.System<ItemSlotsSystem>();
|
||||||
_flatpack = _entityManager.System<FlatpackSystem>();
|
_flatpack = _entityManager.System<FlatpackSystem>();
|
||||||
_materialStorage = _entityManager.System<MaterialStorageSystem>();
|
_materialStorage = _entityManager.System<MaterialStorageSystem>();
|
||||||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
|
||||||
|
|
||||||
PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
|
PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
|
||||||
|
|
||||||
|
|
@ -60,76 +55,54 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
|
|
||||||
{
|
|
||||||
_machinePreview = null;
|
|
||||||
MachineSprite.SetEntity(_machinePreview);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_entityManager.TryGetComponent<FlatpackCreatorComponent>(_owner, out var flatpacker) ||
|
if (!_entityManager.TryGetComponent<FlatpackCreatorComponent>(_owner, out var flatpacker) ||
|
||||||
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
|
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MachineBoardComponent? machineBoardComp = null;
|
var flatpackerEnt = (_owner, flatpacker);
|
||||||
|
|
||||||
if (flatpacker.Packing)
|
if (flatpacker.Packing)
|
||||||
{
|
{
|
||||||
PackButton.Disabled = true;
|
PackButton.Disabled = true;
|
||||||
}
|
}
|
||||||
else if (_currentBoard != null)
|
else if (_currentBoard != null)
|
||||||
{
|
{
|
||||||
Dictionary<string, int> cost;
|
PackButton.Disabled = !_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var curCost)
|
||||||
if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
|
|| !_materialStorage.CanChangeMaterialAmount(_owner, curCost);
|
||||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
|
|
||||||
else
|
|
||||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
|
|
||||||
|
|
||||||
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentBoard == itemSlot.Item)
|
if (_currentBoard == itemSlot.Item)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_machinePreview != null)
|
|
||||||
_entityManager.DeleteEntity(_machinePreview);
|
|
||||||
|
|
||||||
_currentBoard = itemSlot.Item;
|
_currentBoard = itemSlot.Item;
|
||||||
CostHeaderLabel.Visible = _currentBoard != null;
|
CostHeaderLabel.Visible = false;
|
||||||
InsertLabel.Visible = _currentBoard == null;
|
InsertLabel.Visible = _currentBoard == null;
|
||||||
|
|
||||||
if (_currentBoard is not null)
|
if (_currentBoard is null)
|
||||||
{
|
{
|
||||||
string? prototype = null;
|
MachineSprite.SetPrototype(NoBoardEffectId);
|
||||||
Dictionary<string, int>? cost = null;
|
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
|
||||||
|
MachineNameLabel.SetMessage(string.Empty);
|
||||||
|
PackButton.Disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
|
if (_flatpack.TryGetFlatpackResultPrototype(_currentBoard.Value, out var prototype) &&
|
||||||
{
|
_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var cost))
|
||||||
prototype = machineBoardComp.Prototype;
|
{
|
||||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
|
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
|
||||||
}
|
MachineSprite.SetPrototype(prototype);
|
||||||
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
|
MachineNameLabel.SetMessage(proto.Name);
|
||||||
{
|
CostLabel.SetMarkup(GetCostString(cost));
|
||||||
prototype = computerBoard.Prototype;
|
CostHeaderLabel.Visible = true;
|
||||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prototype is not null && cost is not null)
|
|
||||||
{
|
|
||||||
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
|
|
||||||
_machinePreview = _entityManager.Spawn(proto.ID);
|
|
||||||
_spriteSystem.ForceUpdate(_machinePreview.Value);
|
|
||||||
MachineNameLabel.SetMessage(proto.Name);
|
|
||||||
CostLabel.SetMarkup(GetCostString(cost));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_machinePreview = _entityManager.Spawn(NoBoardEffectId);
|
MachineSprite.SetPrototype(NoBoardEffectId);
|
||||||
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
|
CostLabel.SetMarkup(Loc.GetString("flatpacker-ui-board-invalid-label"));
|
||||||
MachineNameLabel.SetMessage(" ");
|
MachineNameLabel.SetMessage(string.Empty);
|
||||||
PackButton.Disabled = true;
|
PackButton.Disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
MachineSprite.SetEntity(_machinePreview);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCostString(Dictionary<string, int> costs)
|
private string GetCostString(Dictionary<string, int> costs)
|
||||||
|
|
@ -151,7 +124,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||||
("amount", amountText),
|
("amount", amountText),
|
||||||
("material", Loc.GetString(matProto.Name)));
|
("material", Loc.GetString(matProto.Name)));
|
||||||
|
|
||||||
msg.AddMarkup(text);
|
msg.TryAddMarkup(text, out _);
|
||||||
|
|
||||||
if (i != orderedCosts.Length - 1)
|
if (i != orderedCosts.Length - 1)
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
|
|
@ -159,12 +132,4 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||||
|
|
||||||
return msg.ToMarkup();
|
return msg.ToMarkup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
base.Close();
|
|
||||||
|
|
||||||
_entityManager.DeleteEntity(_machinePreview);
|
|
||||||
_machinePreview = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
|
|
||||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
|
||||||
<LangVersion>12</LangVersion>
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<OutputPath>..\bin\Content.Client\</OutputPath>
|
<OutputPath>..\bin\Content.Client\</OutputPath>
|
||||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||||
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
|
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<Configurations>Debug;Release;Tools;DebugOpt</Configurations>
|
|
||||||
<Platforms>AnyCPU</Platforms>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<Import Project="../MSBuild/Content.props" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Nett" />
|
<PackageReference Include="Nett" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp" />
|
||||||
|
<PackageReference Include="Pidgin" />
|
||||||
|
<PackageReference Include="Robust.Shared.AuthLib" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Lidgren.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Client.props" />
|
||||||
|
<Import Project="..\RobustToolbox\Imports\Shared.props" />
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
|
|
||||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
|
||||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
using Content.Shared.DeviceLinking.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.DeviceLinking.Systems;
|
||||||
|
|
||||||
|
public sealed class RandomGateSystem : SharedRandomGateSystem;
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
using Content.Shared.DeviceLinking;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.DeviceLinking.UI;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class RandomGateBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private RandomGateSetupWindow? _window;
|
||||||
|
|
||||||
|
public RandomGateBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
_window = this.CreateWindow<RandomGateSetupWindow>();
|
||||||
|
_window.OnApplyPressed += OnProbabilityChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnProbabilityChanged(string value)
|
||||||
|
{
|
||||||
|
if (!float.TryParse(value, out var probability))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SendPredictedMessage(new RandomGateProbabilityChangedMessage(probability));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
if (state is not RandomGateBoundUserInterfaceState castState || _window == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_window.SetProbability(castState.SuccessProbability * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
Title="{Loc 'random-gate-menu-setup'}"
|
||||||
|
MinSize="260 115">
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="5" SeparationOverride="10">
|
||||||
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||||
|
<Label Text="{Loc 'random-gate-menu-settings'}" VerticalAlignment="Center" />
|
||||||
|
<Control HorizontalExpand="True" />
|
||||||
|
<LineEdit Name="ProbabilityInput" MinSize="70 0" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||||
|
<Control HorizontalExpand="True" />
|
||||||
|
<Button Name="ApplyButton"
|
||||||
|
Text="{Loc 'random-gate-menu-apply'}"
|
||||||
|
HorizontalAlignment="Right" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.DeviceLinking.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Window for setting up the random gate probability.
|
||||||
|
/// </summary>
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class RandomGateSetupWindow : FancyWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the "Apply" button is pressed.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<string>? OnApplyPressed;
|
||||||
|
|
||||||
|
public RandomGateSetupWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
ApplyButton.OnPressed += _ => OnApplyPressed?.Invoke(ProbabilityInput.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProbability(float probability)
|
||||||
|
{
|
||||||
|
ProbabilityInput.Text = probability.ToString("0.00");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ public sealed class GuidebookRichPrototypeLink : Control, IPrototypeLinkControl
|
||||||
public void SetMessage(FormattedMessage message)
|
public void SetMessage(FormattedMessage message)
|
||||||
{
|
{
|
||||||
_message = message;
|
_message = message;
|
||||||
_richTextLabel.SetMessage(_message);
|
_richTextLabel.SetMessage(_message, tagsAllowed: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPrototype? LinkedPrototype { get; set; }
|
public IPrototype? LinkedPrototype { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ public sealed partial class DocumentParsingManager
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Pop();
|
msg.Pop();
|
||||||
rt.SetMessage(msg);
|
rt.SetMessage(msg, tagsAllowed: null);
|
||||||
return rt;
|
return rt;
|
||||||
},
|
},
|
||||||
TextParser)
|
TextParser)
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||||
base.Open();
|
base.Open();
|
||||||
|
|
||||||
_window = this.CreateWindow<HealthAnalyzerWindow>();
|
_window = this.CreateWindow<HealthAnalyzerWindow>();
|
||||||
_window.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
|
_window.HealthAnalyzer.OnBodyPartSelected += SendBodyPartMessage; // Shitmed Change
|
||||||
_window.OnTriageStatusChanged += SendTriageStatusMessage; // DeltaV - Medical Records
|
_window.HealthAnalyzer.OnTriageStatusChanged += SendTriageStatusMessage; // DeltaV - Medical Records
|
||||||
_window.OnClaimPatient += SendTriageClaimMessage; // DeltaV - Medical Records
|
_window.HealthAnalyzer.OnClaimPatient += SendTriageClaimMessage; // DeltaV - Medical Records
|
||||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_window != null)
|
if (_window != null)
|
||||||
_window.OnBodyPartSelected -= SendBodyPartMessage;
|
_window.HealthAnalyzer.OnBodyPartSelected -= SendBodyPartMessage;
|
||||||
|
|
||||||
_window?.Dispose();
|
_window?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
<BoxContainer
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
VerticalExpand="True"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<Label
|
||||||
|
Name="NoPatientDataText"
|
||||||
|
Text="{Loc health-analyzer-window-no-patient-data-text}" />
|
||||||
|
|
||||||
|
<!-- Shitmed Change Start -->
|
||||||
|
<Button Name="ReturnButton"
|
||||||
|
Text="{Loc 'health-analyzer-window-return-button-text'}"
|
||||||
|
Margin="0 0 0 10"
|
||||||
|
HorizontalExpand="False"/>
|
||||||
|
<!-- Shitmed Change End -->
|
||||||
|
|
||||||
|
<BoxContainer
|
||||||
|
Name="PatientDataContainer"
|
||||||
|
Margin="0 0 0 5"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
|
||||||
|
<PanelContainer>
|
||||||
|
<!-- Shitmed Change Start -->
|
||||||
|
<SpriteView OverrideDirection="South" Name="SpriteView" Access="Public" SetSize="96 96" />
|
||||||
|
<PanelContainer
|
||||||
|
Name="PartView"
|
||||||
|
SetSize="57 96"
|
||||||
|
Margin="18 0 0 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<PanelContainer
|
||||||
|
SetSize="15 33"
|
||||||
|
Margin="4 0 0 0"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextureButton
|
||||||
|
Name="RightArmButton"
|
||||||
|
MinSize="15 25"
|
||||||
|
StyleClasses="TargetDollButtonRightArm"
|
||||||
|
VerticalAlignment="Top">
|
||||||
|
</TextureButton>
|
||||||
|
<TextureButton
|
||||||
|
Name="RightHandButton"
|
||||||
|
MinSize="15 15"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
StyleClasses="TargetDollButtonRightHand">
|
||||||
|
</TextureButton>
|
||||||
|
</PanelContainer>
|
||||||
|
<PanelContainer
|
||||||
|
SetSize="43 75"
|
||||||
|
Margin="0 0 0 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<TextureButton
|
||||||
|
Name="HeadButton"
|
||||||
|
MinSize="28 23"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
StyleClasses="TargetDollButtonHead">
|
||||||
|
<!--<PanelContainer
|
||||||
|
SetSize="14 15"
|
||||||
|
Margin="-1 9 0 0"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<TextureButton
|
||||||
|
Name="EyesButton"
|
||||||
|
MinSize="14 9"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
StyleClasses="TargetDollButtonEyes">
|
||||||
|
<TextureRect
|
||||||
|
TexturePath="/Textures/Interface/Targeting/Doll/eyes.png"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
SetSize="14 9"/>
|
||||||
|
</TextureButton>
|
||||||
|
<TextureButton
|
||||||
|
Name="MouthButton"
|
||||||
|
SetSize="8 6"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
StyleClasses="TargetDollButtonMouth">
|
||||||
|
<TextureRect
|
||||||
|
TexturePath="/Textures/Interface/Targeting/Doll/mouth.png"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
SetSize="8 6"/>
|
||||||
|
</TextureButton>
|
||||||
|
</PanelContainer>-->
|
||||||
|
</TextureButton>
|
||||||
|
<TextureButton
|
||||||
|
Name="ChestButton"
|
||||||
|
SetSize="28 30"
|
||||||
|
Margin="0 18 0 0"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
StyleClasses="TargetDollButtonChest">
|
||||||
|
</TextureButton>
|
||||||
|
<PanelContainer
|
||||||
|
MinSize="38 35"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<TextureButton
|
||||||
|
Name="GroinButton"
|
||||||
|
MinSize="28 15"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
StyleClasses="TargetDollButtonGroin">
|
||||||
|
</TextureButton>
|
||||||
|
<PanelContainer
|
||||||
|
MinSize="20 30"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<TextureButton
|
||||||
|
Name="LeftLegButton"
|
||||||
|
MinSize="15 28"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
StyleClasses="TargetDollButtonLeftLeg">
|
||||||
|
</TextureButton>
|
||||||
|
<TextureButton
|
||||||
|
Name="LeftFootButton"
|
||||||
|
MinSize="20 10"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
StyleClasses="TargetDollButtonLeftFoot">
|
||||||
|
</TextureButton>
|
||||||
|
</PanelContainer>
|
||||||
|
<PanelContainer
|
||||||
|
MinSize="20 30"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextureButton
|
||||||
|
Name="RightLegButton"
|
||||||
|
MinSize="15 28"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
StyleClasses="TargetDollButtonRightLeg">
|
||||||
|
</TextureButton>
|
||||||
|
<TextureButton
|
||||||
|
Name="RightFootButton"
|
||||||
|
MinSize="20 10"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
StyleClasses="TargetDollButtonRightFoot">
|
||||||
|
</TextureButton>
|
||||||
|
</PanelContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
<PanelContainer
|
||||||
|
SetSize="15 33"
|
||||||
|
Margin="0 0 4 0"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<TextureButton
|
||||||
|
Name="LeftArmButton"
|
||||||
|
MinSize="15 25"
|
||||||
|
StyleClasses="TargetDollButtonLeftArm"
|
||||||
|
VerticalAlignment="Top">
|
||||||
|
</TextureButton>
|
||||||
|
<TextureButton
|
||||||
|
Name="LeftHandButton"
|
||||||
|
MinSize="15 15"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
StyleClasses="TargetDollButtonLeftHand">
|
||||||
|
</TextureButton>
|
||||||
|
</PanelContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
<!-- Shitmed Change Start -->
|
||||||
|
<TextureRect Name="NoDataTex" Access="Public" SetSize="96 96" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/> <!-- DeltaV - Size changed to 96 96 to be consistent with shitmed -->
|
||||||
|
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
|
||||||
|
<RichTextLabel Name="NameLabel" SetWidth="150" />
|
||||||
|
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
|
||||||
|
<Label Name="PartNameLabel"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
|
||||||
|
VerticalAlignment="Top" Name="ScanModeLabel"
|
||||||
|
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<PanelContainer StyleClasses="LowDivider" />
|
||||||
|
|
||||||
|
<!-- Begin DeltaV - Medical Records -->
|
||||||
|
<BoxContainer Name="TriageControls" Orientation="Vertical">
|
||||||
|
<BoxContainer Name="StatusBox"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0 8 0 0">
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<Button Name="ClaimButton"
|
||||||
|
Text="{Loc 'health-analyzer-window-triage-claim'}"
|
||||||
|
MinSize="60 0" />
|
||||||
|
|
||||||
|
<PanelContainer StyleClasses="LowDivider" Margin="0 8 0 0"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<!-- End DeltaV - Medical Records -->
|
||||||
|
|
||||||
|
<GridContainer Margin="0 5 0 0" Columns="2">
|
||||||
|
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
|
||||||
|
<Label Name="StatusLabel" />
|
||||||
|
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
|
||||||
|
<Label Name="TemperatureLabel" />
|
||||||
|
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
|
||||||
|
<Label Name="BloodLabel" />
|
||||||
|
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
|
||||||
|
<Label Name="DamageLabel" />
|
||||||
|
</GridContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
|
||||||
|
|
||||||
|
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<PanelContainer StyleClasses="LowDivider" />
|
||||||
|
|
||||||
|
<BoxContainer
|
||||||
|
Name="GroupsContainer"
|
||||||
|
Margin="0 5 0 5"
|
||||||
|
Orientation="Vertical">
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
|
@ -0,0 +1,441 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
|
using Content.Shared.Damage.Prototypes;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Humanoid;
|
||||||
|
using Content.Shared.Humanoid.Prototypes;
|
||||||
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.MedicalScanner;
|
||||||
|
using Content.Shared.Mobs;
|
||||||
|
using Content.Shared.Mobs.Components;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
namespace Content.Client.HealthAnalyzer.UI;
|
||||||
|
|
||||||
|
using Content.Client._DV.Traits.Assorted; // DeltaV
|
||||||
|
using Content.Shared._DV.Traits.Assorted; // DeltaV
|
||||||
|
using Content.Shared._DV.Medical; // DeltaV - Uncloneable
|
||||||
|
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
|
||||||
|
using Content.Shared._Shitmed.Targeting; // Shitmed
|
||||||
|
|
||||||
|
// Health analyzer UI is split from its window because it's used by both the
|
||||||
|
// health analyzer item and the cryo pod UI.
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class HealthAnalyzerControl : BoxContainer
|
||||||
|
{
|
||||||
|
private readonly IEntityManager _entityManager;
|
||||||
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
private readonly IPrototypeManager _prototypes;
|
||||||
|
private readonly IResourceCache _cache;
|
||||||
|
|
||||||
|
private readonly UnborgableSystem _unborgable; // DeltaV
|
||||||
|
private readonly RedshirtSystem _redshirt; // DeltaV
|
||||||
|
private readonly UncloneableSystem _uncloneable; // DeltaV
|
||||||
|
|
||||||
|
// Shitmed Change Start
|
||||||
|
public event Action<TargetBodyPart?, EntityUid>? OnBodyPartSelected;
|
||||||
|
private EntityUid _spriteViewEntity;
|
||||||
|
|
||||||
|
private readonly EntProtoId _bodyView = "AlertSpriteView";
|
||||||
|
|
||||||
|
private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
|
||||||
|
private EntityUid? _target;
|
||||||
|
// Shitmed Change End
|
||||||
|
|
||||||
|
// Begin DeltaV - Medical Records
|
||||||
|
private readonly ButtonGroup _triageStatusGroup = new();
|
||||||
|
private readonly Dictionary<TriageStatus, Button> _triageControls = new();
|
||||||
|
|
||||||
|
public event Action<TriageStatus>? OnTriageStatusChanged;
|
||||||
|
public event Action? OnClaimPatient;
|
||||||
|
// End DeltaV - Medical Records
|
||||||
|
|
||||||
|
public HealthAnalyzerControl()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
var dependencies = IoCManager.Instance!;
|
||||||
|
_entityManager = dependencies.Resolve<IEntityManager>();
|
||||||
|
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||||||
|
_prototypes = dependencies.Resolve<IPrototypeManager>();
|
||||||
|
_cache = dependencies.Resolve<IResourceCache>();
|
||||||
|
|
||||||
|
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
|
||||||
|
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
|
||||||
|
_uncloneable = _entityManager.System<UncloneableSystem>(); // DeltaV
|
||||||
|
// Shitmed Change Start
|
||||||
|
_bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
|
||||||
|
{
|
||||||
|
{ TargetBodyPart.Head, HeadButton },
|
||||||
|
{ TargetBodyPart.Torso, ChestButton },
|
||||||
|
{ TargetBodyPart.Groin, GroinButton },
|
||||||
|
{ TargetBodyPart.LeftArm, LeftArmButton },
|
||||||
|
{ TargetBodyPart.LeftHand, LeftHandButton },
|
||||||
|
{ TargetBodyPart.RightArm, RightArmButton },
|
||||||
|
{ TargetBodyPart.RightHand, RightHandButton },
|
||||||
|
{ TargetBodyPart.LeftLeg, LeftLegButton },
|
||||||
|
{ TargetBodyPart.LeftFoot, LeftFootButton },
|
||||||
|
{ TargetBodyPart.RightLeg, RightLegButton },
|
||||||
|
{ TargetBodyPart.RightFoot, RightFootButton },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var bodyPartButton in _bodyPartControls)
|
||||||
|
{
|
||||||
|
bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
|
||||||
|
bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key, bodyPartButton.Value);
|
||||||
|
}
|
||||||
|
ReturnButton.OnPressed += _ => ResetBodyPart();
|
||||||
|
// Shitmed Change End
|
||||||
|
|
||||||
|
// Begin DeltaV - Medical Records
|
||||||
|
foreach (var item in Enum.GetValues<TriageStatus>())
|
||||||
|
{
|
||||||
|
var btn = new Button();
|
||||||
|
var ftlKey = item.ToString();
|
||||||
|
btn.Group = _triageStatusGroup;
|
||||||
|
btn.Text = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}");
|
||||||
|
btn.ToolTip = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}.ToolTip");
|
||||||
|
btn.OnPressed += _ => OnTriageStatusChanged?.Invoke(item);
|
||||||
|
btn.AddStyleClass("ButtonSquare");
|
||||||
|
StatusBox.AddChild(btn);
|
||||||
|
_triageControls[item] = btn;
|
||||||
|
}
|
||||||
|
StatusBox.Children.First().AddStyleClass("OpenRight");
|
||||||
|
StatusBox.Children.First().RemoveStyleClass("ButtonSquare");
|
||||||
|
StatusBox.Children.Last().AddStyleClass("OpenLeft");
|
||||||
|
StatusBox.Children.Last().RemoveStyleClass("ButtonSquare");
|
||||||
|
ClaimButton.OnPressed += _ => OnClaimPatient?.Invoke();
|
||||||
|
// End DeltaV - Medical Records
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(HealthAnalyzerUiState state)
|
||||||
|
{
|
||||||
|
var target = _entityManager.GetEntity(state.TargetEntity);
|
||||||
|
// Begin Shitmed
|
||||||
|
_target = target;
|
||||||
|
EntityUid? part = state.Part != null ? _entityManager.GetEntity(state.Part.Value) : null;
|
||||||
|
var isPart = part != null;
|
||||||
|
// End Shitmed
|
||||||
|
|
||||||
|
if (target == null
|
||||||
|
|| !_entityManager.TryGetComponent<DamageableComponent>(isPart ? part : target, out var damageable)) // Shitmed
|
||||||
|
{
|
||||||
|
NoPatientDataText.Visible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin Shitmed
|
||||||
|
ReturnButton.Visible = isPart;
|
||||||
|
PartNameLabel.Visible = isPart;
|
||||||
|
|
||||||
|
if (part != null)
|
||||||
|
PartNameLabel.Text = _entityManager.HasComponent<MetaDataComponent>(part)
|
||||||
|
? Identity.Name(part.Value, _entityManager)
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
||||||
|
// End Shitmed
|
||||||
|
|
||||||
|
NoPatientDataText.Visible = false;
|
||||||
|
|
||||||
|
// Scan Mode
|
||||||
|
|
||||||
|
ScanModeLabel.Text = state.ScanMode.HasValue
|
||||||
|
? state.ScanMode.Value
|
||||||
|
? Loc.GetString("health-analyzer-window-scan-mode-active")
|
||||||
|
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-text");
|
||||||
|
|
||||||
|
ScanModeLabel.FontColorOverride = state.ScanMode.HasValue && state.ScanMode.Value ? Color.Green : Color.Red;
|
||||||
|
|
||||||
|
// Patient Information
|
||||||
|
|
||||||
|
SpriteView.SetEntity(SetupIcon(state.Body) ?? target.Value); // Shitmed Change
|
||||||
|
SpriteView.Visible = state.ScanMode.HasValue && state.ScanMode.Value;
|
||||||
|
PartView.Visible = SpriteView.Visible; // Shitmed Change
|
||||||
|
NoDataTex.Visible = !SpriteView.Visible;
|
||||||
|
|
||||||
|
var name = new FormattedMessage();
|
||||||
|
name.PushColor(Color.White);
|
||||||
|
name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
|
||||||
|
? Identity.Name(target.Value, _entityManager)
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
|
||||||
|
NameLabel.SetMessage(name);
|
||||||
|
|
||||||
|
SpeciesLabel.Text =
|
||||||
|
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
|
||||||
|
out var humanoidAppearanceComponent)
|
||||||
|
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
|
||||||
|
|
||||||
|
// Basic Diagnostic
|
||||||
|
|
||||||
|
TemperatureLabel.Text = !float.IsNaN(state.Temperature)
|
||||||
|
? $"{state.Temperature - Atmospherics.T0C:F1} °C ({state.Temperature:F1} K)"
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
||||||
|
|
||||||
|
BloodLabel.Text = !float.IsNaN(state.BloodLevel)
|
||||||
|
? $"{state.BloodLevel * 100:F1} %"
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
||||||
|
|
||||||
|
StatusLabel.Text =
|
||||||
|
_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent)
|
||||||
|
? GetStatus(mobStateComponent.CurrentState)
|
||||||
|
: Loc.GetString("health-analyzer-window-entity-unknown-text");
|
||||||
|
|
||||||
|
// Total Damage
|
||||||
|
|
||||||
|
DamageLabel.Text = damageable.TotalDamage.ToString();
|
||||||
|
|
||||||
|
// Alerts
|
||||||
|
// DeltaV traits - This is going to be horrid if we just keep adding things like this.
|
||||||
|
var unborgable = _unborgable.IsUnborgable(target.Value);
|
||||||
|
var redshirt = _redshirt.IsRedshirt(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Redshirt
|
||||||
|
var uncloneable = _uncloneable.IsUncloneable(target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Unclonable
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
|
var showAlerts = state.Unrevivable == true || state.Bleeding == true;
|
||||||
|
|
||||||
|
AlertsDivider.Visible = showAlerts;
|
||||||
|
AlertsContainer.Visible = showAlerts;
|
||||||
|
|
||||||
|
if (showAlerts)
|
||||||
|
AlertsContainer.RemoveAllChildren();
|
||||||
|
|
||||||
|
if (state.Unrevivable == true)
|
||||||
|
AlertsContainer.AddChild(new RichTextLabel
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
|
||||||
|
Margin = new Thickness(0, 4),
|
||||||
|
MaxWidth = 300
|
||||||
|
});
|
||||||
|
|
||||||
|
if (state.Bleeding == true)
|
||||||
|
AlertsContainer.AddChild(new RichTextLabel
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
|
||||||
|
Margin = new Thickness(0, 4),
|
||||||
|
MaxWidth = 300
|
||||||
|
});
|
||||||
|
|
||||||
|
if (unborgable) // DeltaV
|
||||||
|
AlertsContainer.AddChild(new RichTextLabel
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("health-analyzer-window-entity-unborgable-text"),
|
||||||
|
Margin = new Thickness(0, 4),
|
||||||
|
MaxWidth = 300
|
||||||
|
});
|
||||||
|
|
||||||
|
if (redshirt) // DeltaV
|
||||||
|
AlertsContainer.AddChild(new RichTextLabel
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("health-analyzer-window-entity-redshirt-text"),
|
||||||
|
Margin = new Thickness(0, 4),
|
||||||
|
MaxWidth = 300
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uncloneable) // DeltaV - Uncloneable
|
||||||
|
AlertsContainer.AddChild(new RichTextLabel
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("health-analyzer-window-entity-uncloneable-text"),
|
||||||
|
Margin = new Thickness(0, 4),
|
||||||
|
MaxWidth = 300
|
||||||
|
});
|
||||||
|
|
||||||
|
// Damage Groups
|
||||||
|
|
||||||
|
var damageSortedGroups =
|
||||||
|
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
|
||||||
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
|
||||||
|
|
||||||
|
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
|
||||||
|
|
||||||
|
// Begin DeltaV - Medical Records
|
||||||
|
if (state.MedicalRecord is not { } records)
|
||||||
|
{
|
||||||
|
TriageControls.Visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriageControls.Visible = true;
|
||||||
|
_triageControls[records.Status].Pressed = true;
|
||||||
|
|
||||||
|
// Update claim button based on claimed status
|
||||||
|
if (records.ClaimedName != null)
|
||||||
|
{
|
||||||
|
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-unclaim", ("claimedBy", records.ClaimedName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-claim");
|
||||||
|
}
|
||||||
|
// End DeltaV - Medical Records
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetStatus(MobState mobState)
|
||||||
|
{
|
||||||
|
return mobState switch
|
||||||
|
{
|
||||||
|
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
|
||||||
|
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
|
||||||
|
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
|
||||||
|
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDiagnosticGroups(
|
||||||
|
Dictionary<string, FixedPoint2> groups,
|
||||||
|
IReadOnlyDictionary<string, FixedPoint2> damageDict)
|
||||||
|
{
|
||||||
|
GroupsContainer.RemoveAllChildren();
|
||||||
|
|
||||||
|
foreach (var (damageGroupId, damageAmount) in groups)
|
||||||
|
{
|
||||||
|
if (damageAmount == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var groupTitleText = $"{Loc.GetString(
|
||||||
|
"health-analyzer-window-damage-group-text",
|
||||||
|
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
|
||||||
|
("amount", damageAmount)
|
||||||
|
)}";
|
||||||
|
|
||||||
|
var groupContainer = new BoxContainer
|
||||||
|
{
|
||||||
|
Align = AlignMode.Begin,
|
||||||
|
Orientation = LayoutOrientation.Vertical,
|
||||||
|
};
|
||||||
|
|
||||||
|
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
|
||||||
|
|
||||||
|
GroupsContainer.AddChild(groupContainer);
|
||||||
|
|
||||||
|
// Show the damage for each type in that group.
|
||||||
|
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
|
||||||
|
|
||||||
|
foreach (var type in group.DamageTypes)
|
||||||
|
{
|
||||||
|
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var damageString = Loc.GetString(
|
||||||
|
"health-analyzer-window-damage-type-text",
|
||||||
|
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
|
||||||
|
("amount", typeAmount)
|
||||||
|
);
|
||||||
|
|
||||||
|
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shitmed Change Start
|
||||||
|
public void SetActiveBodyPart(TargetBodyPart part, TextureButton button)
|
||||||
|
{
|
||||||
|
if (_target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Bit of the ole shitcode until we have Groins in the prototypes.
|
||||||
|
OnBodyPartSelected?.Invoke(part == TargetBodyPart.Groin ? TargetBodyPart.Torso : part, _target.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetBodyPart()
|
||||||
|
{
|
||||||
|
if (_target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnBodyPartSelected?.Invoke(null, _target.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetActiveButtons(bool isHumanoid)
|
||||||
|
{
|
||||||
|
foreach (var button in _bodyPartControls)
|
||||||
|
button.Value.Visible = isHumanoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up the Body Doll using Alert Entity to use in Health Analyzer.
|
||||||
|
/// </summary>
|
||||||
|
private EntityUid? SetupIcon(Dictionary<TargetBodyPart, TargetIntegrity>? body)
|
||||||
|
{
|
||||||
|
if (body is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!_entityManager.Deleted(_spriteViewEntity))
|
||||||
|
_entityManager.QueueDeleteEntity(_spriteViewEntity);
|
||||||
|
|
||||||
|
_spriteViewEntity = _entityManager.Spawn(_bodyView);
|
||||||
|
|
||||||
|
if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int layer = 0;
|
||||||
|
foreach (var (bodyPart, integrity) in body)
|
||||||
|
{
|
||||||
|
// TODO: PartStatusUIController and make it use layers instead of TextureRects when EE refactors alerts.
|
||||||
|
string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
|
||||||
|
int enumValue = (int) integrity;
|
||||||
|
var rsi = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
|
||||||
|
// Shitcode with love from Russia :)
|
||||||
|
if (!_spriteSystem.TryGetLayer(_spriteViewEntity, layer, out _, false))
|
||||||
|
_spriteSystem.AddTextureLayer(_spriteViewEntity, _spriteSystem.Frame0(rsi));
|
||||||
|
else
|
||||||
|
_spriteSystem.LayerSetTexture(_spriteViewEntity, layer, _spriteSystem.Frame0(rsi));
|
||||||
|
_spriteSystem.LayerSetScale(_spriteViewEntity, layer, new Vector2(3f, 3f));
|
||||||
|
layer++;
|
||||||
|
}
|
||||||
|
return _spriteViewEntity;
|
||||||
|
}
|
||||||
|
// Shitmed Change End
|
||||||
|
|
||||||
|
private Texture GetTexture(string texture)
|
||||||
|
{
|
||||||
|
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
|
||||||
|
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
|
||||||
|
|
||||||
|
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
|
||||||
|
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
|
||||||
|
{
|
||||||
|
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _spriteSystem.Frame0(rsiSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label CreateDiagnosticItemLabel(string text)
|
||||||
|
{
|
||||||
|
return new Label
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
|
||||||
|
{
|
||||||
|
var rootContainer = new BoxContainer
|
||||||
|
{
|
||||||
|
Margin = new Thickness(0, 6, 0, 0),
|
||||||
|
VerticalAlignment = VAlignment.Bottom,
|
||||||
|
Orientation = LayoutOrientation.Horizontal,
|
||||||
|
};
|
||||||
|
|
||||||
|
rootContainer.AddChild(new TextureRect
|
||||||
|
{
|
||||||
|
SetSize = new Vector2(30, 30),
|
||||||
|
Texture = GetTexture(id.ToLower())
|
||||||
|
});
|
||||||
|
|
||||||
|
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
|
||||||
|
|
||||||
|
return rootContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,253 +1,16 @@
|
||||||
<controls:FancyWindow
|
<controls:FancyWindow
|
||||||
xmlns="https://spacestation14.io"
|
xmlns="https://spacestation14.io"
|
||||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client.HealthAnalyzer.UI"
|
||||||
MaxHeight="525"
|
MaxHeight="525"
|
||||||
MinWidth="350"> <!-- Shitmed Change -->
|
MinWidth="350"> <!-- Shitmed Change -->
|
||||||
<ScrollContainer
|
<ScrollContainer
|
||||||
Margin="5 5 5 5"
|
Margin="5 5 5 5"
|
||||||
ReturnMeasure="True"
|
ReturnMeasure="True"
|
||||||
VerticalExpand="True">
|
VerticalExpand="True">
|
||||||
<BoxContainer
|
|
||||||
Name="RootContainer"
|
|
||||||
VerticalExpand="True"
|
|
||||||
Orientation="Vertical">
|
|
||||||
<Label
|
|
||||||
Name="NoPatientDataText"
|
|
||||||
Text="{Loc health-analyzer-window-no-patient-data-text}"/>
|
|
||||||
<!-- Shitmed Change Start -->
|
|
||||||
<Button Name="ReturnButton"
|
|
||||||
Text="{Loc 'health-analyzer-window-return-button-text'}"
|
|
||||||
Margin="0 0 0 10"
|
|
||||||
HorizontalExpand="False"/>
|
|
||||||
<!-- Shitmed Change End -->
|
|
||||||
<BoxContainer
|
|
||||||
Name="PatientDataContainer"
|
|
||||||
Margin="0 0 0 5"
|
|
||||||
Orientation="Vertical">
|
|
||||||
<!-- Shitmed Change Start -->
|
|
||||||
<BoxContainer Orientation="Horizontal"
|
|
||||||
Margin="0 0 0 5">
|
|
||||||
<PanelContainer>
|
|
||||||
<SpriteView OverrideDirection="South"
|
|
||||||
Name="SpriteView"
|
|
||||||
Access="Public"
|
|
||||||
SetSize="96 96"/>
|
|
||||||
<PanelContainer
|
|
||||||
Name="PartView"
|
|
||||||
SetSize="57 96"
|
|
||||||
Margin="18 0 0 0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Left">
|
|
||||||
<PanelContainer
|
|
||||||
SetSize="15 33"
|
|
||||||
Margin="4 0 0 0"
|
|
||||||
HorizontalAlignment="Left">
|
|
||||||
<TextureButton
|
|
||||||
Name="RightArmButton"
|
|
||||||
MinSize="15 25"
|
|
||||||
StyleClasses="TargetDollButtonRightArm"
|
|
||||||
VerticalAlignment="Top">
|
|
||||||
</TextureButton>
|
|
||||||
<TextureButton
|
|
||||||
Name="RightHandButton"
|
|
||||||
MinSize="15 15"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
StyleClasses="TargetDollButtonRightHand">
|
|
||||||
</TextureButton>
|
|
||||||
</PanelContainer>
|
|
||||||
<PanelContainer
|
|
||||||
SetSize="43 75"
|
|
||||||
Margin="0 0 0 0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Center">
|
|
||||||
<TextureButton
|
|
||||||
Name="HeadButton"
|
|
||||||
MinSize="28 23"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
StyleClasses="TargetDollButtonHead">
|
|
||||||
<!--<PanelContainer
|
|
||||||
SetSize="15 15"
|
|
||||||
Margin="0 9 0 0"
|
|
||||||
HorizontalAlignment="Center">
|
|
||||||
<TextureButton
|
|
||||||
Name="EyesButton"
|
|
||||||
MinSize="15 9"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
StyleClasses="TargetDollButtonEyes">
|
|
||||||
<TextureRect
|
|
||||||
TexturePath="/Textures/Interface/Targeting/Doll/eyes.png"
|
|
||||||
Stretch="KeepAspectCentered"
|
|
||||||
SetSize="15 9"/>
|
|
||||||
</TextureButton>
|
|
||||||
<TextureButton
|
|
||||||
Name="MouthButton"
|
|
||||||
SetSize="9 6"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
StyleClasses="TargetDollButtonMouth">
|
|
||||||
<TextureRect
|
|
||||||
TexturePath="/Textures/Interface/Targeting/Doll/mouth.png"
|
|
||||||
Stretch="KeepAspectCentered"
|
|
||||||
SetSize="9 6"/>
|
|
||||||
</TextureButton>
|
|
||||||
</PanelContainer>-->
|
|
||||||
</TextureButton>
|
|
||||||
<TextureButton
|
|
||||||
Name="ChestButton"
|
|
||||||
SetSize="28 30"
|
|
||||||
Margin="0 18 0 0"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
StyleClasses="TargetDollButtonChest">
|
|
||||||
</TextureButton>
|
|
||||||
<PanelContainer
|
|
||||||
MinSize="38 35"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
HorizontalAlignment="Center">
|
|
||||||
<TextureButton
|
|
||||||
Name="GroinButton"
|
|
||||||
MinSize="28 15"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
StyleClasses="TargetDollButtonGroin">
|
|
||||||
</TextureButton>
|
|
||||||
<PanelContainer
|
|
||||||
MinSize="20 30"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
HorizontalAlignment="Right">
|
|
||||||
<TextureButton
|
|
||||||
Name="LeftLegButton"
|
|
||||||
MinSize="15 28"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
StyleClasses="TargetDollButtonLeftLeg">
|
|
||||||
</TextureButton>
|
|
||||||
<TextureButton
|
|
||||||
Name="LeftFootButton"
|
|
||||||
MinSize="20 10"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
StyleClasses="TargetDollButtonLeftFoot">
|
|
||||||
</TextureButton>
|
|
||||||
</PanelContainer>
|
|
||||||
<PanelContainer
|
|
||||||
MinSize="20 30"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
HorizontalAlignment="Left">
|
|
||||||
<TextureButton
|
|
||||||
Name="RightLegButton"
|
|
||||||
MinSize="15 28"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
StyleClasses="TargetDollButtonRightLeg">
|
|
||||||
</TextureButton>
|
|
||||||
<TextureButton
|
|
||||||
Name="RightFootButton"
|
|
||||||
MinSize="20 10"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
StyleClasses="TargetDollButtonRightFoot">
|
|
||||||
</TextureButton>
|
|
||||||
</PanelContainer>
|
|
||||||
</PanelContainer>
|
|
||||||
</PanelContainer>
|
|
||||||
<PanelContainer
|
|
||||||
SetSize="15 33"
|
|
||||||
Margin="0 0 4 0"
|
|
||||||
HorizontalAlignment="Right">
|
|
||||||
<TextureButton
|
|
||||||
Name="LeftArmButton"
|
|
||||||
MinSize="15 25"
|
|
||||||
StyleClasses="TargetDollButtonLeftArm"
|
|
||||||
VerticalAlignment="Top">
|
|
||||||
</TextureButton>
|
|
||||||
<TextureButton
|
|
||||||
Name="LeftHandButton"
|
|
||||||
MinSize="15 15"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
StyleClasses="TargetDollButtonLeftHand">
|
|
||||||
</TextureButton>
|
|
||||||
</PanelContainer>
|
|
||||||
</PanelContainer>
|
|
||||||
</PanelContainer>
|
|
||||||
<TextureRect Name="NoDataTex"
|
|
||||||
Access="Public"
|
|
||||||
SetSize="96 96"
|
|
||||||
Visible="false"
|
|
||||||
Stretch="KeepAspectCentered"
|
|
||||||
TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/> <!-- DeltaV - Size changed to 96 96 to be consistent with shitmed -->
|
|
||||||
<BoxContainer Margin="5 0 0 0"
|
|
||||||
Orientation="Vertical"
|
|
||||||
VerticalAlignment="Top">
|
|
||||||
<RichTextLabel Name="NameLabel"
|
|
||||||
SetWidth="150"/>
|
|
||||||
<Label Name="SpeciesLabel"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
StyleClasses="LabelSubText"/>
|
|
||||||
<Label Name="PartNameLabel"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
StyleClasses="LabelSubText"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<Label Margin="0 0 5 0"
|
|
||||||
HorizontalExpand="True"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
VerticalExpand="True"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Name="ScanModeLabel"
|
|
||||||
Text="{Loc 'health-analyzer-window-entity-unknown-text'}"/>
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
||||||
<PanelContainer StyleClasses="LowDivider"/>
|
<ui:HealthAnalyzerControl
|
||||||
|
Name="HealthAnalyzer"
|
||||||
<!-- Begin DeltaV - Medical Records -->
|
Access="Public" /> <!-- DeltaV / Shitmed Change - Make Public-->
|
||||||
<BoxContainer Name="TriageControls" Orientation="Vertical">
|
|
||||||
<BoxContainer Name="StatusBox"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
Margin="0 8 0 0">
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
||||||
<Button Name="ClaimButton"
|
|
||||||
Text="{Loc 'health-analyzer-window-triage-claim'}"
|
|
||||||
MinSize="60 0" />
|
|
||||||
|
|
||||||
<PanelContainer StyleClasses="LowDivider" Margin="0 8 0 0"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<!-- End DeltaV - Medical Records -->
|
|
||||||
|
|
||||||
<GridContainer Margin="0 5 0 0"
|
|
||||||
Columns="2">
|
|
||||||
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}"/>
|
|
||||||
<Label Name="StatusLabel"/>
|
|
||||||
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}"/>
|
|
||||||
<Label Name="TemperatureLabel"/>
|
|
||||||
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}"/>
|
|
||||||
<Label Name="BloodLabel"/>
|
|
||||||
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}"/>
|
|
||||||
<Label Name="DamageLabel"/>
|
|
||||||
</GridContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
||||||
<PanelContainer Name="AlertsDivider"
|
|
||||||
Visible="False"
|
|
||||||
StyleClasses="LowDivider"/>
|
|
||||||
|
|
||||||
<BoxContainer Name="AlertsContainer"
|
|
||||||
Visible="False"
|
|
||||||
Margin="0 5"
|
|
||||||
Orientation="Vertical"
|
|
||||||
HorizontalAlignment="Center">
|
|
||||||
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
||||||
<PanelContainer StyleClasses="LowDivider"/>
|
|
||||||
<!-- Shitmed Change End -->
|
|
||||||
|
|
||||||
<BoxContainer
|
|
||||||
Name="GroupsContainer"
|
|
||||||
Margin="0 5 0 5"
|
|
||||||
Orientation="Vertical">
|
|
||||||
</BoxContainer>
|
|
||||||
|
|
||||||
</BoxContainer>
|
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</controls:FancyWindow>
|
</controls:FancyWindow>
|
||||||
|
|
|
||||||
|
|
@ -1,440 +1,20 @@
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using Content.Client._DV.Traits.Assorted; // DeltaV
|
|
||||||
using Content.Shared._DV.Traits.Assorted; // DeltaV
|
|
||||||
using Content.Shared._DV.Medical; // DeltaV - Uncloneable
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared._DV.MedicalRecords; // DeltaV - Medical Records
|
|
||||||
using Content.Shared._Shitmed.Targeting; // Shitmed
|
|
||||||
using Content.Shared.Damage.Components;
|
|
||||||
using Content.Shared.Damage.Prototypes;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Content.Shared.Humanoid;
|
|
||||||
using Content.Shared.Humanoid.Prototypes;
|
|
||||||
using Content.Shared.IdentityManagement;
|
|
||||||
using Content.Shared.MedicalScanner;
|
using Content.Shared.MedicalScanner;
|
||||||
using Content.Shared.Mobs;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.ResourceManagement;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
|
namespace Content.Client.HealthAnalyzer.UI;
|
||||||
|
|
||||||
namespace Content.Client.HealthAnalyzer.UI
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class HealthAnalyzerWindow : FancyWindow
|
||||||
{
|
{
|
||||||
[GenerateTypedNameReferences]
|
public HealthAnalyzerWindow()
|
||||||
public sealed partial class HealthAnalyzerWindow : FancyWindow
|
|
||||||
{
|
{
|
||||||
private readonly IEntityManager _entityManager;
|
RobustXamlLoader.Load(this);
|
||||||
private readonly SpriteSystem _spriteSystem;
|
}
|
||||||
private readonly IPrototypeManager _prototypes;
|
|
||||||
private readonly IResourceCache _cache;
|
|
||||||
private readonly UnborgableSystem _unborgable; // DeltaV
|
|
||||||
private readonly RedshirtSystem _redshirt; // DeltaV
|
|
||||||
private readonly UncloneableSystem _uncloneable; // DeltaV
|
|
||||||
|
|
||||||
// Shitmed Change Start
|
public void Populate(HealthAnalyzerScannedUserMessage msg)
|
||||||
public event Action<TargetBodyPart?, EntityUid>? OnBodyPartSelected;
|
{
|
||||||
private EntityUid _spriteViewEntity;
|
HealthAnalyzer.Populate(msg.State);
|
||||||
|
|
||||||
[ValidatePrototypeId<EntityPrototype>]
|
|
||||||
private readonly EntProtoId _bodyView = "AlertSpriteView";
|
|
||||||
|
|
||||||
private readonly Dictionary<TargetBodyPart, TextureButton> _bodyPartControls;
|
|
||||||
private EntityUid? _target;
|
|
||||||
// Shitmed Change End
|
|
||||||
|
|
||||||
// Begin DeltaV - Medical Records
|
|
||||||
private readonly ButtonGroup _triageStatusGroup = new();
|
|
||||||
private readonly Dictionary<TriageStatus, Button> _triageControls = new();
|
|
||||||
|
|
||||||
public event Action<TriageStatus>? OnTriageStatusChanged;
|
|
||||||
public event Action? OnClaimPatient;
|
|
||||||
// End DeltaV - Medical Records
|
|
||||||
|
|
||||||
public HealthAnalyzerWindow()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
|
|
||||||
var dependencies = IoCManager.Instance!;
|
|
||||||
_entityManager = dependencies.Resolve<IEntityManager>();
|
|
||||||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
|
||||||
_prototypes = dependencies.Resolve<IPrototypeManager>();
|
|
||||||
_cache = dependencies.Resolve<IResourceCache>();
|
|
||||||
_unborgable = _entityManager.System<UnborgableSystem>(); // DeltaV
|
|
||||||
_redshirt = _entityManager.System<RedshirtSystem>(); // DeltaV
|
|
||||||
_uncloneable = _entityManager.System<UncloneableSystem>(); // DeltaV
|
|
||||||
// Shitmed Change Start
|
|
||||||
_bodyPartControls = new Dictionary<TargetBodyPart, TextureButton>
|
|
||||||
{
|
|
||||||
{ TargetBodyPart.Head, HeadButton },
|
|
||||||
{ TargetBodyPart.Torso, ChestButton },
|
|
||||||
{ TargetBodyPart.Groin, GroinButton },
|
|
||||||
{ TargetBodyPart.LeftArm, LeftArmButton },
|
|
||||||
{ TargetBodyPart.LeftHand, LeftHandButton },
|
|
||||||
{ TargetBodyPart.RightArm, RightArmButton },
|
|
||||||
{ TargetBodyPart.RightHand, RightHandButton },
|
|
||||||
{ TargetBodyPart.LeftLeg, LeftLegButton },
|
|
||||||
{ TargetBodyPart.LeftFoot, LeftFootButton },
|
|
||||||
{ TargetBodyPart.RightLeg, RightLegButton },
|
|
||||||
{ TargetBodyPart.RightFoot, RightFootButton },
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var bodyPartButton in _bodyPartControls)
|
|
||||||
{
|
|
||||||
bodyPartButton.Value.MouseFilter = MouseFilterMode.Stop;
|
|
||||||
bodyPartButton.Value.OnPressed += _ => SetActiveBodyPart(bodyPartButton.Key, bodyPartButton.Value);
|
|
||||||
}
|
|
||||||
ReturnButton.OnPressed += _ => ResetBodyPart();
|
|
||||||
// Shitmed Change End
|
|
||||||
|
|
||||||
// Begin DeltaV - Medical Records
|
|
||||||
foreach (var item in Enum.GetValues<TriageStatus>())
|
|
||||||
{
|
|
||||||
var btn = new Button();
|
|
||||||
var ftlKey = item.ToString();
|
|
||||||
btn.Group = _triageStatusGroup;
|
|
||||||
btn.Text = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}");
|
|
||||||
btn.ToolTip = Loc.GetString($"health-analyzer-window-triage-status-{ftlKey}.ToolTip");
|
|
||||||
btn.OnPressed += _ => OnTriageStatusChanged?.Invoke(item);
|
|
||||||
btn.AddStyleClass("ButtonSquare");
|
|
||||||
StatusBox.AddChild(btn);
|
|
||||||
_triageControls[item] = btn;
|
|
||||||
}
|
|
||||||
StatusBox.Children.First().AddStyleClass("OpenRight");
|
|
||||||
StatusBox.Children.First().RemoveStyleClass("ButtonSquare");
|
|
||||||
StatusBox.Children.Last().AddStyleClass("OpenLeft");
|
|
||||||
StatusBox.Children.Last().RemoveStyleClass("ButtonSquare");
|
|
||||||
ClaimButton.OnPressed += _ => OnClaimPatient?.Invoke();
|
|
||||||
// End DeltaV - Medical Records
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shitmed Change Start
|
|
||||||
public void SetActiveBodyPart(TargetBodyPart part, TextureButton button)
|
|
||||||
{
|
|
||||||
if (_target == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Bit of the ole shitcode until we have Groins in the prototypes.
|
|
||||||
OnBodyPartSelected?.Invoke(part == TargetBodyPart.Groin ? TargetBodyPart.Torso : part, _target.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetBodyPart()
|
|
||||||
{
|
|
||||||
if (_target == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnBodyPartSelected?.Invoke(null, _target.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetActiveButtons(bool isHumanoid)
|
|
||||||
{
|
|
||||||
foreach (var button in _bodyPartControls)
|
|
||||||
button.Value.Visible = isHumanoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not all of this function got messed with, but it was spread enough to warrant being covered entirely by a Shitmed Change
|
|
||||||
public void Populate(HealthAnalyzerScannedUserMessage msg)
|
|
||||||
{
|
|
||||||
// Start-Shitmed
|
|
||||||
_target = _entityManager.GetEntity(msg.TargetEntity);
|
|
||||||
EntityUid? part = msg.Part != null ? _entityManager.GetEntity(msg.Part.Value) : null;
|
|
||||||
var isPart = part != null;
|
|
||||||
|
|
||||||
if (_target == null
|
|
||||||
|| !_entityManager.TryGetComponent<DamageableComponent>(isPart ? part : _target, out var damageable))
|
|
||||||
{
|
|
||||||
NoPatientDataText.Visible = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetActiveButtons(_entityManager.HasComponent<TargetingComponent>(_target.Value));
|
|
||||||
|
|
||||||
ReturnButton.Visible = isPart;
|
|
||||||
PartNameLabel.Visible = isPart;
|
|
||||||
|
|
||||||
if (part != null)
|
|
||||||
PartNameLabel.Text = _entityManager.HasComponent<MetaDataComponent>(part)
|
|
||||||
? Identity.Name(part.Value, _entityManager)
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
|
||||||
|
|
||||||
NoPatientDataText.Visible = false;
|
|
||||||
|
|
||||||
// Scan Mode
|
|
||||||
|
|
||||||
ScanModeLabel.Text = msg.ScanMode.HasValue
|
|
||||||
? msg.ScanMode.Value
|
|
||||||
? Loc.GetString("health-analyzer-window-scan-mode-active")
|
|
||||||
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-text");
|
|
||||||
|
|
||||||
ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
|
|
||||||
|
|
||||||
// Patient Information
|
|
||||||
|
|
||||||
SpriteView.SetEntity(SetupIcon(msg.Body) ?? _target.Value);
|
|
||||||
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
|
|
||||||
PartView.Visible = SpriteView.Visible;
|
|
||||||
NoDataTex.Visible = !SpriteView.Visible;
|
|
||||||
|
|
||||||
var name = new FormattedMessage();
|
|
||||||
name.PushColor(Color.White);
|
|
||||||
name.AddText(_entityManager.HasComponent<MetaDataComponent>(_target.Value)
|
|
||||||
? Identity.Name(_target.Value, _entityManager)
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-text"));
|
|
||||||
NameLabel.SetMessage(name);
|
|
||||||
|
|
||||||
SpeciesLabel.Text =
|
|
||||||
_entityManager.TryGetComponent<HumanoidAppearanceComponent>(_target.Value,
|
|
||||||
out var humanoidAppearanceComponent)
|
|
||||||
? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-species-text");
|
|
||||||
|
|
||||||
// Basic Diagnostic
|
|
||||||
|
|
||||||
TemperatureLabel.Text = !float.IsNaN(msg.Temperature)
|
|
||||||
? $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)"
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
|
||||||
|
|
||||||
BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
|
|
||||||
? $"{msg.BloodLevel * 100:F1} %"
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-value-text");
|
|
||||||
|
|
||||||
StatusLabel.Text =
|
|
||||||
_entityManager.TryGetComponent<MobStateComponent>(_target.Value, out var mobStateComponent)
|
|
||||||
? GetStatus(mobStateComponent.CurrentState)
|
|
||||||
: Loc.GetString("health-analyzer-window-entity-unknown-text");
|
|
||||||
|
|
||||||
// Total Damage
|
|
||||||
|
|
||||||
DamageLabel.Text = damageable.TotalDamage.ToString();
|
|
||||||
|
|
||||||
// Alerts
|
|
||||||
// DeltaV traits - This is going to be horrid if we just keep adding things like this.
|
|
||||||
var unborgable = _unborgable.IsUnborgable(_target.Value);
|
|
||||||
var redshirt = _redshirt.IsRedshirt(_target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Redshirt
|
|
||||||
var uncloneable = _uncloneable.IsUncloneable(_target.Value) && mobStateComponent?.CurrentState == MobState.Dead; // DeltaV - Unclonable
|
|
||||||
// END DeltaV
|
|
||||||
|
|
||||||
var showAlerts = msg.Unrevivable == true || msg.Bleeding == true || unborgable || redshirt || uncloneable; // DeltaV
|
|
||||||
|
|
||||||
AlertsDivider.Visible = showAlerts;
|
|
||||||
AlertsContainer.Visible = showAlerts;
|
|
||||||
|
|
||||||
if (showAlerts)
|
|
||||||
AlertsContainer.RemoveAllChildren();
|
|
||||||
|
|
||||||
if (msg.Unrevivable == true)
|
|
||||||
AlertsContainer.AddChild(new RichTextLabel
|
|
||||||
{
|
|
||||||
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
|
|
||||||
Margin = new Thickness(0, 4),
|
|
||||||
MaxWidth = 300
|
|
||||||
});
|
|
||||||
|
|
||||||
if (msg.Bleeding == true)
|
|
||||||
AlertsContainer.AddChild(new RichTextLabel
|
|
||||||
{
|
|
||||||
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
|
|
||||||
Margin = new Thickness(0, 4),
|
|
||||||
MaxWidth = 300
|
|
||||||
});
|
|
||||||
|
|
||||||
if (unborgable) // DeltaV
|
|
||||||
AlertsContainer.AddChild(new RichTextLabel
|
|
||||||
{
|
|
||||||
Text = Loc.GetString("health-analyzer-window-entity-unborgable-text"),
|
|
||||||
Margin = new Thickness(0, 4),
|
|
||||||
MaxWidth = 300
|
|
||||||
});
|
|
||||||
|
|
||||||
if (redshirt) // DeltaV
|
|
||||||
AlertsContainer.AddChild(new RichTextLabel
|
|
||||||
{
|
|
||||||
Text = Loc.GetString("health-analyzer-window-entity-redshirt-text"),
|
|
||||||
Margin = new Thickness(0, 4),
|
|
||||||
MaxWidth = 300
|
|
||||||
});
|
|
||||||
|
|
||||||
if (uncloneable) // DeltaV - Uncloneable
|
|
||||||
AlertsContainer.AddChild(new RichTextLabel
|
|
||||||
{
|
|
||||||
Text = Loc.GetString("health-analyzer-window-entity-uncloneable-text"),
|
|
||||||
Margin = new Thickness(0, 4),
|
|
||||||
MaxWidth = 300
|
|
||||||
});
|
|
||||||
|
|
||||||
// Damage Groups
|
|
||||||
|
|
||||||
var damageSortedGroups =
|
|
||||||
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
|
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
|
||||||
|
|
||||||
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
|
|
||||||
|
|
||||||
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
|
|
||||||
|
|
||||||
// Begin DeltaV - Medical Records
|
|
||||||
if (msg.MedicalRecord is not {} records)
|
|
||||||
{
|
|
||||||
TriageControls.Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TriageControls.Visible = true;
|
|
||||||
_triageControls[records.Status].Pressed = true;
|
|
||||||
|
|
||||||
// Update claim button based on claimed status
|
|
||||||
if (records.ClaimedName != null)
|
|
||||||
{
|
|
||||||
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-unclaim", ("claimedBy", records.ClaimedName));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ClaimButton.Text = Loc.GetString("health-analyzer-window-triage-claim");
|
|
||||||
}
|
|
||||||
// End DeltaV - Medical Records
|
|
||||||
}
|
|
||||||
// Shitmed Change End
|
|
||||||
private static string GetStatus(MobState mobState)
|
|
||||||
{
|
|
||||||
return mobState switch
|
|
||||||
{
|
|
||||||
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
|
|
||||||
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
|
|
||||||
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
|
|
||||||
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawDiagnosticGroups(
|
|
||||||
Dictionary<string, FixedPoint2> groups,
|
|
||||||
IReadOnlyDictionary<string, FixedPoint2> damageDict)
|
|
||||||
{
|
|
||||||
GroupsContainer.RemoveAllChildren();
|
|
||||||
|
|
||||||
foreach (var (damageGroupId, damageAmount) in groups)
|
|
||||||
{
|
|
||||||
if (damageAmount == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var groupTitleText = $"{Loc.GetString(
|
|
||||||
"health-analyzer-window-damage-group-text",
|
|
||||||
("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
|
|
||||||
("amount", damageAmount)
|
|
||||||
)}";
|
|
||||||
|
|
||||||
var groupContainer = new BoxContainer
|
|
||||||
{
|
|
||||||
Align = BoxContainer.AlignMode.Begin,
|
|
||||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
|
||||||
};
|
|
||||||
|
|
||||||
groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId));
|
|
||||||
|
|
||||||
GroupsContainer.AddChild(groupContainer);
|
|
||||||
|
|
||||||
// Show the damage for each type in that group.
|
|
||||||
var group = _prototypes.Index<DamageGroupPrototype>(damageGroupId);
|
|
||||||
|
|
||||||
foreach (var type in group.DamageTypes)
|
|
||||||
{
|
|
||||||
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var damageString = Loc.GetString(
|
|
||||||
"health-analyzer-window-damage-type-text",
|
|
||||||
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
|
|
||||||
("amount", typeAmount)
|
|
||||||
);
|
|
||||||
|
|
||||||
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Texture GetTexture(string texture)
|
|
||||||
{
|
|
||||||
var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
|
|
||||||
var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
|
|
||||||
|
|
||||||
var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
|
|
||||||
if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
|
|
||||||
{
|
|
||||||
rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _spriteSystem.Frame0(rsiSprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Label CreateDiagnosticItemLabel(string text)
|
|
||||||
{
|
|
||||||
return new Label
|
|
||||||
{
|
|
||||||
Text = text,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private BoxContainer CreateDiagnosticGroupTitle(string text, string id)
|
|
||||||
{
|
|
||||||
var rootContainer = new BoxContainer
|
|
||||||
{
|
|
||||||
Margin = new Thickness(0, 6, 0, 0),
|
|
||||||
VerticalAlignment = VAlignment.Bottom,
|
|
||||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
|
||||||
};
|
|
||||||
|
|
||||||
rootContainer.AddChild(new TextureRect
|
|
||||||
{
|
|
||||||
SetSize = new Vector2(30, 30),
|
|
||||||
Texture = GetTexture(id.ToLower())
|
|
||||||
});
|
|
||||||
|
|
||||||
rootContainer.AddChild(CreateDiagnosticItemLabel(text));
|
|
||||||
|
|
||||||
return rootContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shitmed Change Start
|
|
||||||
/// <summary>
|
|
||||||
/// Sets up the Body Doll using Alert Entity to use in Health Analyzer.
|
|
||||||
/// </summary>
|
|
||||||
private EntityUid? SetupIcon(Dictionary<TargetBodyPart, TargetIntegrity>? body)
|
|
||||||
{
|
|
||||||
if (body is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!_entityManager.Deleted(_spriteViewEntity))
|
|
||||||
_entityManager.QueueDeleteEntity(_spriteViewEntity);
|
|
||||||
|
|
||||||
_spriteViewEntity = _entityManager.Spawn(_bodyView);
|
|
||||||
|
|
||||||
if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
int layer = 0;
|
|
||||||
foreach (var (bodyPart, integrity) in body)
|
|
||||||
{
|
|
||||||
// TODO: PartStatusUIController and make it use layers instead of TextureRects when EE refactors alerts.
|
|
||||||
string enumName = Enum.GetName(typeof(TargetBodyPart), bodyPart) ?? "Unknown";
|
|
||||||
int enumValue = (int) integrity;
|
|
||||||
var rsi = new SpriteSpecifier.Rsi(new ResPath($"/Textures/_Shitmed/Interface/Targeting/Status/{enumName.ToLowerInvariant()}.rsi"), $"{enumName.ToLowerInvariant()}_{enumValue}");
|
|
||||||
// Shitcode with love from Russia :)
|
|
||||||
if (!_spriteSystem.TryGetLayer(_spriteViewEntity, layer, out _, false))
|
|
||||||
_spriteSystem.AddTextureLayer(_spriteViewEntity, _spriteSystem.Frame0(rsi));
|
|
||||||
else
|
|
||||||
_spriteSystem.LayerSetTexture(_spriteViewEntity, layer, _spriteSystem.Frame0(rsi));
|
|
||||||
_spriteSystem.LayerSetScale(_spriteViewEntity, layer, new Vector2(3f, 3f));
|
|
||||||
layer++;
|
|
||||||
}
|
|
||||||
return _spriteViewEntity;
|
|
||||||
}
|
|
||||||
// Shitmed Change End
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public sealed partial class InfoSection : BoxContainer
|
||||||
{
|
{
|
||||||
TitleLabel.Text = title;
|
TitleLabel.Text = title;
|
||||||
if (markup)
|
if (markup)
|
||||||
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
Content.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()), tagsAllowed: null);
|
||||||
else
|
else
|
||||||
Content.SetMessage(text);
|
Content.SetMessage(text);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Content.Client.Info
|
||||||
}
|
}
|
||||||
public void SetInfoBlob(string markup)
|
public void SetInfoBlob(string markup)
|
||||||
{
|
{
|
||||||
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup));
|
_richTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(markup), tagsAllowed: null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
using Content.Shared.Kitchen.EntitySystems;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Content.Client.Kitchen.EntitySystems;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class ReagentGrinderSystem : SharedReagentGrinderSystem;
|
||||||
|
|
@ -20,8 +20,10 @@ namespace Content.Client.Launcher
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||||
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
|
||||||
private LauncherConnectingGui? _control;
|
private LauncherConnectingGui? _control;
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
private Page _currentPage;
|
private Page _currentPage;
|
||||||
private string? _connectFailReason;
|
private string? _connectFailReason;
|
||||||
|
|
@ -61,6 +63,8 @@ namespace Content.Client.Launcher
|
||||||
{
|
{
|
||||||
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
|
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
|
||||||
|
|
||||||
|
_sawmill = _logManager.GetSawmill("launcher-ui");
|
||||||
|
|
||||||
_userInterfaceManager.StateRoot.AddChild(_control);
|
_userInterfaceManager.StateRoot.AddChild(_control);
|
||||||
|
|
||||||
_clientNetManager.ConnectFailed += OnConnectFailed;
|
_clientNetManager.ConnectFailed += OnConnectFailed;
|
||||||
|
|
@ -115,12 +119,12 @@ namespace Content.Client.Launcher
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.InfoS("launcher-ui", $"Redial not possible, no Ss14Address");
|
_sawmill.Info($"Redial not possible, no Ss14Address");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.ErrorS("launcher-ui", $"Redial exception: {ex}");
|
_sawmill.Error($"Redial exception: {ex}");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Prometheus;
|
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
|
using Content.Client.RichText;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
using Content.Client.UserInterface.RichText;
|
||||||
using Content.Shared.MassMedia.Systems;
|
using Content.Shared.MassMedia.Systems;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.RichText; // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
using Robust.Client.UserInterface.RichText;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
|
@ -20,18 +21,6 @@ public sealed partial class ArticleEditorPanel : Control
|
||||||
|
|
||||||
private bool _preview;
|
private bool _preview;
|
||||||
|
|
||||||
// DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
|
||||||
private static readonly Type[] AllowedTags =
|
|
||||||
[
|
|
||||||
typeof(BoldItalicTag),
|
|
||||||
typeof(BoldTag),
|
|
||||||
typeof(BulletTag),
|
|
||||||
typeof(ColorTag),
|
|
||||||
typeof(HeadingTag),
|
|
||||||
typeof(ItalicTag),
|
|
||||||
typeof(MonoTag),
|
|
||||||
];
|
|
||||||
|
|
||||||
public ArticleEditorPanel()
|
public ArticleEditorPanel()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
@ -96,8 +85,8 @@ public sealed partial class ArticleEditorPanel : Control
|
||||||
TextEditPanel.Visible = !_preview;
|
TextEditPanel.Visible = !_preview;
|
||||||
PreviewPanel.Visible = _preview;
|
PreviewPanel.Visible = _preview;
|
||||||
|
|
||||||
var articleBody = Rope.Collapse(ContentField.TextRope); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
var articleBody = Rope.Collapse(ContentField.TextRope);
|
||||||
PreviewLabel.SetMessage(FormattedMessage.FromMarkupPermissive(articleBody), AllowedTags); // DeltaV - Sanitize markup; see https://github.com/space-wizards/space-station-14/pull/41799
|
PreviewLabel.SetMessage(FormattedMessage.FromMarkupPermissive(articleBody), UserFormattableTags.BaseAllowedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
|
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||||
|
|
||||||
|
namespace Content.Client.Medical.Cryogenics;
|
||||||
|
|
||||||
|
|
||||||
|
public sealed class BeakerBarChart : Control
|
||||||
|
{
|
||||||
|
private sealed class Entry
|
||||||
|
{
|
||||||
|
public float WidthFraction; // This entry's width as a fraction of the chart's total width (between 0 and 1)
|
||||||
|
public float TargetAmount;
|
||||||
|
public string Uid; // This UID is used to track entries between frames, for animation.
|
||||||
|
public string? Tooltip;
|
||||||
|
public Color Color;
|
||||||
|
public Label Label;
|
||||||
|
|
||||||
|
public Entry(string uid, Label label)
|
||||||
|
{
|
||||||
|
Uid = uid;
|
||||||
|
Label = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Capacity = 50;
|
||||||
|
|
||||||
|
public Color NotchColor = new(1, 1, 1, 0.25f);
|
||||||
|
public Color BackgroundColor = new(0.1f, 0.1f, 0.1f);
|
||||||
|
|
||||||
|
public int MediumNotchInterval = 5;
|
||||||
|
public int BigNotchInterval = 10;
|
||||||
|
|
||||||
|
// When we have a very large beaker (i.e. bluespace beaker) we might need to increase the distance between notches.
|
||||||
|
// The distance between notches is increased by ScaleMultiplier when the distance between notches is less than
|
||||||
|
// MinSmallNotchScreenDistance in UI units.
|
||||||
|
public int MinSmallNotchScreenDistance = 2;
|
||||||
|
public int ScaleMultiplier = 10;
|
||||||
|
|
||||||
|
public float SmallNotchHeight = 0.1f;
|
||||||
|
public float MediumNotchHeight = 0.25f;
|
||||||
|
public float BigNotchHeight = 1f;
|
||||||
|
|
||||||
|
// We don't animate new entries until this control has been drawn at least once.
|
||||||
|
private bool _hasBeenDrawn = false;
|
||||||
|
|
||||||
|
// This is used to keep the segments of the chart in the same order as the SetEntry calls.
|
||||||
|
// For example: In update 1 we might get cryox, alox, bic (in that order), and in update 2 we get alox, cryox, bic.
|
||||||
|
// To keep the order of the entries the same as the order of the SetEntry calls, we let the old cryox entry
|
||||||
|
// disappear and create a new cryox entry behind the alox entry.
|
||||||
|
private int _nextUpdateableEntry = 0;
|
||||||
|
|
||||||
|
private readonly List<Entry> _entries = new();
|
||||||
|
|
||||||
|
|
||||||
|
public BeakerBarChart()
|
||||||
|
{
|
||||||
|
MouseFilter = MouseFilterMode.Pass;
|
||||||
|
TooltipSupplier = SupplyTooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (var entry in _entries)
|
||||||
|
{
|
||||||
|
entry.TargetAmount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_nextUpdateableEntry = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Either adds a new entry to the chart if the UID doesn't appear yet, or updates the amount of an existing entry.
|
||||||
|
/// </summary>
|
||||||
|
public void SetEntry(
|
||||||
|
string uid,
|
||||||
|
string label,
|
||||||
|
float amount,
|
||||||
|
Color color,
|
||||||
|
Color? textColor = null,
|
||||||
|
string? tooltip = null)
|
||||||
|
{
|
||||||
|
// If we can find an old entry we're allowed to update, update that one.
|
||||||
|
if (TryFindUpdateableEntry(uid, out var index))
|
||||||
|
{
|
||||||
|
_entries[index].TargetAmount = amount;
|
||||||
|
_entries[index].Tooltip = tooltip;
|
||||||
|
_entries[index].Label.Text = label;
|
||||||
|
_nextUpdateableEntry = index + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a new entry.
|
||||||
|
if (amount <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If no text color is provided, use either white or black depending on how dark the background is.
|
||||||
|
textColor ??= (color.R + color.G + color.B < 1.5f ? Color.White : Color.Black);
|
||||||
|
|
||||||
|
var childLabel = new Label
|
||||||
|
{
|
||||||
|
Text = label,
|
||||||
|
ClipText = true,
|
||||||
|
FontColorOverride = textColor,
|
||||||
|
Margin = new Thickness(4, 0, 0, 0)
|
||||||
|
};
|
||||||
|
AddChild(childLabel);
|
||||||
|
|
||||||
|
_entries.Insert(
|
||||||
|
_nextUpdateableEntry,
|
||||||
|
new Entry(uid, childLabel)
|
||||||
|
{
|
||||||
|
WidthFraction = (_hasBeenDrawn ? 0 : amount / Capacity),
|
||||||
|
TargetAmount = amount,
|
||||||
|
Tooltip = tooltip,
|
||||||
|
Color = color
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_nextUpdateableEntry += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryFindUpdateableEntry(string uid, out int index)
|
||||||
|
{
|
||||||
|
for (int i = _nextUpdateableEntry; i < _entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (_entries[i].Uid == uid)
|
||||||
|
{
|
||||||
|
index = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<(Entry, float xMin, float xMax)> EntryRanges(float? pixelWidth = null)
|
||||||
|
{
|
||||||
|
float chartWidth = pixelWidth ?? PixelWidth;
|
||||||
|
var xStart = 0f;
|
||||||
|
|
||||||
|
foreach (var entry in _entries)
|
||||||
|
{
|
||||||
|
var entryWidth = entry.WidthFraction * chartWidth;
|
||||||
|
var xEnd = MathF.Min(xStart + entryWidth, chartWidth);
|
||||||
|
|
||||||
|
yield return (entry, xStart, xEnd);
|
||||||
|
|
||||||
|
xStart = xEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryFindEntry(float x, [NotNullWhen(true)] out Entry? entry)
|
||||||
|
{
|
||||||
|
foreach (var (currentEntry, xMin, xMax) in EntryRanges())
|
||||||
|
{
|
||||||
|
if (xMin <= x && x < xMax)
|
||||||
|
{
|
||||||
|
entry = currentEntry;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
// Tween the amounts to their target amounts.
|
||||||
|
const float tweenInverseHalfLife = 8; // Half life of tween is 1/n
|
||||||
|
var hasChanged = false;
|
||||||
|
|
||||||
|
foreach (var entry in _entries)
|
||||||
|
{
|
||||||
|
var targetWidthFraction = entry.TargetAmount / Capacity;
|
||||||
|
|
||||||
|
if (entry.WidthFraction == targetWidthFraction)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Tween with lerp abuse interpolation
|
||||||
|
entry.WidthFraction = MathHelper.Lerp(
|
||||||
|
entry.WidthFraction,
|
||||||
|
targetWidthFraction,
|
||||||
|
MathHelper.Clamp01(tweenInverseHalfLife * args.DeltaSeconds)
|
||||||
|
);
|
||||||
|
hasChanged = true;
|
||||||
|
|
||||||
|
if (MathF.Abs(entry.WidthFraction - targetWidthFraction) < 0.0001f)
|
||||||
|
entry.WidthFraction = targetWidthFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasChanged)
|
||||||
|
return;
|
||||||
|
|
||||||
|
InvalidateArrange();
|
||||||
|
|
||||||
|
// Remove old entries whose animations have finished.
|
||||||
|
foreach (var entry in _entries)
|
||||||
|
{
|
||||||
|
if (entry.WidthFraction == 0 && entry.TargetAmount == 0)
|
||||||
|
RemoveChild(entry.Label);
|
||||||
|
}
|
||||||
|
|
||||||
|
_entries.RemoveAll(entry => entry.WidthFraction == 0 && entry.TargetAmount == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void MouseMove(GUIMouseMoveEventArgs args)
|
||||||
|
{
|
||||||
|
HideTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
|
{
|
||||||
|
handle.DrawRect(PixelSizeBox, BackgroundColor);
|
||||||
|
|
||||||
|
// Draw the entry backgrounds
|
||||||
|
foreach (var (entry, xMin, xMax) in EntryRanges())
|
||||||
|
{
|
||||||
|
if (xMin != xMax)
|
||||||
|
handle.DrawRect(new(xMin, 0, xMax, PixelHeight), entry.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw notches
|
||||||
|
var unitWidth = PixelWidth / Capacity;
|
||||||
|
var unitsPerNotch = 1;
|
||||||
|
|
||||||
|
while (unitWidth < MinSmallNotchScreenDistance)
|
||||||
|
{
|
||||||
|
// This is here for 1000u bluespace beakers. If the distance between small notches is so small that it would
|
||||||
|
// be very ugly, we reduce the amount of notches by ScaleMultiplier (currently a factor of 10).
|
||||||
|
// (I could use an analytical algorithm here, but it would be more difficult to read with pretty much no
|
||||||
|
// performance benefit, since it loops zero times normally and one time for the bluespace beaker)
|
||||||
|
unitWidth *= ScaleMultiplier;
|
||||||
|
unitsPerNotch *= ScaleMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i <= Capacity / unitsPerNotch; i++)
|
||||||
|
{
|
||||||
|
var x = i * unitWidth;
|
||||||
|
var height = (i % BigNotchInterval == 0 ? BigNotchHeight :
|
||||||
|
i % MediumNotchInterval == 0 ? MediumNotchHeight :
|
||||||
|
SmallNotchHeight) * PixelHeight;
|
||||||
|
var start = new Vector2(x, PixelHeight);
|
||||||
|
var end = new Vector2(x, PixelHeight - height);
|
||||||
|
handle.DrawLine(start, end, NotchColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasBeenDrawn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||||
|
{
|
||||||
|
foreach (var (entry, xMin, xMax) in EntryRanges(finalSize.X))
|
||||||
|
{
|
||||||
|
entry.Label.Arrange(new((int)xMin, 0, (int)xMax, (int)finalSize.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control? SupplyTooltip(Control sender)
|
||||||
|
{
|
||||||
|
var globalMousePos = UserInterfaceManager.MousePositionScaled.Position;
|
||||||
|
var mousePos = globalMousePos - GlobalPosition;
|
||||||
|
|
||||||
|
if (!TryFindEntry(mousePos.X, out var entry) || entry.Tooltip == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var msg = new FormattedMessage();
|
||||||
|
msg.AddText(entry.Tooltip);
|
||||||
|
|
||||||
|
var tooltip = new Tooltip();
|
||||||
|
tooltip.SetMessage(msg);
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
namespace Content.Client.Medical.Cryogenics;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class CryoPodBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private CryoPodWindow? _window;
|
||||||
|
|
||||||
|
public CryoPodBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
_window = this.CreateWindowCenteredLeft<CryoPodWindow>();
|
||||||
|
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||||
|
_window.OnEjectPatientPressed += EjectPatientPressed;
|
||||||
|
_window.OnEjectBeakerPressed += EjectBeakerPressed;
|
||||||
|
_window.OnInjectPressed += InjectPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EjectPatientPressed()
|
||||||
|
{
|
||||||
|
var isLocked =
|
||||||
|
EntMan.TryGetComponent<CryoPodComponent>(Owner, out var cryoComp)
|
||||||
|
&& cryoComp.Locked;
|
||||||
|
|
||||||
|
_window?.SetEjectErrorVisible(isLocked);
|
||||||
|
SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectPatient));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EjectBeakerPressed()
|
||||||
|
{
|
||||||
|
SendMessage(new CryoPodSimpleUiMessage(CryoPodSimpleUiMessage.MessageType.EjectBeaker));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InjectPressed(FixedPoint2 transferAmount)
|
||||||
|
{
|
||||||
|
SendMessage(new CryoPodInjectUiMessage(transferAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
|
{
|
||||||
|
if (_window != null && message is CryoPodUserMessage cryoMsg)
|
||||||
|
{
|
||||||
|
_window.Populate(cryoMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ namespace Content.Client.Medical.Cryogenics;
|
||||||
|
|
||||||
public sealed class CryoPodSystem : SharedCryoPodSystem
|
public sealed class CryoPodSystem : SharedCryoPodSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
|
||||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
|
|
@ -46,8 +45,8 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_appearance.TryGetData<bool>(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
|
if (!Appearance.TryGetData<bool>(uid, CryoPodVisuals.ContainsEntity, out var isOpen, args.Component)
|
||||||
|| !_appearance.TryGetData<bool>(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
|
|| !Appearance.TryGetData<bool>(uid, CryoPodVisuals.IsOn, out var isOn, args.Component))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +63,11 @@ public sealed class CryoPodSystem : SharedCryoPodSystem
|
||||||
_sprite.LayerSetVisible((uid, args.Sprite), CryoPodVisualLayers.Cover, true);
|
_sprite.LayerSetVisible((uid, args.Sprite), CryoPodVisualLayers.Cover, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateUi(Entity<CryoPodComponent> cryoPod)
|
||||||
|
{
|
||||||
|
// Atmos and health scanner aren't predicted currently...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CryoPodVisualLayers : byte
|
public enum CryoPodVisualLayers : byte
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
|
xmlns:health="clr-namespace:Content.Client.HealthAnalyzer.UI"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
xmlns:cryogenics="clr-namespace:Content.Client.Medical.Cryogenics"
|
||||||
|
MinSize="250 300"
|
||||||
|
Resizable="False">
|
||||||
|
|
||||||
|
<Label Name="LoadingPlaceHolder"
|
||||||
|
Text="{Loc 'cryo-pod-window-loading'}"
|
||||||
|
Align="Center"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
VerticalExpand="True"/>
|
||||||
|
|
||||||
|
<BoxContainer Name="Sections"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Visible="False"
|
||||||
|
Margin="10"
|
||||||
|
SeparationOverride="16">
|
||||||
|
<BoxContainer Name="CryoSection"
|
||||||
|
VerticalExpand="True"
|
||||||
|
Orientation="Vertical"
|
||||||
|
MinWidth="250"
|
||||||
|
MaxWidth="250">
|
||||||
|
|
||||||
|
<!-- Flavor text -->
|
||||||
|
<BoxContainer Orientation="Horizontal"
|
||||||
|
SeparationOverride="10"
|
||||||
|
Margin="8 0 0 8">
|
||||||
|
<TextureRect StyleClasses="NTLogoDark"
|
||||||
|
VerticalExpand="True"
|
||||||
|
Stretch="KeepAspectCentered"
|
||||||
|
SetSize="32 32"/>
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
SeparationOverride="-4">
|
||||||
|
<Label Text="{Loc 'cryo-pod-window-product-name'}"
|
||||||
|
StyleClasses="FontLarge"/>
|
||||||
|
<Label Text="{Loc 'cryo-pod-window-product-subtitle'}"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Atmos info -->
|
||||||
|
<BoxContainer Orientation="Horizontal"
|
||||||
|
SeparationOverride="20"
|
||||||
|
Margin="0 0 0 4">
|
||||||
|
<!-- Pressure -->
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<Label Text="{Loc 'gas-analyzer-window-pressure-text'}"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
<Label Name="Pressure"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Temperature -->
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<Label Text="{Loc 'gas-analyzer-window-temperature-text'}"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
<Label Name="Temperature"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Gas mix -->
|
||||||
|
<Control Margin="0 0 0 22">
|
||||||
|
<controls:SplitBar Name="GasMixChart"
|
||||||
|
MinHeight="8"
|
||||||
|
MaxHeight="8"/>
|
||||||
|
</Control>
|
||||||
|
|
||||||
|
<!-- Warnings & status -->
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
VerticalExpand="True"
|
||||||
|
Align="Center"
|
||||||
|
Margin="0 0 0 14"
|
||||||
|
SeparationOverride="20">
|
||||||
|
|
||||||
|
<!-- Ejection error (if the pod is locked) -->
|
||||||
|
<PanelContainer Name="EjectError"
|
||||||
|
Visible="False"
|
||||||
|
HorizontalExpand="True">
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
Margin="6">
|
||||||
|
<Label Text="{Loc 'cryo-pod-window-error-header'}"
|
||||||
|
FontColorOverride="orange"
|
||||||
|
Align="Center"/>
|
||||||
|
<RichTextLabel Text="{Loc 'cryo-pod-window-eject-error'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
|
||||||
|
<!-- Pressure warning -->
|
||||||
|
<PanelContainer Name="LowPressureWarning"
|
||||||
|
Visible="False"
|
||||||
|
HorizontalExpand="True">
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
Margin="6">
|
||||||
|
<Label Text="{Loc 'cryo-pod-window-warning-header'}"
|
||||||
|
FontColorOverride="orange"
|
||||||
|
Align="Center"/>
|
||||||
|
<RichTextLabel Text="{Loc 'cryo-pod-window-low-pressure-warning'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
|
||||||
|
<!-- Temperature warning -->
|
||||||
|
<PanelContainer Name="HighTemperatureWarning"
|
||||||
|
Visible="False"
|
||||||
|
HorizontalExpand="True">
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="orange"/>
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
Margin="6">
|
||||||
|
<Label Text="{Loc 'cryo-pod-window-warning-header'}"
|
||||||
|
FontColorOverride="orange"
|
||||||
|
Align="Center"/>
|
||||||
|
<!-- Note: This placeholder text should never be visible. -->
|
||||||
|
<RichTextLabel Name="HighTemperatureWarningText"
|
||||||
|
Text="Temperature too high."/>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
|
||||||
|
<!-- Status checklist -->
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Horizontal"
|
||||||
|
SeparationOverride="8">
|
||||||
|
<Label Text="{Loc 'cryo-pod-window-status'}"/>
|
||||||
|
<Label Name="StatusLabel"
|
||||||
|
Text="{Loc 'cryo-pod-window-status-not-ready'}"
|
||||||
|
FontColorOverride="Orange"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<GridContainer Columns="2"
|
||||||
|
HSeparationOverride="0"
|
||||||
|
VSeparationOverride="6"
|
||||||
|
Margin="6 3 0 0">
|
||||||
|
<Label Text="⋄"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
<Label Name="PressureCheck"
|
||||||
|
Text="{Loc 'cryo-pod-window-checklist-pressure'}"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
<Label Text="⋄"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
<Label Name="ChemicalsCheck"
|
||||||
|
Text="{Loc 'cryo-pod-window-checklist-chemicals'}"
|
||||||
|
StyleClasses="LabelSubText"
|
||||||
|
FontColorOverride="Orange"/>
|
||||||
|
<Label Text="⋄"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
<Label Name="TemperatureCheck"
|
||||||
|
Text="{Loc 'cryo-pod-window-checklist-temperature'}"
|
||||||
|
StyleClasses="LabelSubText"/>
|
||||||
|
</GridContainer>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Reagents -->
|
||||||
|
<Control HorizontalExpand="True"
|
||||||
|
MinHeight="30">
|
||||||
|
<Label Name="NoBeakerText"
|
||||||
|
Text="{Loc 'cryo-pod-window-chems-no-beaker'}"
|
||||||
|
FontColorOverride="Gray"
|
||||||
|
VerticalExpand="True"
|
||||||
|
VAlign="Center"/>
|
||||||
|
<cryogenics:BeakerBarChart Name="ChemicalsChart"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
VerticalExpand="True"/>
|
||||||
|
</Control>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<BoxContainer Orientation="Vertical"
|
||||||
|
Margin="-2 2 -2 0">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Button Name="Inject1"
|
||||||
|
Text="{Loc 'cryo-pod-window-inject-1u'}"
|
||||||
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenBoth"/>
|
||||||
|
<Button Name="Inject5"
|
||||||
|
Text="{Loc 'cryo-pod-window-inject-5u'}"
|
||||||
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenBoth"/>
|
||||||
|
<Button Name="Inject10"
|
||||||
|
Text="{Loc 'cryo-pod-window-inject-10u'}"
|
||||||
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenBoth"/>
|
||||||
|
<Button Name="Inject20"
|
||||||
|
Text="{Loc 'cryo-pod-window-inject-20u'}"
|
||||||
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenBoth"/>
|
||||||
|
<Button Name="EjectBeakerButton"
|
||||||
|
Text="{Loc 'cryo-pod-window-eject-beaker'}"
|
||||||
|
Disabled="True"
|
||||||
|
StyleClasses="OpenBoth"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<Button Name="EjectPatientButton"
|
||||||
|
Text="{Loc 'cryo-pod-window-eject-patient'}"
|
||||||
|
Disabled="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
StyleClasses="OpenRight"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="HealthSection"
|
||||||
|
VerticalExpand="True"
|
||||||
|
Orientation="Vertical">
|
||||||
|
|
||||||
|
<health:HealthAnalyzerControl Name="HealthAnalyzer"/>
|
||||||
|
|
||||||
|
<!-- This label is used to deal with a stray hline at the end of the health analyzer UI -->
|
||||||
|
<Label Name="NoDamageText"
|
||||||
|
Text="{Loc 'cryo-pod-window-health-no-damage'}"
|
||||||
|
FontColorOverride="DeepSkyBlue"/>
|
||||||
|
<Control VerticalExpand="True"/>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
|
|
@ -0,0 +1,260 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
|
using Content.Shared.EntityConditions.Conditions;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Medical.Cryogenics;
|
||||||
|
using Content.Shared.Temperature;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
namespace Content.Client.Medical.Cryogenics;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class CryoPodWindow : FancyWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
public event Action? OnEjectPatientPressed;
|
||||||
|
public event Action? OnEjectBeakerPressed;
|
||||||
|
public event Action<FixedPoint2>? OnInjectPressed;
|
||||||
|
|
||||||
|
public CryoPodWindow()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
EjectPatientButton.OnPressed += _ => OnEjectPatientPressed?.Invoke();
|
||||||
|
EjectBeakerButton.OnPressed += _ => OnEjectBeakerPressed?.Invoke();
|
||||||
|
Inject1.OnPressed += _ => OnInjectPressed?.Invoke(1);
|
||||||
|
Inject5.OnPressed += _ => OnInjectPressed?.Invoke(5);
|
||||||
|
Inject10.OnPressed += _ => OnInjectPressed?.Invoke(10);
|
||||||
|
Inject20.OnPressed += _ => OnInjectPressed?.Invoke(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(CryoPodUserMessage msg)
|
||||||
|
{
|
||||||
|
// Loading screen
|
||||||
|
if (LoadingPlaceHolder.Visible)
|
||||||
|
{
|
||||||
|
LoadingPlaceHolder.Visible = false;
|
||||||
|
Sections.Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atmosphere
|
||||||
|
var hasCorrectPressure = (msg.GasMix.Pressure > Atmospherics.WarningLowPressure);
|
||||||
|
var hasGas = (msg.GasMix.Pressure > Atmospherics.GasMinMoles);
|
||||||
|
var showsPressureWarning = !hasCorrectPressure;
|
||||||
|
LowPressureWarning.Visible = showsPressureWarning;
|
||||||
|
Pressure.Text = Loc.GetString("gas-analyzer-window-pressure-val-text",
|
||||||
|
("pressure", $"{msg.GasMix.Pressure:0.00}"));
|
||||||
|
Temperature.Text = Loc.GetString("generic-not-available-shorthand");
|
||||||
|
|
||||||
|
if (hasGas)
|
||||||
|
{
|
||||||
|
var celsius = TemperatureHelpers.KelvinToCelsius(msg.GasMix.Temperature);
|
||||||
|
Temperature.Text = Loc.GetString("gas-analyzer-window-temperature-val-text",
|
||||||
|
("tempK", $"{msg.GasMix.Temperature:0.0}"),
|
||||||
|
("tempC", $"{celsius:0.0}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gas mix segmented bar chart
|
||||||
|
GasMixChart.Clear();
|
||||||
|
GasMixChart.Visible = hasGas;
|
||||||
|
|
||||||
|
if (msg.GasMix.Gases != null)
|
||||||
|
{
|
||||||
|
var totalGasAmount = msg.GasMix.Gases.Sum(gas => gas.Amount);
|
||||||
|
|
||||||
|
foreach (var gas in msg.GasMix.Gases)
|
||||||
|
{
|
||||||
|
var color = Color.FromHex($"#{gas.Color}", Color.White);
|
||||||
|
var percent = gas.Amount / totalGasAmount * 100;
|
||||||
|
var localizedName = Loc.GetString(gas.Name);
|
||||||
|
var tooltip = Loc.GetString("gas-analyzer-window-molarity-percentage-text",
|
||||||
|
("gasName", localizedName),
|
||||||
|
("amount", $"{gas.Amount:0.##}"),
|
||||||
|
("percentage", $"{percent:0.#}"));
|
||||||
|
GasMixChart.AddEntry(gas.Amount, color, tooltip: tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health analyzer
|
||||||
|
var maybePatient = _entityManager.GetEntity(msg.Health.TargetEntity);
|
||||||
|
var hasPatient = msg.Health.TargetEntity.HasValue;
|
||||||
|
var hasDamage = (hasPatient
|
||||||
|
&& _entityManager.TryGetComponent(maybePatient, out DamageableComponent? damageable)
|
||||||
|
&& damageable.TotalDamage > 0);
|
||||||
|
|
||||||
|
NoDamageText.Visible = (hasPatient && !hasDamage);
|
||||||
|
HealthSection.Visible = hasPatient;
|
||||||
|
EjectPatientButton.Disabled = !hasPatient;
|
||||||
|
|
||||||
|
if (hasPatient)
|
||||||
|
HealthAnalyzer.Populate(msg.Health);
|
||||||
|
|
||||||
|
// Reagents
|
||||||
|
float? lowestTempRequirement = null;
|
||||||
|
ReagentId? lowestTempReagent = null;
|
||||||
|
var totalBeakerCapacity = msg.BeakerCapacity ?? 0;
|
||||||
|
var availableQuantity = new FixedPoint2();
|
||||||
|
var injectingQuantity =
|
||||||
|
msg.Injecting?.Aggregate(new FixedPoint2(), (sum, r) => sum + r.Quantity)
|
||||||
|
?? new FixedPoint2(); // Either the sum of the reagent quantities in `msg.Injecting` or zero.
|
||||||
|
var hasBeaker = (msg.Beaker != null);
|
||||||
|
|
||||||
|
ChemicalsChart.Clear();
|
||||||
|
ChemicalsChart.Capacity = (totalBeakerCapacity < 1 ? 50 : (int)totalBeakerCapacity);
|
||||||
|
|
||||||
|
var chartMaxChemsQuantity = ChemicalsChart.Capacity - injectingQuantity; // Ensure space for injection buffer
|
||||||
|
|
||||||
|
if (hasBeaker)
|
||||||
|
{
|
||||||
|
foreach (var (reagent, quantity) in msg.Beaker!)
|
||||||
|
{
|
||||||
|
availableQuantity += quantity;
|
||||||
|
|
||||||
|
// Make sure we don't add too many chemicals to the chart, so that there's still enough space to
|
||||||
|
// visualize the injection buffer.
|
||||||
|
var chemsQuantityOvershoot = FixedPoint2.Max(0, availableQuantity - chartMaxChemsQuantity);
|
||||||
|
var chartQuantity = FixedPoint2.Max(0, quantity - chemsQuantityOvershoot);
|
||||||
|
|
||||||
|
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
|
||||||
|
ChemicalsChart.SetEntry(
|
||||||
|
reagent.Prototype,
|
||||||
|
reagentProto.LocalizedName,
|
||||||
|
(float)chartQuantity,
|
||||||
|
reagentProto.SubstanceColor,
|
||||||
|
tooltip: $"{quantity}u {reagentProto.LocalizedName}"
|
||||||
|
);
|
||||||
|
|
||||||
|
var temp = TryFindMaxTemperatureRequirement(reagent);
|
||||||
|
if (lowestTempRequirement == null
|
||||||
|
|| temp < lowestTempRequirement)
|
||||||
|
{
|
||||||
|
lowestTempRequirement = temp;
|
||||||
|
lowestTempReagent = reagent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (injectingQuantity != 0)
|
||||||
|
{
|
||||||
|
var injectingText = (injectingQuantity > 1 ? $"{injectingQuantity}u" : "");
|
||||||
|
ChemicalsChart.SetEntry(
|
||||||
|
"injecting",
|
||||||
|
injectingText,
|
||||||
|
(float)injectingQuantity,
|
||||||
|
Color.MediumSpringGreen,
|
||||||
|
tooltip: Loc.GetString("cryo-pod-window-chems-injecting-tooltip",
|
||||||
|
("quantity", injectingQuantity))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var isBeakerEmpty = (injectingQuantity + availableQuantity == 0);
|
||||||
|
var isChemicalsChartVisible = (hasBeaker || injectingQuantity != 0);
|
||||||
|
NoBeakerText.Visible = !isChemicalsChartVisible;
|
||||||
|
ChemicalsChart.Visible = isChemicalsChartVisible;
|
||||||
|
Inject1.Disabled = (!hasPatient || availableQuantity < 0.1f);
|
||||||
|
Inject5.Disabled = (!hasPatient || availableQuantity <= 1);
|
||||||
|
Inject10.Disabled = (!hasPatient || availableQuantity <= 5);
|
||||||
|
Inject20.Disabled = (!hasPatient || availableQuantity <= 10);
|
||||||
|
EjectBeakerButton.Disabled = !hasBeaker;
|
||||||
|
|
||||||
|
// Temperature warning
|
||||||
|
var hasCorrectTemperature = (lowestTempRequirement == null || lowestTempRequirement > msg.GasMix.Temperature);
|
||||||
|
var showsTemperatureWarning = (!showsPressureWarning && !hasCorrectTemperature);
|
||||||
|
|
||||||
|
HighTemperatureWarning.Visible = showsTemperatureWarning;
|
||||||
|
|
||||||
|
if (showsTemperatureWarning)
|
||||||
|
{
|
||||||
|
var reagentName = _prototypeManager.Index<ReagentPrototype>(lowestTempReagent!.Value.Prototype)
|
||||||
|
.LocalizedName;
|
||||||
|
HighTemperatureWarningText.Text = Loc.GetString("cryo-pod-window-high-temperature-warning",
|
||||||
|
("reagent", reagentName),
|
||||||
|
("temperature", lowestTempRequirement!));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status checklist
|
||||||
|
const float fallbackTemperatureRequirement = 213;
|
||||||
|
var hasTemperatureCheck = (hasGas && hasCorrectTemperature
|
||||||
|
&& (lowestTempRequirement != null || msg.GasMix.Temperature < fallbackTemperatureRequirement));
|
||||||
|
var hasChemicals = (hasBeaker && !isBeakerEmpty);
|
||||||
|
|
||||||
|
UpdateChecklistItem(PressureCheck, Loc.GetString("cryo-pod-window-checklist-pressure"), hasCorrectPressure);
|
||||||
|
UpdateChecklistItem(ChemicalsCheck, Loc.GetString("cryo-pod-window-checklist-chemicals"), hasChemicals);
|
||||||
|
UpdateChecklistItem(TemperatureCheck, Loc.GetString("cryo-pod-window-checklist-temperature"), hasTemperatureCheck);
|
||||||
|
|
||||||
|
var isReady = (hasCorrectPressure && hasChemicals && hasTemperatureCheck);
|
||||||
|
var isCooling = (lowestTempRequirement != null && hasPatient
|
||||||
|
&& msg.Health.Temperature > lowestTempRequirement);
|
||||||
|
var isInjecting = (injectingQuantity > 0);
|
||||||
|
StatusLabel.Text = (!isReady ? Loc.GetString("cryo-pod-window-status-not-ready") :
|
||||||
|
isCooling ? Loc.GetString("cryo-pod-window-status-cooling") :
|
||||||
|
isInjecting ? Loc.GetString("cryo-pod-window-status-injecting") :
|
||||||
|
hasPatient ? Loc.GetString("cryo-pod-window-status-ready-to-inject") :
|
||||||
|
Loc.GetString("cryo-pod-window-status-ready-for-patient"));
|
||||||
|
StatusLabel.FontColorOverride = (isReady ? Color.DeepSkyBlue : Color.Orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateChecklistItem(Label label, string text, bool isOkay)
|
||||||
|
{
|
||||||
|
label.Text = (isOkay ? text : Loc.GetString("cryo-pod-window-checklist-fail", ("item", text)));
|
||||||
|
label.FontColorOverride = (isOkay ? null : Color.Orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float? TryFindMaxTemperatureRequirement(ReagentId reagent)
|
||||||
|
{
|
||||||
|
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent.Prototype);
|
||||||
|
if (reagentProto.Metabolisms == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
float? result = null;
|
||||||
|
|
||||||
|
foreach (var (_, metabolism) in reagentProto.Metabolisms)
|
||||||
|
{
|
||||||
|
foreach (var effect in metabolism.Effects)
|
||||||
|
{
|
||||||
|
if (effect.Conditions == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var condition in effect.Conditions)
|
||||||
|
{
|
||||||
|
// If there are multiple temperature conditions in the same reagent (which could hypothetically
|
||||||
|
// happen, although it currently doesn't), we return the lowest max temperature.
|
||||||
|
if (condition is TemperatureCondition tempCondition
|
||||||
|
&& float.IsFinite(tempCondition.Max)
|
||||||
|
&& (result == null || tempCondition.Max < result))
|
||||||
|
{
|
||||||
|
result = tempCondition.Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEjectErrorVisible(bool isVisible)
|
||||||
|
{
|
||||||
|
EjectError.Visible = isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||||
|
{
|
||||||
|
const float antiJiggleSlackSpace = 80;
|
||||||
|
var oldSize = DesiredSize;
|
||||||
|
var newSize = base.MeasureOverride(availableSize);
|
||||||
|
|
||||||
|
// Reduce how often the height of the window jiggles
|
||||||
|
if (newSize.Y < oldSize.Y && newSize.Y + antiJiggleSlackSpace > oldSize.Y)
|
||||||
|
newSize.Y = oldSize.Y;
|
||||||
|
|
||||||
|
return newSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
using Content.Shared.Medical;
|
||||||
|
|
||||||
|
namespace Content.Client.Medical;
|
||||||
|
|
||||||
|
public sealed class DefibrillatorSystem : SharedDefibrillatorSystem;
|
||||||
|
|
@ -41,9 +41,9 @@ public sealed class NetworkConfiguratorLinkOverlay : Overlay
|
||||||
if (!Colors.TryGetValue(uid, out var color))
|
if (!Colors.TryGetValue(uid, out var color))
|
||||||
{
|
{
|
||||||
color = new Color(
|
color = new Color(
|
||||||
_random.Next(0, 255),
|
_random.NextByte(0, 255),
|
||||||
_random.Next(0, 255),
|
_random.NextByte(0, 255),
|
||||||
_random.Next(0, 255));
|
_random.NextByte(0, 255));
|
||||||
Colors.Add(uid, color);
|
Colors.Add(uid, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,6 @@
|
||||||
<ui:OptionDropDown Name="DropDownPlayerTabColorSetting" Title="{Loc 'ui-options-admin-player-tab-color-setting'}" />
|
<ui:OptionDropDown Name="DropDownPlayerTabColorSetting" Title="{Loc 'ui-options-admin-player-tab-color-setting'}" />
|
||||||
<CheckBox Name="EnablePlayerTabMarkGhosted" Text="{Loc 'ui-options-admin-player-tab-mark-ghosted'}" ToolTip="{Loc 'ui-options-admin-player-tab-mark-ghosted-tooltip'}" /> <!-- DeltaV - Add MarkGhosted -->
|
<CheckBox Name="EnablePlayerTabMarkGhosted" Text="{Loc 'ui-options-admin-player-tab-mark-ghosted'}" ToolTip="{Loc 'ui-options-admin-player-tab-mark-ghosted-tooltip'}" /> <!-- DeltaV - Add MarkGhosted -->
|
||||||
<CheckBox Name="EnablePlayerTabMarkWatchlisted" Text="{Loc 'ui-options-admin-player-tab-mark-watchlisted'}" ToolTip="{Loc 'ui-options-admin-player-tab-mark-watchlisted-tooltip'}" /> <!-- DeltaV - Add MarkWatchlisted -->
|
<CheckBox Name="EnablePlayerTabMarkWatchlisted" Text="{Loc 'ui-options-admin-player-tab-mark-watchlisted'}" ToolTip="{Loc 'ui-options-admin-player-tab-mark-watchlisted-tooltip'}" /> <!-- DeltaV - Add MarkWatchlisted -->
|
||||||
<Label Text="{Loc 'ui-options-admin-logs-title'}"
|
|
||||||
StyleClasses="LabelKeyText"/>
|
|
||||||
<ui:OptionColorSlider Name="ColorSliderLogsHighlight" Title="{Loc 'ui-options-admin-logs-highlight-color'}" />
|
|
||||||
<Label Text="{Loc 'ui-options-admin-overlay-title'}"
|
<Label Text="{Loc 'ui-options-admin-overlay-title'}"
|
||||||
StyleClasses="LabelKeyText"/>
|
StyleClasses="LabelKeyText"/>
|
||||||
<ui:OptionDropDown Name="DropDownOverlayAntagFormat" Title="{Loc 'ui-options-admin-overlay-antag-format'}" />
|
<ui:OptionDropDown Name="DropDownOverlayAntagFormat" Title="{Loc 'ui-options-admin-overlay-antag-format'}" />
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,6 @@ public sealed partial class AdminOptionsTab : Control
|
||||||
playerTabSymbolSettings.Add(new OptionDropDownCVar<string>.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-symbol-setting-{setting.ToString()!.ToLower()}")));
|
playerTabSymbolSettings.Add(new OptionDropDownCVar<string>.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-symbol-setting-{setting.ToString()!.ToLower()}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
Control.AddOptionColorSlider(CCVars.AdminLogsHighlightColor, ColorSliderLogsHighlight);
|
|
||||||
|
|
||||||
Control.AddOptionDropDown(CCVars.AdminPlayerTabSymbolSetting, DropDownPlayerTabSymbolSetting, playerTabSymbolSettings);
|
Control.AddOptionDropDown(CCVars.AdminPlayerTabSymbolSetting, DropDownPlayerTabSymbolSetting, playerTabSymbolSettings);
|
||||||
Control.AddOptionDropDown(CCVars.AdminPlayerTabRoleSetting, DropDownPlayerTabRoleSetting, playerTabRoleSettings);
|
Control.AddOptionDropDown(CCVars.AdminPlayerTabRoleSetting, DropDownPlayerTabRoleSetting, playerTabRoleSettings);
|
||||||
Control.AddOptionDropDown(CCVars.AdminPlayerTabColorSetting, DropDownPlayerTabColorSetting, playerTabColorSettings);
|
Control.AddOptionDropDown(CCVars.AdminPlayerTabColorSetting, DropDownPlayerTabColorSetting, playerTabColorSettings);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using Content.Shared.Inventory.Events;
|
using Content.Shared.Inventory.Events;
|
||||||
using Content.Shared.Overlays;
|
using Content.Shared.Overlays;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using System.Linq;
|
|
||||||
using Robust.Client.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Overlays;
|
namespace Content.Client.Overlays;
|
||||||
|
|
@ -35,6 +33,9 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
|
||||||
{
|
{
|
||||||
base.UpdateInternal(component);
|
base.UpdateInternal(component);
|
||||||
|
|
||||||
|
_overlay.DamageContainers.Clear();
|
||||||
|
_overlay.StatusIcon = null;
|
||||||
|
|
||||||
foreach (var comp in component.Components)
|
foreach (var comp in component.Components)
|
||||||
{
|
{
|
||||||
foreach (var damageContainerId in comp.DamageContainers)
|
foreach (var damageContainerId in comp.DamageContainers)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using Content.Shared.Overlays;
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Content.Shared.StatusIcon.Components;
|
using Content.Shared.StatusIcon.Components;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.Damage.Components;
|
using Content.Shared.Damage.Components;
|
||||||
using Content.Shared.Mobs; // DeltaV - Nuke Health icons.
|
using Content.Shared.Mobs; // DeltaV - Nuke Health icons.
|
||||||
|
|
||||||
|
|
@ -33,9 +32,13 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
|
||||||
{
|
{
|
||||||
base.UpdateInternal(component);
|
base.UpdateInternal(component);
|
||||||
|
|
||||||
foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers))
|
DamageContainers.Clear();
|
||||||
|
foreach (var comp in component.Components)
|
||||||
{
|
{
|
||||||
DamageContainers.Add(damageContainerId);
|
foreach (var damageContainerId in comp.DamageContainers)
|
||||||
|
{
|
||||||
|
DamageContainers.Add(damageContainerId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Content.Client.RichText;
|
||||||
using Content.Shared.Paper;
|
using Content.Shared.Paper;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
|
@ -38,16 +39,6 @@ namespace Content.Client.Paper.UI
|
||||||
// we're able to resize this UI or not. Default to everything enabled:
|
// we're able to resize this UI or not. Default to everything enabled:
|
||||||
private DragMode _allowedResizeModes = ~DragMode.None;
|
private DragMode _allowedResizeModes = ~DragMode.None;
|
||||||
|
|
||||||
private readonly Type[] _allowedTags = new Type[] {
|
|
||||||
typeof(BoldItalicTag),
|
|
||||||
typeof(BoldTag),
|
|
||||||
typeof(BulletTag),
|
|
||||||
typeof(ColorTag),
|
|
||||||
typeof(HeadingTag),
|
|
||||||
typeof(ItalicTag),
|
|
||||||
typeof(MonoTag)
|
|
||||||
};
|
|
||||||
|
|
||||||
public event Action<string>? OnSaved;
|
public event Action<string>? OnSaved;
|
||||||
public event Action? Typing; // DeltaV
|
public event Action? Typing; // DeltaV
|
||||||
public event Action? SubmitPressed; // DeltaV
|
public event Action? SubmitPressed; // DeltaV
|
||||||
|
|
@ -284,7 +275,7 @@ namespace Content.Client.Paper.UI
|
||||||
{
|
{
|
||||||
msg.AddMarkupPermissive("\r\n");
|
msg.AddMarkupPermissive("\r\n");
|
||||||
}
|
}
|
||||||
WrittenTextLabel.SetMessage(msg, _allowedTags, DefaultTextColor);
|
WrittenTextLabel.SetMessage(msg, UserFormattableTags.BaseAllowedTags, DefaultTextColor);
|
||||||
|
|
||||||
WrittenTextLabel.Visible = !isEditing && state.Text.Length > 0;
|
WrittenTextLabel.Visible = !isEditing && state.Text.Length > 0;
|
||||||
BlankPaperIndicator.Visible = !isEditing && state.Text.Length == 0;
|
BlankPaperIndicator.Visible = !isEditing && state.Text.Length == 0;
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,13 @@ public sealed class ParallaxManager : IParallaxManager
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
layers = await Task.WhenAll(
|
// Explicitly allocate params array to avoid sandbox violation since C# 14.
|
||||||
|
var tasks = new[]
|
||||||
|
{
|
||||||
LoadParallaxLayers(parallaxPrototype.Layers, loadedLayers, cancel),
|
LoadParallaxLayers(parallaxPrototype.Layers, loadedLayers, cancel),
|
||||||
LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel)
|
LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel),
|
||||||
);
|
};
|
||||||
|
layers = await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel.ThrowIfCancellationRequested();
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,11 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
|
||||||
base.Open();
|
base.Open();
|
||||||
EntityUid? gridUid = null;
|
EntityUid? gridUid = null;
|
||||||
|
|
||||||
if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
|
if (EntMan.TryGetComponent<StationMapComponent>(Owner, out var comp) && comp.TargetGrid != null)
|
||||||
|
{
|
||||||
|
gridUid = comp.TargetGrid;
|
||||||
|
}
|
||||||
|
else if (EntMan.TryGetComponent<TransformComponent>(Owner, out var xform))
|
||||||
{
|
{
|
||||||
gridUid = xform.GridUid;
|
gridUid = xform.GridUid;
|
||||||
}
|
}
|
||||||
|
|
@ -30,8 +34,8 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
stationName = gridMetaData.EntityName;
|
stationName = gridMetaData.EntityName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EntMan.TryGetComponent<StationMapComponent>(Owner, out var comp) && comp.ShowLocation)
|
if (comp != null && comp.ShowLocation)
|
||||||
_window.Set(stationName, gridUid, Owner);
|
_window.Set(stationName, gridUid, Owner);
|
||||||
else
|
else
|
||||||
_window.Set(stationName, gridUid, null);
|
_window.Set(stationName, gridUid, null);
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,6 @@ namespace Content.Client.Power;
|
||||||
/// Remains in use by portable scrubbers and lathes.
|
/// Remains in use by portable scrubbers and lathes.
|
||||||
public enum PowerDeviceVisualLayers : byte
|
public enum PowerDeviceVisualLayers : byte
|
||||||
{
|
{
|
||||||
Powered
|
Powered,
|
||||||
|
Charging
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public sealed class DoorRemoteStatusControl(Entity<DoorRemoteComponent> ent) : C
|
||||||
OperatingMode.OpenClose => "door-remote-open-close-text",
|
OperatingMode.OpenClose => "door-remote-open-close-text",
|
||||||
OperatingMode.ToggleBolts => "door-remote-toggle-bolt-text",
|
OperatingMode.ToggleBolts => "door-remote-toggle-bolt-text",
|
||||||
OperatingMode.ToggleEmergencyAccess => "door-remote-emergency-access-text",
|
OperatingMode.ToggleEmergencyAccess => "door-remote-emergency-access-text",
|
||||||
|
OperatingMode.ToggleOvercharge => "door-remote-toggle-eletrify-text",
|
||||||
_ => "door-remote-invalid-text"
|
_ => "door-remote-invalid-text"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
using Content.Client.UserInterface.RichText;
|
||||||
|
using Robust.Client.UserInterface.RichText;
|
||||||
|
|
||||||
|
namespace Content.Client.RichText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains rules for what markup tags are allowed to be used by players.
|
||||||
|
/// </summary>
|
||||||
|
public static class UserFormattableTags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The basic set of "rich text" formatting tags that shouldn't cause any issues.
|
||||||
|
/// Limit user rich text to these by default.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Type[] BaseAllowedTags =
|
||||||
|
[
|
||||||
|
typeof(BoldItalicTag),
|
||||||
|
typeof(BoldTag),
|
||||||
|
typeof(BulletTag),
|
||||||
|
typeof(ColorTag),
|
||||||
|
typeof(HeadingTag),
|
||||||
|
typeof(ItalicTag),
|
||||||
|
typeof(MonoTag),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
using Content.Shared.Rootable;
|
|
||||||
|
|
||||||
namespace Content.Client.Rootable;
|
|
||||||
|
|
||||||
public sealed class RootableSystem : SharedRootableSystem;
|
|
||||||
|
|
@ -33,8 +33,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||||
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
|
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
|
||||||
!_mobState.IsDead(uid) &&
|
!_mobState.IsDead(uid) &&
|
||||||
!HasComp<ActiveNPCComponent>(uid) &&
|
!HasComp<ActiveNPCComponent>(uid) &&
|
||||||
TryComp<MindContainerComponent>(uid, out var mindContainer) &&
|
HasComp<MindExaminableComponent>(uid))
|
||||||
mindContainer.ShowExamineInfo)
|
|
||||||
{
|
{
|
||||||
// Begin DeltaV Addition
|
// Begin DeltaV Addition
|
||||||
var ev = new ShowSSDIndicatorEvent();
|
var ev = new ShowSSDIndicatorEvent();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public sealed class GenpopLockerBoundUserInterface(EntityUid owner, Enum uiKey)
|
||||||
|
|
||||||
_menu.OnConfigurationComplete += (name, time, crime) =>
|
_menu.OnConfigurationComplete += (name, time, crime) =>
|
||||||
{
|
{
|
||||||
SendMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
|
SendPredictedMessage(new GenpopLockerIdConfiguredMessage(name, time, crime));
|
||||||
Close();
|
Close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
|
||||||
|
|
||||||
_window = this.CreateWindowCenteredLeft<IFFConsoleWindow>();
|
_window = this.CreateWindowCenteredLeft<IFFConsoleWindow>();
|
||||||
_window.ShowIFF += SendIFFMessage;
|
_window.ShowIFF += SendIFFMessage;
|
||||||
_window.ShowVessel += SendVesselMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
|
@ -44,14 +43,6 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendVesselMessage(bool obj)
|
|
||||||
{
|
|
||||||
SendMessage(new IFFShowVesselMessage()
|
|
||||||
{
|
|
||||||
Show = obj,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,6 @@
|
||||||
<Button Name="ShowIFFOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
|
<Button Name="ShowIFFOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
|
||||||
<Button Name="ShowIFFOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
|
<Button Name="ShowIFFOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|
||||||
<Label Name="ShowVesselLabel" Text="{Loc 'iff-console-show-vessel-label'}" HorizontalExpand="True" StyleClasses="highlight" />
|
|
||||||
<BoxContainer Orientation="Horizontal" MinWidth="120">
|
|
||||||
<Button Name="ShowVesselOnButton" Text="{Loc 'iff-console-on'}" StyleClasses="OpenRight" />
|
|
||||||
<Button Name="ShowVesselOffButton" Text="{Loc 'iff-console-off'}" StyleClasses="OpenLeft" />
|
|
||||||
</BoxContainer>
|
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</controls:FancyWindow>
|
</controls:FancyWindow>
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
|
||||||
IComputerWindow<IFFConsoleBoundUserInterfaceState>
|
IComputerWindow<IFFConsoleBoundUserInterfaceState>
|
||||||
{
|
{
|
||||||
private readonly ButtonGroup _showIFFButtonGroup = new();
|
private readonly ButtonGroup _showIFFButtonGroup = new();
|
||||||
private readonly ButtonGroup _showVesselButtonGroup = new();
|
|
||||||
public event Action<bool>? ShowIFF;
|
public event Action<bool>? ShowIFF;
|
||||||
public event Action<bool>? ShowVessel;
|
|
||||||
|
|
||||||
public IFFConsoleWindow()
|
public IFFConsoleWindow()
|
||||||
{
|
{
|
||||||
|
|
@ -24,11 +22,6 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
|
||||||
ShowIFFOnButton.Group = _showIFFButtonGroup;
|
ShowIFFOnButton.Group = _showIFFButtonGroup;
|
||||||
ShowIFFOnButton.OnPressed += args => ShowIFFPressed(true);
|
ShowIFFOnButton.OnPressed += args => ShowIFFPressed(true);
|
||||||
ShowIFFOffButton.OnPressed += args => ShowIFFPressed(false);
|
ShowIFFOffButton.OnPressed += args => ShowIFFPressed(false);
|
||||||
|
|
||||||
ShowVesselOffButton.Group = _showVesselButtonGroup;
|
|
||||||
ShowVesselOnButton.Group = _showVesselButtonGroup;
|
|
||||||
ShowVesselOnButton.OnPressed += args => ShowVesselPressed(true);
|
|
||||||
ShowVesselOffButton.OnPressed += args => ShowVesselPressed(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowIFFPressed(bool pressed)
|
private void ShowIFFPressed(bool pressed)
|
||||||
|
|
@ -36,19 +29,14 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
|
||||||
ShowIFF?.Invoke(pressed);
|
ShowIFF?.Invoke(pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowVesselPressed(bool pressed)
|
|
||||||
{
|
|
||||||
ShowVessel?.Invoke(pressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateState(IFFConsoleBoundUserInterfaceState state)
|
public void UpdateState(IFFConsoleBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0)
|
if ((state.AllowedFlags & IFFFlags.HideLabel) != 0x0 || (state.AllowedFlags & IFFFlags.Hide) != 0x0)
|
||||||
{
|
{
|
||||||
ShowIFFOffButton.Disabled = false;
|
ShowIFFOffButton.Disabled = false;
|
||||||
ShowIFFOnButton.Disabled = false;
|
ShowIFFOnButton.Disabled = false;
|
||||||
|
|
||||||
if ((state.Flags & IFFFlags.HideLabel) != 0x0)
|
if ((state.Flags & IFFFlags.HideLabel) != 0x0 || (state.Flags & IFFFlags.Hide) != 0x0)
|
||||||
{
|
{
|
||||||
ShowIFFOffButton.Pressed = true;
|
ShowIFFOffButton.Pressed = true;
|
||||||
}
|
}
|
||||||
|
|
@ -62,25 +50,5 @@ public sealed partial class IFFConsoleWindow : FancyWindow,
|
||||||
ShowIFFOffButton.Disabled = true;
|
ShowIFFOffButton.Disabled = true;
|
||||||
ShowIFFOnButton.Disabled = true;
|
ShowIFFOnButton.Disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((state.AllowedFlags & IFFFlags.Hide) != 0x0)
|
|
||||||
{
|
|
||||||
ShowVesselOffButton.Disabled = false;
|
|
||||||
ShowVesselOnButton.Disabled = false;
|
|
||||||
|
|
||||||
if ((state.Flags & IFFFlags.Hide) != 0x0)
|
|
||||||
{
|
|
||||||
ShowVesselOffButton.Pressed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowVesselOnButton.Pressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowVesselOffButton.Disabled = true;
|
|
||||||
ShowVesselOnButton.Disabled = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ public sealed partial class MapScreen : BoxContainer
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsFTLBlocked())
|
if (IsPingBlocked())
|
||||||
{
|
{
|
||||||
MapRebuildButton.Disabled = true;
|
MapRebuildButton.Disabled = true;
|
||||||
ClearMapObjects();
|
ClearMapObjects();
|
||||||
|
|
@ -408,9 +408,21 @@ public sealed partial class MapScreen : BoxContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if we shouldn't be able to select the Scan for Objects button.
|
||||||
|
/// </summary>
|
||||||
|
private bool IsPingBlocked()
|
||||||
|
{
|
||||||
|
return _state switch
|
||||||
|
{
|
||||||
|
FTLState.Available or FTLState.Cooldown => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMapObjectPress(IMapObject mapObject)
|
private void OnMapObjectPress(IMapObject mapObject)
|
||||||
{
|
{
|
||||||
if (IsFTLBlocked())
|
if (IsPingBlocked())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var coordinates = _shuttles.GetMapCoordinates(mapObject);
|
var coordinates = _shuttles.GetMapCoordinates(mapObject);
|
||||||
|
|
@ -506,7 +518,7 @@ public sealed partial class MapScreen : BoxContainer
|
||||||
BumpMapDequeue();
|
BumpMapDequeue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsFTLBlocked() && _nextPing < curTime)
|
if (!IsPingBlocked() && _nextPing < curTime)
|
||||||
{
|
{
|
||||||
MapRebuildButton.Disabled = false;
|
MapRebuildButton.Disabled = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We'll send the "adjusted" position and server will adjust it back when relevant.
|
// We'll send the "adjusted" position and server will adjust it back when relevant.
|
||||||
var mapCoords = new MapCoordinates(InverseMapPosition(args.RelativePosition), ViewingMap);
|
var mapCoords = new MapCoordinates(InverseMapPosition(args.RelativePixelPosition), ViewingMap);
|
||||||
RequestFTL?.Invoke(mapCoords, _ftlAngle);
|
RequestFTL?.Invoke(mapCoords, _ftlAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +180,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
|
||||||
|
|
||||||
// Remove offset so we can floor.
|
// Remove offset so we can floor.
|
||||||
var botLeft = new Vector2(0f, 0f);
|
var botLeft = new Vector2(0f, 0f);
|
||||||
var topRight = botLeft + Size;
|
var topRight = botLeft + PixelSize;
|
||||||
|
|
||||||
var flooredBL = botLeft - originBL;
|
var flooredBL = botLeft - originBL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
StyleClasses="PanelLight"
|
StyleClasses="PanelLight"
|
||||||
Margin="5 5 5 0">
|
Margin="5 5 5 0">
|
||||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 5 0 5">
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 5 0 5">
|
||||||
|
<TextureButton Name="RemoveButton" VerticalAlignment="Top" HorizontalAlignment="Left" Scale="0.5 0.5"/>
|
||||||
<SpriteView Name="ModuleView" Margin="0 0 5 0"/>
|
<SpriteView Name="ModuleView" Margin="0 0 5 0"/>
|
||||||
<BoxContainer RectClipContent="True" HorizontalExpand="True">
|
<BoxContainer RectClipContent="True" HorizontalExpand="True">
|
||||||
<Label Name="ModuleName" HorizontalExpand="True" HorizontalAlignment="Center"/>
|
<Label Name="ModuleName" HorizontalExpand="True" HorizontalAlignment="Center"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<TextureButton Name="RemoveButton" VerticalAlignment="Top" HorizontalAlignment="Right" Scale="0.5 0.5"/>
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,5 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using Content.Shared.Storage.EntitySystems;
|
||||||
using Content.Client.Storage.Components;
|
|
||||||
using Content.Shared.Destructible;
|
|
||||||
using Content.Shared.Foldable;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Lock;
|
|
||||||
using Content.Shared.Movement.Events;
|
|
||||||
using Content.Shared.Storage.Components;
|
|
||||||
using Content.Shared.Storage.EntitySystems;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
|
|
||||||
namespace Content.Client.Storage.Systems;
|
namespace Content.Client.Storage.Systems;
|
||||||
|
|
||||||
public sealed class EntityStorageSystem : SharedEntityStorageSystem
|
public sealed class EntityStorageSystem : SharedEntityStorageSystem;
|
||||||
{
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, EntityUnpausedEvent>(OnEntityUnpausedEvent);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, ComponentInit>(OnComponentInit);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, ComponentStartup>(OnComponentStartup);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, ActivateInWorldEvent>(OnInteract, after: new[] { typeof(LockSystem) });
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, DestructionEventArgs>(OnDestruction);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, FoldAttemptEvent>(OnFoldAttempt);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, ComponentGetState>(OnGetState);
|
|
||||||
SubscribeLocalEvent<EntityStorageComponent, ComponentHandleState>(OnHandleState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
|
|
||||||
{
|
|
||||||
if (component != null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
TryComp<EntityStorageComponent>(uid, out var storage);
|
|
||||||
component = storage;
|
|
||||||
return component != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ public static class ColorExtensions
|
||||||
{
|
{
|
||||||
DebugTools.Assert(lightness is >= 0.0f and <= 1.0f);
|
DebugTools.Assert(lightness is >= 0.0f and <= 1.0f);
|
||||||
|
|
||||||
var oklab = Color.ToLab(c);
|
var oklab = c.LabFromSrgb();
|
||||||
oklab.X = lightness;
|
oklab.X = lightness;
|
||||||
|
|
||||||
return Color.FromLab(oklab);
|
return oklab.LabToSrgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -25,10 +25,10 @@ public static class ColorExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Color NudgeLightness(this Color c, float lightnessShift)
|
public static Color NudgeLightness(this Color c, float lightnessShift)
|
||||||
{
|
{
|
||||||
var oklab = Color.ToLab(c);
|
var oklab = c.LabFromSrgb();
|
||||||
oklab.X = Math.Clamp(oklab.X + lightnessShift, 0, 1);
|
oklab.X = Math.Clamp(oklab.X + lightnessShift, 0, 1);
|
||||||
|
|
||||||
return Color.FromLab(oklab);
|
return oklab.LabToSrgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -39,12 +39,12 @@ public static class ColorExtensions
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static Color NudgeChroma(this Color c, float chromaShift)
|
public static Color NudgeChroma(this Color c, float chromaShift)
|
||||||
{
|
{
|
||||||
var oklab = Color.ToLab(c);
|
var oklab = c.LabFromSrgb();
|
||||||
var oklch = Color.ToLch(oklab);
|
var oklch = Color.ToLch(oklab);
|
||||||
|
|
||||||
oklch.Y = Math.Clamp(oklch.Y + chromaShift, 0, 1);
|
oklch.Y = Math.Clamp(oklch.Y + chromaShift, 0, 1);
|
||||||
|
|
||||||
return Color.FromLab(Color.FromLch(oklch));
|
return Color.FromLch(oklch).LabToSrgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -54,10 +54,43 @@ public static class ColorExtensions
|
||||||
{
|
{
|
||||||
DebugTools.Assert(factor is >= 0.0f and <= 1.0f);
|
DebugTools.Assert(factor is >= 0.0f and <= 1.0f);
|
||||||
|
|
||||||
var okFrom = Color.ToLab(from);
|
var okFrom = from.LabFromSrgb();
|
||||||
var okTo = Color.ToLab(to);
|
var okTo = to.LabFromSrgb();
|
||||||
|
|
||||||
var blended = Vector4.Lerp(okFrom, okTo, factor);
|
var blended = Vector4.Lerp(okFrom, okTo, factor);
|
||||||
return Color.FromLab(blended);
|
return blended.LabToSrgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a nonlinear sRGB ("normal") color to OkLAB.
|
||||||
|
/// </summary>
|
||||||
|
public static Vector4 LabFromSrgb(this Color from)
|
||||||
|
{
|
||||||
|
return Color.ToLab(Color.FromSrgb(from));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts OkLAB to a nonlinear sRGB ("normal") color.
|
||||||
|
/// </summary>
|
||||||
|
public static Color LabToSrgb(this Vector4 from)
|
||||||
|
{
|
||||||
|
return Color.ToSrgb(Color.FromLab(from).SimpleClipGamut());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clips the gamut of the color so that all color channels are in the range 0 -> 1.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This uses no clever perceptual techniques, it literally just clamps the individual channels.
|
||||||
|
/// </remarks>
|
||||||
|
public static Color SimpleClipGamut(this Color from)
|
||||||
|
{
|
||||||
|
return new Color
|
||||||
|
{
|
||||||
|
R = Math.Clamp(from.R, 0, 1),
|
||||||
|
G = Math.Clamp(from.G, 0, 1),
|
||||||
|
B = Math.Clamp(from.B, 0, 1),
|
||||||
|
A = from.A,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,10 @@ public sealed class LabelSheetlet : Sheetlet<PalettedStylesheet>
|
||||||
.Class(StyleClass.LabelMonospaceText)
|
.Class(StyleClass.LabelMonospaceText)
|
||||||
.Prop(Label.StylePropertyFont, robotoMonoBold11),
|
.Prop(Label.StylePropertyFont, robotoMonoBold11),
|
||||||
E<Label>()
|
E<Label>()
|
||||||
.Class(StyleClass.LabelMonospaceHeading)
|
.Class(StyleClass.LabelMonospaceSubHeading)
|
||||||
.Prop(Label.StylePropertyFont, robotoMonoBold12),
|
.Prop(Label.StylePropertyFont, robotoMonoBold12),
|
||||||
E<Label>()
|
E<Label>()
|
||||||
.Class(StyleClass.LabelMonospaceSubHeading)
|
.Class(StyleClass.LabelMonospaceHeading)
|
||||||
.Prop(Label.StylePropertyFont, robotoMonoBold14),
|
.Prop(Label.StylePropertyFont, robotoMonoBold14),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ public static class StyleClass
|
||||||
public const string LabelKeyText = "LabelKeyText";
|
public const string LabelKeyText = "LabelKeyText";
|
||||||
public const string LabelWeak = "LabelWeak"; // replaces `LabelSecondaryColor`
|
public const string LabelWeak = "LabelWeak"; // replaces `LabelSecondaryColor`
|
||||||
public const string LabelMonospaceText = "ConsoleText";
|
public const string LabelMonospaceText = "ConsoleText";
|
||||||
public const string LabelMonospaceHeading = "ConsoleText";
|
public const string LabelMonospaceHeading = "ConsoleHeading";
|
||||||
public const string LabelMonospaceSubHeading = "ConsoleText";
|
public const string LabelMonospaceSubHeading = "ConsoleSubHeading";
|
||||||
|
|
||||||
public const string BackgroundPanel = "BackgroundPanel"; // replaces `AngleRect`
|
public const string BackgroundPanel = "BackgroundPanel"; // replaces `AngleRect`
|
||||||
public const string BackgroundPanelOpenLeft = "BackgroundPanelOpenLeft"; // replaces `BackgroundOpenLeft`
|
public const string BackgroundPanelOpenLeft = "BackgroundPanelOpenLeft"; // replaces `BackgroundOpenLeft`
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,17 @@ public sealed class SurveillanceCameraMonitorBoundUserInterface : BoundUserInter
|
||||||
_window.SubnetRefresh += OnSubnetRefresh;
|
_window.SubnetRefresh += OnSubnetRefresh;
|
||||||
_window.CameraSwitchTimer += OnCameraSwitchTimer;
|
_window.CameraSwitchTimer += OnCameraSwitchTimer;
|
||||||
_window.CameraDisconnect += OnCameraDisconnect;
|
_window.CameraDisconnect += OnCameraDisconnect;
|
||||||
|
|
||||||
|
var xform = EntMan.GetComponent<TransformComponent>(Owner);
|
||||||
|
var gridUid = xform.GridUid ?? xform.MapUid;
|
||||||
|
|
||||||
|
if (gridUid is not null)
|
||||||
|
_window?.SetMap(gridUid.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCameraSelected(string address)
|
private void OnCameraSelected(string address, string? subnet)
|
||||||
{
|
{
|
||||||
SendMessage(new SurveillanceCameraMonitorSwitchMessage(address));
|
SendMessage(new SurveillanceCameraMonitorSwitchMessage(address, subnet));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSubnetRequest(string subnet)
|
private void OnSubnetRequest(string subnet)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,71 @@
|
||||||
<DefaultWindow xmlns="https://spacestation14.io"
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
xmlns:viewport="clr-namespace:Content.Client.Viewport"
|
xmlns:viewport="clr-namespace:Content.Client.Viewport"
|
||||||
|
xmlns:local="clr-namespace:Content.Client.SurveillanceCamera.UI"
|
||||||
Title="{Loc 'surveillance-camera-monitor-ui-window'}">
|
Title="{Loc 'surveillance-camera-monitor-ui-window'}">
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer>
|
||||||
<BoxContainer Orientation="Vertical" MinWidth="350" VerticalExpand="True">
|
<!-- Panel with tabs -->
|
||||||
<!-- lazy -->
|
<BoxContainer Orientation="Vertical" MinWidth="350">
|
||||||
<OptionButton Name="SubnetSelector" />
|
<TabContainer Name="ViewModeTabs" VerticalExpand="True">
|
||||||
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}" />
|
<!-- Camera list tab -->
|
||||||
<ScrollContainer VerticalExpand="True">
|
<BoxContainer Name="{Loc 'surveillance-camera-monitor-ui-tab-list'}" Orientation="Vertical">
|
||||||
<ItemList Name="SubnetList" />
|
<OptionButton Name="SubnetSelector"/>
|
||||||
</ScrollContainer>
|
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}"/>
|
||||||
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}" />
|
<ScrollContainer VerticalExpand="True">
|
||||||
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}" />
|
<ItemList Name="SubnetList"/>
|
||||||
<Label Name="CameraStatus" />
|
</ScrollContainer>
|
||||||
|
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Map view tab -->
|
||||||
|
<BoxContainer Name="{Loc 'surveillance-camera-monitor-ui-tab-map'}" Orientation="Vertical" VerticalExpand="True">
|
||||||
|
<local:SurveillanceCameraNavMapControl Name="CameraMap"
|
||||||
|
VerticalExpand="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinSize="350 350"/>
|
||||||
|
|
||||||
|
<!-- Map legend -->
|
||||||
|
<BoxContainer Name="LegendContainer" Margin="0 10">
|
||||||
|
<TextureRect Stretch="KeepAspectCentered"
|
||||||
|
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
|
||||||
|
Modulate="#FF00FF"
|
||||||
|
SetSize="20 20"
|
||||||
|
Margin="10 0 5 0"/>
|
||||||
|
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-active'}"/>
|
||||||
|
|
||||||
|
<TextureRect Stretch="KeepAspectCentered"
|
||||||
|
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
|
||||||
|
SetSize="20 20"
|
||||||
|
Modulate="#fbff19ff"
|
||||||
|
Margin="10 0 5 0"/>
|
||||||
|
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-selected'}"/>
|
||||||
|
|
||||||
|
<TextureRect Stretch="KeepAspectCentered"
|
||||||
|
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
|
||||||
|
SetSize="20 20"
|
||||||
|
Modulate="#a09f9fff"
|
||||||
|
Margin="10 0 5 0"/>
|
||||||
|
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-inactive'}"/>
|
||||||
|
|
||||||
|
<TextureRect Stretch="KeepAspectCentered"
|
||||||
|
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
|
||||||
|
SetSize="20 20"
|
||||||
|
Modulate="#fa1f1fff"
|
||||||
|
Margin="10 0 5 0"/>
|
||||||
|
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-invalid'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<Button Name="SubnetRefreshButtonMap" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</TabContainer>
|
||||||
|
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<!-- Right panel with camera view -->
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
|
<Label Name="CameraStatus"/>
|
||||||
|
<Control VerticalExpand="True" Margin="5" Name="CameraViewBox">
|
||||||
|
<viewport:ScalingViewport Name="CameraView" MinSize="500 500" MouseFilter="Ignore"/>
|
||||||
|
<TextureRect MinSize="500 500" Name="CameraViewBackground" />
|
||||||
|
</Control>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<Control VerticalExpand="True" HorizontalExpand="True" Margin="5 5 5 5" Name="CameraViewBox">
|
|
||||||
<viewport:ScalingViewport Name="CameraView"
|
|
||||||
VerticalExpand="True"
|
|
||||||
HorizontalExpand="True"
|
|
||||||
MinSize="500 500"
|
|
||||||
MouseFilter="Ignore" />
|
|
||||||
<TextureRect VerticalExpand="True" HorizontalExpand="True" MinSize="500 500" Name="CameraViewBackground" />
|
|
||||||
</Control>
|
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</DefaultWindow>
|
</DefaultWindow>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Content.Client.Resources;
|
||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Shared.DeviceNetwork;
|
using Content.Shared.DeviceNetwork;
|
||||||
using Content.Shared.SurveillanceCamera;
|
using Content.Shared.SurveillanceCamera;
|
||||||
|
using Content.Shared.SurveillanceCamera.Components;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
|
|
@ -21,8 +22,15 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||||
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a camera is selected.
|
||||||
|
/// First parameter contains the camera's address.
|
||||||
|
/// Second optional parameter contains a subnet - if possible, the monitor will switch to this subnet.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<string, string?>? CameraSelected;
|
||||||
|
|
||||||
public event Action<string>? CameraSelected;
|
|
||||||
public event Action<string>? SubnetOpened;
|
public event Action<string>? SubnetOpened;
|
||||||
public event Action? CameraRefresh;
|
public event Action? CameraRefresh;
|
||||||
public event Action? SubnetRefresh;
|
public event Action? SubnetRefresh;
|
||||||
|
|
@ -33,6 +41,7 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||||
private bool _isSwitching;
|
private bool _isSwitching;
|
||||||
private readonly FixedEye _defaultEye = new();
|
private readonly FixedEye _defaultEye = new();
|
||||||
private readonly Dictionary<string, int> _subnetMap = new();
|
private readonly Dictionary<string, int> _subnetMap = new();
|
||||||
|
private EntityUid? _mapUid;
|
||||||
|
|
||||||
private string? SelectedSubnet
|
private string? SelectedSubnet
|
||||||
{
|
{
|
||||||
|
|
@ -68,11 +77,15 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||||
SubnetSelector.OnItemSelected += args =>
|
SubnetSelector.OnItemSelected += args =>
|
||||||
{
|
{
|
||||||
// piss
|
// piss
|
||||||
SubnetOpened!((string) args.Button.GetItemMetadata(args.Id)!);
|
SubnetOpened?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
|
||||||
};
|
};
|
||||||
SubnetRefreshButton.OnPressed += _ => SubnetRefresh!();
|
SubnetRefreshButton.OnPressed += _ => SubnetRefresh?.Invoke();
|
||||||
CameraRefreshButton.OnPressed += _ => CameraRefresh!();
|
SubnetRefreshButtonMap.OnPressed += _ => SubnetRefresh?.Invoke();
|
||||||
CameraDisconnectButton.OnPressed += _ => CameraDisconnect!();
|
CameraRefreshButton.OnPressed += _ => CameraRefresh?.Invoke();
|
||||||
|
CameraDisconnectButton.OnPressed += _ => CameraDisconnect?.Invoke();
|
||||||
|
|
||||||
|
CameraMap.EnableCameraSelection = true;
|
||||||
|
CameraMap.CameraSelected += OnCameraMapSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -80,6 +93,9 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||||
// pass it here so that the UI can change its view.
|
// pass it here so that the UI can change its view.
|
||||||
public void UpdateState(IEye? eye, HashSet<string> subnets, string activeAddress, string activeSubnet, Dictionary<string, string> cameras)
|
public void UpdateState(IEye? eye, HashSet<string> subnets, string activeAddress, string activeSubnet, Dictionary<string, string> cameras)
|
||||||
{
|
{
|
||||||
|
CameraMap.SetActiveCameraAddress(activeAddress);
|
||||||
|
CameraMap.SetAvailableSubnets(subnets);
|
||||||
|
|
||||||
_currentAddress = activeAddress;
|
_currentAddress = activeAddress;
|
||||||
SetCameraView(eye);
|
SetCameraView(eye);
|
||||||
|
|
||||||
|
|
@ -189,6 +205,25 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||||
|
|
||||||
private void OnSubnetListSelect(ItemList.ItemListSelectedEventArgs args)
|
private void OnSubnetListSelect(ItemList.ItemListSelectedEventArgs args)
|
||||||
{
|
{
|
||||||
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!);
|
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMap(EntityUid mapUid)
|
||||||
|
{
|
||||||
|
CameraMap.MapUid = _mapUid = mapUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCameraMapSelected(NetEntity netEntity)
|
||||||
|
{
|
||||||
|
if (_mapUid is null || !_entityManager.TryGetComponent<SurveillanceCameraMapComponent>(_mapUid.Value, out var mapComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!mapComp.Cameras.TryGetValue(netEntity, out var marker) || !marker.Active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(marker.Address))
|
||||||
|
CameraSelected?.Invoke(marker.Address, marker.Subnet);
|
||||||
|
else
|
||||||
|
_entityManager.RaisePredictiveEvent(new RequestCameraMarkerUpdateMessage(netEntity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue