Merge pull request #905 from crystallpunk-14/ed-16-02-2025-planet-light-upstream

Ed 16 02 2025 planet light upstream
This commit is contained in:
Ed
2025-02-17 17:35:23 +03:00
committed by GitHub
1264 changed files with 92694 additions and 399024 deletions

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

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

View File

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

View File

@@ -9,13 +9,14 @@ using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Doors.Components;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Benchmarks;
@@ -32,7 +33,6 @@ public class ComponentQueryBenchmark
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new(10);
private EntityQuery<ItemComponent> _itemQuery;
private EntityQuery<ClothingComponent> _clothingQuery;
private EntityQuery<MapComponent> _mapQuery;
@@ -54,10 +54,10 @@ public class ComponentQueryBenchmark
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
var map = new ResPath(Map);
var opts = DeserializationOptions.Default with {InitializeMaps = true};
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(map, out _, out _, opts))
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).GetAwaiter().GetResult();
_items = new EntityUid[_entMan.Count<ItemComponent>()];

View File

@@ -6,12 +6,13 @@ using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Maps;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Benchmarks;
@@ -20,7 +21,7 @@ public class MapLoadBenchmark
{
private TestPair _pair = default!;
private MapLoaderSystem _mapLoader = default!;
private IMapManager _mapManager = default!;
private SharedMapSystem _mapSys = default!;
[GlobalSetup]
public void Setup()
@@ -36,7 +37,7 @@ public class MapLoadBenchmark
.ToDictionary(x => x.ID, x => x.MapPath.ToString());
_mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
_mapManager = server.ResolveDependency<IMapManager>();
_mapSys = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedMapSystem>();
}
[GlobalCleanup]
@@ -46,23 +47,25 @@ 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;
public Dictionary<string, string> Paths;
private MapId _mapId;
[Benchmark]
public async Task LoadMap()
{
var mapPath = Paths[Map];
var mapPath = new ResPath(Paths[Map]);
var server = _pair.Server;
await server.WaitPost(() =>
{
var success = _mapLoader.TryLoad(new MapId(10), mapPath, out _);
var success = _mapLoader.TryLoadMap(mapPath, out var map, out _);
if (!success)
throw new Exception("Map load failed");
_mapId = map.Value.Comp.MapId;
});
}
@@ -70,9 +73,7 @@ public class MapLoadBenchmark
public void IterationCleanup()
{
var server = _pair.Server;
server.WaitPost(() =>
{
_mapManager.DeleteMap(new MapId(10));
}).Wait();
server.WaitPost(() => _mapSys.DeleteMap(_mapId))
.Wait();
}
}

View File

@@ -7,13 +7,15 @@ using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Mind;
using Content.Server.Warps;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Benchmarks;
@@ -34,7 +36,6 @@ public class PvsBenchmark
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new(10);
private ICommonSession[] _players = default!;
private EntityCoordinates[] _spawns = default!;
public int _cycleOffset = 0;
@@ -65,10 +66,10 @@ public class PvsBenchmark
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
await _pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
var path = new ResPath(Map);
var opts = DeserializationOptions.Default with {InitializeMaps = true};
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(path, out _, out _, opts))
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
});
// Get list of ghost warp positions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,8 +47,7 @@
<PanelContainer Name="AlertsDivider" Visible="False" StyleClasses="LowDivider" />
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Horizontal"
HorizontalExpand="True" HorizontalAlignment="Center">
<BoxContainer Name="AlertsContainer" Visible="False" Margin="0 5" Orientation="Vertical" HorizontalAlignment="Center">
</BoxContainer>

View File

@@ -110,18 +110,29 @@ namespace Content.Client.HealthAnalyzer.UI
// Alerts
AlertsDivider.Visible = msg.Bleeding == true;
AlertsContainer.Visible = msg.Bleeding == true;
var showAlerts = msg.Unrevivable == true || msg.Bleeding == true;
AlertsDivider.Visible = showAlerts;
AlertsContainer.Visible = showAlerts;
if (showAlerts)
AlertsContainer.DisposeAllChildren();
if (msg.Unrevivable == true)
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
Margin = new Thickness(0, 4),
MaxWidth = 300
});
if (msg.Bleeding == true)
{
AlertsContainer.DisposeAllChildren();
AlertsContainer.AddChild(new Label
AlertsContainer.AddChild(new RichTextLabel
{
Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
FontColorOverride = Color.Red,
Margin = new Thickness(0, 4),
MaxWidth = 300
});
}
// Damage Groups

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,59 @@
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 lightScale = viewport.LightRenderTarget.Size / (Vector2) viewport.Size;
var newScale = viewport.RenderScale / (Vector2.One / lightScale);
var localMatrix =
viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale);
var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
var halfDiff = diff / 2;
// Pixels -> Metres -> Half distance.
// If we're zoomed in need to enlarge the bounds further.
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
() =>
{
// We essentially need to draw the cropped version onto the lightrendertarget.
var subRegion = new UIBox2i(halfDiff.X,
halfDiff.Y,
viewport.LightRenderTarget.Size.X + halfDiff.X,
viewport.LightRenderTarget.Size.Y + halfDiff.Y);
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
}, null);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,122 @@
using System.Numerics;
using Content.Shared.Light.Components;
using Content.Shared.Maps;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
namespace Content.Client.Light;
public sealed class RoofOverlay : Overlay
{
private readonly IEntityManager _entManager;
[Dependency] private readonly IMapManager _mapManager = default!;
[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();
private List<Entity<MapGridComponent>> _grids = 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 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;
_grids.Clear();
_mapManager.FindGridsIntersecting(args.MapId, bounds, ref _grids);
for (var i = _grids.Count - 1; i >= 0; i--)
{
var grid = _grids[i];
if (_entManager.HasComponent<RoofComponent>(grid.Owner))
continue;
_grids.RemoveAt(i);
}
if (_grids.Count == 0)
return;
var lightScale = viewport.LightRenderTarget.Size / (Vector2) viewport.Size;
var scale = viewport.RenderScale / (Vector2.One / lightScale);
worldHandle.RenderInRenderTarget(target,
() =>
{
foreach (var grid in _grids)
{
if (!_entManager.TryGetComponent(grid.Owner, out RoofComponent? roof))
continue;
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
worldHandle.SetTransform(matty);
var tileEnumerator = _mapSystem.GetTilesEnumerator(grid.Owner, 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(grid.Owner, 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.Comp.TileSize);
worldHandle.DrawRect(local, roof.Color);
}
}
}, null);
worldHandle.SetTransform(Matrix3x2.Identity);
}
}

