Merge remote-tracking branch 'upstream/master' into ed-16-02-2025-planet-light-upstream

# Conflicts:
#	Content.IntegrationTests/Tests/EntityTest.cs
#	Content.IntegrationTests/Tests/PostMapInitTest.cs
#	Content.Server/Medical/HealthAnalyzerSystem.cs
#	Content.Server/Procedural/DungeonSystem.Rooms.cs
#	Content.Server/Procedural/RoomFillComponent.cs
#	Content.Server/Procedural/RoomFillSystem.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Content.Shared/Projectiles/SharedProjectileSystem.cs
#	Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
#	Resources/Prototypes/Maps/Pools/default.yml
#	Resources/migration.yml
This commit is contained in:
Ed
2025-02-16 11:58:47 +03:00
1174 changed files with 91314 additions and 230894 deletions

45
.github/workflows/publish-testing.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Publish Testing
concurrency:
group: publish-testing
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.6.0
with:
submodules: 'recursive'
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
- name: Get Engine Tag
run: |
cd RobustToolbox
git fetch --depth=1
- name: Install dependencies
run: dotnet restore
- name: Build Packaging
run: dotnet build Content.Packaging --configuration Release --no-restore /m
- name: Package server
run: dotnet run --project Content.Packaging server --platform win-x64 --platform linux-x64 --platform osx-x64 --platform linux-arm64
- name: Package client
run: dotnet run --project Content.Packaging client --no-wipe-release
- name: Publish version
run: Tools/publish_multi_request.py --fork-id wizards-testing
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}

View File

@@ -12,12 +12,12 @@ You want to handle the Build, Clean and Rebuild tasks to prevent missing task er
If you want to learn more about these kinds of things, check out Microsoft's official documentation about MSBuild:
https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
-->
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Python>python3</Python>
<Python Condition="'$(OS)'=='Windows_NT' Or '$(OS)'=='Windows'">py -3</Python>
<ProjectGuid>{C899FCA4-7037-4E49-ABC2-44DE72487110}</ProjectGuid>
<TargetFrameworkMoniker>.NETFramework, Version=v4.7.2</TargetFrameworkMoniker>
<TargetFramework>net4.7.2</TargetFramework>
<RestorePackages>false</RestorePackages>
</PropertyGroup>
<PropertyGroup>

View File

@@ -46,7 +46,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown();
}
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Cog", "Convex"};
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
[ParamsSource(nameof(MapsSource))]
public string Map;

View File

@@ -88,6 +88,7 @@ namespace Content.Client.Actions
return;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
}
@@ -137,6 +138,7 @@ namespace Content.Client.Actions
component.Priority = state.Priority;
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
component.RaiseOnUser = state.RaiseOnUser;
component.RaiseOnAction = state.RaiseOnAction;
component.AutoPopulate = state.AutoPopulate;
component.Temporary = state.Temporary;
component.ItemIconStyle = state.ItemIconStyle;

View File

@@ -11,6 +11,8 @@ public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface
protected override void Open()
{
base.Open();
_menu = new AtmosAlertsComputerWindow(this, Owner);
_menu.OpenCentered();
_menu.OnClose += Close;

View File

@@ -16,6 +16,7 @@ namespace Content.Client.Atmos.EntitySystems
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SpriteSystem _spriteSys = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
private GasTileOverlay _overlay = default!;
@@ -25,7 +26,7 @@ namespace Content.Client.Atmos.EntitySystems
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys);
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
_overlayMan.AddOverlay(_overlay);
}

View File

@@ -21,6 +21,7 @@ namespace Content.Client.Atmos.Overlays
{
private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager;
private readonly SharedTransformSystem _xformSys;
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader;
@@ -46,10 +47,11 @@ namespace Content.Client.Atmos.Overlays
public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys)
public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
{
_entManager = entManager;
_mapManager = IoCManager.Resolve<IMapManager>();
_xformSys = xformSys;
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
ZIndex = GasOverlayZIndex;
@@ -158,7 +160,8 @@ namespace Content.Client.Atmos.Overlays
_fireFrameCounter,
_shader,
overlayQuery,
xformQuery);
xformQuery,
_xformSys);
var mapUid = _mapManager.GetMapEntityId(args.MapId);
@@ -180,7 +183,8 @@ namespace Content.Client.Atmos.Overlays
int[] fireFrameCounter,
ShaderInstance shader,
EntityQuery<GasTileOverlayComponent> overlayQuery,
EntityQuery<TransformComponent> xformQuery) state) =>
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSys) state) =>
{
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
@@ -188,7 +192,7 @@ namespace Content.Client.Atmos.Overlays
return true;
}
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i(

View File

@@ -1,6 +1,6 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinSize="280 160" Title="Temperature Control Unit">
MinSize="280 160" Title="{Loc comp-space-heater-ui-title}">
<BoxContainer Name="VboxContainer" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">

View File

@@ -3,13 +3,15 @@ using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
using Content.Shared.Rotation;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
using Robust.Client.Graphics;
namespace Content.Client.Buckle;
internal sealed class BuckleSystem : SharedBuckleSystem
{
[Dependency] private readonly RotationVisualizerSystem _rotationVisualizerSystem = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
public override void Initialize()
{
@@ -17,6 +19,8 @@ internal sealed class BuckleSystem : SharedBuckleSystem
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
SubscribeLocalEvent<BuckleComponent, BuckledEvent>(OnBuckledEvent);
SubscribeLocalEvent<BuckleComponent, UnbuckledEvent>(OnUnbuckledEvent);
}
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
@@ -28,13 +32,21 @@ internal sealed class BuckleSystem : SharedBuckleSystem
// This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking
// sprite rendering for entity layers & direction dependent sorting.
// Future notes:
// Right now this doesn't handle: other grids, other grids rotating, the camera rotation changing, and many other fun rotation specific things
// The entire thing should be a concern of the engine, or something engine helps to implement properly.
// Give some of the sprite rotations their own drawdepth, maybe as an offset within the rsi, or something like this
// And we won't ever need to set the draw depth manually
if (args.NewRotation == args.OldRotation)
return;
if (!TryComp<SpriteComponent>(uid, out var strapSprite))
return;
var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North;
var angle = _xformSystem.GetWorldRotation(uid) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
var isNorth = angle.GetCardinalDir() == Direction.North;
foreach (var buckledEntity in component.BuckledEntities)
{
if (!TryComp<BuckleComponent>(buckledEntity, out var buckle))
@@ -45,6 +57,7 @@ internal sealed class BuckleSystem : SharedBuckleSystem
if (isNorth)
{
// This will only assign if empty, it won't get overwritten by new depth on multiple calls, which do happen easily
buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth;
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
}
@@ -56,6 +69,42 @@ internal sealed class BuckleSystem : SharedBuckleSystem
}
}
/// <summary>
/// Lower the draw depth of the buckled entity without needing for the strap entity to rotate/move.
/// Only do so when the entity is facing screen-local north
/// </summary>
private void OnBuckledEvent(Entity<BuckleComponent> ent, ref BuckledEvent args)
{
if (!TryComp<SpriteComponent>(args.Strap, out var strapSprite))
return;
if (!TryComp<SpriteComponent>(ent.Owner, out var buckledSprite))
return;
var angle = _xformSystem.GetWorldRotation(args.Strap) + _eye.CurrentEye.Rotation; // Get true screen position, or close enough
if (angle.GetCardinalDir() != Direction.North)
return;
ent.Comp.OriginalDrawDepth ??= buckledSprite.DrawDepth;
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
}
/// <summary>
/// Was the draw depth of the buckled entity lowered? Reset it upon unbuckling.
/// </summary>
private void OnUnbuckledEvent(Entity<BuckleComponent> ent, ref UnbuckledEvent args)
{
if (!TryComp<SpriteComponent>(ent.Owner, out var buckledSprite))
return;
if (!ent.Comp.OriginalDrawDepth.HasValue)
return;
buckledSprite.DrawDepth = ent.Comp.OriginalDrawDepth.Value;
ent.Comp.OriginalDrawDepth = null;
}
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
{
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))

View File

@@ -58,9 +58,7 @@
StyleClasses="LabelBig" />
<BoxContainer Orientation="Horizontal"
Margin="0 0 0 5">
<Label Text="{Loc 'crew-monitoring-user-interface-job'}:"
FontColorOverride="DarkGray" />
<Label Text=":"
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
FontColorOverride="DarkGray" />
<TextureRect Name="PersonJobIcon"
TextureScale="2 2"

View File

@@ -5,17 +5,24 @@ namespace Content.Client.Dice;
public sealed class DiceSystem : SharedDiceSystem
{
protected override void UpdateVisuals(EntityUid uid, DiceComponent? die = null)
public override void Initialize()
{
if (!Resolve(uid, ref die) || !TryComp(uid, out SpriteComponent? sprite))
base.Initialize();
SubscribeLocalEvent<DiceComponent, AfterAutoHandleStateEvent>(OnDiceAfterHandleState);
}
private void OnDiceAfterHandleState(Entity<DiceComponent> entity, ref AfterAutoHandleStateEvent args)
{
if (!TryComp<SpriteComponent>(entity, out var sprite))
return;
// TODO maybe just move each diue to its own RSI?
// TODO maybe just move each die to its own RSI?
var state = sprite.LayerGetState(0).Name;
if (state == null)
return;
var prefix = state.Substring(0, state.IndexOf('_'));
sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}");
sprite.LayerSetState(0, $"{prefix}_{entity.Comp.CurrentValue}");
}
}

View File

@@ -30,7 +30,9 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
_overlay.RemoveOverlay<DoAfterOverlay>();
}
#pragma warning disable RA0028 // No base call in overriden function
public override void Update(float frameTime)
#pragma warning restore RA0028 // No base call in overriden function
{
// Currently this only predicts do afters initiated by the player.

View File

@@ -1,6 +1,7 @@
using Content.Client.Wires.Visualizers;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Power;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
@@ -84,7 +85,8 @@ public sealed class AirlockSystem : SharedAirlockSystem
if (!_appearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
state = DoorState.Closed;
if (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
if (_appearanceSystem.TryGetData<bool>(uid, PowerDeviceVisuals.Powered, out var powered, args.Component)
&& powered)
{
boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component)
&& lights && (state == DoorState.Closed || state == DoorState.Welded);

View File

@@ -43,6 +43,8 @@ namespace Content.Client.GameTicking.Managers
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);

View File

@@ -13,6 +13,7 @@ namespace Content.Client.Ghost
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
[Dependency] private readonly ContentEyeSystem _contentEye = default!;
public int AvailableGhostRoleCount { get; private set; }
@@ -79,8 +80,27 @@ namespace Content.Client.Ghost
if (args.Handled)
return;
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup"), args.Performer);
_contentEye.RequestToggleLight(uid, component);
TryComp<PointLightComponent>(uid, out var light);
if (!component.DrawLight)
{
// normal lighting
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-normal"), args.Performer);
_contentEye.RequestEye(component.DrawFov, true);
}
else if (!light?.Enabled ?? false) // skip this option if we have no PointLightComponent
{
// enable personal light
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-personal-light"), args.Performer);
_pointLightSystem.SetEnabled(uid, true, light);
}
else
{
// fullbright mode
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-fullbright"), args.Performer);
_contentEye.RequestEye(component.DrawFov, false);
_pointLightSystem.SetEnabled(uid, false, light);
}
args.Handled = true;
}

View File

@@ -2,7 +2,6 @@ using Content.Shared.ActionBlocker;
using Content.Shared.Instruments.UI;
using Content.Shared.Interaction;
using Robust.Client.Audio.Midi;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface;
@@ -13,7 +12,6 @@ namespace Content.Client.Instruments.UI
public IEntityManager Entities => EntMan;
[Dependency] public readonly IMidiManager MidiManager = default!;
[Dependency] public readonly IFileDialogManager FileDialogManager = default!;
[Dependency] public readonly IPlayerManager PlayerManager = default!;
[Dependency] public readonly ILocalizationManager Loc = default!;
public readonly InstrumentSystem Instruments;
@@ -41,6 +39,8 @@ namespace Content.Client.Instruments.UI
protected override void Open()
{
base.Open();
_instrumentMenu = this.CreateWindow<InstrumentMenu>();
_instrumentMenu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;

View File

@@ -256,7 +256,7 @@ public sealed class DragDropSystem : SharedDragDropSystem
dragSprite.DrawDepth = (int) DrawDepth.Overlays;
if (!dragSprite.NoRotation)
{
Transform(_dragShadow.Value).WorldRotation = Transform(_draggedEntity.Value).WorldRotation;
_transformSystem.SetWorldRotationNoLerp(_dragShadow.Value, _transformSystem.GetWorldRotation(_draggedEntity.Value));
}
// drag initiated

View File

@@ -0,0 +1,11 @@
using Content.Shared.ItemRecall;
namespace Content.Client.ItemRecall;
/// <summary>
/// System for handling the ItemRecall ability for wizards.
/// </summary>
public sealed partial class ItemRecallSystem : SharedItemRecallSystem
{
}

View File

@@ -33,8 +33,13 @@ namespace Content.Client.Labels.UI
_focused = false;
LabelLineEdit.Text = _label;
};
}
// Give the editor keybard focus, since that's the only
protected override void Opened()
{
base.Opened();
// Give the editor keyboard focus, since that's the only
// thing the user will want to be doing with this UI
LabelLineEdit.GrabKeyboardFocus();
}

View File

