Merge remote-tracking branch 'refs/remotes/wizards/master' into upstream-sync

# Conflicts:
#	Content.Server/Connection/ConnectionManager.cs
#	Resources/Prototypes/Datasets/Names/ai.yml
#	Resources/Prototypes/Datasets/adjectives.yml
#	Resources/Prototypes/Datasets/verbs.yml
#	Resources/Prototypes/Entities/Clothing/Shoes/specific.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml
#	Resources/Prototypes/Entities/Mobs/base.yml
#	Resources/Prototypes/Maps/bagel.yml
#	Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml
#	Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/equipped-HELMET.png
#	Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/icon.png
#	Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/meta.json
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
#	Resources/Textures/Structures/Wallmounts/barsign.rsi/officerbeersky.png
#	Resources/Textures/Structures/Wallmounts/signs.rsi/anomaly.png
#	SpaceStation14.sln
This commit is contained in:
Morb0
2024-08-28 16:40:36 +03:00
560 changed files with 42893 additions and 19486 deletions
-6
View File
@@ -259,12 +259,6 @@ namespace Content.Client.Actions
if (action.ClientExclusive) if (action.ClientExclusive)
{ {
if (instantAction.Event != null)
{
instantAction.Event.Performer = user;
instantAction.Event.Action = actionId;
}
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
} }
else else
@@ -2,10 +2,10 @@ using System.Numerics;
using Content.Client.Administration.Systems; using Content.Client.Administration.Systems;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.GameObjects; using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Administration; namespace Content.Client.Administration;
@@ -15,14 +15,16 @@ internal sealed class AdminNameOverlay : Overlay
private readonly IEntityManager _entityManager; private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager; private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup; private readonly EntityLookupSystem _entityLookup;
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly Font _font; private readonly Font _font;
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup) public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
{ {
_system = system; _system = system;
_entityManager = entityManager; _entityManager = entityManager;
_eyeManager = eyeManager; _eyeManager = eyeManager;
_entityLookup = entityLookup; _entityLookup = entityLookup;
_userInterfaceManager = userInterfaceManager;
ZIndex = 200; ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); _font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
} }
@@ -57,16 +59,18 @@ internal sealed class AdminNameOverlay : Overlay
continue; continue;
} }
var lineoffset = new Vector2(0f, 11f); var uiScale = _userInterfaceManager.RootControl.UIScale;
var lineoffset = new Vector2(0f, 11f) * uiScale;
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center + var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec( new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f); aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
if (playerInfo.Antag) if (playerInfo.Antag)
{ {
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed); args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", uiScale, Color.OrangeRed);
;
} }
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White); args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White); args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
} }
} }
} }
@@ -1,6 +1,8 @@
using Content.Client.Administration.Managers; using Content.Client.Administration.Managers;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
namespace Content.Client.Administration.Systems namespace Content.Client.Administration.Systems
{ {
@@ -11,6 +13,7 @@ namespace Content.Client.Administration.Systems
[Dependency] private readonly IClientAdminManager _adminManager = default!; [Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
private AdminNameOverlay _adminNameOverlay = default!; private AdminNameOverlay _adminNameOverlay = default!;
@@ -19,7 +22,7 @@ namespace Content.Client.Administration.Systems
private void InitializeOverlay() private void InitializeOverlay()
{ {
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup); _adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated; _adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
} }
@@ -1,4 +1,5 @@
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
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;
@@ -11,9 +11,8 @@ 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.Network; using Robust.Shared.Network;
using Robust.Shared.Utility;
using Robust.Shared.Timing;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Bwoink namespace Content.Client.Administration.UI.Bwoink
{ {
@@ -1,4 +1,5 @@
using System.Numerics; using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
@@ -30,7 +30,6 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
_window.AirAlarmModeChanged += OnAirAlarmModeChanged; _window.AirAlarmModeChanged += OnAirAlarmModeChanged;
_window.AutoModeChanged += OnAutoModeChanged; _window.AutoModeChanged += OnAutoModeChanged;
_window.ResyncAllRequested += ResyncAllDevices; _window.ResyncAllRequested += ResyncAllDevices;
_window.AirAlarmTabChange += OnTabChanged;
} }
private void ResyncAllDevices() private void ResyncAllDevices()
@@ -63,11 +62,6 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas)); SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas));
} }
private void OnTabChanged(AirAlarmTab tab)
{
SendMessage(new AirAlarmTabSetMessage(tab));
}
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
{ {
base.UpdateState(state); base.UpdateState(state);
@@ -23,7 +23,6 @@ public sealed partial class AirAlarmWindow : FancyWindow
public event Action<AirAlarmMode>? AirAlarmModeChanged; public event Action<AirAlarmMode>? AirAlarmModeChanged;
public event Action<bool>? AutoModeChanged; public event Action<bool>? AutoModeChanged;
public event Action? ResyncAllRequested; public event Action? ResyncAllRequested;
public event Action<AirAlarmTab>? AirAlarmTabChange;
private RichTextLabel _address => CDeviceAddress; private RichTextLabel _address => CDeviceAddress;
private RichTextLabel _deviceTotal => CDeviceTotal; private RichTextLabel _deviceTotal => CDeviceTotal;
@@ -80,11 +79,6 @@ public sealed partial class AirAlarmWindow : FancyWindow
_tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers")); _tabContainer.SetTabTitle(1, Loc.GetString("air-alarm-ui-window-tab-scrubbers"));
_tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors")); _tabContainer.SetTabTitle(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
_tabContainer.OnTabChanged += idx =>
{
AirAlarmTabChange!((AirAlarmTab) idx);
};
_resyncDevices.OnPressed += _ => _resyncDevices.OnPressed += _ =>
{ {
_ventDevices.RemoveAllChildren(); _ventDevices.RemoveAllChildren();
@@ -117,8 +111,6 @@ public sealed partial class AirAlarmWindow : FancyWindow
{ {
UpdateDeviceData(addr, dev); UpdateDeviceData(addr, dev);
} }
_tabContainer.CurrentTab = (int) state.Tab;
} }
public void UpdateModeSelector(AirAlarmMode mode) public void UpdateModeSelector(AirAlarmMode mode)
@@ -1,4 +1,5 @@
using Content.Shared.CrewManifest; using Content.Client.CrewManifest.UI;
using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
+5 -8
View File
@@ -19,9 +19,6 @@ public sealed partial class EmotesMenu : RadialMenu
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly EntityWhitelistSystem _whitelistSystem;
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote; public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
public EmotesMenu() public EmotesMenu()
@@ -29,8 +26,8 @@ public sealed partial class EmotesMenu : RadialMenu
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_spriteSystem = _entManager.System<SpriteSystem>(); var spriteSystem = _entManager.System<SpriteSystem>();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>(); var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main"); var main = FindControl<RadialContainer>("Main");
@@ -40,8 +37,8 @@ public sealed partial class EmotesMenu : RadialMenu
var player = _playerManager.LocalSession?.AttachedEntity; var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid || if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 || emote.ChatTriggers.Count == 0 ||
!(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue; continue;
if (!emote.Available && if (!emote.Available &&
@@ -63,7 +60,7 @@ public sealed partial class EmotesMenu : RadialMenu
{ {
VerticalAlignment = VAlignment.Center, VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center, HorizontalAlignment = HAlignment.Center,
Texture = _spriteSystem.Frame0(emote.Icon), Texture = spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f), TextureScale = new Vector2(2f, 2f),
}; };
@@ -1,4 +1,5 @@
using Content.Client.Verbs; using Content.Client.Verbs;
using Content.Shared.Verbs;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Console; using Robust.Shared.Console;
@@ -1,5 +1,6 @@
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;
@@ -9,6 +9,7 @@ using Content.Shared.CCVar;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Verbs;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
@@ -194,8 +195,20 @@ namespace Content.Client.ContextMenu.UI
return; return;
// Do we need to do in-range unOccluded checks? // Do we need to do in-range unOccluded checks?
var ignoreFov = !_eyeManager.CurrentEye.DrawFov || var visibility = _verbSystem.Visibility;
(_verbSystem.Visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov;
if (!_eyeManager.CurrentEye.DrawFov)
{
visibility &= ~MenuVisibility.NoFov;
}
var ev = new MenuVisibilityEvent()
{
Visibility = visibility,
};
_entityManager.EventBus.RaiseLocalEvent(player, ref ev);
visibility = ev.Visibility;
_entityManager.TryGetComponent(player, out ExaminerComponent? examiner); _entityManager.TryGetComponent(player, out ExaminerComponent? examiner);
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>(); var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
@@ -209,7 +222,7 @@ namespace Content.Client.ContextMenu.UI
continue; continue;
} }
if (ignoreFov) if ((visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov)
continue; continue;
var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID); var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID);
@@ -1,3 +1,4 @@
using Content.Client.CrewManifest.UI;
using Content.Shared.CrewManifest; using Content.Shared.CrewManifest;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
@@ -1,6 +1,7 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface" xmlns:ui="clr-namespace:Content.Client.UserInterface"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"> xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc door-electronics-configuration-title}">
<Control Name="AccessLevelControlContainer" /> <Control Name="AccessLevelControlContainer" />
</controls:FancyWindow> </controls:FancyWindow>
+1
View File
@@ -110,6 +110,7 @@ namespace Content.Client.Entry
_prototypeManager.RegisterIgnore("lobbyBackground"); _prototypeManager.RegisterIgnore("lobbyBackground");
_prototypeManager.RegisterIgnore("gamePreset"); _prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel"); _prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("playerConnectionWhitelist");
_prototypeManager.RegisterIgnore("spaceBiome"); _prototypeManager.RegisterIgnore("spaceBiome");
_prototypeManager.RegisterIgnore("worldgenConfig"); _prototypeManager.RegisterIgnore("worldgenConfig");
_prototypeManager.RegisterIgnore("gameRule"); _prototypeManager.RegisterIgnore("gameRule");
@@ -23,6 +23,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
private readonly ISawmill _sawmill; private readonly ISawmill _sawmill;
public ProtoId<GuideEntryPrototype> LastEntry;
public GuidebookWindow() public GuidebookWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -90,6 +92,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
_sawmill.Error($"Failed to parse contents of guide document {entry.Id}."); _sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
} }
LastEntry = entry.Id;
} }
public void UpdateGuides( public void UpdateGuides(
@@ -17,6 +17,7 @@ namespace Content.Client.HealthAnalyzer.UI
protected override void Open() protected override void Open()
{ {
base.Open(); base.Open();
_window = this.CreateWindow<HealthAnalyzerWindow>(); _window = this.CreateWindow<HealthAnalyzerWindow>();
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName; _window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
@@ -1,48 +1,64 @@
<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"
SetSize="250 100"> MaxHeight="525"
MinWidth="300">
<ScrollContainer <ScrollContainer
Margin="5 5 5 5"
ReturnMeasure="True"
VerticalExpand="True"> VerticalExpand="True">
<BoxContainer <BoxContainer
Name="RootContainer" Name="RootContainer"
VerticalExpand="True"
Orientation="Vertical"> Orientation="Vertical">
<Label <Label
Name="NoPatientDataText" Name="NoPatientDataText"
Text="{Loc health-analyzer-window-no-patient-data-text}" /> Text="{Loc health-analyzer-window-no-patient-data-text}" />
<BoxContainer <BoxContainer
Name="PatientDataContainer" Name="PatientDataContainer"
Orientation="Vertical" Margin="0 0 0 5"
Margin="0 0 5 10"> Orientation="Vertical">
<BoxContainer Name="ScanModePanel" HorizontalExpand="True" Visible="False" Margin="0 5 0 0"> <BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
<Label <SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
Name="ScanMode" <BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
Align="Left" <RichTextLabel Name="NameLabel" SetWidth="150" />
Text="{Loc health-analyzer-window-scan-mode-text}"/> <Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
<Label </BoxContainer>
Name="ScanModeText" <Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
Align="Right" VerticalAlignment="Top" Name="ScanModeLabel"
HorizontalExpand="True"/> Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
</BoxContainer> </BoxContainer>
<Label
Name="PatientName"/> <PanelContainer StyleClasses="LowDivider" />
<Label
Name="Temperature" <GridContainer Margin="0 5 0 0" Columns="2">
Margin="0 5 0 0"/> <Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
<Label <Label Name="StatusLabel" />
Name="BloodLevel" <Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
Margin="0 5 0 0"/> <Label Name="TemperatureLabel" />
<Label <Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
Name="Bleeding" <Label Name="BloodLabel" />
Margin="0 5 0 0"/> <Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
<Label <Label Name="DamageLabel" />
Name="patientDamageAmount" </GridContainer>
Margin="0 15 0 0"/>
</BoxContainer> </BoxContainer>
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Horizontal"
HorizontalExpand="True" HorizontalAlignment="Center">
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer <BoxContainer
Name="GroupsContainer" Name="GroupsContainer"
Margin="0 5 0 5"
Orientation="Vertical"> Orientation="Vertical">
</BoxContainer> </BoxContainer>
</BoxContainer> </BoxContainer>
</ScrollContainer> </ScrollContainer>
</controls:FancyWindow> </controls:FancyWindow>
@@ -1,12 +1,20 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Client.Message;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Alert;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.MedicalScanner; using Content.Shared.MedicalScanner;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -28,9 +36,6 @@ namespace Content.Client.HealthAnalyzer.UI
private readonly IPrototypeManager _prototypes; private readonly IPrototypeManager _prototypes;
private readonly IResourceCache _cache; private readonly IResourceCache _cache;
private const int AnalyzerHeight = 430;
private const int AnalyzerWidth = 300;
public HealthAnalyzerWindow() public HealthAnalyzerWindow()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -44,8 +49,6 @@ namespace Content.Client.HealthAnalyzer.UI
public void Populate(HealthAnalyzerScannedUserMessage msg) public void Populate(HealthAnalyzerScannedUserMessage msg)
{ {
GroupsContainer.RemoveAllChildren();
var target = _entityManager.GetEntity(msg.TargetEntity); var target = _entityManager.GetEntity(msg.TargetEntity);
if (target == null if (target == null
@@ -57,82 +60,96 @@ namespace Content.Client.HealthAnalyzer.UI
NoPatientDataText.Visible = false; NoPatientDataText.Visible = false;
string entityName = Loc.GetString("health-analyzer-window-entity-unknown-text"); // Scan Mode
if (_entityManager.HasComponent<MetaDataComponent>(target.Value))
{
entityName = Identity.Name(target.Value, _entityManager);
}
if (msg.ScanMode.HasValue) ScanModeLabel.Text = msg.ScanMode.HasValue
{ ? msg.ScanMode.Value
ScanModePanel.Visible = true; ? Loc.GetString("health-analyzer-window-scan-mode-active")
ScanModeText.Text = Loc.GetString(msg.ScanMode.Value ? "health-analyzer-window-scan-mode-active" : "health-analyzer-window-scan-mode-inactive"); : Loc.GetString("health-analyzer-window-scan-mode-inactive")
ScanModeText.FontColorOverride = msg.ScanMode.Value ? Color.Green : Color.Red; : Loc.GetString("health-analyzer-window-entity-unknown-text");
}
else
{
ScanModePanel.Visible = false;
}
PatientName.Text = Loc.GetString( ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
"health-analyzer-window-entity-health-text",
("entityName", entityName)
);
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text", // Patient Information
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
);
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text", SpriteView.SetEntity(target.Value);
("bloodLevel", float.IsNaN(msg.BloodLevel) ? "N/A" : $"{msg.BloodLevel * 100:F1} %")
); 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
AlertsDivider.Visible = msg.Bleeding == true;
AlertsContainer.Visible = msg.Bleeding == true;
if (msg.Bleeding == true) if (msg.Bleeding == true)
{ {
Bleeding.Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"); AlertsContainer.DisposeAllChildren();
Bleeding.FontColorOverride = Color.Red; AlertsContainer.AddChild(new Label
} {
else Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
{ FontColorOverride = Color.Red,
Bleeding.Text = string.Empty; // Clear the text });
} }
patientDamageAmount.Text = Loc.GetString( // Damage Groups
"health-analyzer-window-entity-damage-total-text",
("amount", damageable.TotalDamage)
);
var damageSortedGroups = var damageSortedGroups =
damageable.DamagePerGroup.OrderBy(damage => damage.Value) damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
.ToDictionary(x => x.Key, x => x.Value); .ToDictionary(x => x.Key, x => x.Value);
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict; IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
DrawDiagnosticGroups(damageSortedGroups, damagePerType); DrawDiagnosticGroups(damageSortedGroups, damagePerType);
}
if (_entityManager.TryGetComponent(target, out HungerComponent? hunger) private static string GetStatus(MobState mobState)
&& hunger.StarvationDamage != null {
&& hunger.CurrentThreshold <= HungerThreshold.Starving) return mobState switch
{ {
var box = new Control { Margin = new Thickness(0, 0, 0, 15) }; MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
box.AddChild(CreateDiagnosticGroupTitle( MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
Loc.GetString("health-analyzer-window-malnutrition"), _ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
"malnutrition")); };
GroupsContainer.AddChild(box);
}
SetHeight = AnalyzerHeight;
SetWidth = AnalyzerWidth;
} }
private void DrawDiagnosticGroups( private void DrawDiagnosticGroups(
Dictionary<string, FixedPoint2> groups, IReadOnlyDictionary<string, FixedPoint2> damageDict) Dictionary<string, FixedPoint2> groups,
IReadOnlyDictionary<string, FixedPoint2> damageDict)
{ {
HashSet<string> shownTypes = new(); GroupsContainer.RemoveAllChildren();
// Show the total damage and type breakdown for each damage group. foreach (var (damageGroupId, damageAmount) in groups)
foreach (var (damageGroupId, damageAmount) in groups.Reverse())
{ {
if (damageAmount == 0) if (damageAmount == 0)
continue; continue;
@@ -145,7 +162,6 @@ namespace Content.Client.HealthAnalyzer.UI
var groupContainer = new BoxContainer var groupContainer = new BoxContainer
{ {
Margin = new Thickness(0, 0, 0, 15),
Align = BoxContainer.AlignMode.Begin, Align = BoxContainer.AlignMode.Begin,
Orientation = BoxContainer.LayoutOrientation.Vertical, Orientation = BoxContainer.LayoutOrientation.Vertical,
}; };
@@ -159,23 +175,16 @@ namespace Content.Client.HealthAnalyzer.UI
foreach (var type in group.DamageTypes) foreach (var type in group.DamageTypes)
{ {
if (damageDict.TryGetValue(type, out var typeAmount) && typeAmount > 0) if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
{ continue;
// If damage types are allowed to belong to more than one damage group,
// they may appear twice here. Mark them as duplicate.
if (shownTypes.Contains(type))
continue;
shownTypes.Add(type); var damageString = Loc.GetString(
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
var damageString = Loc.GetString( groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
"health-analyzer-window-damage-type-text",
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
("amount", typeAmount)
);
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, "- ")));
}
} }
} }
} }
@@ -198,7 +207,6 @@ namespace Content.Client.HealthAnalyzer.UI
{ {
return new Label return new Label
{ {
Margin = new Thickness(2, 2),
Text = text, Text = text,
}; };
} }
@@ -207,13 +215,13 @@ namespace Content.Client.HealthAnalyzer.UI
{ {
var rootContainer = new BoxContainer var rootContainer = new BoxContainer
{ {
Margin = new Thickness(0, 6, 0, 0),
VerticalAlignment = VAlignment.Bottom, VerticalAlignment = VAlignment.Bottom,
Orientation = BoxContainer.LayoutOrientation.Horizontal Orientation = BoxContainer.LayoutOrientation.Horizontal,
}; };
rootContainer.AddChild(new TextureRect rootContainer.AddChild(new TextureRect
{ {
Margin = new Thickness(0, 3),
SetSize = new Vector2(30, 30), SetSize = new Vector2(30, 30),
Texture = GetTexture(id.ToLower()) Texture = GetTexture(id.ToLower())
}); });
@@ -731,6 +731,9 @@ namespace Content.Client.Lobby.UI
PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed); PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
SpriteView.SetEntity(PreviewDummy); SpriteView.SetEntity(PreviewDummy);
_entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, Profile.Name); _entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, Profile.Name);
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
SetDirty();
} }
/// <summary> /// <summary>
@@ -792,6 +795,9 @@ namespace Content.Client.Lobby.UI
return; return;
_entManager.System<HumanoidAppearanceSystem>().LoadProfile(PreviewDummy, Profile); _entManager.System<HumanoidAppearanceSystem>().LoadProfile(PreviewDummy, Profile);
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
SetDirty();
} }
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args) private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
@@ -1028,7 +1034,6 @@ namespace Content.Client.Lobby.UI
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager); roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection); _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
Profile = Profile?.WithLoadout(roleLoadout); Profile = Profile?.WithLoadout(roleLoadout);
SetDirty();
ReloadPreview(); ReloadPreview();
}; };
@@ -1037,7 +1042,6 @@ namespace Content.Client.Lobby.UI
roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager); roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection); _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
Profile = Profile?.WithLoadout(roleLoadout); Profile = Profile?.WithLoadout(roleLoadout);
SetDirty();
ReloadPreview(); ReloadPreview();
}; };
@@ -1047,7 +1051,6 @@ namespace Content.Client.Lobby.UI
_loadoutWindow.OnClose += () => _loadoutWindow.OnClose += () =>
{ {
JobOverride = null; JobOverride = null;
SetDirty();
ReloadPreview(); ReloadPreview();
}; };
@@ -1072,7 +1075,6 @@ namespace Content.Client.Lobby.UI
return; return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList())); Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
SetDirty();
ReloadProfilePreview(); ReloadProfilePreview();
} }
@@ -1140,7 +1142,6 @@ namespace Content.Client.Lobby.UI
} }
} }
SetDirty();
ReloadProfilePreview(); ReloadProfilePreview();
} }
@@ -1171,7 +1172,6 @@ namespace Content.Client.Lobby.UI
{ {
Profile = Profile?.WithAge(newAge); Profile = Profile?.WithAge(newAge);
ReloadPreview(); ReloadPreview();
SetDirty();
} }
private void SetSex(Sex newSex) private void SetSex(Sex newSex)
@@ -1195,14 +1195,12 @@ namespace Content.Client.Lobby.UI
UpdateTTSVoicesControls(); // Corvax-TTS UpdateTTSVoicesControls(); // Corvax-TTS
Markings.SetSex(newSex); Markings.SetSex(newSex);
ReloadPreview(); ReloadPreview();
SetDirty();
} }
private void SetGender(Gender newGender) private void SetGender(Gender newGender)
{ {
Profile = Profile?.WithGender(newGender); Profile = Profile?.WithGender(newGender);
ReloadPreview(); ReloadPreview();
SetDirty();
} }
// Corvax-TTS-Start // Corvax-TTS-Start
@@ -1224,7 +1222,6 @@ namespace Content.Client.Lobby.UI
RefreshLoadouts(); RefreshLoadouts();
UpdateSexControls(); // update sex for new species UpdateSexControls(); // update sex for new species
UpdateSpeciesGuidebookIcon(); UpdateSpeciesGuidebookIcon();
SetDirty();
ReloadPreview(); ReloadPreview();
} }
@@ -1,10 +1,24 @@
<controls:FancyWindow xmlns="https://spacestation14.io" <controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
SetSize="800 800" SetSize="800 800"
MinSize="800 64"> MinSize="800 128">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<!--
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
<PanelContainer HorizontalExpand="True" SetHeight="24">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/>
</PanelContainer>
</BoxContainer>
-->
<VerticalTabContainer Name="LoadoutGroupsContainer" <VerticalTabContainer Name="LoadoutGroupsContainer"
VerticalExpand="True" VerticalExpand="True"
HorizontalExpand="True"> HorizontalExpand="True">
</VerticalTabContainer> </VerticalTabContainer>
</BoxContainer>
</controls:FancyWindow> </controls:FancyWindow>
@@ -1,3 +1,4 @@
using System.Numerics;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
@@ -5,6 +6,7 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Client.Lobby.UI.Loadouts; namespace Content.Client.Lobby.UI.Loadouts;
@@ -24,27 +26,36 @@ public sealed partial class LoadoutWindow : FancyWindow
Profile = profile; Profile = profile;
var protoManager = collection.Resolve<IPrototypeManager>(); var protoManager = collection.Resolve<IPrototypeManager>();
foreach (var group in proto.Groups) // Hide if no groups
if (proto.Groups.Count == 0)
{ {
if (!protoManager.TryIndex(group, out var groupProto)) LoadoutGroupsContainer.Visible = false;
continue; SetSize = Vector2.Zero;
}
if (groupProto.Hidden) else
continue; {
foreach (var group in proto.Groups)
var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
container.OnLoadoutPressed += args =>
{ {
OnLoadoutPressed?.Invoke(group, args); if (!protoManager.TryIndex(group, out var groupProto))
}; continue;
container.OnLoadoutUnpressed += args => if (groupProto.Hidden)
{ continue;
OnLoadoutUnpressed?.Invoke(group, args);
}; var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
container.OnLoadoutPressed += args =>
{
OnLoadoutPressed?.Invoke(group, args);
};
container.OnLoadoutUnpressed += args =>
{
OnLoadoutUnpressed?.Invoke(group, args);
};
}
} }
} }
@@ -1,5 +1,6 @@
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
View File
@@ -2,6 +2,7 @@ using Content.Client.Message;
using Content.Client.UserInterface.Systems.EscapeMenu; using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -3,6 +3,7 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Content.Client.TextScreen; using Content.Client.TextScreen;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.MachineLinking.UI; namespace Content.Client.MachineLinking.UI;
@@ -1,3 +1,4 @@
using Content.Client.Options.UI.Tabs;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.Power.Components; using Content.Client.Power.Components;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems; using Content.Shared.Power.EntitySystems;
@@ -27,4 +28,16 @@ public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
component.Powered = state.Powered; component.Powered = state.Powered;
} }
public override bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component)
{
if (component != null)
return true;
if (!TryComp(entity, out ApcPowerReceiverComponent? receiver))
return false;
component = receiver;
return true;
}
} }
@@ -3,6 +3,7 @@ using Content.Client.UserInterface.Controls;
using Content.Shared.Power.Generator; using Content.Shared.Power.Generator;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network;
namespace Content.Client.Power.Generator; namespace Content.Client.Power.Generator;
@@ -1,3 +1,4 @@
using System.Threading.Channels;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Radio.Components; using Content.Shared.Radio.Components;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
@@ -0,0 +1,8 @@
using Content.Shared.Roles.RoleCodeword;
namespace Content.Client.Roles;
public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
{
}
@@ -53,9 +53,9 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
option.Claimed = current.ActiveSeed == seed; option.Claimed = current.ActiveSeed == seed;
var claimIndex = i; var claimIndex = i;
option.ClaimPressed += args => option.ClaimPressed += _ =>
{ {
SendMessage(new MagnetClaimOfferEvent() SendMessage(new MagnetClaimOfferEvent
{ {
Index = claimIndex Index = claimIndex
}); });
@@ -72,20 +72,20 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
{ {
var count = asteroid.MarkerLayers[resource]; var count = asteroid.MarkerLayers[resource];
var container = new BoxContainer() var container = new BoxContainer
{ {
Orientation = BoxContainer.LayoutOrientation.Horizontal, Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true, HorizontalExpand = true,
}; };
var resourceLabel = new Label() var resourceLabel = new Label
{ {
Text = Loc.GetString("salvage-magnet-resources", Text = Loc.GetString("salvage-magnet-resources",
("resource", resource)), ("resource", resource)),
HorizontalAlignment = Control.HAlignment.Left, HorizontalAlignment = Control.HAlignment.Left,
}; };
var countLabel = new Label() var countLabel = new Label
{ {
Text = Loc.GetString("salvage-magnet-resources-count", ("count", count)), Text = Loc.GetString("salvage-magnet-resources-count", ("count", count)),
HorizontalAlignment = Control.HAlignment.Right, HorizontalAlignment = Control.HAlignment.Right,
@@ -98,6 +98,9 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
option.AddContent(container); option.AddContent(container);
} }
break;
case DebrisOffering debris:
option.Title = Loc.GetString($"salvage-magnet-debris-{debris.Id}");
break; break;
case SalvageOffering salvage: case SalvageOffering salvage:
option.Title = Loc.GetString($"salvage-map-wreck"); option.Title = Loc.GetString($"salvage-map-wreck");
@@ -6,7 +6,7 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi;
public sealed class SiliconLawEui : BaseEui public sealed class SiliconLawEui : BaseEui
{ {
public readonly EntityManager _entityManager = default!; private readonly EntityManager _entityManager;
private SiliconLawUi _siliconLawUi; private SiliconLawUi _siliconLawUi;
private EntityUid _target; private EntityUid _target;
@@ -0,0 +1,28 @@
using Content.Shared.Silicons.StationAi;
using Robust.Client.UserInterface;
namespace Content.Client.Silicons.StationAi;
public sealed class StationAiBoundUserInterface : BoundUserInterface
{
private StationAiMenu? _menu;
public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<StationAiMenu>();
_menu.Track(Owner);
_menu.OnAiRadial += args =>
{
SendPredictedMessage(new StationAiRadialMessage()
{
Event = args,
});
};
}
}
@@ -0,0 +1,13 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>
</ui:RadialMenu>
@@ -0,0 +1,128 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Silicons.StationAi;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client.Silicons.StationAi;
[GenerateTypedNameReferences]
public sealed partial class StationAiMenu : RadialMenu
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
public event Action<BaseStationAiAction>? OnAiRadial;
private EntityUid _tracked;
public StationAiMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
}
public void Track(EntityUid owner)
{
_tracked = owner;
if (!_entManager.EntityExists(_tracked))
{
Close();
return;
}
BuildButtons();
UpdatePosition();
}
private void BuildButtons()
{
var ev = new GetStationAiRadialEvent();
_entManager.EventBus.RaiseLocalEvent(_tracked, ref ev);
var main = FindControl<RadialContainer>("Main");
main.DisposeAllChildren();
var sprites = _entManager.System<SpriteSystem>();
foreach (var action in ev.Actions)
{
// TODO: This radial boilerplate is quite annoying
var button = new StationAiMenuButton(action.Event)
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f),
ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null,
};
if (action.Sprite != null)
{
var texture = sprites.Frame0(action.Sprite);
var scale = Vector2.One;
if (texture.Width <= 32)
{
scale *= 2;
}
var tex = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = texture,
TextureScale = scale,
};
button.AddChild(tex);
}
button.OnPressed += args =>
{
OnAiRadial?.Invoke(action.Event);
Close();
};
main.AddChild(button);
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdatePosition();
}
private void UpdatePosition()
{
if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform))
{
Close();
return;
}
if (!xform.Coordinates.IsValid(_entManager))
{
Close();
return;
}
var coords = _entManager.System<SpriteSystem>().GetSpriteScreenCoordinates((_tracked, null, xform));
if (!coords.IsValid)
{
Close();
return;
}
OpenScreenAt(coords.Position, _clyde);
}
}
public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton
{
public BaseStationAiAction Action = action;
}
@@ -4,7 +4,9 @@ using Robust.Client.Graphics;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Silicons.StationAi; namespace Content.Client.Silicons.StationAi;
@@ -12,6 +14,7 @@ public sealed class StationAiOverlay : Overlay
{ {
[Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPrototypeManager _proto = default!;
@@ -22,6 +25,9 @@ public sealed class StationAiOverlay : Overlay
private IRenderTexture? _staticTexture; private IRenderTexture? _staticTexture;
private IRenderTexture? _stencilTexture; private IRenderTexture? _stencilTexture;
private float _updateRate = 1f / 30f;
private float _accumulator;
public StationAiOverlay() public StationAiOverlay()
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
@@ -47,19 +53,22 @@ public sealed class StationAiOverlay : Overlay
_entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform); _entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform);
var gridUid = playerXform?.GridUid ?? EntityUid.Invalid; var gridUid = playerXform?.GridUid ?? EntityUid.Invalid;
_entManager.TryGetComponent(gridUid, out MapGridComponent? grid); _entManager.TryGetComponent(gridUid, out MapGridComponent? grid);
_entManager.TryGetComponent(gridUid, out BroadphaseComponent? broadphase);
var invMatrix = args.Viewport.GetWorldToLocalMatrix(); var invMatrix = args.Viewport.GetWorldToLocalMatrix();
_accumulator -= (float) _timing.FrameTime.TotalSeconds;
if (grid != null) if (grid != null && broadphase != null)
{ {
// TODO: Pass in attached entity's grid.
// TODO: Credit OD on the moved to code
// TODO: Call the moved-to code here.
_visibleTiles.Clear();
var lookups = _entManager.System<EntityLookupSystem>(); var lookups = _entManager.System<EntityLookupSystem>();
var xforms = _entManager.System<SharedTransformSystem>(); var xforms = _entManager.System<SharedTransformSystem>();
_entManager.System<StationAiVisionSystem>().GetView((gridUid, grid), worldBounds, _visibleTiles);
if (_accumulator <= 0f)
{
_accumulator = MathF.Max(0f, _accumulator + _updateRate);
_visibleTiles.Clear();
_entManager.System<StationAiVisionSystem>().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles);
}
var gridMatrix = xforms.GetWorldMatrix(gridUid); var gridMatrix = xforms.GetWorldMatrix(gridUid);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
@@ -0,0 +1,30 @@
using Content.Shared.Doors.Components;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility;
namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem
{
private void InitializeAirlock()
{
SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
}
private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args)
{
args.Actions.Add(new StationAiRadial()
{
Sprite = ent.Comp.BoltsDown ?
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
Event = new StationAiBoltEvent()
{
Bolted = !ent.Comp.BoltsDown,
}
});
}
}
@@ -0,0 +1,32 @@
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Light.Components;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility;
namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem
{
// Used for surveillance camera lights
private void InitializePowerToggle()
{
SubscribeLocalEvent<ItemTogglePointLightComponent, GetStationAiRadialEvent>(OnLightGetRadial);
}
private void OnLightGetRadial(Entity<ItemTogglePointLightComponent> ent, ref GetStationAiRadialEvent args)
{
if (!TryComp(ent.Owner, out ItemToggleComponent? toggle))
return;
args.Actions.Add(new StationAiRadial()
{
Tooltip = Loc.GetString("toggle-light"),
Sprite = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
Event = new StationAiLightEvent()
{
Enabled = !toggle.Activated
}
});
}
}
@@ -5,7 +5,7 @@ using Robust.Shared.Player;
namespace Content.Client.Silicons.StationAi; namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem : EntitySystem public sealed partial class StationAiSystem : SharedStationAiSystem
{ {
[Dependency] private readonly IOverlayManager _overlayMgr = default!; [Dependency] private readonly IOverlayManager _overlayMgr = default!;
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
@@ -15,8 +15,8 @@ public sealed partial class StationAiSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
// InitializeAirlock(); InitializeAirlock();
// InitializePowerToggle(); InitializePowerToggle();
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerAttachedEvent>(OnAiAttached); SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerAttachedEvent>(OnAiAttached);
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached); SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached);
@@ -2,6 +2,7 @@ using Content.Shared.StationRecords;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Enums;
namespace Content.Client.StationRecords; namespace Content.Client.StationRecords;
@@ -1,4 +1,5 @@
using Content.Client.Chemistry.Visualizers; using Content.Client.Chemistry.Visualizers;
using Content.Shared.Chemistry.Components;
namespace Content.Client.Storage.Components; namespace Content.Client.Storage.Components;
@@ -11,6 +11,8 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
private readonly StorageSystem _storage; private readonly StorageSystem _storage;
[Obsolete] public override bool DeferredClose => false;
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
@@ -9,7 +9,12 @@ namespace Content.Client.UserInterface.Controls
{ {
public float Progress; public float Progress;
private readonly ProgressColorSystem _progressColor = IoCManager.Resolve<IEntityManager>().System<ProgressColorSystem>(); private readonly ProgressColorSystem _progressColor;
public ProgressTextureRect()
{
_progressColor = IoCManager.Resolve<IEntityManager>().System<ProgressColorSystem>();
}
protected override void Draw(DrawingHandleScreen handle) protected override void Draw(DrawingHandleScreen handle)
{ {
@@ -121,7 +121,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
var boundKey = hotbarKeys[i]; var boundKey = hotbarKeys[i];
builder = builder.Bind(boundKey, new PointerInputCmdHandler((in PointerInputCmdArgs args) => builder = builder.Bind(boundKey, new PointerInputCmdHandler((in PointerInputCmdArgs args) =>
{ {
if (args.State != BoundKeyState.Up) if (args.State != BoundKeyState.Down)
return false; return false;
TriggerAction(boundId); TriggerAction(boundId);
@@ -219,8 +219,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
if (action.Event != null) if (action.Event != null)
{ {
action.Event.Target = coords; action.Event.Target = coords;
action.Event.Performer = user;
action.Event.Action = actionId;
} }
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
@@ -254,8 +252,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
if (action.Event != null) if (action.Event != null)
{ {
action.Event.Target = entity; action.Event.Target = entity;
action.Event.Performer = user;
action.Event.Action = actionId;
} }
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
@@ -295,8 +291,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{ {
action.Event.Entity = entity; action.Event.Entity = entity;
action.Event.Coords = coords; action.Event.Coords = coords;
action.Event.Performer = user;
action.Event.Action = actionId;
} }
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
@@ -9,6 +9,8 @@ using Content.Client.Chat.UI;
using Content.Client.Examine; using Content.Client.Examine;
using Content.Client.Gameplay; using Content.Client.Gameplay;
using Content.Client.Ghost; using Content.Client.Ghost;
using Content.Client.Mind;
using Content.Client.Roles;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface.Screens; using Content.Client.UserInterface.Screens;
using Content.Client.UserInterface.Systems.Chat.Widgets; using Content.Client.UserInterface.Systems.Chat.Widgets;
@@ -20,6 +22,7 @@ using Content.Shared.Damage.ForceSay;
using Content.Shared.Decals; using Content.Shared.Decals;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Radio; using Content.Shared.Radio;
using Content.Shared.Roles.RoleCodeword;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Input; using Robust.Client.Input;
@@ -60,6 +63,8 @@ public sealed class ChatUIController : UIController
[UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default; [UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default;
[UISystemDependency] private readonly ChatSystem? _chatSys = default; [UISystemDependency] private readonly ChatSystem? _chatSys = default;
[UISystemDependency] private readonly TransformSystem? _transform = default; [UISystemDependency] private readonly TransformSystem? _transform = default;
[UISystemDependency] private readonly MindSystem? _mindSystem = default!;
[UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!;
[ValidatePrototypeId<ColorPalettePrototype>] [ValidatePrototypeId<ColorPalettePrototype>]
private const string ChatNamePalette = "ChatNames"; private const string ChatNamePalette = "ChatNames";
@@ -819,6 +824,19 @@ public sealed class ChatUIController : UIController
msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name"))); msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name")));
} }
// Color any codewords for minds that have roles that use them
if (_player.LocalUser != null && _mindSystem != null && _roleCodewordSystem != null)
{
if (_mindSystem.TryGetMind(_player.LocalUser.Value, out var mindId) && _ent.TryGetComponent(mindId, out RoleCodewordComponent? codewordComp))
{
foreach (var (_, codewordData) in codewordComp.RoleCodewords)
{
foreach (string codeword in codewordData.Codewords)
msg.WrappedMessage = SharedChatSystem.InjectTagAroundString(msg, codeword, "color", codewordData.Color.ToHex());
}
}
}
// Log all incoming chat to repopulate when filter is un-toggled // Log all incoming chat to repopulate when filter is un-toggled
if (!msg.HideChat) if (!msg.HideChat)
{ {
@@ -30,6 +30,7 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
private GuidebookWindow? _guideWindow; private GuidebookWindow? _guideWindow;
private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton; private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton;
private ProtoId<GuideEntryPrototype>? _lastEntry;
public void OnStateEntered(LobbyState state) public void OnStateEntered(LobbyState state)
{ {
@@ -142,7 +143,10 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
GuidebookButton.Pressed = false; GuidebookButton.Pressed = false;
if (_guideWindow != null) if (_guideWindow != null)
{
_guideWindow.ReturnContainer.Visible = false; _guideWindow.ReturnContainer.Visible = false;
_lastEntry = _guideWindow.LastEntry;
}
} }
private void OnWindowOpen() private void OnWindowOpen()
@@ -176,8 +180,6 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
if (GuidebookButton != null) if (GuidebookButton != null)
GuidebookButton.SetClickPressed(!_guideWindow.IsOpen); GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
selected ??= _configuration.GetCVar(CCVars.DefaultGuide);
if (guides == null) if (guides == null)
{ {
guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>() guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
@@ -193,6 +195,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
} }
} }
if (selected == null)
{
if (_lastEntry is { } lastEntry && guides.ContainsKey(lastEntry))
{
selected = _lastEntry;
}
else
{
selected = _configuration.GetCVar(CCVars.DefaultGuide);
}
}
_guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected); _guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
// Expand up to depth-2. // Expand up to depth-2.
@@ -7,6 +7,7 @@ using Robust.Shared.Prototypes;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow; using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.IdentityManagement;
using Robust.Client.Graphics; using Robust.Client.Graphics;
namespace Content.Client.VendingMachines.UI namespace Content.Client.VendingMachines.UI
@@ -15,6 +16,9 @@ namespace Content.Client.VendingMachines.UI
public sealed partial class VendingMachineMenu : FancyWindow public sealed partial class VendingMachineMenu : FancyWindow
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly Dictionary<EntProtoId, EntityUid> _dummies = [];
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected; public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
@@ -33,6 +37,22 @@ namespace Content.Client.VendingMachines.UI
VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data); VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data);
} }
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
// Don't clean up dummies during disposal or we'll just have to spawn them again
if (!disposing)
return;
// Delete any dummy items we spawned
foreach (var entity in _dummies.Values)
{
_entityManager.QueueDeleteEntity(entity);
}
_dummies.Clear();
}
private bool DataFilterCondition(string filter, ListData data) private bool DataFilterCondition(string filter, ListData data)
{ {
if (data is not VendorItemsListData { ItemText: var text }) if (data is not VendorItemsListData { ItemText: var text })
@@ -92,7 +112,14 @@ namespace Content.Client.VendingMachines.UI
if (!_prototypeManager.TryIndex(entry.ID, out var prototype)) if (!_prototypeManager.TryIndex(entry.ID, out var prototype))
continue; continue;
var itemText = $"{prototype.Name} [{entry.Amount}]"; if (!_dummies.TryGetValue(entry.ID, out var dummy))
{
dummy = _entityManager.Spawn(entry.ID);
_dummies.Add(entry.ID, dummy);
}
var itemName = Identity.Name(dummy, _entityManager);
var itemText = $"{itemName} [{entry.Amount}]";
if (itemText.Length > longestEntry.Length) if (itemText.Length > longestEntry.Length)
longestEntry = itemText; longestEntry = itemText;
+11 -39
View File
@@ -67,9 +67,18 @@ namespace Content.Client.Verbs
? Visibility ? Visibility
: Visibility | MenuVisibility.NoFov; : Visibility | MenuVisibility.NoFov;
var ev = new MenuVisibilityEvent()
{
TargetPos = targetPos,
Visibility = visibility,
};
RaiseLocalEvent(player.Value, ref ev);
visibility = ev.Visibility;
// Get entities // Get entities
List<EntityUid> entities; List<EntityUid> entities;
var examineFlags = LookupFlags.All & ~LookupFlags.Sensors;
// Do we have to do FoV checks? // Do we have to do FoV checks?
if ((visibility & MenuVisibility.NoFov) == 0) if ((visibility & MenuVisibility.NoFov) == 0)
@@ -77,15 +86,10 @@ namespace Content.Client.Verbs
var entitiesUnderMouse = gameScreenBase.GetClickableEntities(targetPos).ToHashSet(); var entitiesUnderMouse = gameScreenBase.GetClickableEntities(targetPos).ToHashSet();
bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e); bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e);
// first check the general location.
if (!_examine.CanExamine(player.Value, targetPos, Predicate))
return false;
TryComp(player.Value, out ExaminerComponent? examiner); TryComp(player.Value, out ExaminerComponent? examiner);
// Then check every entity
entities = new(); entities = new();
foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize)) foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags))
{ {
if (_examine.CanExamine(player.Value, targetPos, Predicate, ent, examiner)) if (_examine.CanExamine(player.Value, targetPos, Predicate, ent, examiner))
entities.Add(ent); entities.Add(ent);
@@ -93,7 +97,7 @@ namespace Content.Client.Verbs
} }
else else
{ {
entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize).ToList(); entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList();
} }
if (entities.Count == 0) if (entities.Count == 0)
@@ -137,27 +141,6 @@ namespace Content.Client.Verbs
} }
} }
// Remove any entities that do not have LOS
if ((visibility & MenuVisibility.NoFov) == 0)
{
var xformQuery = GetEntityQuery<TransformComponent>();
var playerPos = _transform.GetMapCoordinates(player.Value, xform: xformQuery.GetComponent(player.Value));
for (var i = entities.Count - 1; i >= 0; i--)
{
var entity = entities[i];
if (!_examine.InRangeUnOccluded(
playerPos,
_transform.GetMapCoordinates(entity, xform: xformQuery.GetComponent(entity)),
ExamineSystemShared.ExamineRange,
null))
{
entities.RemoveSwap(i);
}
}
}
if (entities.Count == 0) if (entities.Count == 0)
return false; return false;
@@ -229,15 +212,4 @@ namespace Content.Client.Verbs
OnVerbsResponse?.Invoke(msg); OnVerbsResponse?.Invoke(msg);
} }
} }
[Flags]
public enum MenuVisibility
{
// What entities can a user see on the entity menu?
Default = 0, // They can only see entities in FoV.
NoFov = 1 << 0, // They ignore FoV restrictions
InContainer = 1 << 1, // They can see through containers.
Invisible = 1 << 2, // They can see entities without sprites and the "HideContextMenu" tag is ignored.
All = NoFov | InContainer | Invisible
}
} }
@@ -1,6 +1,7 @@
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Xenoarchaeology.Equipment; using Content.Shared.Xenoarchaeology.Equipment;
using Microsoft.VisualBasic;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -17,6 +17,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Station.Components; using Content.Shared.Station.Components;
using FastAccessors;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
@@ -267,6 +268,13 @@ namespace Content.IntegrationTests.Tests
.Select(x => x.Job!.Value); .Select(x => x.Job!.Value);
jobs.ExceptWith(spawnPoints); jobs.ExceptWith(spawnPoints);
spawnPoints = entManager.EntityQuery<ContainerSpawnPointComponent>()
.Where(x => x.SpawnType == SpawnPointType.Job)
.Select(x => x.Job!.Value);
jobs.ExceptWith(spawnPoints);
Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}."); Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
} }
@@ -0,0 +1,58 @@
using System.Linq;
using Content.Client.Chat.UI;
using Content.Client.LateJoin;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
namespace Content.IntegrationTests.Tests.UserInterface;
[TestFixture]
public sealed class UiControlTest
{
// You should not be adding to this.
private Type[] _ignored = new Type[]
{
typeof(EmotesMenu),
typeof(LateJoinGui),
};
/// <summary>
/// Tests that all windows can be instantiated successfully.
/// </summary>
[Test]
public async Task TestWindows()
{
var pair = await PoolManager.GetServerClient(new PoolSettings()
{
Connected = true,
});
var activator = pair.Client.ResolveDependency<IDynamicTypeFactory>();
var refManager = pair.Client.ResolveDependency<IReflectionManager>();
var loader = pair.Client.ResolveDependency<IModLoader>();
await pair.Client.WaitAssertion(() =>
{
foreach (var type in refManager.GetAllChildren(typeof(BaseWindow)))
{
if (type.IsAbstract || _ignored.Contains(type))
continue;
if (!loader.IsContentType(type))
continue;
// If it has no empty ctor then skip it instead of figuring out what args it needs.
var ctor = type.GetConstructor(Type.EmptyTypes);
if (ctor == null)
continue;
// Don't inject because the control themselves have to do it.
activator.CreateInstance(type, oneOff: true, inject: false);
}
});
await pair.CleanReturnAsync();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class Blacklist : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "blacklist",
columns: table => new
{
user_id = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_blacklist", x => x.user_id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "blacklist");
}
}
}
@@ -512,6 +512,20 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("assigned_user_id", (string)null); b.ToTable("assigned_user_id", (string)null);
}); });
modelBuilder.Entity("Content.Server.Database.Blacklist",
b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_blacklist");
b.ToTable("blacklist", (string) null);
});
modelBuilder.Entity("Content.Server.Database.BanTemplate", b => modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class Blacklist : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "blacklist",
columns: table => new
{
user_id = table.Column<Guid>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_blacklist", x => x.user_id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "blacklist");
}
}
}
@@ -483,6 +483,19 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("assigned_user_id", (string)null); b.ToTable("assigned_user_id", (string)null);
}); });
modelBuilder.Entity("Content.Server.Database.Blacklist",
b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_blacklist");
b.ToTable("blacklist", (string) null);
});
modelBuilder.Entity("Content.Server.Database.BanTemplate", b => modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
+10
View File
@@ -28,6 +28,7 @@ namespace Content.Server.Database
public DbSet<AdminLog> AdminLog { get; set; } = null!; public DbSet<AdminLog> AdminLog { get; set; } = null!;
public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!; public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!;
public DbSet<Whitelist> Whitelist { get; set; } = null!; public DbSet<Whitelist> Whitelist { get; set; } = null!;
public DbSet<Blacklist> Blacklist { get; set; } = null!;
public DbSet<ServerBan> Ban { get; set; } = default!; public DbSet<ServerBan> Ban { get; set; } = default!;
public DbSet<ServerUnban> Unban { get; set; } = default!; public DbSet<ServerUnban> Unban { get; set; } = default!;
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!; public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
@@ -552,6 +553,15 @@ namespace Content.Server.Database
[Required, Key] public Guid UserId { get; set; } [Required, Key] public Guid UserId { get; set; }
} }
/// <summary>
/// List of users who are on the "blacklist". This is a list that may be used by Whitelist implementations to deny access to certain users.
/// </summary>
[Table("blacklist")]
public class Blacklist
{
[Required, Key] public Guid UserId { get; set; }
}
public class Admin public class Admin
{ {
[Key] public Guid UserId { get; set; } [Key] public Guid UserId { get; set; }
@@ -5,7 +5,6 @@ using Content.Shared.Actions.Events;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Coordinates.Helpers; using Content.Shared.Coordinates.Helpers;
using Content.Shared.Maps; using Content.Shared.Maps;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics; using Content.Shared.Physics;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -19,7 +18,6 @@ namespace Content.Server.Abilities.Mime
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
[Dependency] private readonly TurfSystem _turf = default!; [Dependency] private readonly TurfSystem _turf = default!;
[Dependency] private readonly IMapManager _mapMan = default!; [Dependency] private readonly IMapManager _mapMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
@@ -80,22 +78,13 @@ namespace Content.Server.Abilities.Mime
if (tile == null) if (tile == null)
return; return;
// Check there are no walls there // Check if the tile is blocked by a wall or mob, and don't create the wall if so
if (_turf.IsTileBlocked(tile.Value, CollisionGroup.Impassable)) if (_turf.IsTileBlocked(tile.Value, CollisionGroup.Impassable | CollisionGroup.Opaque))
{ {
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), uid, uid); _popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), uid, uid);
return; return;
} }
// Check there are no mobs there
foreach (var entity in _lookupSystem.GetLocalEntitiesIntersecting(tile.Value, 0f))
{
if (HasComp<MobStateComponent>(entity) && entity != uid)
{
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), uid, uid);
return;
}
}
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", uid)), uid); _popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-popup", ("mime", uid)), uid);
// Make sure we set the invisible wall to despawn properly // Make sure we set the invisible wall to despawn properly
Spawn(component.WallPrototype, _turf.GetTileCenter(tile.Value)); Spawn(component.WallPrototype, _turf.GetTileCenter(tile.Value));
@@ -55,12 +55,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
return; return;
var (actId, act) = _random.Pick(options); var (actId, act) = _random.Pick(options);
if (act.Event != null)
{
act.Event.Performer = args.User;
act.Event.Action = actId;
}
_actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false); _actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false);
args.Handled = true; args.Handled = true;
} }
@@ -94,8 +88,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
var (entActId, entAct) = _random.Pick(entOptions); var (entActId, entAct) = _random.Pick(entOptions);
if (entAct.Event != null) if (entAct.Event != null)
{ {
entAct.Event.Performer = args.User;
entAct.Event.Action = entActId;
entAct.Event.Target = args.Target.Value; entAct.Event.Target = args.Target.Value;
} }
@@ -119,8 +111,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
var (entActId, entAct) = _random.Pick(entWorldOptions); var (entActId, entAct) = _random.Pick(entWorldOptions);
if (entAct.Event != null) if (entAct.Event != null)
{ {
entAct.Event.Performer = args.User;
entAct.Event.Action = entActId;
entAct.Event.Entity = args.Target; entAct.Event.Entity = args.Target;
entAct.Event.Coords = args.ClickLocation; entAct.Event.Coords = args.ClickLocation;
} }
@@ -145,8 +135,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
var (actId, act) = _random.Pick(options); var (actId, act) = _random.Pick(options);
if (act.Event != null) if (act.Event != null)
{ {
act.Event.Performer = args.User;
act.Event.Action = actId;
act.Event.Target = args.ClickLocation; act.Event.Target = args.ClickLocation;
} }
@@ -36,6 +36,7 @@ using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Silicons.Laws; using Content.Server.Silicons.Laws;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
@@ -1,6 +1,7 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.AlertLevel; using Content.Shared.AlertLevel;
using Content.Shared.Power;
namespace Content.Server.AlertLevel; namespace Content.Server.AlertLevel;
@@ -10,6 +10,7 @@ using Content.Shared.Ame.Components;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Power;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
@@ -7,6 +7,7 @@ using Content.Shared.Anomaly.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Content.Shared.Verbs; using Content.Shared.Verbs;
@@ -13,6 +13,7 @@ using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Map; using Robust.Shared.Map;
using System.Numerics; using System.Numerics;
using Content.Shared.Power;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
namespace Content.Server.Anomaly; namespace Content.Server.Anomaly;
@@ -3,6 +3,7 @@ using Content.Shared.UserInterface;
using Content.Server.Advertise; using Content.Server.Advertise;
using Content.Server.Advertise.Components; using Content.Server.Advertise.Components;
using Content.Shared.Arcade; using Content.Shared.Arcade;
using Content.Shared.Power;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -2,6 +2,7 @@ using Content.Server.Power.Components;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Content.Server.Advertise; using Content.Server.Advertise;
using Content.Server.Advertise.Components; using Content.Server.Advertise.Components;
using Content.Shared.Power;
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -36,7 +36,7 @@ public sealed partial class AtmosphereSystem
return; return;
} }
var mixtures = new GasMixture[7]; var mixtures = new GasMixture[8];
for (var i = 0; i < mixtures.Length; i++) for (var i = 0; i < mixtures.Length; i++)
mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C }; mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C };
@@ -65,6 +65,9 @@ public sealed partial class AtmosphereSystem
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard); mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
// 7: Nitrogen (101kpa) for vox rooms
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
foreach (var arg in args) foreach (var arg in args)
{ {
if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid)) if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid))
@@ -11,14 +11,12 @@ namespace Content.Server.Atmos.Monitor.Components;
[RegisterComponent] [RegisterComponent]
public sealed partial class AirAlarmComponent : Component public sealed partial class AirAlarmComponent : Component
{ {
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering; [DataField] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
[ViewVariables] public bool AutoMode { get; set; } = true; [DataField] public bool AutoMode { get; set; } = true;
// Remember to null this afterwards. // Remember to null this afterwards.
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; } [ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
public readonly HashSet<string> KnownDevices = new(); public readonly HashSet<string> KnownDevices = new();
public readonly Dictionary<string, GasVentPumpData> VentData = new(); public readonly Dictionary<string, GasVentPumpData> VentData = new();
public readonly Dictionary<string, GasVentScrubberData> ScrubberData = new(); public readonly Dictionary<string, GasVentScrubberData> ScrubberData = new();
@@ -18,6 +18,7 @@ using Content.Shared.DeviceLinking;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Systems; using Content.Shared.DeviceNetwork.Systems;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Power;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -173,7 +174,6 @@ public sealed class AirAlarmSystem : EntitySystem
subs.Event<AirAlarmUpdateAlarmThresholdMessage>(OnUpdateThreshold); subs.Event<AirAlarmUpdateAlarmThresholdMessage>(OnUpdateThreshold);
subs.Event<AirAlarmUpdateDeviceDataMessage>(OnUpdateDeviceData); subs.Event<AirAlarmUpdateDeviceDataMessage>(OnUpdateDeviceData);
subs.Event<AirAlarmCopyDeviceDataMessage>(OnCopyDeviceData); subs.Event<AirAlarmCopyDeviceDataMessage>(OnCopyDeviceData);
subs.Event<AirAlarmTabSetMessage>(OnTabChange);
}); });
} }
@@ -200,12 +200,6 @@ public sealed class AirAlarmSystem : EntitySystem
SyncRegisterAllDevices(uid); SyncRegisterAllDevices(uid);
} }
private void OnTabChange(EntityUid uid, AirAlarmComponent component, AirAlarmTabSetMessage msg)
{
component.CurrentTab = msg.Tab;
UpdateUI(uid, component);
}
private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, ref PowerChangedEvent args) private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, ref PowerChangedEvent args)
{ {
if (args.Powered) if (args.Powered)
@@ -598,34 +592,19 @@ public sealed class AirAlarmSystem : EntitySystem
var pressure = CalculatePressureAverage(alarm); var pressure = CalculatePressureAverage(alarm);
var temperature = CalculateTemperatureAverage(alarm); var temperature = CalculateTemperatureAverage(alarm);
var dataToSend = new Dictionary<string, IAtmosDeviceData>(); var dataToSend = new List<(string, IAtmosDeviceData)>();
if (alarm.CurrentTab != AirAlarmTab.Settings) foreach (var (addr, data) in alarm.VentData)
{ {
switch (alarm.CurrentTab) dataToSend.Add((addr, data));
{ }
case AirAlarmTab.Vent: foreach (var (addr, data) in alarm.ScrubberData)
foreach (var (addr, data) in alarm.VentData) {
{ dataToSend.Add((addr, data));
dataToSend.Add(addr, data); }
} foreach (var (addr, data) in alarm.SensorData)
{
break; dataToSend.Add((addr, data));
case AirAlarmTab.Scrubber:
foreach (var (addr, data) in alarm.ScrubberData)
{
dataToSend.Add(addr, data);
}
break;
case AirAlarmTab.Sensors:
foreach (var (addr, data) in alarm.SensorData)
{
dataToSend.Add(addr, data);
}
break;
}
} }
var deviceCount = alarm.KnownDevices.Count; var deviceCount = alarm.KnownDevices.Count;
@@ -638,7 +617,7 @@ public sealed class AirAlarmSystem : EntitySystem
_ui.SetUiState( _ui.SetUiState(
uid, uid,
SharedAirAlarmInterfaceKey.Key, SharedAirAlarmInterfaceKey.Key,
new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value, alarm.AutoMode)); new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, highestAlarm.Value, alarm.AutoMode));
} }
private const float Delay = 8f; private const float Delay = 8f;
@@ -7,6 +7,7 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Server.Audio; using Robust.Server.Audio;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -9,6 +9,7 @@ using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -5,31 +5,25 @@ namespace Content.Server.Atmos.Piping.Trinary.Components
[RegisterComponent] [RegisterComponent]
public sealed partial class GasFilterComponent : Component public sealed partial class GasFilterComponent : Component
{ {
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("enabled")] public bool Enabled = true;
public bool Enabled { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("inlet")] [DataField("inlet")]
public string InletName { get; set; } = "inlet"; public string InletName = "inlet";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("filter")] [DataField("filter")]
public string FilterName { get; set; } = "filter"; public string FilterName = "filter";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")] [DataField("outlet")]
public string OutletName { get; set; } = "outlet"; public string OutletName = "outlet";
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public float TransferRate = Atmospherics.MaxTransferRate;
[DataField("transferRate")] [DataField]
public float TransferRate { get; set; } = Atmospherics.MaxTransferRate; public float MaxTransferRate = Atmospherics.MaxTransferRate;
[DataField("maxTransferRate")] [DataField]
public float MaxTransferRate { get; set; } = Atmospherics.MaxTransferRate; public Gas? FilteredGas;
[ViewVariables(VVAccess.ReadWrite)]
public Gas? FilteredGas { get; set; }
} }
} }
@@ -9,26 +9,25 @@ namespace Content.Server.Atmos.Piping.Unary.Components
[Access(typeof(GasVentScrubberSystem))] [Access(typeof(GasVentScrubberSystem))]
public sealed partial class GasVentScrubberComponent : Component public sealed partial class GasVentScrubberComponent : Component
{ {
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public bool Enabled { get; set; } = false; public bool Enabled { get; set; } = false;
[ViewVariables] [DataField]
public bool IsDirty { get; set; } = false; public bool IsDirty { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("outlet")] [DataField("outlet")]
public string OutletName { get; set; } = "pipe"; public string OutletName { get; set; } = "pipe";
[ViewVariables] [DataField]
public readonly HashSet<Gas> FilterGases = new(GasVentScrubberData.DefaultFilterGases); public HashSet<Gas> FilterGases = new(GasVentScrubberData.DefaultFilterGases);
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing; public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing;
/// <summary> /// <summary>
/// Target volume to transfer. If <see cref="WideNet"/> is enabled, actual transfer rate will be much higher. /// Target volume to transfer. If <see cref="WideNet"/> is enabled, actual transfer rate will be much higher.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public float TransferRate public float TransferRate
{ {
get => _transferRate; get => _transferRate;
@@ -37,18 +36,17 @@ namespace Content.Server.Atmos.Piping.Unary.Components
private float _transferRate = Atmospherics.MaxTransferRate; private float _transferRate = Atmospherics.MaxTransferRate;
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("maxTransferRate")]
public float MaxTransferRate = Atmospherics.MaxTransferRate; public float MaxTransferRate = Atmospherics.MaxTransferRate;
/// <summary> /// <summary>
/// As pressure difference approaches this number, the effective volume rate may be smaller than <see /// As pressure difference approaches this number, the effective volume rate may be smaller than <see
/// cref="TransferRate"/> /// cref="TransferRate"/>
/// </summary> /// </summary>
[DataField("maxPressure")] [DataField]
public float MaxPressure = Atmospherics.MaxOutputPressure; public float MaxPressure = Atmospherics.MaxOutputPressure;
[ViewVariables(VVAccess.ReadWrite)] [DataField]
public bool WideNet { get; set; } = false; public bool WideNet { get; set; } = false;
public GasVentScrubberData ToAirAlarmData() public GasVentScrubberData ToAirAlarmData()
@@ -15,6 +15,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public sealed class GasPortableSystem : EntitySystem public sealed class GasPortableSystem : EntitySystem
{ {
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
@@ -33,7 +34,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
return; return;
// If we can't find any ports, cancel the anchoring. // If we can't find any ports, cancel the anchoring.
if(!FindGasPortIn(transform.GridUid, transform.Coordinates, out _)) if (!FindGasPortIn(transform.GridUid, transform.Coordinates, out _))
args.Cancel(); args.Cancel();
} }
@@ -57,7 +58,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
if (!TryComp<MapGridComponent>(gridId, out var grid)) if (!TryComp<MapGridComponent>(gridId, out var grid))
return false; return false;
foreach (var entityUid in grid.GetLocal(coordinates)) foreach (var entityUid in _mapSystem.GetLocal(gridId.Value, grid, coordinates))
{ {
if (EntityManager.TryGetComponent(entityUid, out port)) if (EntityManager.TryGetComponent(entityUid, out port))
{ {
@@ -18,6 +18,7 @@ using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Power;
using Content.Shared.Tools.Systems; using Content.Shared.Tools.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -16,6 +16,7 @@ using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tools.Systems; using Content.Shared.Tools.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -15,6 +15,7 @@ using Content.Server.Administration.Logs;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Power;
namespace Content.Server.Atmos.Portable namespace Content.Server.Atmos.Portable
{ {
@@ -6,6 +6,7 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos.Piping.Portable.Components; using Content.Shared.Atmos.Piping.Portable.Components;
using Content.Shared.Atmos.Visuals; using Content.Shared.Atmos.Visuals;
using Content.Shared.Power;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -2,6 +2,7 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Power;
namespace Content.Server.Audio; namespace Content.Server.Audio;
@@ -1,6 +1,7 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Audio.Jukebox; using Content.Shared.Audio.Jukebox;
using Content.Shared.Power;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Audio.Components; using Robust.Shared.Audio.Components;
+1
View File
@@ -10,6 +10,7 @@ using Content.Shared.Buckle.Components;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Power;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -1,17 +1,14 @@
using System.Globalization;
using System.Linq;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.Ghost;
using Content.Server.Hands.Systems; using Content.Server.Hands.Systems;
using Content.Server.Inventory; using Content.Server.Inventory;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Chat.Systems;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Server.StationRecords; using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems; using Content.Server.StationRecords.Systems;
using Content.Shared.StationRecords;
using Content.Shared.UserInterface;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Bed.Cryostorage; using Content.Shared.Bed.Cryostorage;
using Content.Shared.Chat; using Content.Shared.Chat;
@@ -19,6 +16,8 @@ using Content.Shared.Climbing.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.StationRecords;
using Content.Shared.UserInterface;
using Robust.Server.Audio; using Robust.Server.Audio;
using Robust.Server.Containers; using Robust.Server.Containers;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -27,6 +26,7 @@ using Robust.Shared.Containers;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using System.Globalization;
namespace Content.Server.Bed.Cryostorage; namespace Content.Server.Bed.Cryostorage;
@@ -40,7 +40,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
[Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly ClimbSystem _climb = default!; [Dependency] private readonly ClimbSystem _climb = default!;
[Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly ServerInventorySystem _inventory = default!; [Dependency] private readonly ServerInventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
@@ -210,7 +210,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
if (userId != null && Mind.TryGetMind(userId.Value, out var mind) && if (userId != null && Mind.TryGetMind(userId.Value, out var mind) &&
HasComp<CryostorageContainedComponent>(mind.Value.Comp.CurrentEntity)) HasComp<CryostorageContainedComponent>(mind.Value.Comp.CurrentEntity))
{ {
_gameTicker.OnGhostAttempt(mind.Value, false); _ghostSystem.OnGhostAttempt(mind.Value, false);
} }
} }
+3 -3
View File
@@ -1,5 +1,5 @@
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.GameTicking; using Content.Server.Ghost;
using Content.Server.Humanoid; using Content.Server.Humanoid;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
@@ -17,7 +17,7 @@ namespace Content.Server.Body.Systems;
public sealed class BodySystem : SharedBodySystem public sealed class BodySystem : SharedBodySystem
{ {
[Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
@@ -43,7 +43,7 @@ public sealed class BodySystem : SharedBodySystem
if (_mobState.IsDead(ent) && _mindSystem.TryGetMind(ent, out var mindId, out var mind)) if (_mobState.IsDead(ent) && _mindSystem.TryGetMind(ent, out var mindId, out var mind))
{ {
mind.TimeOfDeath ??= _gameTiming.RealTime; mind.TimeOfDeath ??= _gameTiming.RealTime;
_ticker.OnGhostAttempt(mindId, canReturnGlobal: true, mind: mind); _ghostSystem.OnGhostAttempt(mindId, canReturnGlobal: true, mind: mind);
} }
} }
@@ -1,6 +1,7 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Atmos.Rotting; using Content.Shared.Atmos.Rotting;
using Content.Shared.Buckle.Components; using Content.Shared.Buckle.Components;
using Content.Shared.Power;
namespace Content.Server.Buckle.Systems; namespace Content.Server.Buckle.Systems;
@@ -6,6 +6,7 @@ using Content.Server.Station.Components;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Components;
using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking;
using Content.Shared.Power;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
+6 -6
View File
@@ -1,18 +1,18 @@
using Content.Server.GameTicking; using Content.Server.Ghost;
using Content.Shared.Administration.Logs;
using Content.Shared.Chat;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Shared.Player; using Robust.Shared.Player;
using Content.Shared.Administration.Logs;
using Content.Shared.Chat;
using Content.Shared.Mind.Components;
namespace Content.Server.Chat; namespace Content.Server.Chat;
@@ -23,7 +23,7 @@ public sealed class SuicideSystem : EntitySystem
[Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly SharedSuicideSystem _suicide = default!; [Dependency] private readonly SharedSuicideSystem _suicide = default!;
public override void Initialize() public override void Initialize()
@@ -82,7 +82,7 @@ public sealed class SuicideSystem : EntitySystem
if (_tagSystem.HasTag(victim, "CannotSuicide")) if (_tagSystem.HasTag(victim, "CannotSuicide"))
args.CanReturnToBody = true; args.CanReturnToBody = true;
if (_gameTicker.OnGhostAttempt(victim.Comp.Mind.Value, args.CanReturnToBody, mind: mindComponent)) if (_ghostSystem.OnGhostAttempt(victim.Comp.Mind.Value, args.CanReturnToBody, mind: mindComponent))
args.Handled = true; args.Handled = true;
} }
@@ -2,6 +2,7 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Power;
namespace Content.Server.Chemistry.EntitySystems; namespace Content.Server.Chemistry.EntitySystems;
@@ -5,6 +5,7 @@ using Content.Server.Power.EntitySystems;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Placeable; using Content.Shared.Placeable;
using Content.Shared.Power;
namespace Content.Server.Chemistry.EntitySystems; namespace Content.Server.Chemistry.EntitySystems;
@@ -15,6 +15,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Power;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Player; using Robust.Server.Player;
@@ -1,5 +1,5 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.GameTicking; using Content.Server.Ghost;
using Content.Server.Mind; using Content.Server.Mind;
using Content.Server.NPC; using Content.Server.NPC;
using Content.Server.NPC.HTN; using Content.Server.NPC.HTN;
@@ -21,7 +21,7 @@ namespace Content.Server.Clothing.Systems;
public sealed class CursedMaskSystem : SharedCursedMaskSystem public sealed class CursedMaskSystem : SharedCursedMaskSystem
{ {
[Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly HTNSystem _htn = default!; [Dependency] private readonly HTNSystem _htn = default!;
[Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly NPCSystem _npc = default!;
@@ -39,7 +39,7 @@ public sealed class CursedMaskSystem : SharedCursedMaskSystem
if (TryComp<ActorComponent>(wearer, out var actor) && actor.PlayerSession.GetMind() is { } mind) if (TryComp<ActorComponent>(wearer, out var actor) && actor.PlayerSession.GetMind() is { } mind)
{ {
var session = actor.PlayerSession; var session = actor.PlayerSession;
if (!_ticker.OnGhostAttempt(mind, false)) if (!_ghostSystem.OnGhostAttempt(mind, false))
return; return;
ent.Comp.StolenMind = mind; ent.Comp.StolenMind = mind;
@@ -183,10 +183,6 @@ namespace Content.Server.Communications
private bool CanUse(EntityUid user, EntityUid console) private bool CanUse(EntityUid user, EntityUid console)
{ {
// This shouldn't technically be possible because of BUI but don't trust client.
if (!_interaction.InRangeUnobstructed(console, user))
return false;
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console)) if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent) && !HasComp<EmaggedComponent>(console))
{ {
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent); return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
@@ -0,0 +1,221 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Connection.Whitelist;
using Content.Server.Connection.Whitelist.Conditions;
using Content.Server.Database;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Players.PlayTimeTracking;
using Robust.Shared.Network;
namespace Content.Server.Connection;
/// <summary>
/// Handles whitelist conditions for incoming connections.
/// </summary>
public sealed partial class ConnectionManager
{
private PlayerConnectionWhitelistPrototype[]? _whitelists;
public void PostInit()
{
_cfg.OnValueChanged(CCVars.WhitelistPrototypeList, UpdateWhitelists, true);
}
private void UpdateWhitelists(string s)
{
var list = new List<PlayerConnectionWhitelistPrototype>();
foreach (var id in s.Split(','))
{
if (_prototypeManager.TryIndex(id, out PlayerConnectionWhitelistPrototype? prototype))
{
list.Add(prototype);
}
else
{
_sawmill.Fatal($"Whitelist prototype {id} does not exist. Denying all connections.");
_whitelists = null; // Invalidate the list, causes deny on all connections.
return;
}
}
_whitelists = list.ToArray();
}
private bool IsValid(PlayerConnectionWhitelistPrototype whitelist, int playerCount)
{
return playerCount >= whitelist.MinimumPlayers && playerCount <= whitelist.MaximumPlayers;
}
public async Task<(bool isWhitelisted, string? denyMessage)> IsWhitelisted(PlayerConnectionWhitelistPrototype whitelist, NetUserData data, ISawmill sawmill)
{
var cacheRemarks = await _db.GetAllAdminRemarks(data.UserId);
var cachePlaytime = await _db.GetPlayTimes(data.UserId);
foreach (var condition in whitelist.Conditions)
{
bool matched;
string denyMessage;
switch (condition)
{
case ConditionAlwaysMatch:
matched = true;
denyMessage = Loc.GetString("whitelist-always-deny");
break;
case ConditionManualWhitelistMembership:
matched = await CheckConditionManualWhitelist(data);
denyMessage = Loc.GetString("whitelist-manual");
break;
case ConditionManualBlacklistMembership:
matched = await CheckConditionManualBlacklist(data);
denyMessage = Loc.GetString("whitelist-blacklisted");
break;
case ConditionNotesDateRange conditionNotes:
matched = CheckConditionNotesDateRange(conditionNotes, cacheRemarks);
denyMessage = Loc.GetString("whitelist-notes");
break;
case ConditionPlayerCount conditionPlayerCount:
matched = CheckConditionPlayerCount(conditionPlayerCount);
denyMessage = Loc.GetString("whitelist-player-count");
break;
case ConditionPlaytime conditionPlaytime:
matched = CheckConditionPlaytime(conditionPlaytime, cachePlaytime);
denyMessage = Loc.GetString("whitelist-playtime", ("minutes", conditionPlaytime.MinimumPlaytime));
break;
case ConditionNotesPlaytimeRange conditionNotesPlaytimeRange:
matched = CheckConditionNotesPlaytimeRange(conditionNotesPlaytimeRange, cacheRemarks, cachePlaytime);
denyMessage = Loc.GetString("whitelist-notes");
break;
default:
throw new NotImplementedException($"Whitelist condition {condition.GetType().Name} not implemented");
}
sawmill.Verbose($"User {data.UserName} whitelist condition {condition.GetType().Name} result: {matched}");
sawmill.Verbose($"Action: {condition.Action.ToString()}");
switch (condition.Action)
{
case ConditionAction.Allow:
if (matched)
{
sawmill.Verbose($"User {data.UserName} passed whitelist condition {condition.GetType().Name} and it's a breaking condition");
return (true, denyMessage);
}
break;
case ConditionAction.Deny:
if (matched)
{
sawmill.Verbose($"User {data.UserName} failed whitelist condition {condition.GetType().Name}");
return (false, denyMessage);
}
break;
default:
sawmill.Verbose($"User {data.UserName} failed whitelist condition {condition.GetType().Name} but it's not a breaking condition");
break;
}
}
sawmill.Verbose($"User {data.UserName} passed all whitelist conditions");
return (true, null);
}
#region Condition Checking
private async Task<bool> CheckConditionManualWhitelist(NetUserData data)
{
return await _db.GetWhitelistStatusAsync(data.UserId);
}
private async Task<bool> CheckConditionManualBlacklist(NetUserData data)
{
return await _db.GetBlacklistStatusAsync(data.UserId);
}
private bool CheckConditionNotesDateRange(ConditionNotesDateRange conditionNotes, List<IAdminRemarksRecord> remarks)
{
var range = DateTime.UtcNow.AddDays(-conditionNotes.Range);
return CheckRemarks(remarks,
conditionNotes.IncludeExpired,
conditionNotes.IncludeSecret,
conditionNotes.MinimumSeverity,
conditionNotes.MinimumNotes,
adminRemarksRecord => adminRemarksRecord.CreatedAt > range);
}
private bool CheckConditionPlayerCount(ConditionPlayerCount conditionPlayerCount)
{
var count = _plyMgr.PlayerCount;
return count >= conditionPlayerCount.MinimumPlayers && count <= conditionPlayerCount.MaximumPlayers;
}
private bool CheckConditionPlaytime(ConditionPlaytime conditionPlaytime, List<PlayTime> playtime)
{
var tracker = playtime.Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
if (tracker is null)
{
return false;
}
return tracker.TimeSpent.TotalMinutes >= conditionPlaytime.MinimumPlaytime;
}
private bool CheckConditionNotesPlaytimeRange(
ConditionNotesPlaytimeRange conditionNotesPlaytimeRange,
List<IAdminRemarksRecord> remarks,
List<PlayTime> playtime)
{
var overallTracker = playtime.Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
if (overallTracker is null)
{
return false;
}
return CheckRemarks(remarks,
conditionNotesPlaytimeRange.IncludeExpired,
conditionNotesPlaytimeRange.IncludeSecret,
conditionNotesPlaytimeRange.MinimumSeverity,
conditionNotesPlaytimeRange.MinimumNotes,
adminRemarksRecord => adminRemarksRecord.PlaytimeAtNote >= overallTracker.TimeSpent - TimeSpan.FromMinutes(conditionNotesPlaytimeRange.Range));
}
private bool CheckRemarks(List<IAdminRemarksRecord> remarks, bool includeExpired, bool includeSecret, NoteSeverity minimumSeverity, int MinimumNotes, Func<IAdminRemarksRecord, bool> additionalCheck)
{
var utcNow = DateTime.UtcNow;
var notes = remarks.Count(r => r is AdminNoteRecord note && note.Severity >= minimumSeverity && (includeSecret || !note.Secret) && (includeExpired || note.ExpirationTime == null || note.ExpirationTime > utcNow));
if (notes < MinimumNotes)
{
return false;
}
foreach (var adminRemarksRecord in remarks)
{
// If we're not including expired notes, skip them
if (!includeExpired && (adminRemarksRecord.ExpirationTime == null || adminRemarksRecord.ExpirationTime <= utcNow))
continue;
// In order to get the severity of the remark, we need to see if it's an AdminNoteRecord.
if (adminRemarksRecord is not AdminNoteRecord adminNoteRecord)
continue;
// We want to filter out secret notes if we're not including them.
if (!includeSecret && adminNoteRecord.Secret)
continue;
// At this point, we need to remove the note if it's not within the severity range.
if (adminNoteRecord.Severity < minimumSeverity)
continue;
// Perform the additional check specific to each method
if (!additionalCheck(adminRemarksRecord))
continue;
// If we've made it this far, we have a match
return true;
}
// No matches
return false;
}
#endregion
}
+35 -14
View File
@@ -1,5 +1,9 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server.Connection.Whitelist;
using Content.Server.Connection.Whitelist.Conditions;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Corvax.Interfaces.Server; using Content.Corvax.Interfaces.Server;
@@ -16,6 +20,7 @@ using Robust.Server.Player;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -28,6 +33,7 @@ namespace Content.Server.Connection
public interface IConnectionManager public interface IConnectionManager
{ {
void Initialize(); void Initialize();
void PostInit();
Task<bool> HavePrivilegedJoin(NetUserId userId); // Corvax-Queue Task<bool> HavePrivilegedJoin(NetUserId userId); // Corvax-Queue
/// <summary> /// <summary>
@@ -46,7 +52,7 @@ namespace Content.Server.Connection
/// <summary> /// <summary>
/// Handles various duties like guest username assignment, bans, connection logs, etc... /// Handles various duties like guest username assignment, bans, connection logs, etc...
/// </summary> /// </summary>
public sealed class ConnectionManager : IConnectionManager public sealed partial class ConnectionManager : IConnectionManager
{ {
[Dependency] private readonly IServerDbManager _dbManager = default!; [Dependency] private readonly IServerDbManager _dbManager = default!;
[Dependency] private readonly IPlayerManager _plyMgr = default!; [Dependency] private readonly IPlayerManager _plyMgr = default!;
@@ -55,14 +61,16 @@ namespace Content.Server.Connection
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILocalizationManager _loc = default!; [Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!; [Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
private ISharedSponsorsManager? _sponsorsMgr; // Corvax-Sponsors private ISharedSponsorsManager? _sponsorsMgr; // Corvax-Sponsors
private IServerVPNGuardManager? _vpnGuardMgr; // Corvax-VPNGuard private IServerVPNGuardManager? _vpnGuardMgr; // Corvax-VPNGuard
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
private ISawmill _sawmill = default!; private ISawmill _sawmill = default!;
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
public void Initialize() public void Initialize()
{ {
@@ -297,20 +305,33 @@ namespace Content.Server.Connection
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null); return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
} }
if (_cfg.GetCVar(CCVars.WhitelistEnabled)) // Checks for whitelist IF it's enabled AND the user isn't an admin. Admins are always allowed.
if (_cfg.GetCVar(CCVars.WhitelistEnabled) && adminData is null)
{ {
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers); if (_whitelists is null)
var max = _cfg.GetCVar(CCVars.WhitelistMaxPlayers);
var playerCountValid = _plyMgr.PlayerCount >= min && _plyMgr.PlayerCount < max;
if (playerCountValid && await _db.GetWhitelistStatusAsync(userId) == false
&& adminData is null)
{ {
var msg = Loc.GetString(_cfg.GetCVar(CCVars.WhitelistReason)); _sawmill.Error("Whitelist enabled but no whitelists loaded.");
// was the whitelist playercount changed? // Misconfigured, deny everyone.
if (min > 0 || max < int.MaxValue) return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-misconfigured"), null);
msg += "\n" + Loc.GetString("whitelist-playercount-invalid", ("min", min), ("max", max)); }
return (ConnectionDenyReason.Whitelist, msg, null);
foreach (var whitelist in _whitelists)
{
if (!IsValid(whitelist, _plyMgr.PlayerCount))
{
// Not valid for current player count.
continue;
}
var whitelistStatus = await IsWhitelisted(whitelist, e.UserData, _sawmill);
if (!whitelistStatus.isWhitelisted)
{
// Not whitelisted.
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-fail-prefix", ("msg", whitelistStatus.denyMessage!)), null);
}
// Whitelisted, don't check any more.
break;
} }
} }
@@ -0,0 +1,117 @@
using Content.Server.Administration;
using Content.Server.Database;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Connection.Whitelist;
[AdminCommand(AdminFlags.Ban)]
public sealed class AddBlacklistCommand : LocalizedCommands
{
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
[Dependency] private readonly IServerDbManager _db = default!;
public override string Command => "blacklistadd";
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length == 0)
{
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
shell.WriteLine(Help);
return;
}
if (args.Length > 1)
{
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
shell.WriteLine(Help);
return;
}
var name = args[0];
var data = await _playerLocator.LookupIdByNameAsync(name);
if (data == null)
{
shell.WriteError(Loc.GetString("cmd-blacklistadd-not-found", ("username", args[0])));
return;
}
var guid = data.UserId;
var isBlacklisted = await _db.GetBlacklistStatusAsync(guid);
if (isBlacklisted)
{
shell.WriteLine(Loc.GetString("cmd-blacklistadd-existing", ("username", data.Username)));
return;
}
await _db.AddToBlacklistAsync(guid);
shell.WriteLine(Loc.GetString("cmd-blacklistadd-added", ("username", data.Username)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
return CompletionResult.FromHint(Loc.GetString("cmd-blacklistadd-arg-player"));
}
return CompletionResult.Empty;
}
}
[AdminCommand(AdminFlags.Ban)]
public sealed class RemoveBlacklistCommand : LocalizedCommands
{
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
[Dependency] private readonly IServerDbManager _db = default!;
public override string Command => "blacklistremove";
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length == 0)
{
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
shell.WriteLine(Help);
return;
}
if (args.Length > 1)
{
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
shell.WriteLine(Help);
return;
}
var name = args[0];
var data = await _playerLocator.LookupIdByNameAsync(name);
if (data == null)
{
shell.WriteError(Loc.GetString("cmd-blacklistremove-not-found", ("username", args[0])));
return;
}
var guid = data.UserId;
var isBlacklisted = await _db.GetBlacklistStatusAsync(guid);
if (!isBlacklisted)
{
shell.WriteLine(Loc.GetString("cmd-blacklistremove-existing", ("username", data.Username)));
return;
}
await _db.RemoveFromBlacklistAsync(guid);
shell.WriteLine(Loc.GetString("cmd-blacklistremove-removed", ("username", data.Username)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
return CompletionResult.FromHint(Loc.GetString("cmd-blacklistremove-arg-player"));
}
return CompletionResult.Empty;
}
}
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Robust.Shared.Network;
namespace Content.Server.Connection.Whitelist.Conditions;
/// <summary>
/// Condition that always matches
/// </summary>
public sealed partial class ConditionAlwaysMatch : WhitelistCondition
{
}
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Content.Server.Database;
using Robust.Shared.Network;
namespace Content.Server.Connection.Whitelist.Conditions;
/// <summary>
/// Condition that matches if the player is in the manual blacklist.
/// </summary>
public sealed partial class ConditionManualBlacklistMembership : WhitelistCondition
{
}
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Content.Server.Database;
using Robust.Shared.Network;
namespace Content.Server.Connection.Whitelist.Conditions;
/// <summary>
/// Condition that matches if the player is in the manual whitelist.
/// </summary>
public sealed partial class ConditionManualWhitelistMembership : WhitelistCondition
{
}
@@ -0,0 +1,34 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared.Database;
using Robust.Shared.Network;
namespace Content.Server.Connection.Whitelist.Conditions;
/// <summary>
/// Condition that matches if the player has notes within a certain date range.
/// </summary>
public sealed partial class ConditionNotesDateRange : WhitelistCondition
{
[DataField]
public bool IncludeExpired = false;
[DataField]
public NoteSeverity MinimumSeverity = NoteSeverity.Minor;
/// <summary>
/// The minimum number of notes required.
/// </summary>
[DataField]
public int MinimumNotes = 1;
/// <summary>
/// Range in days to check for notes.
/// </summary>
[DataField]
public int Range = int.MaxValue;
[DataField]
public bool IncludeSecret = false;
}

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