View File

@@ -0,0 +1,96 @@
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;
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true);
if (_grids.Count == 0)
return;
var lightScale = viewport.LightRenderTarget.Size / (Vector2) viewport.Size;
var scale = viewport.RenderScale / (Vector2.One / lightScale);
args.WorldHandle.RenderInRenderTarget(target,
() =>
{
var invMatrix = target.GetWorldToLocalMatrix(viewport.Eye, scale);
foreach (var grid in _grids)
{
var gridInvMatrix = _xformSystem.GetInvWorldMatrix(grid);
var localBounds = gridInvMatrix.TransformBox(bounds);
_entities.Clear();
_lookup.GetLocalEntitiesIntersecting(grid.Owner, localBounds, _entities);
if (_entities.Count == 0)
continue;
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
foreach (var ent in _entities)
{
var xform = _xformQuery.Comp(ent);
var tile = _mapSystem.LocalToTile(grid.Owner, grid, xform.Coordinates);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
worldHandle.SetTransform(matty);
// Yes I am fully aware this leads to overlap. If you really want to have alpha then you'll need
// to turn the squares into polys.
// Additionally no shadows so if you make it too big it's going to go through a 1x wall.
var local = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(ent.Comp.Range);
worldHandle.DrawRect(local, ent.Comp.Color);
}
}
}, null);
}
}

View File

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

View File

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

View File

@@ -5,17 +5,15 @@
SetSize="800 800"
MinSize="800 128">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<!--
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
<PanelContainer HorizontalExpand="True" SetHeight="24">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/>
<LineEdit Name="RoleNameEdit" VerticalExpand="True" HorizontalExpand="True"/>
</PanelContainer>
</BoxContainer>
-->
<VerticalTabContainer Name="LoadoutGroupsContainer"
VerticalExpand="True"
HorizontalExpand="True">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
using Content.Shared._CP14.Roof;
using Content.Shared.Ghost;
using Content.Shared.Light.EntitySystems;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Console;
@@ -11,10 +12,12 @@ public sealed class CP14RoofSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedRoofSystem _roof = default!;
private bool _roofVisible = true;
public bool DisabledByCommand = false;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<GhostComponent> _ghostQuery;
private EntityQuery<TransformComponent> _xformQuery;
@@ -34,8 +37,11 @@ public sealed class CP14RoofSystem : EntitySystem
_ghostQuery = GetEntityQuery<GhostComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
SubscribeLocalEvent<CP14RoofComponent, ComponentStartup>(RoofStartup);
SubscribeLocalEvent<CP14RoofComponent, ComponentRemove>(RoofRemove);
SubscribeLocalEvent<GhostComponent, CP14ToggleRoofVisibilityAction>(OnToggleRoof);
}
@@ -93,6 +99,25 @@ public sealed class CP14RoofSystem : EntitySystem
return;
UpdateVisibility(ent, sprite);
//var xform = Transform(ent);
//
//if (_gridQuery.TryComp(xform.GridUid, out var grid))
//{
// var index = _map.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
// _roof.SetRoof((xform.GridUid.Value, grid, null), index, true);
//}
}
private void RoofRemove(Entity<CP14RoofComponent> ent, ref ComponentRemove args)
{
//var xform = Transform(ent);
//
//if (_gridQuery.TryComp(xform.GridUid, out var grid))
//{
// var index = _map.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
// _roof.SetRoof((xform.GridUid.Value, grid, null), index, false);
//}
}
private void UpdateVisibility(Entity<CP14RoofComponent> ent, SpriteComponent sprite)

View File

@@ -11,6 +11,8 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using System.Linq;
using System.Numerics;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests.Body
{
@@ -57,7 +59,6 @@ namespace Content.IntegrationTests.Tests.Body
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var mapLoader = entityManager.System<MapLoaderSystem>();
var mapSys = entityManager.System<SharedMapSystem>();
@@ -69,17 +70,13 @@ namespace Content.IntegrationTests.Tests.Body
GridAtmosphereComponent relevantAtmos = default;
var startingMoles = 0.0f;
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
await server.WaitPost(() =>
{
mapSys.CreateMap(out var mapId);
Assert.That(mapLoader.TryLoad(mapId, testMapName, out var roots));
var query = entityManager.GetEntityQuery<MapGridComponent>();
var grids = roots.Where(x => query.HasComponent(x));
Assert.That(grids, Is.Not.Empty);
grid = grids.First();
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
grid = gridEnt!.Value.Owner;
});
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");
@@ -148,18 +145,13 @@ namespace Content.IntegrationTests.Tests.Body
RespiratorComponent respirator = null;
EntityUid human = default;
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml");
await server.WaitPost(() =>
{
mapSys.CreateMap(out var mapId);
Assert.That(mapLoader.TryLoad(mapId, testMapName, out var ents), Is.True);
var query = entityManager.GetEntityQuery<MapGridComponent>();
grid = ents
.Select<EntityUid, EntityUid?>(x => x)
.FirstOrDefault((uid) => uid.HasValue && query.HasComponent(uid.Value), null);
Assert.That(grid, Is.Not.Null);
Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt));
grid = gridEnt!.Value.Owner;
});
Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found.");

View File

@@ -2,10 +2,11 @@
using System.Linq;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests.Body;
@@ -111,13 +112,12 @@ public sealed class SaveLoadReparentTest
Is.Not.Empty
);
const string mapPath = $"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml";
var mapPath = new ResPath($"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml");
mapLoader.SaveMap(mapId, mapPath);
maps.DeleteMap(mapId);
Assert.That(mapLoader.TrySaveMap(mapId, mapPath));
mapSys.DeleteMap(mapId);
mapSys.CreateMap(out mapId);
Assert.That(mapLoader.TryLoad(mapId, mapPath, out _), Is.True);
Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _), Is.True);
var query = EnumerateQueryEnumerator(
entities.EntityQueryEnumerator<BodyComponent>()
@@ -173,7 +173,7 @@ public sealed class SaveLoadReparentTest
});
}
maps.DeleteMap(mapId);
entities.DeleteEntity(map);
}
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
#nullable enable
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.Minds;
@@ -37,8 +38,8 @@ public sealed partial class MindTests
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
// Create a new map.
int mapId = 1;
await pair.Server.WaitPost(() => conHost.ExecuteCommand($"addmap {mapId}"));
MapId mapId = default;
await pair.Server.WaitPost(() => pair.Server.System<SharedMapSystem>().CreateMap(out mapId));
await pair.RunTicksSync(5);
// Client is not attached to anything
@@ -54,7 +55,7 @@ public sealed partial class MindTests
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value);
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
Assert.That(xform.MapID, Is.EqualTo(mapId));
await pair.CleanReturnAsync();
}