@@ -64,7 +64,7 @@ public sealed partial class LatheMenu : DefaultWindow
if (_entityManager.TryGetComponent<LatheComponent>(Entity, out var latheComponent))
{
if (!latheComponent.DynamicRecipes.Any())
if (!latheComponent.DynamicPacks.Any())
{
ServerListButton.Visible = false;
}

View File

@@ -0,0 +1,58 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
namespace Content.Client.Light;
/// <summary>
/// This exists just to copy <see cref="BeforeLightTargetOverlay"/> to the light render target
/// </summary>
public sealed class AfterLightTargetOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
[Dependency] private readonly IOverlayManager _overlay = default!;
public const int ContentZIndex = LightBlurOverlay.ContentZIndex + 1;
public AfterLightTargetOverlay()
{
IoCManager.InjectDependencies(this);
ZIndex = ContentZIndex;
}
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.Viewport;
var worldHandle = args.WorldHandle;
if (viewport.Eye == null)
return;
var lightOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
var bounds = args.WorldBounds;
// at 1-1 render scale it's mostly fine but at 4x4 it's way too fkn big
var newScale = viewport.RenderScale / 2f;
var localMatrix =
viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale);
var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
var halfDiff = diff / 2;
// Pixels -> Metres -> Half distance.
// If we're zoomed in need to enlarge the bounds further.
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
() =>
{
// We essentially need to draw the cropped version onto the lightrendertarget.
var subRegion = new UIBox2i(halfDiff.X,
halfDiff.Y,
viewport.LightRenderTarget.Size.X + halfDiff.X,
viewport.LightRenderTarget.Size.Y + halfDiff.Y);
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
}, null);
}
}

View File

@@ -0,0 +1,51 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
namespace Content.Client.Light;
/// <summary>
/// Handles an enlarged lighting target so content can use large blur radii.
/// </summary>
public sealed class BeforeLightTargetOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
[Dependency] private readonly IClyde _clyde = default!;
public IRenderTexture EnlargedLightTarget = default!;
public Box2Rotated EnlargedBounds;
/// <summary>
/// In metres
/// </summary>
private float _skirting = 1.5f;
public const int ContentZIndex = -10;
public BeforeLightTargetOverlay()
{
IoCManager.InjectDependencies(this);
ZIndex = ContentZIndex;
}
protected override void Draw(in OverlayDrawArgs args)
{
// Code is weird but I don't think engine should be enlarging the lighting render target arbitrarily either, maybe via cvar?
// The problem is the blur has no knowledge of pixels outside the viewport so with a large enough blur radius you get sampling issues.
var size = args.Viewport.LightRenderTarget.Size + (int) (_skirting * EyeManager.PixelsPerMeter);
EnlargedBounds = args.WorldBounds.Enlarged(_skirting / 2f);
// This just exists to copy the lightrendertarget and write back to it.
if (EnlargedLightTarget?.Size != size)
{
EnlargedLightTarget = _clyde
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy");
}
args.WorldHandle.RenderInRenderTarget(EnlargedLightTarget,
() =>
{
}, _clyde.GetClearColor(args.MapUid));
}
}

View File

@@ -0,0 +1,36 @@
using Robust.Client.Graphics;
namespace Content.Client.Light.EntitySystems;
public sealed class PlanetLightSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
_overlayMan.AddOverlay(new LightBlurOverlay());
_overlayMan.AddOverlay(new AfterLightTargetOverlay());
}
private void OnClearColor(ref GetClearColorEvent ev)
{
ev.Color = Color.Transparent;
}
public override void Shutdown()
{
base.Shutdown();
_overlayMan.RemoveOverlay<BeforeLightTargetOverlay>();
_overlayMan.RemoveOverlay<RoofOverlay>();
_overlayMan.RemoveOverlay<TileEmissionOverlay>();
_overlayMan.RemoveOverlay<LightBlurOverlay>();
_overlayMan.RemoveOverlay<AfterLightTargetOverlay>();
}
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.Light.EntitySystems;
namespace Content.Client.Light.EntitySystems;
/// <inheritdoc/>
public sealed class RoofSystem : SharedRoofSystem
{
}

View File

@@ -0,0 +1,44 @@
using Robust.Client.Graphics;
using Robust.Shared.Enums;
namespace Content.Client.Light;
/// <summary>
/// Essentially handles blurring for content-side light overlays.
/// </summary>
public sealed class LightBlurOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1;
private IRenderTarget? _blurTarget;
public LightBlurOverlay()
{
IoCManager.InjectDependencies(this);
ZIndex = ContentZIndex;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
return;
var beforeOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
var size = beforeOverlay.EnlargedLightTarget.Size;
if (_blurTarget?.Size != size)
{
_blurTarget = _clyde
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-blur");
}
var target = beforeOverlay.EnlargedLightTarget;
// Yeah that's all this does keep walkin.
_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 2f);
}
}

View File

@@ -0,0 +1,33 @@
using Content.Client.GameTicking.Managers;
using Content.Shared;
using Content.Shared.Light.Components;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
namespace Content.Client.Light;
/// <inheritdoc/>
public sealed class LightCycleSystem : SharedLightCycleSystem
{
[Dependency] private readonly ClientGameTicker _ticker = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override void Update(float frameTime)
{
base.Update(frameTime);
var mapQuery = AllEntityQuery<LightCycleComponent, MapLightComponent>();
while (mapQuery.MoveNext(out var uid, out var cycle, out var map))
{
if (!cycle.Running)
continue;
var time = (float) _timing.CurTime
.Add(cycle.Offset)
.Subtract(_ticker.RoundStartTimeSpan)
.TotalSeconds;
var color = GetColor((uid, cycle), cycle.OriginalColor, time);
map.AmbientLightColor = color;
}
}
}

View File

@@ -0,0 +1,100 @@
using System.Numerics;
using Content.Shared.Light.Components;
using Content.Shared.Maps;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map.Components;
namespace Content.Client.Light;
public sealed class RoofOverlay : Overlay
{
private readonly IEntityManager _entManager;
[Dependency] private readonly IOverlayManager _overlay = default!;
private readonly EntityLookupSystem _lookup;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _xformSystem;
private readonly HashSet<Entity<OccluderComponent>> _occluders = new();
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
public const int ContentZIndex = BeforeLightTargetOverlay.ContentZIndex + 1;
public RoofOverlay(IEntityManager entManager)
{
_entManager = entManager;
IoCManager.InjectDependencies(this);
_lookup = _entManager.System<EntityLookupSystem>();
_mapSystem = _entManager.System<SharedMapSystem>();
_xformSystem = _entManager.System<SharedTransformSystem>();
ZIndex = ContentZIndex;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
return;
var mapEnt = _mapSystem.GetMap(args.MapId);
if (!_entManager.TryGetComponent(mapEnt, out RoofComponent? roofComp) ||
!_entManager.TryGetComponent(mapEnt, out MapGridComponent? grid))
{
return;
}
var viewport = args.Viewport;
var eye = args.Viewport.Eye;
var worldHandle = args.WorldHandle;
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
var bounds = lightoverlay.EnlargedBounds;
var target = lightoverlay.EnlargedLightTarget;
worldHandle.RenderInRenderTarget(target,
() =>
{
var invMatrix = target.GetWorldToLocalMatrix(eye, viewport.RenderScale / 2f);
var gridMatrix = _xformSystem.GetWorldMatrix(mapEnt);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
worldHandle.SetTransform(matty);
var tileEnumerator = _mapSystem.GetTilesEnumerator(mapEnt, grid, bounds);
// Due to stencilling we essentially draw on unrooved tiles
while (tileEnumerator.MoveNext(out var tileRef))
{
if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0)
{
// Check if the tile is occluded in which case hide it anyway.
// This is to avoid lit walls bleeding over to unlit tiles.
_occluders.Clear();
_lookup.GetLocalEntitiesIntersecting(mapEnt, tileRef.GridIndices, _occluders);
var found = false;
foreach (var occluder in _occluders)
{
if (!occluder.Comp.Enabled)
continue;
found = true;
break;
}
if (!found)
continue;
}
var local = _lookup.GetLocalBounds(tileRef, grid.TileSize);
worldHandle.DrawRect(local, roofComp.Color);
}
}, null);
}
}

View File

@@ -0,0 +1,90 @@
using System.Numerics;
using Content.Shared.Light.Components;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.Client.Light;
public sealed class TileEmissionOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
private SharedMapSystem _mapSystem;
private SharedTransformSystem _xformSystem;
private readonly EntityLookupSystem _lookup;
private readonly EntityQuery<TransformComponent> _xformQuery;
private readonly HashSet<Entity<TileEmissionComponent>> _entities = new();
private List<Entity<MapGridComponent>> _grids = new();
public const int ContentZIndex = RoofOverlay.ContentZIndex + 1;
public TileEmissionOverlay(IEntityManager entManager)
{
IoCManager.InjectDependencies(this);
_lookup = entManager.System<EntityLookupSystem>();
_mapSystem = entManager.System<SharedMapSystem>();
_xformSystem = entManager.System<SharedTransformSystem>();
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
ZIndex = ContentZIndex;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
return;
var mapId = args.MapId;
var worldHandle = args.WorldHandle;
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
var bounds = lightoverlay.EnlargedBounds;
var target = lightoverlay.EnlargedLightTarget;
var viewport = args.Viewport;
args.WorldHandle.RenderInRenderTarget(target,
() =>
{
var invMatrix = target.GetWorldToLocalMatrix(viewport.Eye, viewport.RenderScale / 2f);
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true);
foreach (var grid in _grids)
{
var gridInvMatrix = _xformSystem.GetInvWorldMatrix(grid);
var localBounds = gridInvMatrix.TransformBox(bounds);
_entities.Clear();
_lookup.GetLocalEntitiesIntersecting(grid.Owner, localBounds, _entities);
if (_entities.Count == 0)
continue;
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
foreach (var ent in _entities)
{
var xform = _xformQuery.Comp(ent);
var tile = _mapSystem.LocalToTile(grid.Owner, grid, xform.Coordinates);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
worldHandle.SetTransform(matty);
// Yes I am fully aware this leads to overlap. If you really want to have alpha then you'll need
// to turn the squares into polys.
// Additionally no shadows so if you make it too big it's going to go through a 1x wall.
var local = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(ent.Comp.Range);
worldHandle.DrawRect(local, ent.Comp.Color);
}
}
}, null);
}
}

View File

