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:
45
.github/workflows/publish-testing.yml
vendored
Normal file
45
.github/workflows/publish-testing.yml
vendored
Normal 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 }}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace Content.Client.GameTicking.Managers
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
|
||||
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
|
||||
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal file
11
Content.Client/ItemRecall/ItemRecallSystem.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
58
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
58
Content.Client/Light/AfterLightTargetOverlay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
36
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
36
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class RoofSystem : SharedRoofSystem
|
||||
{
|
||||
|
||||
}
|
||||
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
44
Content.Client/Light/LightBlurOverlay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
33
Content.Client/Light/LightCycleSystem.cs
Normal file
33
Content.Client/Light/LightCycleSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Content.Client/Light/RoofOverlay.cs
Normal file
100
Content.Client/Light/RoofOverlay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
90
Content.Client/Light/TileEmissionOverlay.cs
Normal file
90
Content.Client/Light/TileEmissionOverlay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -20,6 +20,8 @@ public sealed class NewsWriterBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<NewsWriterMenu>();
|
||||
|
||||
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed class CrewMonitoringBoundUserInterface : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
EntityUid? gridUid = null;
|
||||
var stationName = string.Empty;
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace Content.Client.Nuke
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<NukeMenu>();
|
||||
|
||||
_menu.OnKeypadButtonPressed += i =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -163,16 +163,6 @@ public sealed partial class DockingScreen : BoxContainer
|
||||
}
|
||||
|
||||
dockContainer.AddDock(dock, DockingControl);
|
||||
|
||||
dockContainer.ViewPressed += () =>
|
||||
{
|
||||
OnDockPress(dock);
|
||||
};
|
||||
|
||||
dockContainer.UndockPressed += () =>
|
||||
{
|
||||
UndockRequest?.Invoke(dock.Entity);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,6 +21,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new();
|
||||
|
||||
if (_type == SurveillanceCameraSetupUiKey.Router)
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
2117
Content.Server.Database/Migrations/Postgres/20250211131539_LoadoutNames.Designer.cs
generated
Normal file
2117
Content.Server.Database/Migrations/Postgres/20250211131539_LoadoutNames.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
2041
Content.Server.Database/Migrations/Sqlite/20250211131517_LoadoutNames.Designer.cs
generated
Normal file
2041
Content.Server.Database/Migrations/Sqlite/20250211131517_LoadoutNames.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user