View File

@@ -9,7 +9,6 @@ using Content.Server.Spawners.Components;
using Content.Server.Station.Components;
using Content.Shared.CCVar;
using Content.Shared.Roles;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -17,6 +16,9 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Content.Shared.Station.Components;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -30,25 +32,30 @@ namespace Content.IntegrationTests.Tests
private static readonly string[] NoSpawnMaps =
{
"CentComm",
"Dart"
//CrystallEdge Map replacement
//CrystallEdge Map replacement end
};
private static readonly string[] Grids =
{
"/Maps/centcomm.yml",
"/Maps/Shuttles/cargo.yml",
"/Maps/Shuttles/emergency.yml",
"/Maps/Shuttles/infiltrator.yml",
//CrystallEdge Map replacement
//CrystallEdge Map replacement end
};
private static readonly string[] DoNotMapWhitelist =
{
//CrystallEdge Map replacement
"/Maps/_CP14/dev_map.yml",
//CrystallEdge Map replacement end
};
private static readonly string[] GameMaps =
{//CrystallEdge Map replacement
{
//CrystallEdge Map replacement
"Dev",
"CentComm",
"MeteorArena",
//CrystallEdge maps
"Comoss",
"Factoria",
//CrystallEdge Map replacement end
@@ -66,33 +73,72 @@ namespace Content.IntegrationTests.Tests
var entManager = server.ResolveDependency<IEntityManager>();
var mapLoader = entManager.System<MapLoaderSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapManager = server.ResolveDependency<IMapManager>();
var cfg = server.ResolveDependency<IConfigurationManager>();
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
var path = new ResPath(mapFile);
await server.WaitPost(() =>
{
mapSystem.CreateMap(out var mapId);
try
{
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoad(mapId, mapFile, out var roots));
Assert.That(roots.Where(uid => entManager.HasComponent<MapGridComponent>(uid)), Is.Not.Empty);
#pragma warning restore NUnit2045
Assert.That(mapLoader.TryLoadGrid(mapId, path, out var grid));
}
catch (Exception ex)
{
throw new Exception($"Failed to load map {mapFile}, was it saved as a map instead of a grid?", ex);
}
try
mapSystem.DeleteMap(mapId);
});
await server.WaitRunTicks(1);
await pair.CleanReturnAsync();
}
/// <summary>
/// Asserts that shuttles are loadable and have been saved as grids and not maps.
/// </summary>
[Test]
public async Task ShuttlesLoadableTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entManager = server.ResolveDependency<IEntityManager>();
var resMan = server.ResolveDependency<IResourceManager>();
var mapLoader = entManager.System<MapLoaderSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var cfg = server.ResolveDependency<IConfigurationManager>();
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
var shuttleFolder = new ResPath("/Maps/Shuttles");
var shuttles = resMan
.ContentFindFiles(shuttleFolder)
.Where(filePath =>
filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
.ToArray();
await server.WaitPost(() =>
{
Assert.Multiple(() =>
{
mapManager.DeleteMap(mapId);
}
catch (Exception ex)
{
throw new Exception($"Failed to delete map {mapFile}", ex);
}
foreach (var path in shuttles)
{
mapSystem.CreateMap(out var mapId);
try
{
Assert.That(mapLoader.TryLoadGrid(mapId, path, out _),
$"Failed to load shuttle {path}, was it saved as a map instead of a grid?");
}
catch (Exception ex)
{
throw new Exception($"Failed to load shuttle {path}, was it saved as a map instead of a grid?",
ex);
}
mapSystem.DeleteMap(mapId);
}
});
});
await server.WaitRunTicks(1);
@@ -106,12 +152,16 @@ namespace Content.IntegrationTests.Tests
var server = pair.Server;
var resourceManager = server.ResolveDependency<IResourceManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
var loader = server.System<MapLoaderSystem>();
var mapFolder = new ResPath("/Maps");
var maps = resourceManager
.ContentFindFiles(mapFolder)
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
.ToArray();
var v7Maps = new List<ResPath>();
foreach (var map in maps)
{
var rootedPath = map.ToRootedPath();
@@ -134,13 +184,101 @@ namespace Content.IntegrationTests.Tests
var root = yamlStream.Documents[0].RootNode;
var meta = root["meta"];
var postMapInit = meta["postmapinit"].AsBool();
var version = meta["format"].AsInt();
// TODO MAP TESTS
// Move this to some separate test?
CheckDoNotMap(map, root, protoManager);
if (version >= 7)
{
v7Maps.Add(map);
continue;
}
var postMapInit = meta["postmapinit"].AsBool();
Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit");
}
var deps = server.ResolveDependency<IEntitySystemManager>().DependencyCollection;
foreach (var map in v7Maps)
{
Assert.That(IsPreInit(map, loader, deps));
}
// Check that the test actually does manage to catch post-init maps and isn't just blindly passing everything.
// To that end, create a new post-init map and try verify it.
var mapSys = server.System<SharedMapSystem>();
MapId id = default;
await server.WaitPost(() => mapSys.CreateMap(out id, runMapInit: false));
await server.WaitPost(() => server.EntMan.Spawn(null, new MapCoordinates(0, 0, id)));
// First check that a pre-init version passes
var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml");
Assert.That(loader.TrySaveMap(id, path));
Assert.That(IsPreInit(path, loader, deps));
// and the post-init version fails.
await server.WaitPost(() => mapSys.InitializeMap(id));
Assert.That(loader.TrySaveMap(id, path));
Assert.That(IsPreInit(path, loader, deps), Is.False);
await pair.CleanReturnAsync();
}
/// <summary>
/// Check that maps do not have any entities that belong to the DoNotMap entity category
/// </summary>
private void CheckDoNotMap(ResPath map, YamlNode node, IPrototypeManager protoManager)
{
if (DoNotMapWhitelist.Contains(map.ToString()))
return;
var yamlEntities = node["entities"];
if (!protoManager.TryIndex<EntityCategoryPrototype>("DoNotMap", out var dnmCategory))
return;
Assert.Multiple(() =>
{
foreach (var yamlEntity in (YamlSequenceNode)yamlEntities)
{
var protoId = yamlEntity["proto"].AsString();
// This doesn't properly handle prototype migrations, but thats not a significant issue.
if (!protoManager.TryIndex(protoId, out var proto, false))
continue;
Assert.That(!proto.Categories.Contains(dnmCategory),
$"\nMap {map} contains entities in the DO NOT MAP category ({proto.Name})");
}
});
}
private bool IsPreInit(ResPath map, MapLoaderSystem loader, IDependencyCollection deps)
{
if (!loader.TryReadFile(map, out var data))
{
Assert.Fail($"Failed to read {map}");
return false;
}
var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default);
if (!reader.TryProcessData())
{
Assert.Fail($"Failed to process {map}");
return false;
}
foreach (var mapId in reader.MapYamlIds)
{
var mapData = reader.YamlEntities[mapId];
if (mapData.PostInit)
return false;
}
return true;
}
[Test, TestCaseSource(nameof(GameMaps))]
public async Task GameMapsLoadableTest(string mapProto)
{
@@ -157,16 +295,16 @@ namespace Content.IntegrationTests.Tests
var protoManager = server.ResolveDependency<IPrototypeManager>();
var ticker = entManager.EntitySysManager.GetEntitySystem<GameTicker>();
var shuttleSystem = entManager.EntitySysManager.GetEntitySystem<ShuttleSystem>();
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
var cfg = server.ResolveDependency<IConfigurationManager>();
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
await server.WaitPost(() =>
{
mapSystem.CreateMap(out var mapId);
MapId mapId;
try
{
ticker.LoadGameMap(protoManager.Index<GameMapPrototype>(mapProto), mapId, null);
var opts = DeserializationOptions.Default with {InitializeMaps = true};
ticker.LoadGameMap(protoManager.Index<GameMapPrototype>(mapProto), out mapId, opts);
}
catch (Exception ex)
{
@@ -204,20 +342,18 @@ namespace Content.IntegrationTests.Tests
if (entManager.TryGetComponent<StationEmergencyShuttleComponent>(station, out var stationEvac))
{
var shuttlePath = stationEvac.EmergencyShuttlePath;
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoad(shuttleMap, shuttlePath.ToString(), out var roots));
EntityUid shuttle = default!;
Assert.DoesNotThrow(() =>
{
shuttle = roots.First(uid => entManager.HasComponent<MapGridComponent>(uid));
}, $"Failed to load {shuttlePath}");
Assert.That(mapLoader.TryLoadGrid(shuttleMap, shuttlePath, out var shuttle),
$"Failed to load {shuttlePath}");
Assert.That(
shuttleSystem.TryFTLDock(shuttle,
entManager.GetComponent<ShuttleComponent>(shuttle), targetGrid.Value),
shuttleSystem.TryFTLDock(shuttle!.Value.Owner,
entManager.GetComponent<ShuttleComponent>(shuttle!.Value.Owner),
targetGrid.Value),
$"Unable to dock {shuttlePath} to {mapProto}");
#pragma warning restore NUnit2045
}
mapSystem.DeleteMap(shuttleMap);
mapManager.DeleteMap(shuttleMap);
*/ //CP14 Disable FTL test
if (entManager.HasComponent<StationJobsComponent>(station))
@@ -255,7 +391,7 @@ namespace Content.IntegrationTests.Tests
try
{
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
catch (Exception ex)
{
@@ -319,11 +455,9 @@ namespace Content.IntegrationTests.Tests
var server = pair.Server;
var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
var mapManager = server.ResolveDependency<IMapManager>();
var resourceManager = server.ResolveDependency<IResourceManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
var cfg = server.ResolveDependency<IConfigurationManager>();
var mapSystem = server.System<SharedMapSystem>();
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
var gameMaps = protoManager.EnumeratePrototypes<GameMapPrototype>().Select(o => o.MapPath).ToHashSet();
@@ -334,7 +468,7 @@ namespace Content.IntegrationTests.Tests
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal))
.ToArray();
var mapNames = new List<string>();
var mapPaths = new List<ResPath>();
foreach (var map in maps)
{
if (gameMaps.Contains(map))
@@ -345,32 +479,46 @@ namespace Content.IntegrationTests.Tests
{
continue;
}
mapNames.Add(rootedPath.ToString());
mapPaths.Add(rootedPath);
}
await server.WaitPost(() =>
{
Assert.Multiple(() =>
{
foreach (var mapName in mapNames)
// This bunch of files contains a random mixture of both map and grid files.
// TODO MAPPING organize files
var opts = MapLoadOptions.Default with
{
DeserializationOptions = DeserializationOptions.Default with
{
InitializeMaps = true,
LogOrphanedGrids = false
}
};
HashSet<Entity<MapComponent>> maps;
foreach (var path in mapPaths)
{
mapSystem.CreateMap(out var mapId);
try
{
Assert.That(mapLoader.TryLoad(mapId, mapName, out _));
Assert.That(mapLoader.TryLoadGeneric(path, out maps, out _, opts));
}
catch (Exception ex)
{
throw new Exception($"Failed to load map {mapName}", ex);
throw new Exception($"Failed to load map {path}", ex);
}
try
{
mapManager.DeleteMap(mapId);
foreach (var map in maps)
{
server.EntMan.DeleteEntity(map);
}
}
catch (Exception ex)
{
throw new Exception($"Failed to delete map {mapName}", ex);
throw new Exception($"Failed to delete map {path}", ex);
}
}
});

View File

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

View File

@@ -1,11 +1,8 @@
using System.Linq;
using Content.Shared.CCVar;
using Content.Shared.CCVar;
using Content.Shared.Salvage;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests;
@@ -24,7 +21,6 @@ public sealed class SalvageTest
var entManager = server.ResolveDependency<IEntityManager>();
var mapLoader = entManager.System<MapLoaderSystem>();
var mapManager = server.ResolveDependency<IMapManager>();
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
var cfg = server.ResolveDependency<IConfigurationManager>();
var mapSystem = entManager.System<SharedMapSystem>();
@@ -34,13 +30,10 @@ public sealed class SalvageTest
{
foreach (var salvage in prototypeManager.EnumeratePrototypes<SalvageMapPrototype>())
{
var mapFile = salvage.MapPath;
mapSystem.CreateMap(out var mapId);
try
{
Assert.That(mapLoader.TryLoad(mapId, mapFile.ToString(), out var roots));
Assert.That(roots.Where(uid => entManager.HasComponent<MapGridComponent>(uid)), Is.Not.Empty);
Assert.That(mapLoader.TryLoadGrid(mapId, salvage.MapPath, out var grid));
}
catch (Exception ex)
{
@@ -49,7 +42,7 @@ public sealed class SalvageTest
try
{
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
catch (Exception ex)
{

View File

@@ -3,6 +3,7 @@ using Content.Shared.CCVar;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -16,7 +17,7 @@ namespace Content.IntegrationTests.Tests
[Test]
public async Task SaveLoadMultiGridMap()
{
const string mapPath = @"/Maps/Test/TestMap.yml";
var mapPath = new ResPath("/Maps/Test/TestMap.yml");
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
@@ -31,7 +32,7 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
var dir = new ResPath(mapPath).Directory;
var dir = mapPath.Directory;
resManager.UserData.CreateDir(dir);
mapSystem.CreateMap(out var mapId);
@@ -39,23 +40,25 @@ 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));
Assert.Multiple(() => mapManager.DeleteMap(mapId));
Assert.That(mapLoader.TrySaveMap(mapId, mapPath));
mapSystem.DeleteMap(mapId);
});
await server.WaitIdleAsync();
MapId newMap = default;
await server.WaitAssertion(() =>
{
Assert.That(mapLoader.TryLoad(new MapId(10), mapPath, out _));
Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _));
newMap = map!.Value.Comp.MapId;
});
await server.WaitIdleAsync();
@@ -63,7 +66,7 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
{
if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(10, 10), out var gridUid, out var mapGrid) ||
if (!mapManager.TryFindGridAt(newMap, new Vector2(10, 10), out var gridUid, out var mapGrid) ||
!sEntities.TryGetComponent<TransformComponent>(gridUid, out var gridXform))
{
Assert.Fail();
@@ -73,11 +76,11 @@ 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)));
});
}
{
if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(-8, -8), out var gridUid, out var mapGrid) ||
if (!mapManager.TryFindGridAt(newMap, new Vector2(-8, -8), out var gridUid, out var mapGrid) ||
!sEntities.TryGetComponent<TransformComponent>(gridUid, out var gridXform))
{
Assert.Fail();
@@ -87,7 +90,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(-8, -8)));
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(2, (TileRenderFlag) 1, 254)));
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 2, flags: 1, variant: 254)));
});
}
});