@@ -43,7 +43,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
[UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor;

View File

@@ -1016,6 +1016,13 @@ namespace Content.Client.Lobby.UI
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
_loadoutWindow.OpenCenteredLeft();
_loadoutWindow.OnNameChanged += name =>
{
roleLoadout.EntityName = name;
Profile = Profile.WithLoadout(roleLoadout);
SetDirty();
};
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
{
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);

View File

@@ -5,17 +5,15 @@
SetSize="800 800"
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"/>
<LineEdit Name="RoleNameEdit" VerticalExpand="True" HorizontalExpand="True"/>
</PanelContainer>
</BoxContainer>
-->
<VerticalTabContainer Name="LoadoutGroupsContainer"
VerticalExpand="True"
HorizontalExpand="True">

View File

@@ -1,7 +1,9 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Dataset;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Random.Helpers;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
@@ -13,6 +15,7 @@ namespace Content.Client.Lobby.UI.Loadouts;
[GenerateTypedNameReferences]
public sealed partial class LoadoutWindow : FancyWindow
{
public event Action<string>? OnNameChanged;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
@@ -25,6 +28,23 @@ public sealed partial class LoadoutWindow : FancyWindow
RobustXamlLoader.Load(this);
Profile = profile;
var protoManager = collection.Resolve<IPrototypeManager>();
RoleNameEdit.IsValid = text => text.Length <= HumanoidCharacterProfile.MaxLoadoutNameLength;
// Hide if we can't edit the name.
if (!proto.CanCustomizeName)
{
RoleNameBox.Visible = false;
}
else
{
var name = loadout.EntityName;
RoleNameEdit.ToolTip = Loc.GetString(
"loadout-name-edit-tooltip",
("max", HumanoidCharacterProfile.MaxLoadoutNameLength));
RoleNameEdit.Text = name ?? string.Empty;
RoleNameEdit.OnTextChanged += args => OnNameChanged?.Invoke(args.Text);
}
// Hide if no groups
if (proto.Groups.Count == 0)

View File

@@ -1,5 +1,8 @@
using Content.Shared.Magic;
using Content.Shared.Magic;
using Content.Shared.Magic.Events;
namespace Content.Client.Magic;
public sealed class MagicSystem : SharedMagicSystem;
public sealed class MagicSystem : SharedMagicSystem
{
}

View File

@@ -102,7 +102,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
return;
StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix));
StartDragging(gridUid, Vector2.Transform(mousePos.Position, _transformSystem.GetInvWorldMatrix(gridUid)));
}
if (!TryComp(_dragging, out TransformComponent? xform))
@@ -117,11 +117,11 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
return;
}
var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix);
var localToWorld = Vector2.Transform(_localPosition, _transformSystem.GetWorldMatrix(xform));
if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return;
var requestedGridOrigin = mousePos.Position - xform.WorldRotation.RotateVec(_localPosition);
var requestedGridOrigin = mousePos.Position - _transformSystem.GetWorldRotation(xform).RotateVec(_localPosition);
_lastMousePosition = new MapCoordinates(requestedGridOrigin, mousePos.MapId);
RaiseNetworkEvent(new GridDragRequestPosition()

View File

@@ -20,6 +20,8 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<NewsWriterMenu>();
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;

View File

@@ -14,6 +14,8 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
protected override void Open()
{
base.Open();
EntityUid? gridUid = null;
var stationName = string.Empty;

View File

@@ -18,6 +18,8 @@ namespace Content.Client.Nuke
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<NukeMenu>();
_menu.OnKeypadButtonPressed += i =>

View File

@@ -4,6 +4,7 @@ using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Client.Orbit;
@@ -11,8 +12,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly AnimationPlayerSystem _animations = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly string _orbitAnimationKey = "orbiting";
private readonly string _orbitStopKey = "orbiting_stop";
public override void Initialize()
@@ -21,11 +22,11 @@ public sealed class OrbitVisualsSystem : EntitySystem
SubscribeLocalEvent<OrbitVisualsComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<OrbitVisualsComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<OrbitVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
}
private void OnComponentInit(EntityUid uid, OrbitVisualsComponent component, ComponentInit args)
{
_robustRandom.SetSeed((int)_timing.CurTime.TotalMilliseconds);
component.OrbitDistance =
_robustRandom.NextFloat(0.75f * component.OrbitDistance, 1.25f * component.OrbitDistance);
@@ -38,15 +39,10 @@ public sealed class OrbitVisualsSystem : EntitySystem
}
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
return;
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey))
{
_animations.Stop(uid, animationPlayer, _orbitStopKey);
_animations.Stop((uid, animationPlayer), _orbitStopKey);
}
_animations.Play(uid, animationPlayer, GetOrbitAnimation(component), _orbitAnimationKey);
}
private void OnComponentRemove(EntityUid uid, OrbitVisualsComponent component, ComponentRemove args)
@@ -57,14 +53,9 @@ public sealed class OrbitVisualsSystem : EntitySystem
sprite.EnableDirectionOverride = false;
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (_animations.HasRunningAnimation(uid, animationPlayer, _orbitAnimationKey))
{
_animations.Stop(uid, animationPlayer, _orbitAnimationKey);
}
if (!_animations.HasRunningAnimation(uid, animationPlayer, _orbitStopKey))
{
_animations.Play(uid, animationPlayer, GetStopAnimation(component, sprite), _orbitStopKey);
_animations.Play((uid, animationPlayer), GetStopAnimation(component, sprite), _orbitStopKey);
}
}
@@ -74,7 +65,8 @@ public sealed class OrbitVisualsSystem : EntitySystem
foreach (var (orbit, sprite) in EntityManager.EntityQuery<OrbitVisualsComponent, SpriteComponent>())
{
var angle = new Angle(Math.PI * 2 * orbit.Orbit);
var progress = (float)(_timing.CurTime.TotalSeconds / orbit.OrbitLength) % 1;
var angle = new Angle(Math.PI * 2 * progress);
var vec = angle.RotateVec(new Vector2(orbit.OrbitDistance, 0));
sprite.Rotation = angle;
@@ -82,38 +74,6 @@ public sealed class OrbitVisualsSystem : EntitySystem
}
}
private void OnAnimationCompleted(EntityUid uid, OrbitVisualsComponent component, AnimationCompletedEvent args)
{
if (args.Key == _orbitAnimationKey && TryComp(uid, out AnimationPlayerComponent? animationPlayer))
{
_animations.Play(uid, animationPlayer, GetOrbitAnimation(component), _orbitAnimationKey);
}
}
private Animation GetOrbitAnimation(OrbitVisualsComponent component)
{
var length = component.OrbitLength;
return new Animation()
{
Length = TimeSpan.FromSeconds(length),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(OrbitVisualsComponent),
Property = nameof(OrbitVisualsComponent.Orbit),
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(0.0f, 0f),
new AnimationTrackProperty.KeyFrame(1.0f, length),
},
InterpolationMode = AnimationInterpolationMode.Linear
}
}
};
}
private Animation GetStopAnimation(OrbitVisualsComponent component, SpriteComponent sprite)
{
var length = component.OrbitStopLength;

View File

@@ -32,6 +32,11 @@ public sealed class TargetOutlineSystem : EntitySystem
/// </summary>
public EntityWhitelist? Whitelist = null;
/// <summary>
/// Blacklist that the target must satisfy.
/// </summary>
public EntityWhitelist? Blacklist = null;
/// <summary>
/// Predicate the target must satisfy.
/// </summary>
@@ -93,15 +98,16 @@ public sealed class TargetOutlineSystem : EntitySystem
RemoveHighlights();
}
public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, CancellableEntityEventArgs? validationEvent)
public void Enable(float range, bool checkObstructions, Func<EntityUid, bool>? predicate, EntityWhitelist? whitelist, EntityWhitelist? blacklist, CancellableEntityEventArgs? validationEvent)
{
Range = range;
CheckObstruction = checkObstructions;
Predicate = predicate;
Whitelist = whitelist;
Blacklist = blacklist;
ValidationEvent = validationEvent;
_enabled = Predicate != null || Whitelist != null || ValidationEvent != null;
_enabled = Predicate != null || Whitelist != null || Blacklist != null || ValidationEvent != null;
}
public override void Update(float frameTime)

View File

@@ -10,8 +10,6 @@ namespace Content.Client.Pinpointer.UI;
[GenerateTypedNameReferences]
public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl>
{
[Dependency] private readonly IEntityManager _entMan = default!;
public readonly EntityCoordinates BeaconPosition;
public Action<EntityCoordinates>? OnPressed;
public string? Label => BeaconNameLabel.Text;

View File

@@ -12,6 +12,8 @@ public sealed class PowerMonitoringConsoleBoundUserInterface : BoundUserInterfac
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<PowerMonitoringWindow>();
_menu.SetEntity(Owner);
_menu.SendPowerMonitoringConsoleMessageAction += SendPowerMonitoringConsoleMessage;

View File

@@ -4,14 +4,18 @@ using Robust.Client.GameObjects;
namespace Content.Client.Power.Visualizers;
public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComponent>
public sealed class CableVisualizerSystem : EntitySystem
{
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CableVisualizerComponent, AppearanceChangeEvent>(OnAppearanceChange, after: new[] { typeof(SubFloorHideSystem) });
}
protected override void OnAppearanceChange(EntityUid uid, CableVisualizerComponent component, ref AppearanceChangeEvent args)
private void OnAppearanceChange(EntityUid uid, CableVisualizerComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
@@ -23,7 +27,7 @@ public sealed class CableVisualizerSystem : VisualizerSystem<CableVisualizerComp
return;
}
if (!AppearanceSystem.TryGetData<WireVisDirFlags>(uid, WireVisVisuals.ConnectedMask, out var mask, args.Component))
if (!_appearanceSystem.TryGetData<WireVisDirFlags>(uid, WireVisVisuals.ConnectedMask, out var mask, args.Component))
mask = WireVisDirFlags.None;
args.Sprite.LayerSetState(0, $"{component.StatePrefix}{(int) mask}");

View File

@@ -23,7 +23,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
if (_enableShuttlePosition)
{
_overlay = new EmergencyShuttleOverlay(EntityManager);
_overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
overlayManager.AddOverlay(_overlay);
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
}
@@ -57,23 +57,26 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
/// </summary>
public sealed class EmergencyShuttleOverlay : Overlay
{
private IEntityManager _entManager;
private readonly EntityQuery<TransformComponent> _transformQuery;
private readonly SharedTransformSystem _transformSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityUid? StationUid;
public Box2? Position;
public EmergencyShuttleOverlay(IEntityManager entManager)
public EmergencyShuttleOverlay(EntityQuery<TransformComponent> transformQuery, SharedTransformSystem transformSystem)
{
_entManager = entManager;
_transformQuery = transformQuery;
_transformSystem = transformSystem;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (Position == null || !_entManager.TryGetComponent<TransformComponent>(StationUid, out var xform)) return;
if (Position == null || !_transformQuery.TryGetComponent(StationUid, out var xform))
return;
args.WorldHandle.SetTransform(xform.WorldMatrix);
args.WorldHandle.SetTransform(_transformSystem.GetWorldMatrix(xform));
args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100));
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}

View File

@@ -1,24 +1,14 @@
using System.Text;
using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Systems;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map;
namespace Content.Client.Shuttles.UI;
[GenerateTypedNameReferences]
public sealed partial class DockObject : PanelContainer
{
[PublicAPI]
public event Action? UndockPressed;
[PublicAPI]
public event Action? ViewPressed;
public BoxContainer ContentsContainer => Contents;
public DockObject()

View File

@@ -163,16 +163,6 @@ public sealed partial class DockingScreen : BoxContainer
}
dockContainer.AddDock(dock, DockingControl);
dockContainer.ViewPressed += () =>
{
OnDockPress(dock);
};
dockContainer.UndockPressed += () =>
{
UndockRequest?.Invoke(dock.Entity);
};
}
}

View File

@@ -3,12 +3,12 @@ using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Content.Client.Administration.Managers;
using Content.Shared.Database;
using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
@@ -24,6 +24,7 @@ public sealed class ContentSpriteSystem : EntitySystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
private ContentSpriteControl _control = new();
@@ -42,12 +43,12 @@ public sealed class ContentSpriteSystem : EntitySystem
{
base.Shutdown();
foreach (var queued in _control._queuedTextures)
foreach (var queued in _control.QueuedTextures)
{
queued.Tcs.SetCanceled();
}
_control._queuedTextures.Clear();
_control.QueuedTextures.Clear();
_ui.RootControl.RemoveChild(_control);
}
@@ -103,7 +104,7 @@ public sealed class ContentSpriteSystem : EntitySystem
var texture = _clyde.CreateRenderTarget(new Vector2i(size.X, size.Y), new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "export");
var tcs = new TaskCompletionSource(cancelToken);
_control._queuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
_control.QueuedTextures.Enqueue((texture, direction, entity, includeId, tcs));
await tcs.Task;
}
@@ -113,13 +114,21 @@ public sealed class ContentSpriteSystem : EntitySystem
if (!_adminManager.IsAdmin())
return;
var target = ev.Target;
Verb verb = new()
{
Text = Loc.GetString("export-entity-verb-get-data-text"),
Category = VerbCategory.Debug,
Act = () =>
Act = async () =>
{
Export(ev.Target);
try
{
await Export(target);
}
catch (Exception e)
{
_runtimeLog.LogException(e, $"{nameof(ContentSpriteSystem)}.{nameof(Export)}");
}
},
};
@@ -141,7 +150,7 @@ public sealed class ContentSpriteSystem : EntitySystem
Direction Direction,
EntityUid Entity,
bool IncludeId,
TaskCompletionSource Tcs)> _queuedTextures = new();
TaskCompletionSource Tcs)> QueuedTextures = new();
private ISawmill _sawmill;
@@ -155,7 +164,7 @@ public sealed class ContentSpriteSystem : EntitySystem
{
base.Draw(handle);
while (_queuedTextures.TryDequeue(out var queued))
while (QueuedTextures.TryDequeue(out var queued))
{
if (queued.Tcs.Task.IsCanceled)
continue;

View File

@@ -4,6 +4,7 @@ using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Stealth.Components;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
@@ -85,6 +86,9 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
if (data.HideOnStealth && TryComp<StealthComponent>(ent, out var stealth) && stealth.Enabled)
return false;
if (TryComp<SpriteComponent>(ent, out var sprite) && !sprite.Visible)
return false;
if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer))
return false;

View File

@@ -1,39 +1,80 @@
using Content.Client.Storage.Systems;
using Content.Client.UserInterface.Systems.Storage;
using Content.Client.UserInterface.Systems.Storage.Controls;
using Content.Shared.Storage;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Storage;
[UsedImplicitly]
public sealed class StorageBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entManager = default!;
private readonly StorageSystem _storage;
[Obsolete] public override bool DeferredClose => false;
private StorageWindow? _window;
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
_storage = _entManager.System<StorageSystem>();
}
protected override void Open()
{
base.Open();
if (_entManager.TryGetComponent<StorageComponent>(Owner, out var comp))
_storage.OpenStorageWindow((Owner, comp));
_window = IoCManager.Resolve<IUserInterfaceManager>()
.GetUIController<StorageUIController>()
.CreateStorageWindow(Owner);
if (EntMan.TryGetComponent(Owner, out StorageComponent? storage))
{
_window.UpdateContainer((Owner, storage));
}
_window.OnClose += Close;
_window.FlagDirty();
}
public void Refresh()
{
_window?.FlagDirty();
}
public void Reclaim()
{
if (_window == null)
return;
_window.OnClose -= Close;
_window.Orphan();
_window = null;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
Reclaim();
}
public void Hide()
{
if (_window == null)
return;
_storage.CloseStorageWindow(Owner);
_window.Visible = false;
}
public void Show()
{
if (_window == null)
return;
_window.Visible = true;
}
public void ReOpen()
{
_window?.Orphan();
_window = null;
Open();
}
}

View File

