Merge pull request #428 from crystallpunk-14/ed-29-08-2024
Upstream sync
This commit is contained in:
@@ -259,12 +259,6 @@ namespace Content.Client.Actions
|
||||
|
||||
if (action.ClientExclusive)
|
||||
{
|
||||
if (instantAction.Event != null)
|
||||
{
|
||||
instantAction.Event.Performer = user;
|
||||
instantAction.Event.Action = actionId;
|
||||
}
|
||||
|
||||
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -2,10 +2,10 @@ using System.Numerics;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
@@ -15,14 +15,16 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
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;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
@@ -57,16 +59,18 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
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 +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
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, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : 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 Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.Systems
|
||||
{
|
||||
@@ -11,6 +13,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -19,7 +22,7 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
private void InitializeOverlay()
|
||||
{
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup);
|
||||
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.Bwoink
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
|
||||
@@ -30,7 +30,6 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
_window.AirAlarmModeChanged += OnAirAlarmModeChanged;
|
||||
_window.AutoModeChanged += OnAutoModeChanged;
|
||||
_window.ResyncAllRequested += ResyncAllDevices;
|
||||
_window.AirAlarmTabChange += OnTabChanged;
|
||||
}
|
||||
|
||||
private void ResyncAllDevices()
|
||||
@@ -63,11 +62,6 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
SendMessage(new AirAlarmUpdateAlarmThresholdMessage(address, type, threshold, gas));
|
||||
}
|
||||
|
||||
private void OnTabChanged(AirAlarmTab tab)
|
||||
{
|
||||
SendMessage(new AirAlarmTabSetMessage(tab));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
@@ -23,7 +23,6 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
public event Action<AirAlarmMode>? AirAlarmModeChanged;
|
||||
public event Action<bool>? AutoModeChanged;
|
||||
public event Action? ResyncAllRequested;
|
||||
public event Action<AirAlarmTab>? AirAlarmTabChange;
|
||||
|
||||
private RichTextLabel _address => CDeviceAddress;
|
||||
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(2, Loc.GetString("air-alarm-ui-window-tab-sensors"));
|
||||
|
||||
_tabContainer.OnTabChanged += idx =>
|
||||
{
|
||||
AirAlarmTabChange!((AirAlarmTab) idx);
|
||||
};
|
||||
|
||||
_resyncDevices.OnPressed += _ =>
|
||||
{
|
||||
_ventDevices.RemoveAllChildren();
|
||||
@@ -117,8 +111,6 @@ public sealed partial class AirAlarmWindow : FancyWindow
|
||||
{
|
||||
UpdateDeviceData(addr, dev);
|
||||
}
|
||||
|
||||
_tabContainer.CurrentTab = (int) state.Tab;
|
||||
}
|
||||
|
||||
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.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
Text="{Loc 'news-read-ui-next-text'}"
|
||||
ToolTip="{Loc 'news-read-ui-next-tooltip'}"/>
|
||||
</BoxContainer>
|
||||
<controls:StripeBack Name="АrticleNameContainer">
|
||||
<controls:StripeBack Name="ArticleNameContainer">
|
||||
<PanelContainer>
|
||||
<Label Name="PageNum" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="4,0,0,0"/>
|
||||
<Label Name="PageName" Align="Center"/>
|
||||
|
||||
@@ -19,9 +19,6 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly EntityWhitelistSystem _whitelistSystem;
|
||||
|
||||
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
|
||||
|
||||
public EmotesMenu()
|
||||
@@ -29,8 +26,8 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
||||
var spriteSystem = _entManager.System<SpriteSystem>();
|
||||
var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
|
||||
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
|
||||
@@ -40,8 +37,8 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
if (emote.Category == EmoteCategory.Invalid ||
|
||||
emote.ChatTriggers.Count == 0 ||
|
||||
!(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
|
||||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
|
||||
!(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
|
||||
whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
|
||||
continue;
|
||||
|
||||
if (!emote.Available &&
|
||||
@@ -63,7 +60,7 @@ public sealed partial class EmotesMenu : RadialMenu
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Texture = _spriteSystem.Frame0(emote.Icon),
|
||||
Texture = spriteSystem.Frame0(emote.Icon),
|
||||
TextureScale = new Vector2(2f, 2f),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
MinSize="400 225">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
|
||||
<TextEdit Name="MessageInput" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 0 5" MinHeight="100" />
|
||||
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
<Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
<Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
|
||||
<OptionButton Name="AlertLevelButton" StyleClasses="OpenRight" Access="Public" />
|
||||
<OptionButton Name="AlertLevelButton" ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}" StyleClasses="OpenRight" Access="Public" />
|
||||
|
||||
<Control MinSize="10 10" />
|
||||
|
||||
<RichTextLabel Name="CountdownLabel" VerticalExpand="True" />
|
||||
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" Access="Public" />
|
||||
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}" Access="Public" />
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Materials;
|
||||
using Content.Client.Materials.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Construction.Components;
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Spawners\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -194,8 +195,20 @@ namespace Content.Client.ContextMenu.UI
|
||||
return;
|
||||
|
||||
// Do we need to do in-range unOccluded checks?
|
||||
var ignoreFov = !_eyeManager.CurrentEye.DrawFov ||
|
||||
(_verbSystem.Visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov;
|
||||
var visibility = _verbSystem.Visibility;
|
||||
|
||||
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);
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
@@ -209,7 +222,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreFov)
|
||||
if ((visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov)
|
||||
continue;
|
||||
|
||||
var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.CrewManifest.UI;
|
||||
using Content.Shared.CrewManifest;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
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" />
|
||||
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace Content.Client.Entry
|
||||
_prototypeManager.RegisterIgnore("lobbyBackground");
|
||||
_prototypeManager.RegisterIgnore("gamePreset");
|
||||
_prototypeManager.RegisterIgnore("noiseChannel");
|
||||
_prototypeManager.RegisterIgnore("playerConnectionWhitelist");
|
||||
_prototypeManager.RegisterIgnore("spaceBiome");
|
||||
_prototypeManager.RegisterIgnore("worldgenConfig");
|
||||
_prototypeManager.RegisterIgnore("gameRule");
|
||||
|
||||
@@ -99,8 +99,8 @@ namespace Content.Client.Ghost
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-ghost-visibility-popup"), args.Performer);
|
||||
|
||||
var locId = GhostVisibility ? "ghost-gui-toggle-ghost-visibility-popup-off" : "ghost-gui-toggle-ghost-visibility-popup-on";
|
||||
Popup.PopupEntity(Loc.GetString(locId), args.Performer);
|
||||
if (uid == _playerManager.LocalEntity)
|
||||
ToggleGhostVisibility();
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
public ProtoId<GuideEntryPrototype> LastEntry;
|
||||
|
||||
public GuidebookWindow()
|
||||
{
|
||||
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}.");
|
||||
}
|
||||
|
||||
LastEntry = entry.Id;
|
||||
}
|
||||
|
||||
public void UpdateGuides(
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<HealthAnalyzerWindow>();
|
||||
|
||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
@@ -1,48 +1,64 @@
|
||||
<controls:FancyWindow
|
||||
<controls:FancyWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="250 100">
|
||||
MaxHeight="525"
|
||||
MinWidth="300">
|
||||
<ScrollContainer
|
||||
Margin="5 5 5 5"
|
||||
ReturnMeasure="True"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Name="RootContainer"
|
||||
VerticalExpand="True"
|
||||
Orientation="Vertical">
|
||||
<Label
|
||||
Name="NoPatientDataText"
|
||||
Text="{Loc health-analyzer-window-no-patient-data-text}" />
|
||||
|
||||
<BoxContainer
|
||||
Name="PatientDataContainer"
|
||||
Orientation="Vertical"
|
||||
Margin="0 0 5 10">
|
||||
<BoxContainer Name="ScanModePanel" HorizontalExpand="True" Visible="False" Margin="0 5 0 0">
|
||||
<Label
|
||||
Name="ScanMode"
|
||||
Align="Left"
|
||||
Text="{Loc health-analyzer-window-scan-mode-text}"/>
|
||||
<Label
|
||||
Name="ScanModeText"
|
||||
Align="Right"
|
||||
HorizontalExpand="True"/>
|
||||
Margin="0 0 0 5"
|
||||
Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
|
||||
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
|
||||
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
|
||||
<RichTextLabel Name="NameLabel" SetWidth="150" />
|
||||
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
|
||||
</BoxContainer>
|
||||
<Label Margin="0 0 5 0" HorizontalExpand="True" HorizontalAlignment="Right" VerticalExpand="True"
|
||||
VerticalAlignment="Top" Name="ScanModeLabel"
|
||||
Text="{Loc 'health-analyzer-window-entity-unknown-text'}" />
|
||||
</BoxContainer>
|
||||
<Label
|
||||
Name="PatientName"/>
|
||||
<Label
|
||||
Name="Temperature"
|
||||
Margin="0 5 0 0"/>
|
||||
<Label
|
||||
Name="BloodLevel"
|
||||
Margin="0 5 0 0"/>
|
||||
<Label
|
||||
Name="Bleeding"
|
||||
Margin="0 5 0 0"/>
|
||||
<Label
|
||||
Name="patientDamageAmount"
|
||||
Margin="0 15 0 0"/>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
|
||||
<GridContainer Margin="0 5 0 0" Columns="2">
|
||||
<Label Text="{Loc 'health-analyzer-window-entity-status-text'}" />
|
||||
<Label Name="StatusLabel" />
|
||||
<Label Text="{Loc 'health-analyzer-window-entity-temperature-text'}" />
|
||||
<Label Name="TemperatureLabel" />
|
||||
<Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}" />
|
||||
<Label Name="BloodLabel" />
|
||||
<Label Text="{Loc 'health-analyzer-window-entity-damage-total-text'}" />
|
||||
<Label Name="DamageLabel" />
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
|
||||
|
||||
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Horizontal"
|
||||
HorizontalExpand="True" HorizontalAlignment="Center">
|
||||
|
||||
</BoxContainer>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
|
||||
<BoxContainer
|
||||
Name="GroupsContainer"
|
||||
Margin="0 5 0 5"
|
||||
Orientation="Vertical">
|
||||
</BoxContainer>
|
||||
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MedicalScanner;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -28,9 +36,6 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
private readonly IPrototypeManager _prototypes;
|
||||
private readonly IResourceCache _cache;
|
||||
|
||||
private const int AnalyzerHeight = 430;
|
||||
private const int AnalyzerWidth = 300;
|
||||
|
||||
public HealthAnalyzerWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -44,8 +49,6 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
|
||||
public void Populate(HealthAnalyzerScannedUserMessage msg)
|
||||
{
|
||||
GroupsContainer.RemoveAllChildren();
|
||||
|
||||
var target = _entityManager.GetEntity(msg.TargetEntity);
|
||||
|
||||
if (target == null
|
||||
@@ -57,82 +60,96 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
|
||||
NoPatientDataText.Visible = false;
|
||||
|
||||
string entityName = Loc.GetString("health-analyzer-window-entity-unknown-text");
|
||||
if (_entityManager.HasComponent<MetaDataComponent>(target.Value))
|
||||
{
|
||||
entityName = Identity.Name(target.Value, _entityManager);
|
||||
}
|
||||
// Scan Mode
|
||||
|
||||
if (msg.ScanMode.HasValue)
|
||||
{
|
||||
ScanModePanel.Visible = true;
|
||||
ScanModeText.Text = Loc.GetString(msg.ScanMode.Value ? "health-analyzer-window-scan-mode-active" : "health-analyzer-window-scan-mode-inactive");
|
||||
ScanModeText.FontColorOverride = msg.ScanMode.Value ? Color.Green : Color.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScanModePanel.Visible = false;
|
||||
}
|
||||
ScanModeLabel.Text = msg.ScanMode.HasValue
|
||||
? msg.ScanMode.Value
|
||||
? Loc.GetString("health-analyzer-window-scan-mode-active")
|
||||
: Loc.GetString("health-analyzer-window-scan-mode-inactive")
|
||||
: Loc.GetString("health-analyzer-window-entity-unknown-text");
|
||||
|
||||
PatientName.Text = Loc.GetString(
|
||||
"health-analyzer-window-entity-health-text",
|
||||
("entityName", entityName)
|
||||
);
|
||||
ScanModeLabel.FontColorOverride = msg.ScanMode.HasValue && msg.ScanMode.Value ? Color.Green : Color.Red;
|
||||
|
||||
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
|
||||
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
|
||||
);
|
||||
// Patient Information
|
||||
|
||||
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
|
||||
("bloodLevel", float.IsNaN(msg.BloodLevel) ? "N/A" : $"{msg.BloodLevel * 100:F1} %")
|
||||
);
|
||||
SpriteView.SetEntity(target.Value);
|
||||
|
||||
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)
|
||||
{
|
||||
Bleeding.Text = Loc.GetString("health-analyzer-window-entity-bleeding-text");
|
||||
Bleeding.FontColorOverride = Color.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
Bleeding.Text = string.Empty; // Clear the text
|
||||
AlertsContainer.DisposeAllChildren();
|
||||
AlertsContainer.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
|
||||
FontColorOverride = Color.Red,
|
||||
});
|
||||
}
|
||||
|
||||
patientDamageAmount.Text = Loc.GetString(
|
||||
"health-analyzer-window-entity-damage-total-text",
|
||||
("amount", damageable.TotalDamage)
|
||||
);
|
||||
// Damage Groups
|
||||
|
||||
var damageSortedGroups =
|
||||
damageable.DamagePerGroup.OrderBy(damage => damage.Value)
|
||||
damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
|
||||
|
||||
DrawDiagnosticGroups(damageSortedGroups, damagePerType);
|
||||
}
|
||||
|
||||
if (_entityManager.TryGetComponent(target, out HungerComponent? hunger)
|
||||
&& hunger.StarvationDamage != null
|
||||
&& hunger.CurrentThreshold <= HungerThreshold.Starving)
|
||||
private static string GetStatus(MobState mobState)
|
||||
{
|
||||
return mobState switch
|
||||
{
|
||||
var box = new Control { Margin = new Thickness(0, 0, 0, 15) };
|
||||
|
||||
box.AddChild(CreateDiagnosticGroupTitle(
|
||||
Loc.GetString("health-analyzer-window-malnutrition"),
|
||||
"malnutrition"));
|
||||
|
||||
GroupsContainer.AddChild(box);
|
||||
}
|
||||
|
||||
SetHeight = AnalyzerHeight;
|
||||
SetWidth = AnalyzerWidth;
|
||||
MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
|
||||
MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
|
||||
MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
|
||||
_ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
|
||||
};
|
||||
}
|
||||
|
||||
private void DrawDiagnosticGroups(
|
||||
Dictionary<string, FixedPoint2> groups, IReadOnlyDictionary<string, FixedPoint2> damageDict)
|
||||
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.Reverse())
|
||||
foreach (var (damageGroupId, damageAmount) in groups)
|
||||
{
|
||||
if (damageAmount == 0)
|
||||
continue;
|
||||
@@ -145,7 +162,6 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
|
||||
var groupContainer = new BoxContainer
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 15),
|
||||
Align = BoxContainer.AlignMode.Begin,
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
};
|
||||
@@ -159,23 +175,16 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
if (damageDict.TryGetValue(type, out var typeAmount) && typeAmount > 0)
|
||||
{
|
||||
// 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;
|
||||
if (!damageDict.TryGetValue(type, out var typeAmount) || typeAmount <= 0)
|
||||
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(
|
||||
"health-analyzer-window-damage-type-text",
|
||||
("damageType", _prototypes.Index<DamageTypePrototype>(type).LocalizedName),
|
||||
("amount", typeAmount)
|
||||
);
|
||||
|
||||
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, "- ")));
|
||||
}
|
||||
groupContainer.AddChild(CreateDiagnosticItemLabel(damageString.Insert(0, " · ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +207,6 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
{
|
||||
return new Label
|
||||
{
|
||||
Margin = new Thickness(2, 2),
|
||||
Text = text,
|
||||
};
|
||||
}
|
||||
@@ -207,13 +215,13 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
{
|
||||
var rootContainer = new BoxContainer
|
||||
{
|
||||
Margin = new Thickness(0, 6, 0, 0),
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
};
|
||||
|
||||
rootContainer.AddChild(new TextureRect
|
||||
{
|
||||
Margin = new Thickness(0, 3),
|
||||
SetSize = new Vector2(30, 30),
|
||||
Texture = GetTexture(id.ToLower())
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Content.Client.Launcher
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
|
||||
private LauncherConnectingGui? _control;
|
||||
|
||||
@@ -58,7 +59,7 @@ namespace Content.Client.Launcher
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg);
|
||||
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard);
|
||||
|
||||
_userInterfaceManager.StateRoot.AddChild(_control);
|
||||
|
||||
|
||||
@@ -18,21 +18,38 @@
|
||||
<Control VerticalExpand="True" Margin="0 0 0 8">
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectingStatus">
|
||||
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
|
||||
<!-- Who the fuck named these cont- oh wait I did -->
|
||||
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectFail" Visible="False">
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectFail" Visible="False" SeparationOverride="10">
|
||||
<RichTextLabel Name="ConnectFailReason" VerticalAlignment="Stretch"/>
|
||||
<Button Name="RetryButton" Text="{Loc 'connecting-retry'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||
<BoxContainer Orientation="Horizontal" Align="Center">
|
||||
<Button Name="RetryButton"
|
||||
Text="{Loc 'connecting-retry'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="CopyButton"
|
||||
Text="{Loc 'connecting-copy'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="Disconnected">
|
||||
<BoxContainer Orientation="Vertical" Name="Disconnected" Visible="False" SeparationOverride="10">
|
||||
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
|
||||
<Label Name="DisconnectReason" Align="Center" />
|
||||
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||
<BoxContainer Orientation="Horizontal" Align="Center" VerticalAlignment="Bottom">
|
||||
<Button Name="ReconnectButton"
|
||||
Text="{Loc 'connecting-reconnect'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="CopyButtonDisconnected"
|
||||
Text="{Loc 'connecting-copy'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
|
||||
|
||||
@@ -28,14 +28,16 @@ namespace Content.Client.Launcher
|
||||
private readonly IRobustRandom _random;
|
||||
private readonly IPrototypeManager _prototype;
|
||||
private readonly IConfigurationManager _cfg;
|
||||
private readonly IClipboardManager _clipboard;
|
||||
|
||||
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
|
||||
IPrototypeManager prototype, IConfigurationManager config)
|
||||
IPrototypeManager prototype, IConfigurationManager config, IClipboardManager clipboard)
|
||||
{
|
||||
_state = state;
|
||||
_random = random;
|
||||
_prototype = prototype;
|
||||
_cfg = config;
|
||||
_clipboard = clipboard;
|
||||
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
@@ -44,8 +46,11 @@ namespace Content.Client.Launcher
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
ChangeLoginTip();
|
||||
ReconnectButton.OnPressed += ReconnectButtonPressed;
|
||||
RetryButton.OnPressed += ReconnectButtonPressed;
|
||||
ReconnectButton.OnPressed += ReconnectButtonPressed;
|
||||
|
||||
CopyButton.OnPressed += CopyButtonPressed;
|
||||
CopyButtonDisconnected.OnPressed += CopyButtonDisconnectedPressed;
|
||||
ExitButton.OnPressed += _ => _state.Exit();
|
||||
|
||||
var addr = state.Address;
|
||||
@@ -78,6 +83,24 @@ namespace Content.Client.Launcher
|
||||
_state.RetryConnect();
|
||||
}
|
||||
|
||||
private void CopyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
CopyText(ConnectFailReason.Text);
|
||||
}
|
||||
|
||||
private void CopyButtonDisconnectedPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
CopyText(DisconnectReason.Text);
|
||||
}
|
||||
|
||||
private void CopyText(string? text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
_clipboard.SetText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectFailReasonChanged(string? reason)
|
||||
{
|
||||
ConnectFailReason.SetMessage(reason == null
|
||||
|
||||
@@ -719,6 +719,9 @@ namespace Content.Client.Lobby.UI
|
||||
PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
|
||||
SpriteView.SetEntity(PreviewDummy);
|
||||
_entManager.System<MetaDataSystem>().SetEntityName(PreviewDummy, Profile.Name);
|
||||
|
||||
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -779,6 +782,9 @@ namespace Content.Client.Lobby.UI
|
||||
return;
|
||||
|
||||
_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)
|
||||
@@ -1015,7 +1021,6 @@ namespace Content.Client.Lobby.UI
|
||||
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
Profile = Profile?.WithLoadout(roleLoadout);
|
||||
SetDirty();
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
@@ -1024,7 +1029,6 @@ namespace Content.Client.Lobby.UI
|
||||
roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
Profile = Profile?.WithLoadout(roleLoadout);
|
||||
SetDirty();
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
@@ -1034,7 +1038,6 @@ namespace Content.Client.Lobby.UI
|
||||
_loadoutWindow.OnClose += () =>
|
||||
{
|
||||
JobOverride = null;
|
||||
SetDirty();
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
@@ -1059,7 +1062,6 @@ namespace Content.Client.Lobby.UI
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
|
||||
SetDirty();
|
||||
ReloadProfilePreview();
|
||||
}
|
||||
|
||||
@@ -1143,7 +1145,6 @@ namespace Content.Client.Lobby.UI
|
||||
// CP14 - Custom HumanoidSkinColor - End
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
ReloadProfilePreview();
|
||||
}
|
||||
|
||||
@@ -1174,7 +1175,6 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Profile = Profile?.WithAge(newAge);
|
||||
ReloadPreview();
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetSex(Sex newSex)
|
||||
@@ -1197,14 +1197,12 @@ namespace Content.Client.Lobby.UI
|
||||
UpdateGenderControls();
|
||||
Markings.SetSex(newSex);
|
||||
ReloadPreview();
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetGender(Gender newGender)
|
||||
{
|
||||
Profile = Profile?.WithGender(newGender);
|
||||
ReloadPreview();
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetSpecies(string newSpecies)
|
||||
@@ -1218,7 +1216,6 @@ namespace Content.Client.Lobby.UI
|
||||
RefreshLoadouts();
|
||||
UpdateSexControls(); // update sex for new species
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
SetDirty();
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
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"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
</VerticalTabContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
@@ -5,6 +6,7 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Client.Lobby.UI.Loadouts;
|
||||
|
||||
@@ -24,27 +26,36 @@ public sealed partial class LoadoutWindow : FancyWindow
|
||||
Profile = profile;
|
||||
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))
|
||||
continue;
|
||||
|
||||
if (groupProto.Hidden)
|
||||
continue;
|
||||
|
||||
var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
|
||||
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
|
||||
_groups.Add(container);
|
||||
|
||||
container.OnLoadoutPressed += args =>
|
||||
LoadoutGroupsContainer.Visible = false;
|
||||
SetSize = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var group in proto.Groups)
|
||||
{
|
||||
OnLoadoutPressed?.Invoke(group, args);
|
||||
};
|
||||
if (!protoManager.TryIndex(group, out var groupProto))
|
||||
continue;
|
||||
|
||||
container.OnLoadoutUnpressed += args =>
|
||||
{
|
||||
OnLoadoutUnpressed?.Invoke(group, args);
|
||||
};
|
||||
if (groupProto.Hidden)
|
||||
continue;
|
||||
|
||||
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 Content.Client.UserInterface.Controls;
|
||||
using Prometheus;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Client.Message;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Client.TextScreen;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.MachineLinking.UI;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Options.UI.Tabs;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Client.Power.Components;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
@@ -27,4 +28,16 @@ public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
|
||||
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 Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.Power.Generator;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Channels;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
|
||||
8
Content.Client/Roles/RoleCodewordSystem.cs
Normal file
8
Content.Client/Roles/RoleCodewordSystem.cs
Normal file
@@ -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;
|
||||
var claimIndex = i;
|
||||
|
||||
option.ClaimPressed += args =>
|
||||
option.ClaimPressed += _ =>
|
||||
{
|
||||
SendMessage(new MagnetClaimOfferEvent()
|
||||
SendMessage(new MagnetClaimOfferEvent
|
||||
{
|
||||
Index = claimIndex
|
||||
});
|
||||
@@ -72,20 +72,20 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
var count = asteroid.MarkerLayers[resource];
|
||||
|
||||
var container = new BoxContainer()
|
||||
var container = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
var resourceLabel = new Label()
|
||||
var resourceLabel = new Label
|
||||
{
|
||||
Text = Loc.GetString("salvage-magnet-resources",
|
||||
("resource", resource)),
|
||||
HorizontalAlignment = Control.HAlignment.Left,
|
||||
};
|
||||
|
||||
var countLabel = new Label()
|
||||
var countLabel = new Label
|
||||
{
|
||||
Text = Loc.GetString("salvage-magnet-resources-count", ("count", count)),
|
||||
HorizontalAlignment = Control.HAlignment.Right,
|
||||
@@ -98,6 +98,9 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
|
||||
option.AddContent(container);
|
||||
}
|
||||
|
||||
break;
|
||||
case DebrisOffering debris:
|
||||
option.Title = Loc.GetString($"salvage-magnet-debris-{debris.Id}");
|
||||
break;
|
||||
case SalvageOffering salvage:
|
||||
option.Title = Loc.GetString($"salvage-map-wreck");
|
||||
|
||||
@@ -35,6 +35,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
|
||||
public bool ShowIFF { get; set; } = true;
|
||||
public bool ShowDocks { get; set; } = true;
|
||||
public bool RotateWithEntity { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Raised if the user left-clicks on the radar control with the relevant entitycoordinates.
|
||||
@@ -109,6 +110,8 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
|
||||
ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange);
|
||||
|
||||
RotateWithEntity = state.RotateWithEntity;
|
||||
|
||||
_docks = state.Docks;
|
||||
}
|
||||
|
||||
@@ -138,7 +141,8 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
|
||||
var offset = _coordinates.Value.Position;
|
||||
var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value);
|
||||
var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId);
|
||||
var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
|
||||
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||
var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
|
||||
Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi;
|
||||
|
||||
public sealed class SiliconLawEui : BaseEui
|
||||
{
|
||||
public readonly EntityManager _entityManager = default!;
|
||||
private readonly EntityManager _entityManager;
|
||||
|
||||
private SiliconLawUi _siliconLawUi;
|
||||
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,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
13
Content.Client/Silicons/StationAi/StationAiMenu.xaml
Normal file
13
Content.Client/Silicons/StationAi/StationAiMenu.xaml
Normal file
@@ -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>
|
||||
128
Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs
Normal file
128
Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs
Normal file
@@ -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;
|
||||
}
|
||||
128
Content.Client/Silicons/StationAi/StationAiOverlay.cs
Normal file
128
Content.Client/Silicons/StationAi/StationAiOverlay.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
public sealed class StationAiOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly HashSet<Vector2i> _visibleTiles = new();
|
||||
|
||||
private IRenderTexture? _staticTexture;
|
||||
private IRenderTexture? _stencilTexture;
|
||||
|
||||
private float _updateRate = 1f / 30f;
|
||||
private float _accumulator;
|
||||
|
||||
public StationAiOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_stencilTexture?.Texture.Size != args.Viewport.Size)
|
||||
{
|
||||
_staticTexture?.Dispose();
|
||||
_stencilTexture?.Dispose();
|
||||
_stencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil");
|
||||
_staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: "station-ai-static");
|
||||
}
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
var worldBounds = args.WorldBounds;
|
||||
|
||||
var playerEnt = _player.LocalEntity;
|
||||
_entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform);
|
||||
var gridUid = playerXform?.GridUid ?? EntityUid.Invalid;
|
||||
_entManager.TryGetComponent(gridUid, out MapGridComponent? grid);
|
||||
_entManager.TryGetComponent(gridUid, out BroadphaseComponent? broadphase);
|
||||
|
||||
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
|
||||
_accumulator -= (float) _timing.FrameTime.TotalSeconds;
|
||||
|
||||
if (grid != null && broadphase != null)
|
||||
{
|
||||
var lookups = _entManager.System<EntityLookupSystem>();
|
||||
var xforms = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
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 matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||
|
||||
// Draw visible tiles to stencil
|
||||
worldHandle.RenderInRenderTarget(_stencilTexture!, () =>
|
||||
{
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
foreach (var tile in _visibleTiles)
|
||||
{
|
||||
var aabb = lookups.GetLocalBounds(tile, grid.TileSize);
|
||||
worldHandle.DrawRect(aabb, Color.White);
|
||||
}
|
||||
},
|
||||
Color.Transparent);
|
||||
|
||||
// Once this is gucci optimise rendering.
|
||||
worldHandle.RenderInRenderTarget(_staticTexture!,
|
||||
() =>
|
||||
{
|
||||
worldHandle.SetTransform(invMatrix);
|
||||
var shader = _proto.Index<ShaderPrototype>("CameraStatic").Instance();
|
||||
worldHandle.UseShader(shader);
|
||||
worldHandle.DrawRect(worldBounds, Color.White);
|
||||
},
|
||||
Color.Black);
|
||||
}
|
||||
// Not on a grid
|
||||
else
|
||||
{
|
||||
worldHandle.RenderInRenderTarget(_stencilTexture!, () =>
|
||||
{
|
||||
},
|
||||
Color.Transparent);
|
||||
|
||||
worldHandle.RenderInRenderTarget(_staticTexture!,
|
||||
() =>
|
||||
{
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.DrawRect(worldBounds, Color.Black);
|
||||
}, Color.Black);
|
||||
}
|
||||
|
||||
// Use the lighting as a mask
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds);
|
||||
|
||||
// Draw the static
|
||||
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
worldHandle.DrawTextureRect(_staticTexture!.Texture, worldBounds);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(null);
|
||||
|
||||
}
|
||||
}
|
||||
30
Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
Normal file
30
Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
Normal file
@@ -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,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
32
Content.Client/Silicons/StationAi/StationAiSystem.Light.cs
Normal file
32
Content.Client/Silicons/StationAi/StationAiSystem.Light.cs
Normal file
@@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
80
Content.Client/Silicons/StationAi/StationAiSystem.cs
Normal file
80
Content.Client/Silicons/StationAi/StationAiSystem.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
public sealed partial class StationAiSystem : SharedStationAiSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
private StationAiOverlay? _overlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeAirlock();
|
||||
InitializePowerToggle();
|
||||
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerAttachedEvent>(OnAiAttached);
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached);
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, ComponentInit>(OnAiOverlayInit);
|
||||
SubscribeLocalEvent<StationAiOverlayComponent, ComponentRemove>(OnAiOverlayRemove);
|
||||
}
|
||||
|
||||
private void OnAiOverlayInit(Entity<StationAiOverlayComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
var attachedEnt = _player.LocalEntity;
|
||||
|
||||
if (attachedEnt != ent.Owner)
|
||||
return;
|
||||
|
||||
AddOverlay();
|
||||
}
|
||||
|
||||
private void OnAiOverlayRemove(Entity<StationAiOverlayComponent> ent, ref ComponentRemove args)
|
||||
{
|
||||
var attachedEnt = _player.LocalEntity;
|
||||
|
||||
if (attachedEnt != ent.Owner)
|
||||
return;
|
||||
|
||||
RemoveOverlay();
|
||||
}
|
||||
|
||||
private void AddOverlay()
|
||||
{
|
||||
if (_overlay != null)
|
||||
return;
|
||||
|
||||
_overlay = new StationAiOverlay();
|
||||
_overlayMgr.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void RemoveOverlay()
|
||||
{
|
||||
if (_overlay == null)
|
||||
return;
|
||||
|
||||
_overlayMgr.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
|
||||
private void OnAiAttached(Entity<StationAiOverlayComponent> ent, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
AddOverlay();
|
||||
}
|
||||
|
||||
private void OnAiDetached(Entity<StationAiOverlayComponent> ent, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
RemoveOverlay();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMgr.RemoveOverlay<StationAiOverlay>();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.StationRecords;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.StationRecords;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Chemistry.Visualizers;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
|
||||
namespace Content.Client.Storage.Components;
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
|
||||
private readonly StorageSystem _storage;
|
||||
|
||||
[Obsolete] public override bool DeferredClose => false;
|
||||
|
||||
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
@@ -9,7 +9,12 @@ namespace Content.Client.UserInterface.Controls
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -121,7 +121,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
var boundKey = hotbarKeys[i];
|
||||
builder = builder.Bind(boundKey, new PointerInputCmdHandler((in PointerInputCmdArgs args) =>
|
||||
{
|
||||
if (args.State != BoundKeyState.Up)
|
||||
if (args.State != BoundKeyState.Down)
|
||||
return false;
|
||||
|
||||
TriggerAction(boundId);
|
||||
@@ -219,8 +219,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
if (action.Event != null)
|
||||
{
|
||||
action.Event.Target = coords;
|
||||
action.Event.Performer = user;
|
||||
action.Event.Action = actionId;
|
||||
}
|
||||
|
||||
_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)
|
||||
{
|
||||
action.Event.Target = entity;
|
||||
action.Event.Performer = user;
|
||||
action.Event.Action = actionId;
|
||||
}
|
||||
|
||||
_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.Coords = coords;
|
||||
action.Event.Performer = user;
|
||||
action.Event.Action = actionId;
|
||||
}
|
||||
|
||||
_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.Gameplay;
|
||||
using Content.Client.Ghost;
|
||||
using Content.Client.Mind;
|
||||
using Content.Client.Roles;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
@@ -20,6 +22,7 @@ using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Radio;
|
||||
using Content.Shared.Roles.RoleCodeword;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -60,6 +63,8 @@ public sealed class ChatUIController : UIController
|
||||
[UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default;
|
||||
[UISystemDependency] private readonly ChatSystem? _chatSys = default;
|
||||
[UISystemDependency] private readonly TransformSystem? _transform = default;
|
||||
[UISystemDependency] private readonly MindSystem? _mindSystem = default!;
|
||||
[UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!;
|
||||
|
||||
[ValidatePrototypeId<ColorPalettePrototype>]
|
||||
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")));
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!msg.HideChat)
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
|
||||
private GuidebookWindow? _guideWindow;
|
||||
private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton;
|
||||
private ProtoId<GuideEntryPrototype>? _lastEntry;
|
||||
|
||||
public void OnStateEntered(LobbyState state)
|
||||
{
|
||||
@@ -142,7 +143,10 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
GuidebookButton.Pressed = false;
|
||||
|
||||
if (_guideWindow != null)
|
||||
{
|
||||
_guideWindow.ReturnContainer.Visible = false;
|
||||
_lastEntry = _guideWindow.LastEntry;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowOpen()
|
||||
@@ -176,8 +180,6 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
|
||||
|
||||
selected ??= _configuration.GetCVar(CCVars.DefaultGuide);
|
||||
|
||||
if (guides == null)
|
||||
{
|
||||
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);
|
||||
|
||||
// Expand up to depth-2.
|
||||
|
||||
@@ -21,6 +21,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Client.Inventory.ClientInventorySystem;
|
||||
|
||||
@@ -399,6 +400,9 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
foreach (var slotData in clientInv.SlotData.Values)
|
||||
{
|
||||
AddSlot(slotData);
|
||||
|
||||
if (_inventoryButton != null)
|
||||
_inventoryButton.Visible = true;
|
||||
}
|
||||
|
||||
UpdateInventoryHotbar(_playerInventory);
|
||||
@@ -406,6 +410,9 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
|
||||
private void UnloadSlots()
|
||||
{
|
||||
if (_inventoryButton != null)
|
||||
_inventoryButton.Visible = false;
|
||||
|
||||
_playerUid = null;
|
||||
_playerInventory = null;
|
||||
foreach (var slotGroup in _slotGroups.Values)
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<Control HorizontalAlignment="Center">
|
||||
<!-- Needs to default to invisible because if we attach to a non-slots entity this will never get unset -->
|
||||
<controls:SlotButton
|
||||
Name="InventoryButton"
|
||||
Access="Public"
|
||||
Visible="False"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalExpand="False"
|
||||
VerticalExpand="False"
|
||||
|
||||
@@ -7,14 +7,17 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.DecalPlacer;
|
||||
using Content.Client.UserInterface.Systems.Sandbox.Windows;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controllers.Implementations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
@@ -27,10 +30,12 @@ namespace Content.Client.UserInterface.Systems.Sandbox;
|
||||
[UsedImplicitly]
|
||||
public sealed class SandboxUIController : UIController, IOnStateChanged<GameplayState>, IOnSystemChanged<SandboxSystem>
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly ILightManager _light = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
[UISystemDependency] private readonly DebugPhysicsSystem _debugPhysics = default!;
|
||||
[UISystemDependency] private readonly MarkerSystem _marker = default!;
|
||||
@@ -116,6 +121,21 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
||||
_window.ShowMarkersButton.Pressed = _marker.MarkersVisible;
|
||||
_window.ShowBbButton.Pressed = (_debugPhysics.Flags & PhysicsDebugFlags.Shapes) != 0x0;
|
||||
|
||||
_window.AiOverlayButton.OnPressed += args =>
|
||||
{
|
||||
var player = _player.LocalEntity;
|
||||
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
var pnent = EntityManager.GetNetEntity(player.Value);
|
||||
|
||||
// Need NetworkedAddComponent but engine PR.
|
||||
if (args.Button.Pressed)
|
||||
_console.ExecuteCommand($"addcomp {pnent.Id} StationAiOverlay");
|
||||
else
|
||||
_console.ExecuteCommand($"rmcomp {pnent.Id} StationAiOverlay");
|
||||
};
|
||||
_window.RespawnButton.OnPressed += _ => _sandbox.Respawn();
|
||||
_window.SpawnTilesButton.OnPressed += _ => TileSpawningController.ToggleWindow();
|
||||
_window.SpawnEntitiesButton.OnPressed += _ => EntitySpawningController.ToggleWindow();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Title="{Loc sandbox-window-title}"
|
||||
Resizable="False">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4">
|
||||
<Button Name="AiOverlayButton" Access="Public" Text="{Loc sandbox-window-ai-overlay-button}" ToggleMode="True"/>
|
||||
<Button Name="RespawnButton" Access="Public" Text="{Loc sandbox-window-respawn-button}"/>
|
||||
<Button Name="SpawnEntitiesButton" Access="Public" Text="{Loc sandbox-window-spawn-entities-button}"/>
|
||||
<Button Name="SpawnTilesButton" Access="Public" Text="{Loc sandbox-window-spawn-tiles-button}"/>
|
||||
|
||||
16
Content.Client/VendingMachines/UI/VendingMachineItem.xaml
Normal file
16
Content.Client/VendingMachines/UI/VendingMachineItem.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<EntityPrototypeView
|
||||
Name="ItemPrototype"
|
||||
Margin="4 4"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MinSize="32 32"
|
||||
/>
|
||||
<Label Name="NameLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
19
Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs
Normal file
19
Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.VendingMachines.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class VendingMachineItem : BoxContainer
|
||||
{
|
||||
public VendingMachineItem(EntProtoId entProto, string text)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ItemPrototype.SetPrototype(entProto);
|
||||
|
||||
NameLabel.Text = text;
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,10 @@
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:style="clr-namespace:Content.Client.Stylesheets">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin ="4 4" Access="Public"/>
|
||||
<ItemList Name="VendingContents"
|
||||
SizeFlagsStretchRatio="8"
|
||||
VerticalExpand="True"
|
||||
ItemSeparation="2"
|
||||
Margin="4 0"
|
||||
SelectMode="Button"
|
||||
StyleClasses="transparentBackgroundItemList">
|
||||
</ItemList>
|
||||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Name="MainContainer" Orientation="Vertical">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin ="4 4"/>
|
||||
<co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 0"/>
|
||||
<!-- Footer -->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
using Robust.Client.UserInterface;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.VendingMachines.UI
|
||||
{
|
||||
@@ -20,8 +20,9 @@ namespace Content.Client.VendingMachines.UI
|
||||
|
||||
private readonly Dictionary<EntProtoId, EntityUid> _dummies = [];
|
||||
|
||||
public event Action<ItemList.ItemListSelectedEventArgs>? OnItemSelected;
|
||||
public event Action<string>? OnSearchChanged;
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
|
||||
|
||||
private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) };
|
||||
|
||||
public VendingMachineMenu()
|
||||
{
|
||||
@@ -29,15 +30,10 @@ namespace Content.Client.VendingMachines.UI
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
SearchBar.OnTextChanged += _ =>
|
||||
{
|
||||
OnSearchChanged?.Invoke(SearchBar.Text);
|
||||
};
|
||||
|
||||
VendingContents.OnItemSelected += args =>
|
||||
{
|
||||
OnItemSelected?.Invoke(args);
|
||||
};
|
||||
VendingContents.SearchBar = SearchBar;
|
||||
VendingContents.DataFilterCondition += DataFilterCondition;
|
||||
VendingContents.GenerateItem += GenerateButton;
|
||||
VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -56,41 +52,64 @@ namespace Content.Client.VendingMachines.UI
|
||||
_dummies.Clear();
|
||||
}
|
||||
|
||||
private bool DataFilterCondition(string filter, ListData data)
|
||||
{
|
||||
if (data is not VendorItemsListData { ItemText: var text })
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return true;
|
||||
|
||||
return text.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not VendorItemsListData { ItemProtoID: var protoID, ItemText: var text })
|
||||
return;
|
||||
|
||||
button.AddChild(new VendingMachineItem(protoID, text));
|
||||
|
||||
button.ToolTip = text;
|
||||
button.StyleBoxOverride = _styleBox;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the list of available items on the vending machine interface
|
||||
/// and sets icons based on their prototypes
|
||||
/// </summary>
|
||||
public void Populate(List<VendingMachineInventoryEntry> inventory, out List<int> filteredInventory, string? filter = null)
|
||||
public void Populate(List<VendingMachineInventoryEntry> inventory)
|
||||
{
|
||||
filteredInventory = new();
|
||||
|
||||
if (inventory.Count == 0)
|
||||
if (inventory.Count == 0 && VendingContents.Visible)
|
||||
{
|
||||
VendingContents.Clear();
|
||||
var outOfStockText = Loc.GetString("vending-machine-component-try-eject-out-of-stock");
|
||||
VendingContents.AddItem(outOfStockText);
|
||||
SetSizeAfterUpdate(outOfStockText.Length, VendingContents.Count);
|
||||
SearchBar.Visible = false;
|
||||
VendingContents.Visible = false;
|
||||
|
||||
var outOfStockLabel = new Label()
|
||||
{
|
||||
Text = Loc.GetString("vending-machine-component-try-eject-out-of-stock"),
|
||||
Margin = new Thickness(4, 4),
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Stretch,
|
||||
HorizontalAlignment = HAlignment.Center
|
||||
};
|
||||
|
||||
MainContainer.AddChild(outOfStockLabel);
|
||||
|
||||
SetSizeAfterUpdate(outOfStockLabel.Text.Length, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (inventory.Count != VendingContents.Count)
|
||||
{
|
||||
if (inventory.Count > VendingContents.Count)
|
||||
VendingContents.AddItem(string.Empty);
|
||||
else
|
||||
VendingContents.RemoveAt(VendingContents.Count - 1);
|
||||
}
|
||||
|
||||
var longestEntry = string.Empty;
|
||||
var spriteSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
var listData = new List<VendorItemsListData>();
|
||||
|
||||
var filterCount = 0;
|
||||
for (var i = 0; i < inventory.Count; i++)
|
||||
{
|
||||
var entry = inventory[i];
|
||||
var vendingItem = VendingContents[i - filterCount];
|
||||
vendingItem.Text = string.Empty;
|
||||
vendingItem.Icon = null;
|
||||
|
||||
if (!_prototypeManager.TryIndex(entry.ID, out var prototype))
|
||||
continue;
|
||||
|
||||
if (!_dummies.TryGetValue(entry.ID, out var dummy))
|
||||
{
|
||||
@@ -99,36 +118,25 @@ namespace Content.Client.VendingMachines.UI
|
||||
}
|
||||
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
Texture? icon = null;
|
||||
if (_prototypeManager.TryIndex<EntityPrototype>(entry.ID, out var prototype))
|
||||
{
|
||||
icon = spriteSystem.GetPrototypeIcon(prototype).Default;
|
||||
}
|
||||
var itemText = $"{itemName} [{entry.Amount}]";
|
||||
|
||||
// search filter
|
||||
if (!string.IsNullOrEmpty(filter) &&
|
||||
!itemName.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))
|
||||
{
|
||||
VendingContents.Remove(vendingItem);
|
||||
filterCount++;
|
||||
continue;
|
||||
}
|
||||
if (itemText.Length > longestEntry.Length)
|
||||
longestEntry = itemText;
|
||||
|
||||
if (itemName.Length > longestEntry.Length)
|
||||
longestEntry = itemName;
|
||||
|
||||
vendingItem.Text = $"{itemName} [{entry.Amount}]";
|
||||
vendingItem.Icon = icon;
|
||||
filteredInventory.Add(i);
|
||||
listData.Add(new VendorItemsListData(prototype.ID, itemText, i));
|
||||
}
|
||||
|
||||
VendingContents.PopulateList(listData);
|
||||
|
||||
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
|
||||
}
|
||||
|
||||
private void SetSizeAfterUpdate(int longestEntryLength, int contentCount)
|
||||
{
|
||||
SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 300),
|
||||
SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400),
|
||||
Math.Clamp(contentCount * 50, 150, 350));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record VendorItemsListData(EntProtoId ItemProtoID, string ItemText, int ItemIndex) : ListData;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.VendingMachines.UI;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using System.Linq;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
@@ -14,9 +17,6 @@ namespace Content.Client.VendingMachines
|
||||
[ViewVariables]
|
||||
private List<VendingMachineInventoryEntry> _cachedInventory = new();
|
||||
|
||||
[ViewVariables]
|
||||
private List<int> _cachedFilteredIndex = new();
|
||||
|
||||
public VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
@@ -34,9 +34,10 @@ namespace Content.Client.VendingMachines
|
||||
_menu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
_menu.OnSearchChanged += OnSearchChanged;
|
||||
|
||||
_menu.Populate(_cachedInventory, out _cachedFilteredIndex);
|
||||
_menu.Populate(_cachedInventory);
|
||||
|
||||
_menu.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
@@ -48,15 +49,21 @@ namespace Content.Client.VendingMachines
|
||||
|
||||
_cachedInventory = newState.Inventory;
|
||||
|
||||
_menu?.Populate(_cachedInventory, out _cachedFilteredIndex, _menu.SearchBar.Text);
|
||||
_menu?.Populate(_cachedInventory);
|
||||
}
|
||||
|
||||
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
if (data is not VendorItemsListData { ItemIndex: var itemIndex })
|
||||
return;
|
||||
|
||||
if (_cachedInventory.Count == 0)
|
||||
return;
|
||||
|
||||
var selectedItem = _cachedInventory.ElementAtOrDefault(_cachedFilteredIndex.ElementAtOrDefault(args.ItemIndex));
|
||||
var selectedItem = _cachedInventory.ElementAtOrDefault(itemIndex);
|
||||
|
||||
if (selectedItem == null)
|
||||
return;
|
||||
@@ -77,10 +84,5 @@ namespace Content.Client.VendingMachines
|
||||
_menu.OnClose -= Close;
|
||||
_menu.Dispose();
|
||||
}
|
||||
|
||||
private void OnSearchChanged(string? filter)
|
||||
{
|
||||
_menu?.Populate(_cachedInventory, out _cachedFilteredIndex, filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,18 @@ namespace Content.Client.Verbs
|
||||
? Visibility
|
||||
: Visibility | MenuVisibility.NoFov;
|
||||
|
||||
var ev = new MenuVisibilityEvent()
|
||||
{
|
||||
TargetPos = targetPos,
|
||||
Visibility = visibility,
|
||||
};
|
||||
|
||||
RaiseLocalEvent(player.Value, ref ev);
|
||||
visibility = ev.Visibility;
|
||||
|
||||
// Get entities
|
||||
List<EntityUid> entities;
|
||||
var examineFlags = LookupFlags.All & ~LookupFlags.Sensors;
|
||||
|
||||
// Do we have to do FoV checks?
|
||||
if ((visibility & MenuVisibility.NoFov) == 0)
|
||||
@@ -77,15 +86,10 @@ namespace Content.Client.Verbs
|
||||
var entitiesUnderMouse = gameScreenBase.GetClickableEntities(targetPos).ToHashSet();
|
||||
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);
|
||||
|
||||
// Then check every entity
|
||||
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))
|
||||
entities.Add(ent);
|
||||
@@ -93,7 +97,7 @@ namespace Content.Client.Verbs
|
||||
}
|
||||
else
|
||||
{
|
||||
entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize).ToList();
|
||||
entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList();
|
||||
}
|
||||
|
||||
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)
|
||||
return false;
|
||||
|
||||
@@ -229,15 +212,4 @@ namespace Content.Client.Verbs
|
||||
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,8 +1,8 @@
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300">
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300" MaxWidth="500">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Margin="4" Orientation="Vertical">
|
||||
<Label Name="VoteCaller" />
|
||||
<Label Name="VoteTitle" />
|
||||
<RichTextLabel Name="VoteTitle" />
|
||||
|
||||
<GridContainer Columns="3" Name="VoteOptionsContainer" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Voting.UI
|
||||
{
|
||||
@@ -48,7 +49,7 @@ namespace Content.Client.Voting.UI
|
||||
|
||||
public void UpdateData()
|
||||
{
|
||||
VoteTitle.Text = _vote.Title;
|
||||
VoteTitle.SetMessage(FormattedMessage.FromUnformatted(_vote.Title));
|
||||
VoteCaller.Text = Loc.GetString("ui-vote-created", ("initiator", _vote.Initiator));
|
||||
|
||||
for (var i = 0; i < _voteButtons.Length; i++)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Xenoarchaeology.Equipment;
|
||||
using Microsoft.VisualBasic;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -17,6 +17,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Station.Components;
|
||||
using FastAccessors;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -240,10 +241,17 @@ namespace Content.IntegrationTests.Tests
|
||||
var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys);
|
||||
|
||||
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
||||
.Where(x => x.SpawnType == SpawnPointType.Job)
|
||||
.Select(x => x.Job!.Value);
|
||||
.Where(x => x.SpawnType == SpawnPointType.Job && x.Job != null)
|
||||
.Select(x => x.Job.Value);
|
||||
|
||||
jobs.ExceptWith(spawnPoints);
|
||||
|
||||
spawnPoints = entManager.EntityQuery<ContainerSpawnPointComponent>()
|
||||
.Where(x => x.SpawnType is SpawnPointType.Job or SpawnPointType.Unset && x.Job != null)
|
||||
.Select(x => x.Job.Value);
|
||||
|
||||
jobs.ExceptWith(spawnPoints);
|
||||
|
||||
Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
|
||||
*/ //CP14 disable job spawners test
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public static class ServerPackaging
|
||||
new PlatformReg("win-x86", "Windows", false),
|
||||
new PlatformReg("linux-x86", "Linux", false),
|
||||
new PlatformReg("linux-arm", "Linux", false),
|
||||
new PlatformReg("freebsd-x64", "FreeBSD", false),
|
||||
};
|
||||
|
||||
private static List<string> PlatformRids => Platforms
|
||||
|
||||
1769
Content.Server.Database/Migrations/Postgres/20240112194620_Blacklist.Designer.cs
generated
Normal file
1769
Content.Server.Database/Migrations/Postgres/20240112194620_Blacklist.Designer.cs
generated
Normal file
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
1913
Content.Server.Database/Migrations/Postgres/20240606121555_ban_notify_trigger.Designer.cs
generated
Normal file
1913
Content.Server.Database/Migrations/Postgres/20240606121555_ban_notify_trigger.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ban_notify_trigger : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("""
|
||||
create or replace function send_server_ban_notification()
|
||||
returns trigger as $$
|
||||
declare
|
||||
x_server_id integer;
|
||||
begin
|
||||
select round.server_id into x_server_id from round where round.round_id = NEW.round_id;
|
||||
|
||||
perform pg_notify('ban_notification', json_build_object('ban_id', NEW.server_ban_id, 'server_id', x_server_id)::text);
|
||||
return NEW;
|
||||
end;
|
||||
$$ LANGUAGE plpgsql;
|
||||
""");
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
create or replace trigger notify_on_server_ban_insert
|
||||
after insert on server_ban
|
||||
for each row
|
||||
execute function send_server_ban_notification();
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("""
|
||||
drop trigger notify_on_server_ban_insert on server_ban;
|
||||
drop function send_server_ban_notification;
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,6 +512,20 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
||||
1701
Content.Server.Database/Migrations/Sqlite/20240112194612_Blacklist.Designer.cs
generated
Normal file
1701
Content.Server.Database/Migrations/Sqlite/20240112194612_Blacklist.Designer.cs
generated
Normal file
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);
|
||||
});
|
||||
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Content.Server.Database
|
||||
public DbSet<AdminLog> AdminLog { get; set; } = null!;
|
||||
public DbSet<AdminLogPlayer> AdminLogPlayer { 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<ServerUnban> Unban { get; set; } = default!;
|
||||
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
|
||||
@@ -551,6 +552,15 @@ namespace Content.Server.Database
|
||||
[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
|
||||
{
|
||||
[Key] public Guid UserId { get; set; }
|
||||
@@ -705,6 +715,11 @@ namespace Content.Server.Database
|
||||
/// Intended for use with residential IP ranges that are often used maliciously.
|
||||
/// </remarks>
|
||||
BlacklistedRange = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Represents having all possible exemption flags.
|
||||
/// </summary>
|
||||
All = int.MaxValue,
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@@ -903,7 +918,7 @@ namespace Content.Server.Database
|
||||
Panic = 3,
|
||||
/*
|
||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
||||
*
|
||||
*
|
||||
* If baby jail is removed, please reserve this value for as long as can reasonably be done to prevent causing ambiguity in connection denial reasons.
|
||||
* Reservation by commenting out the value is likely sufficient for this purpose, but may impact projects which depend on SS14 like SS14.Admin.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
@@ -19,7 +18,6 @@ namespace Content.Server.Abilities.Mime
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly IMapManager _mapMan = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
@@ -80,22 +78,13 @@ namespace Content.Server.Abilities.Mime
|
||||
if (tile == null)
|
||||
return;
|
||||
|
||||
// Check there are no walls there
|
||||
if (_turf.IsTileBlocked(tile.Value, CollisionGroup.Impassable))
|
||||
// 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 | CollisionGroup.Opaque))
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("mime-invisible-wall-failed"), uid, uid);
|
||||
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);
|
||||
// Make sure we set the invisible wall to despawn properly
|
||||
Spawn(component.WallPrototype, _turf.GetTileCenter(tile.Value));
|
||||
|
||||
@@ -55,12 +55,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
return;
|
||||
|
||||
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);
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -94,8 +88,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
var (entActId, entAct) = _random.Pick(entOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
entAct.Event.Performer = args.User;
|
||||
entAct.Event.Action = entActId;
|
||||
entAct.Event.Target = args.Target.Value;
|
||||
}
|
||||
|
||||
@@ -119,8 +111,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
var (entActId, entAct) = _random.Pick(entWorldOptions);
|
||||
if (entAct.Event != null)
|
||||
{
|
||||
entAct.Event.Performer = args.User;
|
||||
entAct.Event.Action = entActId;
|
||||
entAct.Event.Entity = args.Target;
|
||||
entAct.Event.Coords = args.ClickLocation;
|
||||
}
|
||||
@@ -145,8 +135,6 @@ public sealed class ActionOnInteractSystem : EntitySystem
|
||||
var (actId, act) = _random.Pick(options);
|
||||
if (act.Event != null)
|
||||
{
|
||||
act.Event.Performer = args.User;
|
||||
act.Event.Action = actId;
|
||||
act.Event.Target = args.ClickLocation;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Database;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
public sealed partial class BanManager
|
||||
{
|
||||
// Responsible for ban notification handling.
|
||||
// Ban notifications are sent through the database to notify the entire server group that a new ban has been added,
|
||||
// so that people will get kicked if they are banned on a different server than the one that placed the ban.
|
||||
//
|
||||
// Ban notifications are currently sent by a trigger in the database, automatically.
|
||||
|
||||
/// <summary>
|
||||
/// The notification channel used to broadcast information about new bans.
|
||||
/// </summary>
|
||||
public const string BanNotificationChannel = "ban_notification";
|
||||
|
||||
// Rate limit to avoid undue load from mass-ban imports.
|
||||
// Only process 10 bans per 30 second interval.
|
||||
//
|
||||
// I had the idea of maybe binning this by postgres transaction ID,
|
||||
// to avoid any possibility of dropping a normal ban by coincidence.
|
||||
// Didn't bother implementing this though.
|
||||
private static readonly TimeSpan BanNotificationRateLimitTime = TimeSpan.FromSeconds(30);
|
||||
private const int BanNotificationRateLimitCount = 10;
|
||||
|
||||
private readonly object _banNotificationRateLimitStateLock = new();
|
||||
private TimeSpan _banNotificationRateLimitStart;
|
||||
private int _banNotificationRateLimitCount;
|
||||
|
||||
private void OnDatabaseNotification(DatabaseNotification notification)
|
||||
{
|
||||
if (notification.Channel != BanNotificationChannel)
|
||||
return;
|
||||
|
||||
if (notification.Payload == null)
|
||||
{
|
||||
_sawmill.Error("Got ban notification with null payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
BanNotificationData data;
|
||||
try
|
||||
{
|
||||
data = JsonSerializer.Deserialize<BanNotificationData>(notification.Payload)
|
||||
?? throw new JsonException("Content is null");
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
_sawmill.Error($"Got invalid JSON in ban notification: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBanRateLimit())
|
||||
{
|
||||
_sawmill.Verbose("Not processing ban notification due to rate limit");
|
||||
return;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => ProcessBanNotification(data));
|
||||
}
|
||||
|
||||
private async void ProcessBanNotification(BanNotificationData data)
|
||||
{
|
||||
if ((await _entryManager.ServerEntity).Id == data.ServerId)
|
||||
{
|
||||
_sawmill.Verbose("Not processing ban notification: came from this server");
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Verbose($"Processing ban notification for ban {data.BanId}");
|
||||
var ban = await _db.GetServerBanAsync(data.BanId);
|
||||
if (ban == null)
|
||||
{
|
||||
_sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?");
|
||||
return;
|
||||
}
|
||||
|
||||
KickMatchingConnectedPlayers(ban, "ban notification");
|
||||
}
|
||||
|
||||
private bool CheckBanRateLimit()
|
||||
{
|
||||
lock (_banNotificationRateLimitStateLock)
|
||||
{
|
||||
var now = _gameTiming.RealTime;
|
||||
if (_banNotificationRateLimitStart + BanNotificationRateLimitTime < now)
|
||||
{
|
||||
// Rate limit period expired, restart it.
|
||||
_banNotificationRateLimitCount = 1;
|
||||
_banNotificationRateLimitStart = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
_banNotificationRateLimitCount += 1;
|
||||
return _banNotificationRateLimitCount <= BanNotificationRateLimitCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data sent along the notification channel for a single ban notification.
|
||||
/// </summary>
|
||||
private sealed class BanNotificationData
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the new ban object in the database to check.
|
||||
/// </summary>
|
||||
[JsonRequired, JsonPropertyName("ban_id")]
|
||||
public int BanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The id of the server the ban was made on.
|
||||
/// This is used to avoid double work checking the ban on the originating server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is optional in case the ban was made outside a server (SS14.Admin)
|
||||
/// </remarks>
|
||||
[JsonPropertyName("server_id")]
|
||||
public int? ServerId { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Database;
|
||||
@@ -12,16 +13,18 @@ using Content.Shared.Players;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
public sealed class BanManager : IBanManager, IPostInjectInit
|
||||
public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
@@ -29,9 +32,13 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||
[Dependency] private readonly ServerDbEntryManager _entryManager = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly UserDbDataManager _userDbData = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
@@ -39,12 +46,34 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
||||
public const string JobPrefix = "Job:";
|
||||
|
||||
private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
|
||||
// Cached ban exemption flags are used to handle
|
||||
private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_netManager.RegisterNetMessage<MsgRoleBans>();
|
||||
|
||||
_db.SubscribeToNotifications(OnDatabaseNotification);
|
||||
|
||||
_userDbData.AddOnLoadPlayer(CachePlayerData);
|
||||
_userDbData.AddOnPlayerDisconnect(ClearPlayerData);
|
||||
}
|
||||
|
||||
private async Task CachePlayerData(ICommonSession player, CancellationToken cancel)
|
||||
{
|
||||
// Yeah so role ban loading code isn't integrated with exempt flag loading code.
|
||||
// Have you seen how garbage role ban code code is? I don't feel like refactoring it right now.
|
||||
|
||||
var flags = await _db.GetBanExemption(player.UserId, cancel);
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
_cachedBanExemptions[player] = flags;
|
||||
}
|
||||
|
||||
private void ClearPlayerData(ICommonSession player)
|
||||
{
|
||||
_cachedBanExemptions.Remove(player);
|
||||
}
|
||||
|
||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
@@ -168,17 +197,43 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
||||
_sawmill.Info(logMessage);
|
||||
_chat.SendAdminAlert(logMessage);
|
||||
|
||||
// If we're not banning a player we don't care about disconnecting people
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
// Is the player connected?
|
||||
if (!_playerManager.TryGetSessionById(target.Value, out var targetPlayer))
|
||||
return;
|
||||
// If they are, kick them
|
||||
var message = banDef.FormatBanMessage(_cfg, _localizationManager);
|
||||
targetPlayer.Channel.Disconnect(message);
|
||||
KickMatchingConnectedPlayers(banDef, "newly placed ban");
|
||||
}
|
||||
|
||||
private void KickMatchingConnectedPlayers(ServerBanDef def, string source)
|
||||
{
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
if (BanMatchesPlayer(player, def))
|
||||
{
|
||||
KickForBanDef(player, def);
|
||||
_sawmill.Info($"Kicked player {player.Name} ({player.UserId}) through {source}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban)
|
||||
{
|
||||
var playerInfo = new BanMatcher.PlayerInfo
|
||||
{
|
||||
UserId = player.UserId,
|
||||
Address = player.Channel.RemoteEndPoint.Address,
|
||||
HWId = player.Channel.UserData.HWId,
|
||||
// It's possible for the player to not have cached data loading yet due to coincidental timing.
|
||||
// If this is the case, we assume they have all flags to avoid false-positives.
|
||||
ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All),
|
||||
IsNewPlayer = false,
|
||||
};
|
||||
|
||||
return BanMatcher.BanMatches(ban, playerInfo);
|
||||
}
|
||||
|
||||
private void KickForBanDef(ICommonSession player, ServerBanDef def)
|
||||
{
|
||||
var message = def.FormatBanMessage(_cfg, _localizationManager);
|
||||
player.Channel.Disconnect(message);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Job Bans
|
||||
|
||||
@@ -36,6 +36,7 @@ using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.AlertLevel;
|
||||
using Content.Shared.Power;
|
||||
|
||||
namespace Content.Server.AlertLevel;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Ame.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
@@ -75,7 +76,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
if (!TryComp<AnomalyComponent>(ent.Comp.ConnectedAnomaly, out var anomaly))
|
||||
return;
|
||||
|
||||
DisconneсtFromAnomaly(ent, anomaly);
|
||||
DisconnectFromAnomaly(ent, anomaly);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
|
||||
@@ -124,7 +125,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
|
||||
//TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconneсtFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
{
|
||||
if (ent.Comp.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Map;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.UserInterface;
|
||||
using Content.Server.Advertise;
|
||||
using Content.Server.Advertise.Components;
|
||||
using Content.Shared.Arcade;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Power.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Server.Advertise;
|
||||
using Content.Server.Advertise.Components;
|
||||
using Content.Shared.Power;
|
||||
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed partial class AtmosphereSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var mixtures = new GasMixture[7];
|
||||
var mixtures = new GasMixture[8];
|
||||
for (var i = 0; i < mixtures.Length; i++)
|
||||
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].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)
|
||||
{
|
||||
if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid))
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed partial class AtmosphereSystem
|
||||
newGridAtmos = AddComp<GridAtmosphereComponent>(newGrid);
|
||||
|
||||
// We assume the tiles on the new grid have the same coordinates as they did on the old grid...
|
||||
var enumerator = mapGrid.GetAllTilesEnumerator();
|
||||
var enumerator = _mapSystem.GetAllTilesEnumerator(newGrid, mapGrid);
|
||||
|
||||
while (enumerator.MoveNext(out var tile))
|
||||
{
|
||||
@@ -176,7 +176,7 @@ public sealed partial class AtmosphereSystem
|
||||
tile.AdjacentBits = AtmosDirection.Invalid;
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var direction = (AtmosDirection)(1 << i);
|
||||
var adjacentIndices = tile.GridIndices.Offset(direction);
|
||||
|
||||
TileAtmosphere? adjacent;
|
||||
@@ -196,7 +196,7 @@ public sealed partial class AtmosphereSystem
|
||||
AddActiveTile(atmos, adjacent);
|
||||
|
||||
var oppositeIndex = i.ToOppositeIndex();
|
||||
var oppositeDirection = (AtmosDirection) (1 << oppositeIndex);
|
||||
var oppositeDirection = (AtmosDirection)(1 << oppositeIndex);
|
||||
|
||||
if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
|
||||
{
|
||||
@@ -269,7 +269,7 @@ public sealed partial class AtmosphereSystem
|
||||
private void GridFixTileVacuum(TileAtmosphere tile)
|
||||
{
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable == false );
|
||||
DebugTools.Assert(tile.Air?.Immutable == false);
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.ArchivedCycle = 0;
|
||||
|
||||
|
||||
@@ -11,14 +11,12 @@ namespace Content.Server.Atmos.Monitor.Components;
|
||||
[RegisterComponent]
|
||||
public sealed partial class AirAlarmComponent : Component
|
||||
{
|
||||
[ViewVariables] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
||||
[ViewVariables] public bool AutoMode { get; set; } = true;
|
||||
[DataField] public AirAlarmMode CurrentMode { get; set; } = AirAlarmMode.Filtering;
|
||||
[DataField] public bool AutoMode { get; set; } = true;
|
||||
|
||||
// Remember to null this afterwards.
|
||||
[ViewVariables] public IAirAlarmModeUpdate? CurrentModeUpdater { get; set; }
|
||||
|
||||
[ViewVariables] public AirAlarmTab CurrentTab { get; set; }
|
||||
|
||||
public readonly HashSet<string> KnownDevices = new();
|
||||
public readonly Dictionary<string, GasVentPumpData> VentData = new();
|
||||
public readonly Dictionary<string, GasVentScrubberData> ScrubberData = new();
|
||||
|
||||
@@ -18,6 +18,7 @@ using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
@@ -173,7 +174,6 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
subs.Event<AirAlarmUpdateAlarmThresholdMessage>(OnUpdateThreshold);
|
||||
subs.Event<AirAlarmUpdateDeviceDataMessage>(OnUpdateDeviceData);
|
||||
subs.Event<AirAlarmCopyDeviceDataMessage>(OnCopyDeviceData);
|
||||
subs.Event<AirAlarmTabSetMessage>(OnTabChange);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,12 +200,6 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
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)
|
||||
{
|
||||
if (args.Powered)
|
||||
@@ -598,34 +592,19 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
|
||||
var pressure = CalculatePressureAverage(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)
|
||||
{
|
||||
case AirAlarmTab.Vent:
|
||||
foreach (var (addr, data) in alarm.VentData)
|
||||
{
|
||||
dataToSend.Add(addr, data);
|
||||
}
|
||||
|
||||
break;
|
||||
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;
|
||||
}
|
||||
dataToSend.Add((addr, data));
|
||||
}
|
||||
foreach (var (addr, data) in alarm.ScrubberData)
|
||||
{
|
||||
dataToSend.Add((addr, data));
|
||||
}
|
||||
foreach (var (addr, data) in alarm.SensorData)
|
||||
{
|
||||
dataToSend.Add((addr, data));
|
||||
}
|
||||
|
||||
var deviceCount = alarm.KnownDevices.Count;
|
||||
@@ -638,7 +617,7 @@ public sealed class AirAlarmSystem : EntitySystem
|
||||
_ui.SetUiState(
|
||||
uid,
|
||||
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;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -151,6 +152,7 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
}
|
||||
|
||||
_deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload);
|
||||
Alert(uid, component.LastAlarmState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,31 +5,25 @@ namespace Content.Server.Atmos.Piping.Trinary.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class GasFilterComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
[DataField]
|
||||
public bool Enabled = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("inlet")]
|
||||
public string InletName { get; set; } = "inlet";
|
||||
public string InletName = "inlet";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("filter")]
|
||||
public string FilterName { get; set; } = "filter";
|
||||
public string FilterName = "filter";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("outlet")]
|
||||
public string OutletName { get; set; } = "outlet";
|
||||
public string OutletName = "outlet";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float TransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
[DataField("transferRate")]
|
||||
public float TransferRate { get; set; } = Atmospherics.MaxTransferRate;
|
||||
[DataField]
|
||||
public float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
[DataField("maxTransferRate")]
|
||||
public float MaxTransferRate { get; set; } = Atmospherics.MaxTransferRate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Gas? FilteredGas { get; set; }
|
||||
[DataField]
|
||||
public Gas? FilteredGas;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,25 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[Access(typeof(GasVentScrubberSystem))]
|
||||
public sealed partial class GasVentScrubberComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public bool IsDirty { get; set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("outlet")]
|
||||
public string OutletName { get; set; } = "pipe";
|
||||
|
||||
[ViewVariables]
|
||||
public readonly HashSet<Gas> FilterGases = new(GasVentScrubberData.DefaultFilterGases);
|
||||
[DataField]
|
||||
public HashSet<Gas> FilterGases = new(GasVentScrubberData.DefaultFilterGases);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing;
|
||||
|
||||
/// <summary>
|
||||
/// Target volume to transfer. If <see cref="WideNet"/> is enabled, actual transfer rate will be much higher.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float TransferRate
|
||||
{
|
||||
get => _transferRate;
|
||||
@@ -37,18 +36,17 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
|
||||
private float _transferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("maxTransferRate")]
|
||||
[DataField]
|
||||
public float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
|
||||
/// <summary>
|
||||
/// As pressure difference approaches this number, the effective volume rate may be smaller than <see
|
||||
/// cref="TransferRate"/>
|
||||
/// </summary>
|
||||
[DataField("maxPressure")]
|
||||
[DataField]
|
||||
public float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool WideNet { get; set; } = false;
|
||||
|
||||
public GasVentScrubberData ToAirAlarmData()
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class GasPortableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
|
||||
@@ -33,7 +34,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (!TryComp<MapGridComponent>(gridId, out var grid))
|
||||
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))
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user