View File

@@ -1,25 +1,25 @@
using System.IO;
using System.Linq;
using Content.Shared.CCVar;
using Robust.Server.GameObjects;
using Robust.Server.Maps;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests
{
/// <summary>
/// Tests that a map's yaml does not change when saved consecutively.
/// Tests that a grid's yaml does not change when saved consecutively.
/// </summary>
[TestFixture]
public sealed class SaveLoadSaveTest
{
[Test]
public async Task SaveLoadSave()
public async Task CreateSaveLoadSaveGrid()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
@@ -30,22 +30,21 @@ namespace Content.IntegrationTests.Tests
var cfg = server.ResolveDependency<IConfigurationManager>();
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
var testSystem = server.System<SaveLoadSaveTestSystem>();
testSystem.Enabled = true;
var rp1 = new ResPath("/save load save 1.yml");
var rp2 = new ResPath("/save load save 2.yml");
await server.WaitPost(() =>
{
mapSystem.CreateMap(out var mapId0);
// TODO: Properly find the "main" station grid.
var grid0 = mapManager.CreateGridEntity(mapId0);
mapLoader.Save(grid0.Owner, "save load save 1.yml");
entManager.RunMapInit(grid0.Owner, entManager.GetComponent<MetaDataComponent>(grid0));
Assert.That(mapLoader.TrySaveGrid(grid0.Owner, rp1));
mapSystem.CreateMap(out var mapId1);
EntityUid grid1 = default!;
#pragma warning disable NUnit2045
Assert.That(mapLoader.TryLoad(mapId1, "save load save 1.yml", out var roots, new MapLoadOptions() { LoadMap = false }), $"Failed to load test map {TestMap}");
Assert.DoesNotThrow(() =>
{
grid1 = roots.First(uid => entManager.HasComponent<MapGridComponent>(uid));
});
#pragma warning restore NUnit2045
mapLoader.Save(grid1, "save load save 2.yml");
Assert.That(mapLoader.TryLoadGrid(mapId1, rp1, out var grid1));
Assert.That(mapLoader.TrySaveGrid(grid1!.Value, rp2));
});
await server.WaitIdleAsync();
@@ -54,14 +53,12 @@ namespace Content.IntegrationTests.Tests
string one;
string two;
var rp1 = new ResPath("/save load save 1.yml");
await using (var stream = userData.Open(rp1, FileMode.Open))
using (var reader = new StreamReader(stream))
{
one = await reader.ReadToEndAsync();
}
var rp2 = new ResPath("/save load save 2.yml");
await using (var stream = userData.Open(rp2, FileMode.Open))
using (var reader = new StreamReader(stream))
{
@@ -87,6 +84,7 @@ namespace Content.IntegrationTests.Tests
TestContext.Error.WriteLine(twoTmp);
}
});
testSystem.Enabled = false;
await pair.CleanReturnAsync();
}
@@ -101,8 +99,12 @@ namespace Content.IntegrationTests.Tests
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
var mapManager = server.ResolveDependency<IMapManager>();
var mapSystem = server.System<SharedMapSystem>();
var mapSys = server.System<SharedMapSystem>();
var testSystem = server.System<SaveLoadSaveTestSystem>();
testSystem.Enabled = true;
var rp1 = new ResPath("/load save ticks save 1.yml");
var rp2 = new ResPath("/load save ticks save 2.yml");
MapId mapId = default;
var cfg = server.ResolveDependency<IConfigurationManager>();
@@ -111,10 +113,10 @@ namespace Content.IntegrationTests.Tests
// Load bagel.yml as uninitialized map, and save it to ensure it's up to date.
server.Post(() =>
{
mapSystem.CreateMap(out mapId, runMapInit: false);
mapManager.SetMapPaused(mapId, true);
Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}");
mapLoader.SaveMap(mapId, "load save ticks save 1.yml");
var path = new ResPath(TestMap);
Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}");
mapId = map!.Value.Comp.MapId;
Assert.That(mapLoader.TrySaveMap(mapId, rp1));
});
// Run 5 ticks.
@@ -122,7 +124,7 @@ namespace Content.IntegrationTests.Tests
await server.WaitPost(() =>
{
mapLoader.SaveMap(mapId, "/load save ticks save 2.yml");
Assert.That(mapLoader.TrySaveMap(mapId, rp2));
});
await server.WaitIdleAsync();
@@ -131,13 +133,13 @@ namespace Content.IntegrationTests.Tests
string one;
string two;
await using (var stream = userData.Open(new ResPath("/load save ticks save 1.yml"), FileMode.Open))
await using (var stream = userData.Open(rp1, FileMode.Open))
using (var reader = new StreamReader(stream))
{
one = await reader.ReadToEndAsync();
}
await using (var stream = userData.Open(new ResPath("/load save ticks save 2.yml"), FileMode.Open))
await using (var stream = userData.Open(rp2, FileMode.Open))
using (var reader = new StreamReader(stream))
{
two = await reader.ReadToEndAsync();
@@ -163,7 +165,8 @@ namespace Content.IntegrationTests.Tests
}
});
await server.WaitPost(() => mapManager.DeleteMap(mapId));
testSystem.Enabled = false;
await server.WaitPost(() => mapSys.DeleteMap(mapId));
await pair.CleanReturnAsync();
}
@@ -184,29 +187,31 @@ namespace Content.IntegrationTests.Tests
var server = pair.Server;
var mapLoader = server.System<MapLoaderSystem>();
var mapSystem = server.System<SharedMapSystem>();
var mapManager = server.ResolveDependency<IMapManager>();
var mapSys = server.System<SharedMapSystem>();
var userData = server.ResolveDependency<IResourceManager>().UserData;
var cfg = server.ResolveDependency<IConfigurationManager>();
Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False);
var testSystem = server.System<SaveLoadSaveTestSystem>();
testSystem.Enabled = true;
MapId mapId = default;
const string fileA = "/load tick load a.yml";
const string fileB = "/load tick load b.yml";
MapId mapId1 = default;
MapId mapId2 = default;
var fileA = new ResPath("/load tick load a.yml");
var fileB = new ResPath("/load tick load b.yml");
string yamlA;
string yamlB;
// Load & save the first map
server.Post(() =>
{
mapSystem.CreateMap(out mapId, runMapInit: false);
mapManager.SetMapPaused(mapId, true);
Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}");
mapLoader.SaveMap(mapId, fileA);
var path = new ResPath(TestMap);
Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}");
mapId1 = map!.Value.Comp.MapId;
Assert.That(mapLoader.TrySaveMap(mapId1, fileA));
});
await server.WaitIdleAsync();
await using (var stream = userData.Open(new ResPath(fileA), FileMode.Open))
await using (var stream = userData.Open(fileA, FileMode.Open))
using (var reader = new StreamReader(stream))
{
yamlA = await reader.ReadToEndAsync();
@@ -217,16 +222,15 @@ namespace Content.IntegrationTests.Tests
// Load & save the second map
server.Post(() =>
{
mapManager.DeleteMap(mapId);
mapSystem.CreateMap(out mapId, runMapInit: false);
mapManager.SetMapPaused(mapId, true);
Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}");
mapLoader.SaveMap(mapId, fileB);
var path = new ResPath(TestMap);
Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}");
mapId2 = map!.Value.Comp.MapId;
Assert.That(mapLoader.TrySaveMap(mapId2, fileB));
});
await server.WaitIdleAsync();
await using (var stream = userData.Open(new ResPath(fileB), FileMode.Open))
await using (var stream = userData.Open(fileB, FileMode.Open))
using (var reader = new StreamReader(stream))
{
yamlB = await reader.ReadToEndAsync();
@@ -234,8 +238,32 @@ namespace Content.IntegrationTests.Tests
Assert.That(yamlA, Is.EqualTo(yamlB));
await server.WaitPost(() => mapManager.DeleteMap(mapId));
testSystem.Enabled = false;
await server.WaitPost(() => mapSys.DeleteMap(mapId1));
await server.WaitPost(() => mapSys.DeleteMap(mapId2));
await pair.CleanReturnAsync();
}
/// <summary>
/// Simple system that modifies the data saved to a yaml file by removing the timestamp.
/// Required by some tests that validate that re-saving a map does not modify it.
/// </summary>
private sealed class SaveLoadSaveTestSystem : EntitySystem
{
public bool Enabled;
public override void Initialize()
{
SubscribeLocalEvent<AfterSerializationEvent>(OnAfterSave);
}
private void OnAfterSave(AfterSerializationEvent ev)
{
if (!Enabled)
return;
// Remove timestamp.
((MappingDataNode)ev.Node["meta"]).Remove("time");
}
}
}
}