@@ -4,7 +4,8 @@ using Content.Client.Animations;
using Content.Shared.Hands;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Collections;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Timing;
@@ -13,114 +14,91 @@ namespace Content.Client.Storage.Systems;
public sealed class StorageSystem : SharedStorageSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!;
private readonly List<Entity<StorageComponent>> _openStorages = new();
public int OpenStorageAmount => _openStorages.Count;
public event Action<Entity<StorageComponent>>? StorageUpdated;
public event Action<Entity<StorageComponent>?>? StorageOrderChanged;
private Dictionary<EntityUid, ItemStorageLocation> _oldStoredItems = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
}
private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref ComponentHandleState args)
{
if (args.Current is not StorageComponentState state)
return;
component.Grid.Clear();
component.Grid.AddRange(state.Grid);
component.MaxItemSize = state.MaxItemSize;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
_oldStoredItems.Clear();
foreach (var item in component.StoredItems)
{
_oldStoredItems.Add(item.Key, item.Value);
}
component.StoredItems.Clear();
foreach (var (nent, location) in state.StoredItems)
{
var ent = EnsureEntity<StorageComponent>(nent, uid);
component.StoredItems[ent] = location;
}
component.SavedLocations.Clear();
foreach (var loc in state.SavedLocations)
{
component.SavedLocations[loc.Key] = new(loc.Value);
}
var uiDirty = !component.StoredItems.SequenceEqual(_oldStoredItems);
if (uiDirty && UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
storageBui.Refresh();
// Make sure nesting still updated.
var player = _player.LocalEntity;
if (NestedStorage && player != null && ContainerSystem.TryGetContainingContainer((uid, null, null), out var container) &&
UI.TryGetOpenUi<StorageBoundUserInterface>(container.Owner, StorageComponent.StorageUiKey.Key, out var containerBui))
{
containerBui.Hide();
}
}
}
public override void UpdateUI(Entity<StorageComponent?> entity)
{
if (Resolve(entity.Owner, ref entity.Comp))
StorageUpdated?.Invoke((entity, entity.Comp));
}
public void OpenStorageWindow(Entity<StorageComponent> entity)
{
if (_openStorages.Contains(entity))
if (UI.TryGetOpenUi<StorageBoundUserInterface>(entity.Owner, StorageComponent.StorageUiKey.Key, out var sBui))
{
if (_openStorages.LastOrDefault() == entity)
{
CloseStorageWindow((entity, entity.Comp));
}
else
{
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storageEnt in reverseStorages)
{
if (storageEnt == entity)
break;
CloseStorageBoundUserInterface(storageEnt.Owner);
_openStorages.Remove(entity);
}
}
return;
}
ClearNonParentStorages(entity);
_openStorages.Add(entity);
Entity<StorageComponent>? last = _openStorages.LastOrDefault();
StorageOrderChanged?.Invoke(last);
}
public void CloseStorageWindow(Entity<StorageComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
if (!_openStorages.Contains((entity, entity.Comp)))
return;
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storage in reverseStorages)
{
CloseStorageBoundUserInterface(storage.Owner);
_openStorages.Remove(storage);
if (storage.Owner == entity.Owner)
break;
}
Entity<StorageComponent>? last = null;
if (_openStorages.Any())
last = _openStorages.LastOrDefault();
StorageOrderChanged?.Invoke(last);
}
private void ClearNonParentStorages(EntityUid uid)
{
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
var reverseStorages = storages.Reverse();
foreach (var storage in reverseStorages)
{
if (storage.Comp.Container.Contains(uid))
break;
CloseStorageBoundUserInterface(storage.Owner);
_openStorages.Remove(storage);
sBui.Refresh();
}
}
private void CloseStorageBoundUserInterface(Entity<UserInterfaceComponent?> entity)
protected override void HideStorageWindow(EntityUid uid, EntityUid actor)
{
if (!Resolve(entity, ref entity.Comp, false))
return;
if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui)
return;
bui.Close();
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
storageBui.Hide();
}
}
private void OnShutdown(Entity<StorageComponent> ent, ref ComponentShutdown args)
protected override void ShowStorageWindow(EntityUid uid, EntityUid actor)
{
CloseStorageWindow((ent, ent.Comp));
if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
{
storageBui.Show();
}
}
/// <inheritdoc />
@@ -142,7 +120,7 @@ public sealed class StorageSystem : SharedStorageSystem
{
if (!_timing.IsFirstTimePredicted)
return;
if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) ||
!Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
{

View File

@@ -27,6 +27,8 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<StoreMenu>();
if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store))
_menu.Title = Loc.GetString(store.Name);

View File

@@ -21,6 +21,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
protected override void Open()
{
base.Open();
_window = new();
if (_type == SurveillanceCameraSetupUiKey.Router)

View File

@@ -87,9 +87,6 @@ public sealed partial class DialogWindow : FancyWindow
Prompts.AddChild(box);
}
// Grab keyboard focus for the first dialog entry
_promptLines[0].Item2.GrabKeyboardFocus();
OkButton.OnPressed += _ => Confirm();
CancelButton.OnPressed += _ =>
@@ -110,6 +107,14 @@ public sealed partial class DialogWindow : FancyWindow
OpenCentered();
}
protected override void Opened()
{
base.Opened();
// Grab keyboard focus for the first dialog entry
_promptLines[0].Item2.GrabKeyboardFocus();
}
private void Confirm()
{
var results = new Dictionary<string, string>();

View File

@@ -7,7 +7,6 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Graphics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -96,7 +95,6 @@ public sealed partial class FancyTree : Control
private void LoadIcons()
{
IconColor = TryGetStyleProperty(StylePropertyIconColor, out Color color) ? color : Color.White;
string? path;
if (!TryGetStyleProperty(StylePropertyIconExpanded, out IconExpanded))
IconExpanded = _resCache.GetTexture(DefaultIconExpanded);

View File

@@ -952,7 +952,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
var range = entityAction.CheckCanAccess ? action.Range : -1;
_interactionOutline?.SetEnabled(false);
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, null);
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
}
/// <summary>

View File

@@ -56,19 +56,20 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
_window = UIManager.CreateWindow<CharacterWindow>();
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
_window.OnClose += DeactivateButton;
_window.OnOpen += ActivateButton;
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCharacterMenu,
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<CharacterUIController>();
InputCmdHandler.FromDelegate(_ => ToggleWindow()))
.Register<CharacterUIController>();
}
public void OnStateExited(GameplayState state)
{
if (_window != null)
{
_window.Dispose();
_window.Close();
_window = null;
}
@@ -105,18 +106,27 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
}
CharacterButton.OnPressed += CharacterButtonPressed;
}
if (_window == null)
private void DeactivateButton()
{
if (CharacterButton == null)
{
return;
}
_window.OnClose += DeactivateButton;
_window.OnOpen += ActivateButton;
CharacterButton.Pressed = false;
}
private void DeactivateButton() => CharacterButton!.Pressed = false;
private void ActivateButton() => CharacterButton!.Pressed = true;
private void ActivateButton()
{
if (CharacterButton == null)
{
return;
}
CharacterButton.Pressed = true;
}
private void CharacterUpdated(CharacterData data)
{
@@ -150,7 +160,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
var objectiveLabel = new RichTextLabel
{
StyleClasses = {StyleNano.StyleClassTooltipActionTitle}
StyleClasses = { StyleNano.StyleClassTooltipActionTitle }
};
objectiveLabel.SetMessage(objectiveText);
@@ -245,10 +255,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
if (_window == null)
return;
if (CharacterButton != null)
{
CharacterButton.SetClickPressed(!_window.IsOpen);
}
CharacterButton?.SetClickPressed(!_window.IsOpen);
if (_window.IsOpen)
{

View File

@@ -31,13 +31,12 @@ public sealed class HotbarUIController : UIController
ReloadHotbar();
}
public void Setup(HandsContainer handsContainer, StorageContainer storageContainer)
public void Setup(HandsContainer handsContainer)
{
_inventory = UIManager.GetUIController<InventoryUIController>();
_hands = UIManager.GetUIController<HandsUIController>();
_storage = UIManager.GetUIController<StorageUIController>();
_hands.RegisterHandContainer(handsContainer);
_storage.RegisterStorageContainer(storageContainer);
}
public void ReloadHotbar()

View File

@@ -1,7 +1,6 @@
<widgets:HotbarGui
xmlns="https://spacestation14.io"
xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
xmlns:storage="clr-namespace:Content.Client.UserInterface.Systems.Storage.Controls"
xmlns:hands="clr-namespace:Content.Client.UserInterface.Systems.Hands.Controls"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
Name="HotbarInterface"
@@ -13,10 +12,8 @@
<BoxContainer Name="StorageContainer"
Access="Public"
HorizontalAlignment="Center"
HorizontalExpand="True"
Margin="10">
<storage:StorageContainer
Name="StoragePanel"
Visible="False"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Name="Hotbar" HorizontalAlignment="Center">
<inventory:ItemSlotButtonContainer

View File

@@ -15,7 +15,7 @@ public sealed partial class HotbarGui : UIWidget
StatusPanelLeft.SetSide(HandUILocation.Left);
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
hotbarController.Setup(HandContainer, StoragePanel);
hotbarController.Setup(HandContainer);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
}

View File

@@ -59,7 +59,7 @@ public sealed class ItemGridPiece : Control, IEntityControl
Location = location;
Visible = true;
MouseFilter = MouseFilterMode.Pass;
MouseFilter = MouseFilterMode.Stop;
TooltipSupplier = SupplyTooltip;
@@ -105,8 +105,11 @@ public sealed class ItemGridPiece : Control, IEntityControl
return;
}
if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity && _storageController.DraggingGhost != this)
if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity &&
_storageController.DraggingGhost != this)
{
return;
}
var adjustedShape = _entityManager.System<ItemSystem>().GetAdjustedItemShape((Entity, itemComponent), Location.Rotation, Vector2i.Zero);
var boundingGrid = adjustedShape.GetBoundingBox();

View File