View File

@@ -4,10 +4,12 @@ using System.Numerics;
using Content.Server.Shuttles.Systems;
using Content.Tests;
using Robust.Server.GameObjects;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests.Shuttle;
@@ -106,8 +108,9 @@ public sealed class DockTest : ContentUnitTest
{
mapGrid = entManager.AddComponent<MapGridComponent>(map.MapUid);
entManager.DeleteEntity(map.Grid);
Assert.That(entManager.System<MapLoaderSystem>().TryLoad(otherMap.MapId, "/Maps/Shuttles/emergency.yml", out var rootUids));
shuttle = rootUids[0];
var path = new ResPath("/Maps/Shuttles/emergency.yml");
Assert.That(entManager.System<MapLoaderSystem>().TryLoadGrid(otherMap.MapId, path, out var grid));
shuttle = grid!.Value.Owner;
var dockingConfig = dockingSystem.GetDockingConfig(shuttle, map.MapUid);
Assert.That(dockingConfig, Is.EqualTo(null));

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,215 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.Database;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
/// <summary>
/// Allows admins to change certain CVars. This is different than the "cvar" command which is host only and can change any CVar.
/// </summary>
/// <remarks>
/// Possible todo for future, store default values for cvars, and allow resetting to default.
/// </remarks>
[AnyCommand]
public sealed class ChangeCvarCommand : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly CVarControlManager _cVarControlManager = default!;
/// <summary>
/// Searches the list of cvars for a cvar that matches the search string.
/// </summary>
private void SearchCVars(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
{
shell.WriteLine(Loc.GetString("cmd-changecvar-search-no-arguments"));
return;
}
var cvars = _cVarControlManager.GetAllRunnableCvars(shell);
var matches = cvars
.Where(c =>
c.Name.Contains(args[1], StringComparison.OrdinalIgnoreCase)
|| c.ShortHelp?.Contains(args[1], StringComparison.OrdinalIgnoreCase) == true
|| c.LongHelp?.Contains(args[1], StringComparison.OrdinalIgnoreCase) == true
) // Might be very slow and stupid, but eh.
.ToList();
if (matches.Count == 0)
{
shell.WriteLine(Loc.GetString("cmd-changecvar-search-no-matches"));
return;
}
shell.WriteLine(Loc.GetString("cmd-changecvar-search-matches", ("count", matches.Count)));
shell.WriteLine(string.Join("\n", matches.Select(FormatCVarFullHelp)));
}
/// <summary>
/// Formats a CVar into a string for display.
/// </summary>
private string FormatCVarFullHelp(ChangableCVar cvar)
{
if (cvar.LongHelp != null && cvar.ShortHelp != null)
{
return $"{cvar.Name} - {cvar.LongHelp}";
}
// There is no help, no one is coming. We are all doomed.
return cvar.Name;
}
public string Command => "changecvar";
public string Description { get; } = Loc.GetString("cmd-changecvar-desc");
public string Help { get; } = Loc.GetString("cmd-changecvar-help");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length == 0)
{
shell.WriteLine(Loc.GetString("cmd-changecvar-no-arguments"));
return;
}
var cvars = _cVarControlManager.GetAllRunnableCvars(shell);
var cvar = args[0];
if (cvar == "?")
{
if (cvars.Count == 0)
{
shell.WriteLine(Loc.GetString("cmd-changecvar-no-cvars"));
return;
}
shell.WriteLine(Loc.GetString("cmd-changecvar-available-cvars"));
shell.WriteLine(string.Join("\n", cvars.Select(FormatCVarFullHelp)));
return;
}
if (cvar == "search")
{
SearchCVars(shell, argStr, args);
return;
}
if (!_configurationManager.IsCVarRegistered(cvar)) // Might be a redunat check with the if statement below.
{
shell.WriteLine(Loc.GetString("cmd-changecvar-cvar-not-registered", ("cvar", cvar)));
return;
}
if (cvars.All(c => c.Name != cvar))
{
shell.WriteLine(Loc.GetString("cmd-changecvar-cvar-not-allowed"));
return;
}
if (args.Length == 1)
{
var value = _configurationManager.GetCVar<object>(cvar);
shell.WriteLine(value.ToString()!);
}
else
{
var value = args[1];
var type = _configurationManager.GetCVarType(cvar);
try
{
var parsed = CVarCommandUtil.ParseObject(type, value);
// Value check, is it in the min/max range?
var control = _cVarControlManager.GetCVar(cvar)!.Control; // Null check is done above.
var allowed = true;
if (control is { Min: not null, Max: not null })
{
switch (parsed) // This looks bad, and im not sorry.
{
case int intVal:
{
if (intVal < (int)control.Min || intVal > (int)control.Max)
{
allowed = false;
}
break;
}
case float floatVal:
{
if (floatVal < (float)control.Min || floatVal > (float)control.Max)
{
allowed = false;
}
break;
}
case long longVal:
{
if (longVal < (long)control.Min || longVal > (long)control.Max)
{
allowed = false;
}
break;
}
case ushort ushortVal:
{
if (ushortVal < (ushort)control.Min || ushortVal > (ushort)control.Max)
{
allowed = false;
}
break;
}
}
}
if (!allowed)
{
shell.WriteError(Loc.GetString("cmd-changecvar-value-out-of-range",
("min", control.Min ?? "-∞"),
("max", control.Max ?? "∞")));
return;
}
var oldValue = _configurationManager.GetCVar<object>(cvar);
_configurationManager.SetCVar(cvar, parsed);
_adminLogManager.Add(LogType.AdminCommands,
LogImpact.High,
$"{shell.Player!.Name} ({shell.Player!.UserId}) changed CVAR {cvar} from {oldValue.ToString()} to {parsed.ToString()}"
);
shell.WriteLine(Loc.GetString("cmd-changecvar-success", ("cvar", cvar), ("old", oldValue), ("value", parsed)));
}
catch (FormatException)
{
shell.WriteError(Loc.GetString("cmd-cvar-parse-error", ("type", type)));
}
}
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
var cvars = _cVarControlManager.GetAllRunnableCvars(shell);
if (args.Length == 1)
{
return CompletionResult.FromHintOptions(
cvars
.Select(c => new CompletionOption(c.Name, c.ShortHelp ?? c.Name)),
Loc.GetString("cmd-changecvar-arg-name"));
}
var cvar = args[0];
if (!_configurationManager.IsCVarRegistered(cvar))
return CompletionResult.Empty;
var type = _configurationManager.GetCVarType(cvar);
return CompletionResult.FromHint($"<{type.Name}>");
}
}