@@ -3,7 +3,9 @@ using System.Linq;
using System.Numerics;
using Content.Client.Hands.Systems;
using Content.Client.Items.Systems;
using Content.Client.Storage;
using Content.Client.Storage.Systems;
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Item;
using Content.Shared.Storage;
@@ -11,12 +13,14 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Storage.Controls;
public sealed class StorageContainer : BaseWindow
public sealed class StorageWindow : BaseWindow
{
[Dependency] private readonly IEntityManager _entity = default!;
private readonly StorageUIController _storageController;
@@ -27,6 +31,20 @@ public sealed class StorageContainer : BaseWindow
private readonly GridContainer _backgroundGrid;
private readonly GridContainer _sidebar;
private Control _titleContainer;
private Label _titleLabel;
// Needs to be nullable in case a piece is in default spot.
private readonly Dictionary<EntityUid, (ItemStorageLocation? Loc, ItemGridPiece Control)> _pieces = new();
private readonly List<Control> _controlGrid = new();
private ValueList<EntityUid> _contained = new();
private ValueList<EntityUid> _toRemove = new();
private TextureButton? _backButton;
private bool _isDirty;
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPiecePressed;
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
@@ -51,9 +69,10 @@ public sealed class StorageContainer : BaseWindow
private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
private Texture? _sidebarFatTexture;
public StorageContainer()
public StorageWindow()
{
IoCManager.InjectDependencies(this);
Resizable = false;
_storageController = UserInterfaceManager.GetUIController<StorageUIController>();
@@ -63,6 +82,7 @@ public sealed class StorageContainer : BaseWindow
_sidebar = new GridContainer
{
Name = "SideBar",
HSeparationOverride = 0,
VSeparationOverride = 0,
Columns = 1
@@ -70,21 +90,48 @@ public sealed class StorageContainer : BaseWindow
_pieceGrid = new GridContainer
{
Name = "PieceGrid",
HSeparationOverride = 0,
VSeparationOverride = 0
};
_backgroundGrid = new GridContainer
{
Name = "BackgroundGrid",
HSeparationOverride = 0,
VSeparationOverride = 0
};
_titleLabel = new Label()
{
HorizontalExpand = true,
Name = "StorageLabel",
ClipText = true,
Text = "Dummy",
StyleClasses =
{
"FancyWindowTitle",
}
};
_titleContainer = new PanelContainer()
{
StyleClasses =
{
"WindowHeadingBackground"
},
Children =
{
_titleLabel
}
};
var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
_titleContainer,
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
@@ -130,12 +177,22 @@ public sealed class StorageContainer : BaseWindow
if (entity == null)
return;
if (UserInterfaceManager.GetUIController<StorageUIController>().WindowTitle)
{
_titleLabel.Text = Identity.Name(entity.Value, _entity);
_titleContainer.Visible = true;
}
else
{
_titleContainer.Visible = false;
}
BuildGridRepresentation();
}
private void BuildGridRepresentation()
{
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || !comp.Grid.Any())
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || comp.Grid.Count == 0)
return;
var boundingGrid = comp.Grid.GetBoundingBox();
@@ -144,12 +201,13 @@ public sealed class StorageContainer : BaseWindow
#region Sidebar
_sidebar.Children.Clear();
_sidebar.Rows = boundingGrid.Height + 1;
var rows = boundingGrid.Height + 1;
_sidebar.Rows = rows;
var exitButton = new TextureButton
{
TextureNormal = _entity.System<StorageSystem>().OpenStorageAmount == 1
?_exitTexture
: _backTexture,
Name = "ExitButton",
TextureNormal = _exitTexture,
Scale = new Vector2(2, 2),
};
exitButton.OnPressed += _ =>
@@ -165,8 +223,10 @@ public sealed class StorageContainer : BaseWindow
args.Handle();
}
};
var exitContainer = new BoxContainer
{
Name = "ExitContainer",
Children =
{
new TextureRect
@@ -182,28 +242,70 @@ public sealed class StorageContainer : BaseWindow
}
}
};
_sidebar.AddChild(exitContainer);
for (var i = 0; i < boundingGrid.Height - 1; i++)
var offset = 2;
if (_entity.System<StorageSystem>().NestedStorage && rows > 0)
{
_sidebar.AddChild(new TextureRect
_backButton = new TextureButton
{
Texture = _sidebarMiddleTexture,
TextureScale = new Vector2(2, 2),
});
TextureNormal = _backTexture,
Scale = new Vector2(2, 2),
};
_backButton.OnPressed += _ =>
{
var containerSystem = _entity.System<SharedContainerSystem>();
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
_entity.TryGetComponent(container.Owner, out StorageComponent? storage))
{
Close();
if (_entity.System<SharedUserInterfaceSystem>()
.TryGetOpenUi<StorageBoundUserInterface>(container.Owner,
StorageComponent.StorageUiKey.Key,
out var parentBui))
{
parentBui.Show();
}
}
};
var backContainer = new BoxContainer
{
Name = "ExitContainer",
Children =
{
new TextureRect
{
Texture = rows > 2 ? _sidebarMiddleTexture : _sidebarBottomTexture,
TextureScale = new Vector2(2, 2),
Children =
{
_backButton,
}
}
}
};
_sidebar.AddChild(backContainer);
}
if (boundingGrid.Height > 0)
var fillerRows = rows - offset;
for (var i = 0; i < fillerRows; i++)
{
_sidebar.AddChild(new TextureRect
{
Texture = _sidebarBottomTexture,
Texture = i != (fillerRows - 1) ? _sidebarMiddleTexture : _sidebarBottomTexture,
TextureScale = new Vector2(2, 2),
});
}
#endregion
BuildItemPieces();
FlagDirty();
}
public void BuildBackground()
@@ -240,70 +342,127 @@ public sealed class StorageContainer : BaseWindow
}
}
public void Reclaim(ItemStorageLocation location, ItemGridPiece draggingGhost)
{
draggingGhost.OnPiecePressed += OnPiecePressed;
draggingGhost.OnPieceUnpressed += OnPieceUnpressed;
_pieces[draggingGhost.Entity] = (location, draggingGhost);
draggingGhost.Location = location;
var controlIndex = GetGridIndex(draggingGhost);
_controlGrid[controlIndex].AddChild(draggingGhost);
}
private int GetGridIndex(ItemGridPiece piece)
{
return piece.Location.Position.X + piece.Location.Position.Y * _pieceGrid.Columns;
}
public void FlagDirty()
{
_isDirty = true;
}
public void RemoveGrid(ItemGridPiece control)
{
control.Orphan();
_pieces.Remove(control.Entity);
control.OnPiecePressed -= OnPiecePressed;
control.OnPieceUnpressed -= OnPieceUnpressed;
}
public void BuildItemPieces()
{
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp))
return;
if (!storageComp.Grid.Any())
if (storageComp.Grid.Count == 0)
return;
var boundingGrid = storageComp.Grid.GetBoundingBox();
var size = _emptyTexture!.Size * 2;
var containedEntities = storageComp.Container.ContainedEntities.Reverse().ToArray();
_contained.Clear();
_contained.AddRange(storageComp.Container.ContainedEntities.Reverse());
//todo. at some point, we may want to only rebuild the pieces that have actually received new data.
_pieceGrid.RemoveAllChildren();
_pieceGrid.Rows = boundingGrid.Height + 1;
_pieceGrid.Columns = boundingGrid.Width + 1;
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
// Build the grid representation
if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
{
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
_pieceGrid.Rows = boundingGrid.Height + 1;
_pieceGrid.Columns = boundingGrid.Width + 1;
_controlGrid.Clear();
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
{
var control = new Control
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
{
MinSize = size
};
var currentPosition = new Vector2i(x, y);
foreach (var (itemEnt, itemPos) in storageComp.StoredItems)
{
if (itemPos.Position != currentPosition)
continue;
if (_entity.TryGetComponent<ItemComponent>(itemEnt, out var itemEntComponent))
var control = new Control
{
ItemGridPiece gridPiece;
MinSize = size
};
if (_storageController.CurrentlyDragging?.Entity is { } dragging
&& dragging == itemEnt)
{
_storageController.CurrentlyDragging.Orphan();
gridPiece = _storageController.CurrentlyDragging;
}
else
{
gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), itemPos, _entity)
{
MinSize = size,
Marked = Array.IndexOf(containedEntities, itemEnt) switch
{
0 => ItemGridPieceMarks.First,
1 => ItemGridPieceMarks.Second,
_ => null,
}
};
gridPiece.OnPiecePressed += OnPiecePressed;
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
}
_controlGrid.Add(control);
_pieceGrid.AddChild(control);
}
}
}
control.AddChild(gridPiece);
}
_toRemove.Clear();
// Remove entities no longer relevant / Update existing ones
foreach (var (ent, data) in _pieces)
{
if (storageComp.StoredItems.TryGetValue(ent, out var updated))
{
if (data.Loc.Equals(updated))
{
DebugTools.Assert(data.Control.Location == updated);
continue;
}
_pieceGrid.AddChild(control);
// Update
data.Control.Location = updated;
var index = GetGridIndex(data.Control);
data.Control.Orphan();
_controlGrid[index].AddChild(data.Control);
_pieces[ent] = (updated, data.Control);
continue;
}
_toRemove.Add(ent);
}
foreach (var ent in _toRemove)
{
_pieces.Remove(ent, out var data);
data.Control.Orphan();
}
// Add new ones
foreach (var (ent, loc) in storageComp.StoredItems)
{
if (_pieces.TryGetValue(ent, out var existing))
{
DebugTools.Assert(existing.Loc == loc);
continue;
}
if (_entity.TryGetComponent<ItemComponent>(ent, out var itemEntComponent))
{
var gridPiece = new ItemGridPiece((ent, itemEntComponent), loc, _entity)
{
MinSize = size,
Marked = _contained.IndexOf(ent) switch
{
0 => ItemGridPieceMarks.First,
1 => ItemGridPieceMarks.Second,
_ => null,
}
};
gridPiece.OnPiecePressed += OnPiecePressed;
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
var controlIndex = loc.Position.X + loc.Position.Y * (boundingGrid.Width + 1);
_controlGrid[controlIndex].AddChild(gridPiece);
_pieces[ent] = (loc, gridPiece);
}
}
}
@@ -315,6 +474,35 @@ public sealed class StorageContainer : BaseWindow
if (!IsOpen)
return;
if (_isDirty)
{
_isDirty = false;
BuildItemPieces();
}
var containerSystem = _entity.System<SharedContainerSystem>();
if (_backButton != null)
{
if (StorageEntity != null && _entity.System<StorageSystem>().NestedStorage)
{
if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
_entity.HasComponent<StorageComponent>(container.Owner))
{
_backButton.Visible = true;
}
else
{
_backButton.Visible = false;
}
}
// Hide the button.
else
{
_backButton.Visible = false;
}
}
var itemSystem = _entity.System<ItemSystem>();
var storageSystem = _entity.System<StorageSystem>();
var handsSystem = _entity.System<HandsSystem>();
@@ -324,7 +512,7 @@ public sealed class StorageContainer : BaseWindow
child.ModulateSelfOverride = Color.FromHex("#222222");
}
if (UserInterfaceManager.CurrentlyHovered is StorageContainer con && con != this)
if (UserInterfaceManager.CurrentlyHovered is StorageWindow con && con != this)
return;
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
@@ -373,7 +561,7 @@ public sealed class StorageContainer : BaseWindow
continue;
float spot = 0;
var marked = new List<Control>();
var marked = new ValueList<Control>();
foreach (var location in locations.Value)
{
@@ -500,14 +688,4 @@ public sealed class StorageContainer : BaseWindow
}
}
}
public override void Close()
{
base.Close();
if (StorageEntity == null)
return;
_entity.System<StorageSystem>().CloseStorageWindow(StorageEntity.Value);
}
}

View File

@@ -2,6 +2,7 @@ using System.Numerics;
using Content.Client.Examine;
using Content.Client.Hands.Systems;
using Content.Client.Interaction;
using Content.Client.Storage;
using Content.Client.Storage.Systems;
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
using Content.Client.UserInterface.Systems.Storage.Controls;
@@ -9,9 +10,9 @@ using Content.Client.Verbs.UI;
using Content.Shared.CCVar;
using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Storage;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
@@ -23,19 +24,23 @@ namespace Content.Client.UserInterface.Systems.Storage;
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
{
/*
* Things are a bit over the shop but essentially
* - Clicking into storagewindow is handled via storagewindow
* - Clicking out of it is via ItemGridPiece
* - Dragging around is handled here
* - Drawing is handled via ItemGridPiece
* - StorageSystem handles any sim stuff around open windows.
*/
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[UISystemDependency] private readonly StorageSystem _storage = default!;
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
private StorageContainer? _container;
private Vector2? _lastContainerPosition;
private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
public ItemGridPiece? DraggingGhost;
public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
public Angle DraggingRotation = Angle.Zero;
public bool StaticStorageUIEnabled;
public bool OpaqueStorageWindow;
@@ -43,6 +48,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
public bool IsDragging => _menuDragHelper.IsDragging;
public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
public bool WindowTitle { get; private set; } = false;
public StorageUIController()
{
_menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
@@ -52,106 +59,88 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
base.Initialize();
UIManager.OnScreenChanged += OnScreenChange;
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
_configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
}
private void OnScreenChange((UIScreen? Old, UIScreen? New) obj)
{
// Handle reconnects with hotbargui.
// Essentially HotbarGui / the screen gets loaded AFTER gamestates at the moment (because clientgameticker manually changes it via event)
// and changing this may be a massive change.
// So instead we'll just manually reload it for now.
if (!StaticStorageUIEnabled ||
obj.New == null ||
!EntityManager.TryGetComponent(_player.LocalEntity, out UserInterfaceUserComponent? userComp))
{
return;
}
// UISystemDependency not injected at this point so do it the old fashion way, I love ordering issues.
var uiSystem = EntityManager.System<SharedUserInterfaceSystem>();
foreach (var bui in uiSystem.GetActorUis((_player.LocalEntity.Value, userComp)))
{
if (!uiSystem.TryGetOpenUi<StorageBoundUserInterface>(bui.Entity, StorageComponent.StorageUiKey.Key, out var storageBui))
continue;
storageBui.ReOpen();
}
}
private void OnStorageWindowTitle(bool obj)
{
WindowTitle = obj;
}
private void OnOpaqueWindowChanged(bool obj)
{
OpaqueStorageWindow = obj;
}
private void OnStaticStorageChanged(bool obj)
{
StaticStorageUIEnabled = obj;
}
public StorageWindow CreateStorageWindow(EntityUid uid)
{
var window = new StorageWindow();
window.MouseFilter = Control.MouseFilterMode.Pass;
window.OnPiecePressed += (args, piece) =>
{
OnPiecePressed(args, window, piece);
};
window.OnPieceUnpressed += (args, piece) =>
{
OnPieceUnpressed(args, window, piece);
};
if (StaticStorageUIEnabled)
{
UIManager.GetActiveUIWidgetOrNull<HotbarGui>()?.StorageContainer.AddChild(window);
}
else
{
window.OpenCenteredLeft();
}
return window;
}
public void OnSystemLoaded(StorageSystem system)
{
_input.FirstChanceOnKeyEvent += OnMiddleMouse;
system.StorageUpdated += OnStorageUpdated;
system.StorageOrderChanged += OnStorageOrderChanged;
}
public void OnSystemUnloaded(StorageSystem system)
{
_input.FirstChanceOnKeyEvent -= OnMiddleMouse;
system.StorageUpdated -= OnStorageUpdated;
system.StorageOrderChanged -= OnStorageOrderChanged;
}
private void OnStorageOrderChanged(Entity<StorageComponent>? nullEnt)
{
if (_container == null)
return;
if (IsDragging)
_menuDragHelper.EndDrag();
_container.UpdateContainer(nullEnt);
if (nullEnt is not null)
{
// center it if we knock it off screen somehow.
if (!StaticStorageUIEnabled &&
(_lastContainerPosition == null ||
_lastContainerPosition.Value.X < 0 ||
_lastContainerPosition.Value.Y < 0 ||
_lastContainerPosition.Value.X > _ui.WindowRoot.Width ||
_lastContainerPosition.Value.Y > _ui.WindowRoot.Height))
{
_container.OpenCenteredAt(new Vector2(0.5f, 0.75f));
}
else
{
_container.Open();
var pos = !StaticStorageUIEnabled && _lastContainerPosition != null
? _lastContainerPosition.Value
: Vector2.Zero;
LayoutContainer.SetPosition(_container, pos);
}
if (StaticStorageUIEnabled)
{
// we have to orphan it here because Open() sets the parent.
_container.Orphan();
Hotbar?.StorageContainer.AddChild(_container);
}
_lastContainerPosition = _container.GlobalPosition;
}
else
{
_lastContainerPosition = _container.GlobalPosition;
_container.Close();
}
}
private void OnStaticStorageChanged(bool obj)
{
if (StaticStorageUIEnabled == obj)
return;
StaticStorageUIEnabled = obj;
_lastContainerPosition = null;
if (_container == null)
return;
if (!_container.IsOpen)
return;
_container.Orphan();
if (StaticStorageUIEnabled)
{
Hotbar?.StorageContainer.AddChild(_container);
}
else
{
_ui.WindowRoot.AddChild(_container);
}
if (_entity.TryGetComponent<StorageComponent>(_container.StorageEntity, out var comp))
OnStorageOrderChanged((_container.StorageEntity.Value, comp));
}
private void OnOpaqueWindowChanged(bool obj)
{
if (OpaqueStorageWindow == obj)
return;
OpaqueStorageWindow = obj;
_container?.BuildBackground();
}
/// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
@@ -190,7 +179,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
binding.Mod3 == Keyboard.Key.Control))
return;
if (!IsDragging && _entity.System<HandsSystem>().GetActiveHandEntity() == null)
if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
return;
//clamp it to a cardinal.
@@ -198,43 +187,18 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (DraggingGhost != null)
DraggingGhost.Location.Rotation = DraggingRotation;
if (IsDragging || (_container != null && UIManager.CurrentlyHovered == _container))
if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
keyEvent.Handle();
}
private void OnStorageUpdated(Entity<StorageComponent> uid)
private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{
if (_container?.StorageEntity != uid)
return;
_container.BuildItemPieces();
}
public void RegisterStorageContainer(StorageContainer container)
{
if (_container != null)
{
container.OnPiecePressed -= OnPiecePressed;
container.OnPieceUnpressed -= OnPieceUnpressed;
}
_container = container;
container.OnPiecePressed += OnPiecePressed;
container.OnPieceUnpressed += OnPieceUnpressed;
if (!StaticStorageUIEnabled)
_container.Orphan();
}
private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
{
if (IsDragging || !_container?.IsOpen == true)
if (IsDragging || !window.IsOpen)
return;
if (args.Function == ContentKeyFunctions.MoveStoredItem)
{
DraggingRotation = control.Location.Rotation;
_menuDragHelper.MouseDown(control);
_menuDragHelper.Update(0f);
@@ -242,17 +206,17 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
}
else if (args.Function == ContentKeyFunctions.SaveItemLocation)
{
if (_container?.StorageEntity is not {} storage)
if (window.StorageEntity is not {} storage)
return;
_entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
_entity.GetNetEntity(control.Entity),
_entity.GetNetEntity(storage)));
EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
EntityManager.GetNetEntity(control.Entity),
EntityManager.GetNetEntity(storage)));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.ExamineEntity)
{
_entity.System<ExamineSystem>().DoExamine(control.Entity);
EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
args.Handle();
}
else if (args.Function == EngineKeyFunctions.UseSecondary)
@@ -262,62 +226,102 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
}
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{
_entity.RaisePredictiveEvent(
new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: false));
EntityManager.RaisePredictiveEvent(
new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
_entity.RaisePredictiveEvent(new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: true));
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: true));
args.Handle();
}
window.FlagDirty();
}
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
{
if (args.Function != ContentKeyFunctions.MoveStoredItem)
return;
if (_container?.StorageEntity is not { } storageEnt|| !_entity.TryGetComponent<StorageComponent>(storageEnt, out var storageComp))
return;
// Want to get the control under the dragged control.
// This means we can drag the original control around (and not hide the original).
control.MouseFilter = Control.MouseFilterMode.Ignore;
var targetControl = UIManager.MouseGetControl(args.PointerLocation);
var targetStorage = targetControl as StorageWindow;
control.MouseFilter = Control.MouseFilterMode.Pass;
if (DraggingGhost is { } draggingGhost)
var localPlayer = _player.LocalEntity;
window.RemoveGrid(control);
window.FlagDirty();
// If we tried to drag it on top of another grid piece then cancel out.
if (targetControl is ItemGridPiece || window.StorageEntity is not { } sourceStorage || localPlayer == null)
{
window.Reclaim(control.Location, control);
args.Handle();
_menuDragHelper.EndDrag();
return;
}
if (_menuDragHelper.IsDragging && DraggingGhost is { } draggingGhost)
{
var dragEnt = draggingGhost.Entity;
var dragLoc = draggingGhost.Location;
var itemSys = _entity.System<SharedItemSystem>();
var position = _container.GetMouseGridPieceLocation(dragEnt, dragLoc);
var itemBounding = itemSys.GetAdjustedItemShape(dragEnt, dragLoc).GetBoundingBox();
var gridBounding = storageComp.Grid.GetBoundingBox();
// The extended bounding box for if this is out of the window is the grid bounding box dimensions combined
// with the item shape bounding box dimensions. Plus 1 on the left for the sidebar. This makes it so that.
// dropping an item on the floor requires dragging it all the way out of the window.
var left = gridBounding.Left - itemBounding.Width - 1;
var bottom = gridBounding.Bottom - itemBounding.Height;
var top = gridBounding.Top;
var right = gridBounding.Right;
var lenientBounding = new Box2i(left, bottom, right, top);
if (lenientBounding.Contains(position))
// Dragging in the same storage
// The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
if (targetStorage == window)
{
_entity.RaisePredictiveEvent(new StorageSetItemLocationEvent(
_entity.GetNetEntity(draggingGhost.Entity),
_entity.GetNetEntity(storageEnt),
new ItemStorageLocation(DraggingRotation, position)));
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
var newLocation = new ItemStorageLocation(DraggingRotation, position);
EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
EntityManager.GetNetEntity(draggingGhost.Entity),
EntityManager.GetNetEntity(sourceStorage),
newLocation));
window.Reclaim(newLocation, control);
}
// Dragging to new storage
else if (targetStorage?.StorageEntity != null && targetStorage != window)
{
var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
var newLocation = new ItemStorageLocation(DraggingRotation, position);
// Check it fits and we can move to hand (no free transfers).
if (_storage.ItemFitsInGridLocation(
(dragEnt, null),
(targetStorage.StorageEntity.Value, null),
newLocation))
{
// Can drop and move.
EntityManager.RaisePredictiveEvent(new StorageTransferItemEvent(
EntityManager.GetNetEntity(dragEnt),
EntityManager.GetNetEntity(targetStorage.StorageEntity.Value),
newLocation));
targetStorage.Reclaim(newLocation, control);
DraggingRotation = Angle.Zero;
}
else
{
// Cancel it (rather than dropping).
window.Reclaim(dragLoc, control);
}
}
_menuDragHelper.EndDrag();
_container?.BuildItemPieces();
targetStorage?.FlagDirty();
}
else //if we just clicked, then take it out of the bag.
// If we just clicked, then take it out of the bag.
else
{
_menuDragHelper.EndDrag();
_entity.RaisePredictiveEvent(new StorageInteractWithItemEvent(
_entity.GetNetEntity(control.Entity),
_entity.GetNetEntity(storageEnt)));
EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
EntityManager.GetNetEntity(control.Entity),
EntityManager.GetNetEntity(sourceStorage)));
}
_menuDragHelper.EndDrag();
args.Handle();
}
@@ -326,14 +330,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (_menuDragHelper.Dragged is not { } dragged)
return false;
DraggingGhost!.Orphan();
DraggingRotation = dragged.Location.Rotation;
DraggingGhost = new ItemGridPiece(
(dragged.Entity, _entity.GetComponent<ItemComponent>(dragged.Entity)),
dragged.Location,
_entity);
DraggingGhost.MouseFilter = Control.MouseFilterMode.Ignore;
DraggingGhost.Visible = true;
DraggingGhost.Orphan();
UIManager.PopupRoot.AddChild(DraggingGhost);
SetDraggingRotation();
@@ -344,6 +342,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
if (DraggingGhost == null)
return false;
SetDraggingRotation();
return true;
}
@@ -356,7 +355,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
var offset = ItemGridPiece.GetCenterOffset(
(DraggingGhost.Entity, null),
new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
_entity);
EntityManager);
// I don't know why it divides the position by 2. Hope this helps! -emo
LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
@@ -366,18 +365,13 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
{
if (DraggingGhost == null)
return;
DraggingGhost.Visible = false;
DraggingGhost = null;
DraggingRotation = Angle.Zero;
}
public override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
_menuDragHelper.Update(args.DeltaSeconds);
if (!StaticStorageUIEnabled && _container?.Parent != null && _lastContainerPosition != null)
_lastContainerPosition = _container.GlobalPosition;
}
}