View File

@@ -1,11 +1,9 @@
using System.Linq;
using System.Numerics;
using Content.Server.GameTicking;
using Content.Server.Maps;
using Content.Shared.Administration;
using Robust.Server.Maps;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.EntitySerialization;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -25,6 +23,7 @@ namespace Content.Server.Administration.Commands
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var entityManager = IoCManager.Resolve<IEntityManager>();
var gameTicker = entityManager.EntitySysManager.GetEntitySystem<GameTicker>();
var mapSys = entityManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
if (args.Length is not (2 or 4 or 5))
{
@@ -32,29 +31,28 @@ namespace Content.Server.Administration.Commands
return;
}
if (prototypeManager.TryIndex<GameMapPrototype>(args[1], out var gameMap))
{
if (!int.TryParse(args[0], out var mapId))
return;
var loadOptions = new MapLoadOptions()
{
LoadMap = false,
};
var stationName = args.Length == 5 ? args[4] : null;
if (args.Length >= 4 && int.TryParse(args[2], out var x) && int.TryParse(args[3], out var y))
{
loadOptions.Offset = new Vector2(x, y);
}
var grids = gameTicker.LoadGameMap(gameMap, new MapId(mapId), loadOptions, stationName);
shell.WriteLine($"Loaded {grids.Count} grids.");
}
else
if (!prototypeManager.TryIndex<GameMapPrototype>(args[1], out var gameMap))
{
shell.WriteError($"The given map prototype {args[0]} is invalid.");
return;
}
if (!int.TryParse(args[0], out var mapId))
return;
var stationName = args.Length == 5 ? args[4] : null;
Vector2? offset = null;
if (args.Length >= 4)
offset = new Vector2(int.Parse(args[2]), int.Parse(args[3]));
var id = new MapId(mapId);
var grids = mapSys.MapExists(id)
? gameTicker.MergeGameMap(gameMap, id, stationName: stationName, offset: offset)
: gameTicker.LoadGameMapWithId(gameMap, id, stationName: stationName, offset: offset);
shell.WriteLine($"Loaded {grids.Count} grids.");
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)