View File

@@ -3,8 +3,10 @@ using Content.Client.UserInterface.Systems.Gameplay;
using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Systems.Viewport;
@@ -15,6 +17,7 @@ public sealed class ViewportUIController : UIController
[Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[UISystemDependency] private readonly SharedTransformSystem? _transformSystem = default!;
public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15);
public const int ViewportHeight = 15;
private MainViewport? Viewport => UIManager.ActiveScreen?.GetWidget<MainViewport>();
@@ -93,8 +96,11 @@ public sealed class ViewportUIController : UIController
_entMan.TryGetComponent(ent, out EyeComponent? eye);
if (eye?.Eye == _eyeManager.CurrentEye
&& _entMan.GetComponent<TransformComponent>(ent.Value).WorldPosition == default)
return; // nothing to worry about, the player is just in null space... actually that is probably a problem?
&& _entMan.GetComponent<TransformComponent>(ent.Value).MapID == MapId.Nullspace)
{
// nothing to worry about, the player is just in null space... actually that is probably a problem?
return;
}
// Currently, this shouldn't happen. This likely happened because the main eye was set to null. When this
// does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings:

View File

@@ -144,7 +144,7 @@ namespace Content.Client.Viewport
_inputManager.ViewportKeyEvent(this, args);
}
protected override void Draw(DrawingHandleScreen handle)
protected override void Draw(IRenderHandle handle)
{
EnsureViewportCreated();
@@ -170,7 +170,7 @@ namespace Content.Client.Viewport
var drawBox = GetDrawBox();
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
}

View File

@@ -1,15 +1,11 @@
using Content.Client.Ghost;
using Content.Shared.Voting;
namespace Content.Client.Voting;
public sealed class VotingSystem : EntitySystem
{
public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions
[Dependency] private readonly GhostSystem _ghostSystem = default!;
public override void Initialize()
{
base.Initialize();

View File

@@ -7,6 +7,7 @@ using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Prototypes;
using Content.Shared.Stacks;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
@@ -104,51 +105,34 @@ public sealed class CargoTest
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var testMap = await pair.CreateTestMap();
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
var protoManager = server.ProtoMan;
var compFact = server.ResolveDependency<IComponentFactory>();
await server.WaitAssertion(() =>
{
var mapId = testMap.MapId;
var grid = mapManager.CreateGridEntity(mapId);
var coord = new EntityCoordinates(grid.Owner, 0, 0);
var protoIds = protoManager.EnumeratePrototypes<EntityPrototype>()
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // Grids are not for sale.
.Select(p => p.ID)
.Where(p => p.Components.ContainsKey("StaticPrice"))
.ToList();
foreach (var proto in protoIds)
{
var ent = entManager.SpawnEntity(proto, coord);
// Sanity check
Assert.That(proto.TryGetComponent<StaticPriceComponent>(out var staticPriceComp, compFact), Is.True);
if (entManager.TryGetComponent<StackPriceComponent>(ent, out var stackpricecomp)
&& stackpricecomp.Price > 0)
if (proto.TryGetComponent<StackPriceComponent>(out var stackPriceComp, compFact) && stackPriceComp.Price > 0)
{
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
{
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
$"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
}
Assert.That(staticPriceComp.Price, Is.EqualTo(0),
$"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
}
if (entManager.HasComponent<StackComponent>(ent))
if (proto.HasComponent<StackComponent>(compFact))
{
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
{
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
$"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
}
Assert.That(staticPriceComp.Price, Is.EqualTo(0),
$"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
}
entManager.DeleteEntity(ent);
}
mapManager.DeleteMap(mapId);
});
await pair.CleanReturnAsync();

View File

@@ -39,7 +39,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill"))
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Select(p => p.ID)
.ToList();
@@ -102,7 +102,7 @@ namespace Content.IntegrationTests.Tests
.Where(p => !p.Abstract)
.Where(p => !pair.IsTestPrototype(p))
.Where(p => !p.Components.ContainsKey("MapGrid")) // This will smash stuff otherwise.
.Where(p => !p.Components.ContainsKey("RoomFill"))
.Where(p => !p.Components.ContainsKey("RoomFill")) // This comp can delete all entities, and spawn others
.Select(p => p.ID)
.ToList();
foreach (var protoId in protoIds)

View File

@@ -26,6 +26,7 @@ public sealed class LatheTest
var compFactory = server.ResolveDependency<IComponentFactory>();
var materialStorageSystem = server.System<SharedMaterialStorageSystem>();
var whitelistSystem = server.System<EntityWhitelistSystem>();
var latheSystem = server.System<SharedLatheSystem>();
await server.WaitAssertion(() =>
{
@@ -74,14 +75,14 @@ public sealed class LatheTest
}
}
// Collect all the recipes assigned to this lathe
var recipes = new List<ProtoId<LatheRecipePrototype>>();
recipes.AddRange(latheComp.StaticRecipes);
recipes.AddRange(latheComp.DynamicRecipes);
// Collect all possible recipes assigned to this lathe
var recipes = new HashSet<ProtoId<LatheRecipePrototype>>();
latheSystem.AddRecipesFromPacks(recipes, latheComp.StaticPacks);
latheSystem.AddRecipesFromPacks(recipes, latheComp.DynamicPacks);
if (latheProto.TryGetComponent<EmagLatheRecipesComponent>(out var emagRecipesComp, compFactory))
{
recipes.AddRange(emagRecipesComp.EmagStaticRecipes);
recipes.AddRange(emagRecipesComp.EmagDynamicRecipes);
latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagStaticPacks);
latheSystem.AddRecipesFromPacks(recipes, emagRecipesComp.EmagDynamicPacks);
}
// Check each recipe assigned to this lathe

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Tag;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -29,7 +30,9 @@ public sealed class StaticFieldValidationTest
Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos), Is.Empty);
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos), Is.Empty);
@@ -39,7 +42,9 @@ public sealed class StaticFieldValidationTest
Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos), Has.Count.EqualTo(1));
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos), Has.Count.EqualTo(2));
Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2));
@@ -88,24 +93,48 @@ public sealed class StaticFieldValidationTest
public static EntProtoId Tag = "StaticFieldTestEnt";
}
[Reflect(false)]
private sealed class EntProtoIdTValid
{
public static EntProtoId<TransformComponent> Tag = "StaticFieldTestEnt";
}
[Reflect(false)]
private sealed class EntProtoIdInvalid
{
public static EntProtoId Tag = string.Empty;
}
[Reflect(false)]
private sealed class EntProtoIdTInvalid
{
public static EntProtoId<TransformComponent> Tag = string.Empty;
}
[Reflect(false)]
private sealed class EntProtoIdArrayValid
{
public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
}
[Reflect(false)]
private sealed class EntProtoIdTArrayValid
{
public static EntProtoId<TransformComponent>[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"];
}
[Reflect(false)]
private sealed class EntProtoIdArrayInvalid
{
public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty];
}
[Reflect(false)]
private sealed class EntProtoIdTArrayInvalid
{
public static EntProtoId<TransformComponent>[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty];
}
[Reflect(false)]
private sealed class ProtoIdTestValid
{

View File

@@ -52,13 +52,16 @@ public sealed class ResearchTest
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.ResolveDependency<IEntityManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
var compFact = server.ResolveDependency<IComponentFactory>();
var latheSys = entMan.System<SharedLatheSystem>();
await server.WaitAssertion(() =>
{
var allEnts = protoManager.EnumeratePrototypes<EntityPrototype>();
var allLathes = new HashSet<LatheComponent>();
var latheTechs = new HashSet<ProtoId<LatheRecipePrototype>>();
foreach (var proto in allEnts)
{
if (proto.Abstract)
@@ -69,30 +72,31 @@ public sealed class ResearchTest
if (!proto.TryGetComponent<LatheComponent>(out var lathe, compFact))
continue;
allLathes.Add(lathe);
}
var latheTechs = new HashSet<string>();
foreach (var lathe in allLathes)
{
if (lathe.DynamicRecipes == null)
continue;
latheSys.AddRecipesFromPacks(latheTechs, lathe.DynamicPacks);
foreach (var recipe in lathe.DynamicRecipes)
{
latheTechs.Add(recipe);
}
if (proto.TryGetComponent<EmagLatheRecipesComponent>(out var emag, compFact))
latheSys.AddRecipesFromPacks(latheTechs, emag.EmagDynamicPacks);
}
Assert.Multiple(() =>
{
// check that every recipe a tech adds can be made on some lathe
var unlockedTechs = new HashSet<ProtoId<LatheRecipePrototype>>();
foreach (var tech in protoManager.EnumeratePrototypes<TechnologyPrototype>())
{
unlockedTechs.UnionWith(tech.RecipeUnlocks);
foreach (var recipe in tech.RecipeUnlocks)
{
Assert.That(latheTechs, Does.Contain(recipe), $"Recipe \"{recipe}\" cannot be unlocked on any lathes.");
Assert.That(latheTechs, Does.Contain(recipe), $"Recipe '{recipe}' from tech '{tech.ID}' cannot be unlocked on any lathes.");
}
}
// now check that every dynamic recipe a lathe lists can be unlocked
foreach (var recipe in latheTechs)
{
Assert.That(unlockedTechs, Does.Contain(recipe), $"Recipe '{recipe}' is dynamic on a lathe but cannot be unlocked by research.");
}
});
});

View File

@@ -39,12 +39,12 @@ namespace Content.IntegrationTests.Tests
{
var mapGrid = mapManager.CreateGridEntity(mapId);
xformSystem.SetWorldPosition(mapGrid, new Vector2(10, 10));
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag) 1, 255));
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 1, flags: 1, variant: 255));
}
{
var mapGrid = mapManager.CreateGridEntity(mapId);
xformSystem.SetWorldPosition(mapGrid, new Vector2(-8, -8));
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(2, (TileRenderFlag) 1, 254));
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 2, flags: 1, variant: 254));
}
Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath));
@@ -73,7 +73,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(10, 10)));
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(1, (TileRenderFlag) 1, 255)));
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 1, flags: 1, variant: 255)));
});
}
{
@@ -87,7 +87,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(-8, -8)));
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(2, (TileRenderFlag) 1, 254)));
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 2, flags: 1, variant: 254)));
});
}
});

View File

@@ -5,6 +5,7 @@ using Content.Server.Store.Systems;
using Content.Server.Traitor.Uplink;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.Store;
using Content.Shared.Store.Components;
using Content.Shared.StoreDiscount.Components;
@@ -64,6 +65,7 @@ public sealed class StoreTests
await server.WaitAssertion(() =>
{
var invSystem = entManager.System<InventorySystem>();
var mindSystem = entManager.System<SharedMindSystem>();
human = entManager.SpawnEntity("HumanUniformDummy", coordinates);
uniform = entManager.SpawnEntity("UniformDummy", coordinates);
@@ -72,6 +74,9 @@ public sealed class StoreTests
Assert.That(invSystem.TryEquip(human, uniform, "jumpsuit"));
Assert.That(invSystem.TryEquip(human, pda, "id"));
var mind = mindSystem.CreateMind(null);
mindSystem.TransferTo(mind, human, mind: mind);
FixedPoint2 originalBalance = 20;
uplinkSystem.AddUplink(human, originalBalance, null, true);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class LoadoutNames : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "entity_name",
table: "profile_role_loadout",
type: "character varying(256)",
maxLength: 256,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "entity_name",
table: "profile_role_loadout");
}
}
}

View File

@@ -975,6 +975,11 @@ namespace Content.Server.Database.Migrations.Postgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("EntityName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("entity_name");
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class LoadoutNames : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "entity_name",
table: "profile_role_loadout",
type: "TEXT",
maxLength: 256,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "entity_name",
table: "profile_role_loadout");
}
}
}

View File

@@ -921,6 +921,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("profile_role_loadout_id");
b.Property<string>("EntityName")
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasColumnName("entity_name");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");

View File

@@ -480,6 +480,12 @@ namespace Content.Server.Database
/// </summary>
public string RoleName { get; set; } = string.Empty;
/// <summary>
/// Custom name of the role loadout if it supports it.
/// </summary>
[MaxLength(256)]
public string? EntityName { get; set; }
/// <summary>
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
/// </summary>

View File

@@ -118,8 +118,9 @@ namespace Content.Server.Administration.Commands
}
var xform = _entManager.GetComponent<TransformComponent>(playerEntity);
var xformSystem = _entManager.System<SharedTransformSystem>();
xform.Coordinates = coords;
xform.AttachToGridOrMap();
xformSystem.AttachToGridOrMap(playerEntity, xform);
if (_entManager.TryGetComponent(playerEntity, out PhysicsComponent? physics))
{
_entManager.System<SharedPhysicsSystem>().SetLinearVelocity(playerEntity, Vector2.Zero, body: physics);

View File

@@ -11,6 +11,7 @@ using Content.Server.StationRecords.Systems;
using Content.Shared.Administration;
using Content.Shared.Administration.Events;
using Content.Shared.CCVar;
using Content.Shared.Forensics.Components;
using Content.Shared.GameTicking;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;

View File

@@ -137,7 +137,7 @@ public sealed partial class AdminVerbSystem
var board = Spawn("ChessBoard", xform.Coordinates);
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
xform.Coordinates = EntityCoordinates.FromMap(_mapManager, session.Position);
xform.WorldRotation = Angle.Zero;
_transformSystem.SetWorldRotationNoLerp((args.Target, xform), Angle.Zero);
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", chessName, Loc.GetString("admin-smite-chess-dimension-description"))
@@ -892,5 +892,36 @@ public sealed partial class AdminVerbSystem
Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description"))
};
args.Verbs.Add(superslip);
var omniaccentName = Loc.GetString("admin-smite-omni-accent-name").ToLowerInvariant();
Verb omniaccent = new()
{
Text = omniaccentName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Interface/Actions/voice-mask.rsi"), "icon"),
Act = () =>
{
EnsureComp<BarkAccentComponent>(args.Target);
EnsureComp<BleatingAccentComponent>(args.Target);
EnsureComp<FrenchAccentComponent>(args.Target);
EnsureComp<GermanAccentComponent>(args.Target);
EnsureComp<LizardAccentComponent>(args.Target);
EnsureComp<MobsterAccentComponent>(args.Target);
EnsureComp<MothAccentComponent>(args.Target);
EnsureComp<OwOAccentComponent>(args.Target);
EnsureComp<SkeletonAccentComponent>(args.Target);
EnsureComp<SouthernAccentComponent>(args.Target);
EnsureComp<SpanishAccentComponent>(args.Target);
EnsureComp<StutteringAccentComponent>(args.Target);
if (_random.Next(0, 8) == 0)
{
EnsureComp<BackwardsAccentComponent>(args.Target); // was asked to make this at a low chance idk
}
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", omniaccentName, Loc.GetString("admin-smite-omni-accent-description"))
};
args.Verbs.Add(omniaccent);
}
}

View File

@@ -370,7 +370,7 @@ namespace Content.Server.Administration.Systems
}
if (lawBoundComponent != null && target != null)
if (lawBoundComponent != null && target != null && _adminManager.HasAdminFlag(player, AdminFlags.Moderator))
{
args.Verbs.Add(new Verb()
{

View File

@@ -1,38 +1,79 @@
using Content.Server.Administration;
using Content.Server.Chat;
using Content.Server.Chat.Systems;
using Content.Shared.Administration;
using Robust.Shared.Audio;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
namespace Content.Server.Announcements
namespace Content.Server.Announcements;
[AdminCommand(AdminFlags.Moderator)]
public sealed class AnnounceCommand : LocalizedEntityCommands
{
[AdminCommand(AdminFlags.Moderator)]
public sealed class AnnounceCommand : IConsoleCommand
{
public string Command => "announce";
public string Description => "Send an in-game announcement.";
public string Help => $"{Command} <sender> <message> or {Command} <message> to send announcement as CentCom.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var chat = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceManager _res = default!;
if (args.Length == 0)
public override string Command => "announce";
public override string Description => Loc.GetString("cmd-announce-desc");
public override string Help => Loc.GetString("cmd-announce-help", ("command", Command));
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
switch (args.Length)
{
case 0:
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
return;
case > 4:
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
return;
}
var message = args[0];
var sender = Loc.GetString("cmd-announce-sender");
var color = Color.Gold;
var sound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg");
// Optional sender argument
if (args.Length >= 2)
sender = args[1];
// Optional color argument
if (args.Length >= 3)
{
try
{
shell.WriteError("Not enough arguments! Need at least 1.");
color = Color.FromHex(args[2]);
}
catch
{
shell.WriteError(Loc.GetString("shell-invalid-color-hex"));
return;
}
if (args.Length == 1)
{
chat.DispatchGlobalAnnouncement(args[0], colorOverride: Color.Gold);
}
else
{
// Explicit IEnumerable<string> due to overload ambiguity on .NET 9
var message = string.Join(' ', (IEnumerable<string>)new ArraySegment<string>(args, 1, args.Length-1));
chat.DispatchGlobalAnnouncement(message, args[0], colorOverride: Color.Gold);
}
shell.WriteLine("Sent!");
}
// Optional sound argument
if (args.Length >= 4)
sound = new SoundPathSpecifier(args[3]);
_chat.DispatchGlobalAnnouncement(message, sender, true, sound, color);
shell.WriteLine(Loc.GetString("shell-command-success"));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return args.Length switch
{
1 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-message")),
2 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-sender")),
3 => CompletionResult.FromHint(Loc.GetString("cmd-announce-arg-color")),
4 => CompletionResult.FromHintOptions(
CompletionHelper.AudioFilePath(args[3], _proto, _res),
Loc.GetString("cmd-announce-arg-sound")
),
_ => CompletionResult.Empty
};
}
}

View File

@@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
using Content.Server.Jittering;
using Content.Server.Mind;
using Content.Server.Stunnable;
using Content.Shared.Actions;
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects;
@@ -24,8 +23,6 @@ public sealed class InnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
{
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly AnomalySystem _anomaly = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly IChatManager _chat = default!;

View File

@@ -10,10 +10,8 @@ using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Pinpointer;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -25,11 +23,9 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly NavMapSystem _navMapSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
private const float UpdateTime = 1.0f;
@@ -54,7 +50,7 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged);
}
#region Event handling
#region Event handling
private void OnConsoleInit(EntityUid uid, AtmosAlertsComputerComponent component, ComponentInit args)
{

View File

@@ -61,9 +61,9 @@ public sealed partial class AtmosphereSystem
mixtures[5].Temperature = 5000f;
// 6: (Walk-In) Freezer
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
mixtures[6].Temperature = 235f; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesFreezer);
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesFreezer);
mixtures[6].Temperature = Atmospherics.FreezerTemp; // 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);

View File

@@ -118,7 +118,7 @@ namespace Content.Server.Atmos.EntitySystems
return;
// Used by ExperiencePressureDifference to correct push/throw directions from tile-relative to physics world.
var gridWorldRotation = xforms.GetComponent(gridAtmosphere).WorldRotation;
var gridWorldRotation = _transformSystem.GetWorldRotation(gridAtmosphere);
// If we're using monstermos, smooth out the yeet direction to follow the flow
if (MonstermosEqualization)

View File

@@ -178,7 +178,7 @@ namespace Content.Server.Atmos.EntitySystems
if (tile.Hotspot.Bypassing)
{
tile.Hotspot.Volume = tile.Air.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
tile.Hotspot.Volume = tile.Air.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate;
tile.Hotspot.Temperature = tile.Air.Temperature;
}
else
@@ -187,7 +187,7 @@ namespace Content.Server.Atmos.EntitySystems
affected.Temperature = tile.Hotspot.Temperature;
React(affected, tile);
tile.Hotspot.Temperature = affected.Temperature;
tile.Hotspot.Volume = affected.ReactionResults[GasReaction.Fire] * Atmospherics.FireGrowthRate;
tile.Hotspot.Volume = affected.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate;
Merge(tile.Air, affected);
}

View File

@@ -9,10 +9,12 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Database;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Systems;
@@ -37,6 +39,7 @@ namespace Content.Server.Atmos.Monitor.Systems;
public sealed class AirAlarmSystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _access = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
@@ -296,6 +299,7 @@ public sealed class AirAlarmSystem : EntitySystem
addr = netConn.Address;
}
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} mode to {args.Mode}");
SetMode(uid, addr, args.Mode, false);
}
else
@@ -307,15 +311,26 @@ public sealed class AirAlarmSystem : EntitySystem
private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args)
{
component.AutoMode = args.Enabled;
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} auto mode to {args.Enabled}");
UpdateUI(uid, component);
}
private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args)
{
if (AccessCheck(uid, args.Actor, component))
{
if (args.Gas != null)
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Gas} {args.Type} threshold using {ToPrettyString(uid)}");
else
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Type} threshold using {ToPrettyString(uid)}");
SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas);
}
else
{
UpdateUI(uid, component);
}
}
private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args)
@@ -323,6 +338,8 @@ public sealed class AirAlarmSystem : EntitySystem
if (AccessCheck(uid, args.Actor, component)
&& _deviceList.ExistsInDeviceList(uid, args.Address))
{
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} settings using {ToPrettyString(uid)}");
SetDeviceData(uid, args.Address, args.Data);
}
else
@@ -344,6 +361,7 @@ public sealed class AirAlarmSystem : EntitySystem
case GasVentPumpData ventData:
foreach (string addr in component.VentData.Keys)
{
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to vent {addr}");
SetData(uid, addr, args.Data);
}
break;
@@ -351,6 +369,7 @@ public sealed class AirAlarmSystem : EntitySystem
case GasVentScrubberData scrubberData:
foreach (string addr in component.ScrubberData.Keys)
{
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to scrubber {addr}");
SetData(uid, addr, args.Data);
}
break;
@@ -379,6 +398,7 @@ public sealed class AirAlarmSystem : EntitySystem
if (!_access.IsAllowed(user.Value, uid, reader))
{
_popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value);
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Low, $"{ToPrettyString(user)} attempted to access {ToPrettyString(uid)} without access");
return false;
}