View File

@@ -1,10 +1,10 @@
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Map;
using System.Linq;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Commands;
@@ -48,7 +48,7 @@ public sealed class PersistenceSave : IConsoleCommand
}
var mapLoader = _system.GetEntitySystem<MapLoaderSystem>();
mapLoader.SaveMap(mapId, saveFilePath);
mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath));
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
}
}

View File

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

View File

@@ -0,0 +1,125 @@
using System.Linq;
using System.Reflection;
using Content.Shared.CCVar.CVarAccess;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
namespace Content.Server.Administration.Managers;
/// <summary>
/// Manages the control of CVars via the <see cref="Content.Shared.CCVar.CVarAccess.CVarControl"/> attribute.
/// </summary>
public sealed class CVarControlManager : IPostInjectInit
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
[Dependency] private readonly ILogManager _logger = default!;
private readonly List<ChangableCVar> _changableCvars = new();
private ISawmill _sawmill = default!;
void IPostInjectInit.PostInject()
{
_sawmill = _logger.GetSawmill("cvarcontrol");
}
public void Initialize()
{
RegisterCVars();
}
private void RegisterCVars()
{
if (_changableCvars.Count != 0)
{
_sawmill.Warning("CVars already registered, overwriting.");
_changableCvars.Clear();
}
var validCvarsDefs = _reflectionManager.FindTypesWithAttribute<CVarDefsAttribute>();
foreach (var type in validCvarsDefs)
{
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy))
{
var allowed = field.GetCustomAttribute<CVarControl>();
if (allowed == null)
{
continue;
}
var cvarDef = (CVarDef)field.GetValue(null)!;
_changableCvars.Add(new ChangableCVar(cvarDef.Name, allowed, _localizationManager));
}
}
_sawmill.Info($"Registered {_changableCvars.Count} CVars.");
}
/// <summary>
/// Gets all CVars that the player can change.
/// </summary>
public List<ChangableCVar> GetAllRunnableCvars(IConsoleShell shell)
{
// Not a player, running as server. We COULD return all cvars,
// but a check later down the line will prevent it from anyways. Use the "cvar" command instead.
if (shell.Player == null)
return [];
return GetAllRunnableCvars(shell.Player);
}
public List<ChangableCVar> GetAllRunnableCvars(ICommonSession session)
{
var adminData = _adminManager.GetAdminData(session);
if (adminData == null)
return []; // Not an admin
return _changableCvars
.Where(cvar => adminData.HasFlag(cvar.Control.AdminFlags))
.ToList();
}
public ChangableCVar? GetCVar(string name)
{
return _changableCvars.FirstOrDefault(cvar => cvar.Name == name);
}
}
public sealed class ChangableCVar
{
private const string LocPrefix = "changecvar";
public string Name { get; }
// Holding a reference to the attribute might be skrunkly? Not sure how much mem it eats up.
public CVarControl Control { get; }
public string? ShortHelp;
public string? LongHelp;
public ChangableCVar(string name, CVarControl control, ILocalizationManager loc)
{
Name = name;
Control = control;
if (loc.TryGetString($"{LocPrefix}-simple-{name.Replace('.', '_')}", out var simple))
{
ShortHelp = simple;
}
if (loc.TryGetString($"{LocPrefix}-full-{name.Replace('.', '_')}", out var longHelp))
{
LongHelp = longHelp;
}
// If one is set and the other is not, we throw
if (ShortHelp == null && LongHelp != null || ShortHelp != null && LongHelp == null)
{
throw new InvalidOperationException("Short and long help must both be set or both be null.");
}
}
}

View File

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

View File

@@ -1,8 +1,7 @@
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Systems;
@@ -11,8 +10,7 @@ namespace Content.Server.Administration.Systems;
/// </summary>
public sealed class AdminTestArenaSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
public const string ArenaMapPath = "/Maps/Test/admin_test_arena.yml";
@@ -28,26 +26,24 @@ public sealed class AdminTestArenaSystem : EntitySystem
{
return (arenaMap, arenaGrid);
}
else
{
ArenaGrid[admin.UserId] = null;
return (arenaMap, null);
}
}
ArenaMap[admin.UserId] = _mapManager.GetMapEntityId(_mapManager.CreateMap());
_metaDataSystem.SetEntityName(ArenaMap[admin.UserId], $"ATAM-{admin.Name}");
var grids = _map.LoadMap(Comp<MapComponent>(ArenaMap[admin.UserId]).MapId, ArenaMapPath);
if (grids.Count != 0)
{
_metaDataSystem.SetEntityName(grids[0], $"ATAG-{admin.Name}");
ArenaGrid[admin.UserId] = grids[0];
}
else
{
ArenaGrid[admin.UserId] = null;
return (arenaMap, null);
}
return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]);
var path = new ResPath(ArenaMapPath);
if (!_loader.TryLoadMap(path, out var map, out var grids))
throw new Exception($"Failed to load admin arena");
ArenaMap[admin.UserId] = map.Value.Owner;
_metaDataSystem.SetEntityName(map.Value.Owner, $"ATAM-{admin.Name}");
var grid = grids.FirstOrNull();
ArenaGrid[admin.UserId] = grid?.Owner;
if (grid != null)
_metaDataSystem.SetEntityName(grid.Value.Owner, $"ATAG-{admin.Name}");
return (map.Value.Owner, grid?.Owner);
}
}

View File

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

View File

@@ -105,7 +105,7 @@ namespace Content.Server.Administration.Systems
mark.Text = Loc.GetString("toolshed-verb-mark");
mark.Message = Loc.GetString("toolshed-verb-mark-description");
mark.Category = VerbCategory.Admin;
mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", Enumerable.Repeat(args.Target, 1), out _);
mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", new List<EntityUid> {args.Target}, out _);
mark.Impact = LogImpact.Low;
args.Verbs.Add(mark);
@@ -370,7 +370,7 @@ namespace Content.Server.Administration.Systems
}
if (lawBoundComponent != null && target != null)
if (lawBoundComponent != null && target != null && _adminManager.HasAdminFlag(player, AdminFlags.Moderator))
{
args.Verbs.Add(new Verb()
{

View File

@@ -9,8 +9,7 @@ public sealed class MarkedCommand : ToolshedCommand
[CommandImplementation]
public IEnumerable<EntityUid> Marked(IInvocationContext ctx)
{
var res = (IEnumerable<EntityUid>?)ctx.ReadVar("marked");
res ??= Array.Empty<EntityUid>();
return res;
var marked = ctx.ReadVar("marked") as IEnumerable<EntityUid>;
return marked ?? Array.Empty<EntityUid>();
}
}

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