View File

@@ -9,9 +9,11 @@ using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tag;
@@ -25,6 +27,7 @@ namespace Content.Server.Atmos.Monitor.Systems;
// a danger), and atmos (which triggers based on set thresholds).
public sealed class AtmosMonitorSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
@@ -393,21 +396,74 @@ public sealed class AtmosMonitorSystem : EntitySystem
if (!Resolve(uid, ref monitor))
return;
// Used for logging after the switch statement
string logPrefix = "";
string logValueSuffix = "";
AtmosAlarmThreshold? logPreviousThreshold = null;
switch (type)
{
case AtmosMonitorThresholdType.Pressure:
logPrefix = "pressure";
logValueSuffix = "kPa";
logPreviousThreshold = monitor.PressureThreshold;
monitor.PressureThreshold = threshold;
break;
case AtmosMonitorThresholdType.Temperature:
logPrefix = "temperature";
logValueSuffix = "K";
logPreviousThreshold = monitor.TemperatureThreshold;
monitor.TemperatureThreshold = threshold;
break;
case AtmosMonitorThresholdType.Gas:
if (gas == null || monitor.GasThresholds == null)
return;
logPrefix = ((Gas) gas).ToString();
logValueSuffix = "kPa";
monitor.GasThresholds.TryGetValue((Gas) gas, out logPreviousThreshold);
monitor.GasThresholds[(Gas) gas] = threshold;
break;
}
// Admin log each change separately rather than logging the whole state
if (logPreviousThreshold != null)
{
if (threshold.Ignore != logPreviousThreshold.Ignore)
{
string enabled = threshold.Ignore ? "disabled" : "enabled";
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(uid)} {logPrefix} thresholds {enabled}"
);
}
foreach (var change in threshold.GetChanges(logPreviousThreshold))
{
if (change.Current.Enabled != change.Previous?.Enabled)
{
string enabled = change.Current.Enabled ? "enabled" : "disabled";
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(uid)} {logPrefix} {change.Type} {enabled}"
);
}
if (change.Current.Value != change.Previous?.Value)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(uid)} {logPrefix} {change.Type} changed from {change.Previous?.Value} {logValueSuffix} to {change.Current.Value} {logValueSuffix}"
);
}
}
}
}
/// <summary>

View File

@@ -11,8 +11,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
[RegisterComponent]
public sealed partial class GasVentPumpComponent : Component
{
/// <summary>
/// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered.
/// By default, all air vents start enabled, whether linked to an alarm or not.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = false;
public bool Enabled { get; set; } = true;
[ViewVariables]
public bool IsDirty { get; set; } = false;

View File

@@ -9,8 +9,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components
[Access(typeof(GasVentScrubberSystem))]
public sealed partial class GasVentScrubberComponent : Component
{
/// <summary>
/// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered.
/// By default, all air scrubbers start enabled, whether linked to an alarm or not.
/// </summary>
[DataField]
public bool Enabled { get; set; } = false;
public bool Enabled { get; set; } = true;
[DataField]
public bool IsDirty { get; set; } = false;

View File

@@ -9,6 +9,9 @@ using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
@@ -16,6 +19,7 @@ using Content.Shared.Atmos.Piping.Unary;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
@@ -30,6 +34,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[UsedImplicitly]
public sealed class GasVentPumpSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
@@ -40,6 +45,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -63,9 +69,10 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
//Bingo waz here
if (_weldable.IsWelded(uid))
{
return;
}
if (!_powerReceiverSystem.IsPowered(uid))
return;
var nodeName = vent.PumpDirection switch
{
@@ -207,7 +214,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
private void OnPowerChanged(EntityUid uid, GasVentPumpComponent component, ref PowerChangedEvent args)
{
component.Enabled = args.Powered;
UpdateState(uid, component);
}
@@ -232,6 +238,44 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentPumpData? setData))
break;
var previous = component.ToAirAlarmData();
if (previous.Enabled != setData.Enabled)
{
string enabled = setData.Enabled ? "enabled" : "disabled" ;
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
}
if (previous.PumpDirection != setData.PumpDirection)
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
if (previous.PressureChecks != setData.PressureChecks)
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure check changed to {setData.PressureChecks}");
if (previous.ExternalPressureBound != setData.ExternalPressureBound)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(uid)} external pressure bound changed from {previous.ExternalPressureBound} kPa to {setData.ExternalPressureBound} kPa"
);
}
if (previous.InternalPressureBound != setData.InternalPressureBound)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(uid)} internal pressure bound changed from {previous.InternalPressureBound} kPa to {setData.InternalPressureBound} kPa"
);
}
if (previous.PressureLockoutOverride != setData.PressureLockoutOverride)
{
string enabled = setData.PressureLockoutOverride ? "enabled" : "disabled" ;
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure lockout override {enabled}");
}
component.FromAirAlarmData(setData);
UpdateState(uid, component);
@@ -277,7 +321,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
_ambientSoundSystem.SetAmbience(uid, false);
_appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Welded, appearance);
}
else if (!vent.Enabled)
else if (!_powerReceiverSystem.IsPowered(uid) || !vent.Enabled)
{
_ambientSoundSystem.SetAmbience(uid, false);
_appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Off, appearance);

View File

@@ -10,12 +10,15 @@ using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Unary.Visuals;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.Power;
using Content.Shared.Tools.Systems;
@@ -27,6 +30,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[UsedImplicitly]
public sealed class GasVentScrubberSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
@@ -34,6 +38,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly WeldableSystem _weldable = default!;
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
public override void Initialize()
{
@@ -55,6 +60,9 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
var timeDelta = args.dt;
if (!_powerReceiverSystem.IsPowered(uid))
return;
if (!scrubber.Enabled || !_nodeContainer.TryGetNode(uid, scrubber.OutletName, out PipeNode? outlet))
return;
@@ -138,7 +146,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
private void OnPowerChanged(EntityUid uid, GasVentScrubberComponent component, ref PowerChangedEvent args)
{
component.Enabled = args.Powered;
UpdateState(uid, component);
}
@@ -163,6 +170,43 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentScrubberData? setData))
break;
var previous = component.ToAirAlarmData();
if (previous.Enabled != setData.Enabled)
{
string enabled = setData.Enabled ? "enabled" : "disabled" ;
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
}
// TODO: IgnoreAlarms?
if (previous.PumpDirection != setData.PumpDirection)
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
// TODO: This is iterating through both sets, it could probably be faster but they're both really small sets anyways
foreach (Gas gas in previous.FilterGases)
if (!setData.FilterGases.Contains(gas))
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering disabled");
foreach (Gas gas in setData.FilterGases)
if (!previous.FilterGases.Contains(gas))
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering enabled");
if (previous.VolumeRate != setData.VolumeRate)
{
_adminLogger.Add(
LogType.AtmosDeviceSetting,
LogImpact.Medium,
$"{ToPrettyString(uid)} volume rate changed from {previous.VolumeRate} L to {setData.VolumeRate} L"
);
}
if (previous.WideNet != setData.WideNet)
{
string enabled = setData.WideNet ? "enabled" : "disabled" ;
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} WideNet {enabled}");
}
component.FromAirAlarmData(setData);
UpdateState(uid, component);
@@ -185,7 +229,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
_ambientSoundSystem.SetAmbience(uid, false);
_appearance.SetData(uid, ScrubberVisuals.State, ScrubberState.Welded, appearance);
}
else if (!scrubber.Enabled)
else if (!_powerReceiverSystem.IsPowered(uid) || !scrubber.Enabled)
{
_ambientSoundSystem.SetAmbience(uid, false);
_appearance.SetData(uid, ScrubberVisuals.State, ScrubberState.Off, appearance);

View File

@@ -15,7 +15,7 @@ namespace Content.Server.Atmos.Reactions
var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
var temperature = mixture.Temperature;
var location = holder as TileAtmosphere;
mixture.ReactionResults[GasReaction.Fire] = 0;
mixture.ReactionResults[(byte)GasReaction.Fire] = 0;
// More plasma released at higher temperatures.
var temperatureScale = 0f;
@@ -60,7 +60,7 @@ namespace Content.Server.Atmos.Reactions
energyReleased += Atmospherics.FirePlasmaEnergyReleased * plasmaBurnRate;
energyReleased /= heatScale; // adjust energy to make sure speedup doesn't cause mega temperature rise
mixture.ReactionResults[GasReaction.Fire] += plasmaBurnRate * (1 + oxygenBurnRate);
mixture.ReactionResults[(byte)GasReaction.Fire] += plasmaBurnRate * (1 + oxygenBurnRate);
}
}
@@ -80,7 +80,7 @@ namespace Content.Server.Atmos.Reactions
}
}
return mixture.ReactionResults[GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
return mixture.ReactionResults[(byte)GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Content.Server.Atmos.Reactions
var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
var temperature = mixture.Temperature;
var location = holder as TileAtmosphere;
mixture.ReactionResults[GasReaction.Fire] = 0f;
mixture.ReactionResults[(byte)GasReaction.Fire] = 0f;
var burnedFuel = 0f;
var initialTrit = mixture.GetMoles(Gas.Tritium);
@@ -45,7 +45,7 @@ namespace Content.Server.Atmos.Reactions
// Conservation of mass is important.
mixture.AdjustMoles(Gas.WaterVapor, burnedFuel);
mixture.ReactionResults[GasReaction.Fire] += burnedFuel;
mixture.ReactionResults[(byte)GasReaction.Fire] += burnedFuel;
}
energyReleased /= heatScale; // adjust energy to make sure speedup doesn't cause mega temperature rise
@@ -65,7 +65,7 @@ namespace Content.Server.Atmos.Reactions
}
}
return mixture.ReactionResults[GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
return mixture.ReactionResults[(byte)GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
}
}
}

View File

@@ -96,7 +96,7 @@ public sealed class BeamSystem : SharedBeamSystem
_physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics);
_physics.SetCanCollide(ent, true, manager: manager, body: physics);
_broadphase.RegenerateContacts(ent, physics, manager);
_broadphase.RegenerateContacts((ent, physics, manager));
var distanceLength = distanceCorrection.Length();

View File

@@ -13,6 +13,7 @@ using Content.Shared.Damage.Prototypes;
using Content.Shared.Drunk;
using Content.Shared.FixedPoint;
using Content.Shared.Forensics;
using Content.Shared.Forensics.Components;
using Content.Shared.HealthExaminable;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;

View File

@@ -7,8 +7,15 @@ namespace Content.Server.Botany.Components;
[RegisterComponent]
public sealed partial class PlantHolderComponent : Component
{
/// <summary>
/// Game time for the next plant reagent update.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate = TimeSpan.Zero;
/// <summary>
/// Time between plant reagent consumption updates.
/// </summary>
[DataField]
public TimeSpan UpdateDelay = TimeSpan.FromSeconds(3);
@@ -18,18 +25,31 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public int MissingGas;
/// <summary>
/// Time between plant growth updates.
/// </summary>
[DataField]
public TimeSpan CycleDelay = TimeSpan.FromSeconds(15f);
/// <summary>
/// Game time when the plant last did a growth update.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan LastCycle = TimeSpan.Zero;
/// <summary>
/// Sound played when any reagent is transferred into the plant holder.
/// </summary>
[DataField]
public SoundSpecifier? WateringSound;
[DataField]
public bool UpdateSpriteAfterUpdate;
/// <summary>
/// Set to true if the plant holder displays plant warnings (e.g. water low) in the sprite and
/// examine text. Used to differentiate hydroponic trays from simple soil plots.
/// </summary>
[DataField]
public bool DrawWarnings = false;
@@ -60,9 +80,16 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public bool Harvest;
/// <summary>
/// Set to true if this plant has been clipped by seed clippers. Used to prevent a single plant
/// from repeatedly being clipped.
/// </summary>
[DataField]
public bool Sampled;
/// <summary>
/// Multiplier for the number of entities produced at harvest.
/// </summary>
[DataField]
public int YieldMod = 1;
@@ -81,15 +108,28 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public SeedData? Seed;
/// <summary>
/// True if the plant is losing health due to too high/low temperature.
/// </summary>
[DataField]
public bool ImproperHeat;
/// <summary>
/// True if the plant is losing health due to too high/low pressure.
/// </summary>
[DataField]
public bool ImproperPressure;
/// <summary>
/// Not currently used.
/// </summary>
[DataField]
public bool ImproperLight;
/// <summary>
/// Set to true to force a plant update (visuals, component, etc.) regardless of the current
/// update cycle time. Typically used when some interaction affects this plant.
/// </summary>
[DataField]
public bool ForceUpdate;

View File

@@ -10,7 +10,6 @@ using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Diagnostics.CodeAnalysis;
@@ -25,10 +24,8 @@ public sealed partial class BotanySystem : EntitySystem
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPointLightSystem _light = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly FixtureSystem _fixtureSystem = default!;
[Dependency] private readonly RandomHelperSystem _randomHelper = default!;
public override void Initialize()
@@ -84,7 +81,7 @@ public sealed partial class BotanySystem : EntitySystem
if (!TryGetSeed(component, out var seed))
return;
using (args.PushGroup(nameof(SeedComponent)))
using (args.PushGroup(nameof(SeedComponent), 1))
{
var name = Loc.GetString(seed.DisplayName);
args.PushMarkup(Loc.GetString($"seed-component-description", ("seedName", name)));

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