Merge pull request #1331 from NullWanderer/2024/06/06-upstream-merge

Upstream merge
This commit is contained in:
Null 2024-06-11 17:12:03 +02:00 committed by GitHub
commit 21cd750ff4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
674 changed files with 6928 additions and 4570 deletions

View File

@ -1,19 +1,17 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
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.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
@ -58,15 +56,20 @@ public class PvsBenchmark
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
_sys = _entMan.System<SharedTransformSystem>();
SetupAsync().Wait();
}
private async Task SetupAsync()
{
// Spawn the map
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
await _pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).Wait();
});
// Get list of ghost warp positions
_spawns = _entMan.AllComponentsList<WarpPointComponent>()
@ -76,17 +79,19 @@ public class PvsBenchmark
Array.Resize(ref _players, PlayerCount);
// Spawn "Players".
_pair.Server.WaitPost(() =>
// Spawn "Players"
_players = await _pair.Server.AddDummySessions(PlayerCount);
await _pair.Server.WaitPost(() =>
{
var mind = _pair.Server.System<MindSystem>();
for (var i = 0; i < PlayerCount; i++)
{
var pos = _spawns[i % _spawns.Length];
var uid =_entMan.SpawnEntity("MobHuman", pos);
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
_players[i] = new DummySession{AttachedEntity = uid};
mind.ControlMob(_players[i].UserId, uid);
}
}).Wait();
});
// Repeatedly move players around so that they "explore" the map and see lots of entities.
// This will populate their PVS data with out-of-view entities.
@ -168,20 +173,4 @@ public class PvsBenchmark
}).Wait();
_pair.Server.PvsTick(_players);
}
private sealed class DummySession : ICommonSession
{
public SessionStatus Status => SessionStatus.InGame;
public EntityUid? AttachedEntity {get; set; }
public NetUserId UserId => default;
public string Name => string.Empty;
public short Ping => default;
public INetChannel Channel { get; set; } = default!;
public LoginType AuthType => default;
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
public DateTime ConnectedTime { get; set; }
public SessionState State => default!;
public SessionData Data => default!;
public bool ClientSide { get; set; }
}
}

View File

@ -1,5 +1,7 @@
using Content.Shared.Access.Systems;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.Access.UI
{
@ -40,7 +42,7 @@ namespace Content.Client.Access.UI
SendMessage(new AgentIDCardJobChangedMessage(newJob));
}
public void OnJobIconChanged(string newJobIconId)
public void OnJobIconChanged(ProtoId<StatusIconPrototype> newJobIconId)
{
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
}

View File

@ -38,7 +38,7 @@ namespace Content.Client.Access.UI
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
public void SetAllowedIcons(HashSet<string> icons, string currentJobIconId)
public void SetAllowedIcons(HashSet<ProtoId<StatusIconPrototype>> icons, string currentJobIconId)
{
IconGrid.DisposeAllChildren();
@ -46,10 +46,8 @@ namespace Content.Client.Access.UI
var i = 0;
foreach (var jobIconId in icons)
{
if (!_prototypeManager.TryIndex<StatusIconPrototype>(jobIconId, out var jobIcon))
{
if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
continue;
}
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
@ -77,7 +75,7 @@ namespace Content.Client.Access.UI
};
jobIconButton.AddChild(jobIconTexture);
jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID);
jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId);
IconGrid.AddChild(jobIconButton);
if (jobIconId.Equals(currentJobIconId))

View File

@ -27,6 +27,9 @@ namespace Content.Client.Access.UI
private string? _lastJobTitle;
private string? _lastJobProto;
// The job that will be picked if the ID doesn't have a job on the station.
private static ProtoId<JobPrototype> _defaultJob = "Passenger";
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
{
@ -65,7 +68,6 @@ namespace Content.Client.Access.UI
}
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
_accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons);
@ -172,11 +174,15 @@ namespace Content.Client.Access.UI
new List<ProtoId<AccessLevelPrototype>>());
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
if (jobIndex >= 0)
// If the job index is < 0 that means they don't have a job registered in the station records.
// For example, a new ID from a box would have no job index.
if (jobIndex < 0)
{
JobPresetOptionButton.SelectId(jobIndex);
jobIndex = _jobPrototypeIds.IndexOf(_defaultJob);
}
JobPresetOptionButton.SelectId(jobIndex);
_lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle;
_lastJobProto = state.TargetIdJobPrototype;

View File

@ -147,7 +147,7 @@ public sealed partial class BanPanel : DefaultWindow
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
}
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);

View File

@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
public Matrix3 SpaceMatrix;
public Matrix3x2 SpaceMatrix;
public MapId Map;
private readonly Font _font;
@ -78,7 +78,8 @@ public sealed class ExplosionDebugOverlay : Overlay
if (SpaceTiles == null)
return;
gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds);
Matrix3x2.Invert(SpaceMatrix, out var invSpace);
gridBounds = invSpace.TransformBox(args.WorldBounds);
DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize);
}
@ -86,7 +87,7 @@ public sealed class ExplosionDebugOverlay : Overlay
private void DrawText(
DrawingHandleScreen handle,
Box2 gridBounds,
Matrix3 transform,
Matrix3x2 transform,
Dictionary<int, List<Vector2i>> tileSets,
ushort tileSize)
{
@ -103,7 +104,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (!gridBounds.Contains(centre))
continue;
var worldCenter = transform.Transform(centre);
var worldCenter = Vector2.Transform(centre, transform);
var screenCenter = _eyeManager.WorldToScreen(worldCenter);
@ -119,7 +120,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (tileSets.TryGetValue(0, out var set))
{
var epicenter = set.First();
var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize);
var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform);
var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24);
var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}";
handle.DrawString(_font, screenCenter, text);
@ -148,11 +149,12 @@ public sealed class ExplosionDebugOverlay : Overlay
if (SpaceTiles == null)
return;
gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2);
Matrix3x2.Invert(SpaceMatrix, out var invSpace);
gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2);
handle.SetTransform(SpaceMatrix);
DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize);
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
}
private void DrawTiles(

View File

@ -1,15 +1,21 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
Text="{Loc Object type:}" />
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc Search...}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
</BoxContainer>
<cc:HSeparator/>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ObjectList">
</BoxContainer>
</ScrollContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<ot:ObjectsTabHeader Name="ListHeader"/>
<cc:HSeparator/>
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
</BoxContainer>
</BoxContainer>
</Control>

View File

@ -1,5 +1,7 @@
using Content.Client.Station;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly List<ObjectsTabEntry> _objects = new();
private List<ObjectsTabSelection> _selections = new();
private readonly List<ObjectsTabSelection> _selections = new();
private bool _ascending = false; // Set to false for descending order by default
private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
// Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
// OR
// I can do this.
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate;
public ObjectsTab()
{
@ -42,6 +44,30 @@ public sealed partial class ObjectsTab : Control
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
}
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
RefreshObjectList();
// Set initial selection and refresh the list to apply the initial sort order
var defaultSelection = ObjectsTabSelection.Grids;
ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
RefreshObjectList(defaultSelection); // Refresh the list with the default selection
// Initialize the next update time
_nextUpdate = TimeSpan.Zero;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
return;
_nextUpdate = _timing.CurTime + _updateFrequency;
RefreshObjectList();
}
@ -81,32 +107,72 @@ public sealed partial class ObjectsTab : Control
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
foreach (var control in _objects)
entities.Sort((a, b) =>
{
ObjectList.RemoveChild(control);
var valueA = GetComparableValue(a, _headerClicked);
var valueB = GetComparableValue(b, _headerClicked);
return _ascending ? Comparer<object>.Default.Compare(valueA, valueB) : Comparer<object>.Default.Compare(valueB, valueA);
});
var listData = new List<ObjectsListData>();
for (int index = 0; index < entities.Count; index++)
{
var info = entities[index];
listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor));
}
_objects.Clear();
foreach (var (name, nent) in entities)
{
var ctrl = new ObjectsTabEntry(name, nent);
_objects.Add(ctrl);
ObjectList.AddChild(ctrl);
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
}
SearchList.PopulateList(listData);
}
protected override void FrameUpdate(FrameEventArgs args)
private void GenerateButton(ListData data, ListContainerButton button)
{
base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
return;
// I do not care for precision.
_nextUpdate = _timing.CurTime + _updateFrequency;
var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
button.ToolTip = $"{info.Name}, {info.Entity}";
// Add key binding event handler
entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data);
button.AddChild(entry);
}
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not ObjectsListData { FilteringString: var filteringString })
return false;
// If the filter is empty, do not filter out any entries
if (string.IsNullOrEmpty(filter))
return true;
return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
}
private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
{
return header switch
{
ObjectsTabHeader.Header.ObjectName => entity.Name,
ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
_ => entity.Name
};
}
private void HeaderClicked(ObjectsTabHeader.Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
RefreshObjectList();
}
@ -118,3 +184,4 @@ public sealed partial class ObjectsTab : Control
}
}
public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData;

View File

@ -1,6 +1,6 @@
<ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel"/>
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
@ -14,4 +14,4 @@
HorizontalExpand="True"
ClipText="True"/>
</BoxContainer>
</ContainerButton>
</PanelContainer>

View File

@ -1,19 +1,21 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabEntry : ContainerButton
public sealed partial class ObjectsTabEntry : PanelContainer
{
public NetEntity AssocEntity;
public ObjectsTabEntry(string name, NetEntity nent)
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
{
RobustXamlLoader.Load(this);
AssocEntity = nent;
EIDLabel.Text = nent.ToString();
NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox;
}
}

View File

@ -0,0 +1,21 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel" Access="Public"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="ObjectNameLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-object-name}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="EntityIDLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-entity-id}"
MouseFilter="Pass"/>
</BoxContainer>
</Control>

View File

@ -0,0 +1,86 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab
{
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabHeader : Control
{
public event Action<Header>? OnHeaderClicked;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
public ObjectsTabHeader()
{
RobustXamlLoader.Load(this);
ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
EntityIDLabel.OnKeyBindDown += EntityIDClicked;
}
public Label GetHeader(Header header)
{
return header switch
{
Header.ObjectName => ObjectNameLabel,
Header.EntityID => EntityIDLabel,
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
};
}
public void ResetHeaderText()
{
ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
}
public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
{
ResetHeaderText();
var arrow = ascending ? ArrowUp : ArrowDown;
GetHeader(headerClicked).Text += $" {arrow}";
}
private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
{
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
OnHeaderClicked?.Invoke(header);
args.Handle();
}
private void ObjectNameClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.ObjectName);
}
private void EntityIDClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.EntityID);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
}
}
public enum Header
{
ObjectName,
EntityID
}
}
}

View File

@ -53,6 +53,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
@ -110,7 +111,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
_players = players;
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
var sortedPlayers = new List<PlayerInfo>(players);
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();

View File

@ -1,55 +0,0 @@
using Content.Shared.Antag;
using Content.Shared.Revolutionary.Components;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Antag;
/// <summary>
/// Used for assigning specified icons for antags.
/// </summary>
public sealed class AntagStatusIconSystem : SharedStatusIconSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<InitialInfectedComponent, GetStatusIconsEvent>(GetIcon);
}
/// <summary>
/// Adds a Status Icon on an entity if the player is supposed to see it.
/// </summary>
private void GetIcon<T>(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent
{
var ent = _player.LocalSession?.AttachedEntity;
var canEv = new CanDisplayStatusIconsEvent(ent);
RaiseLocalEvent(uid, ref canEv);
if (!canEv.Cancelled)
ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon));
}
/// <summary>
/// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal
/// with a special case where if someone is a head rev we only want to display the headrev icon.
/// </summary>
private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev)
{
if (HasComp<HeadRevolutionaryComponent>(uid))
return;
GetIcon(uid, comp, ref ev);
}
}

View File

@ -66,7 +66,7 @@ public sealed class AtmosDebugOverlay : Overlay
DrawData(msg, handle);
}
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
}
private void DrawData(DebugMessage msg,

View File

@ -190,7 +190,7 @@ namespace Content.Client.Atmos.Overlays
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(in state.WorldBounds).Enlarged(grid.TileSize);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i(
(int) MathF.Floor(floatBounds.Left),
(int) MathF.Floor(floatBounds.Bottom),
@ -249,7 +249,7 @@ namespace Content.Client.Atmos.Overlays
});
drawHandle.UseShader(null);
drawHandle.SetTransform(Matrix3.Identity);
drawHandle.SetTransform(Matrix3x2.Identity);
}
private void DrawMapOverlay(

View File

@ -24,8 +24,11 @@ public sealed class ChasmFallingVisualsSystem : EntitySystem
private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
if (!TryComp<SpriteComponent>(uid, out var sprite) ||
TerminatingOrDeleted(uid))
{
return;
}
component.OriginalScale = sprite.Scale;

View File

@ -1,7 +1,8 @@
using System.Numerics;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Speech;
using Content.Shared.Whitelist;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
@ -19,6 +20,7 @@ public sealed partial class EmotesMenu : RadialMenu
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly EntityWhitelistSystem _whitelistSystem;
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
@ -28,6 +30,7 @@ public sealed partial class EmotesMenu : RadialMenu
RobustXamlLoader.Load(this);
_spriteSystem = _entManager.System<SpriteSystem>();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main");
@ -37,8 +40,8 @@ public sealed partial class EmotesMenu : RadialMenu
var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 ||
!(player.HasValue && (emote.Whitelist?.IsValid(player.Value, _entManager) ?? true)) ||
(emote.Blacklist?.IsValid(player.Value, _entManager) ?? false))
!(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue;
if (!emote.Available &&

View File

@ -3,6 +3,7 @@ using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Hands;
using Content.Shared.Item;
using Content.Shared.Rounding;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
@ -150,6 +151,9 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
if (!TryComp(uid, out AppearanceComponent? appearance))
return;
if (!TryComp<ItemComponent>(uid, out var item))
return;
if (!AppearanceSystem.TryGetData<float>(uid, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
return;
@ -159,7 +163,8 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
{
var layer = new PrototypeLayerData();
var key = "inhand-" + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
var heldPrefix = item.HeldPrefix == null ? "inhand-" : $"{item.HeldPrefix}-inhand-";
var key = heldPrefix + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
layer.State = key;

View File

@ -38,9 +38,9 @@ namespace Content.Client.Clickable
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
bottom = Matrix3.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix());
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
@ -48,8 +48,8 @@ namespace Content.Client.Clickable
Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos));
var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
// Check explicitly defined click-able bounds
if (CheckDirBound(sprite, relativeRotation, localPos))
@ -79,8 +79,8 @@ namespace Content.Client.Clickable
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
var inverseMatrix = Matrix3.Invert(matrix);
var layerLocal = inverseMatrix.Transform(localPos);
Matrix3x2.Invert(matrix, out var inverseMatrix);
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);

View File

@ -35,6 +35,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands
var showHealthBarsComponent = new ShowHealthBarsComponent
{
DamageContainers = args.ToList(),
HealthStatusIcon = null,
NetSyncEnabled = false
};

View File

@ -27,6 +27,7 @@ namespace Content.Client.Construction
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@ -195,9 +196,8 @@ namespace Content.Client.Construction
if (GhostPresent(loc))
return false;
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
return false;
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))

View File

@ -2,6 +2,7 @@ using System.Linq;
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Placement;
@ -23,6 +24,7 @@ namespace Content.Client.Construction.UI
/// </summary>
internal sealed class ConstructionMenuPresenter : IDisposable
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
@ -30,6 +32,7 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected;
@ -78,6 +81,7 @@ namespace Content.Client.Construction.UI
// This is a lot easier than a factory
IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@ -157,7 +161,7 @@ namespace Content.Client.Construction.UI
if (_playerManager.LocalSession == null
|| _playerManager.LocalEntity == null
|| (recipe.EntityWhitelist != null && !recipe.EntityWhitelist.IsValid(_playerManager.LocalEntity.Value)))
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
continue;
if (!string.IsNullOrEmpty(search))

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.Decals;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@ -113,7 +114,7 @@ namespace Content.Client.Decals.Overlays
handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
}
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
}
}
}

View File

@ -51,7 +51,7 @@ public sealed class DecalPlacementOverlay : Overlay
var handle = args.WorldHandle;
handle.SetTransform(worldMatrix);
var localPos = invMatrix.Transform(mousePos.Position);
var localPos = Vector2.Transform(mousePos.Position, invMatrix);
if (snap)
{
@ -63,6 +63,6 @@ public sealed class DecalPlacementOverlay : Overlay
var box = new Box2Rotated(aabb, rotation, localPos);
handle.DrawTextureRect(_sprite.Frame0(decal.Sprite), box, color);
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
}
}

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@ -43,7 +44,7 @@ public sealed partial class UltraVisionOverlay : Overlay
var worldHandle = args.WorldHandle;
var viewport = args.WorldBounds;
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_ultraVisionShader);
worldHandle.DrawRect(viewport, Color.White);
worldHandle.UseShader(null); // important - as of writing, construction overlay breaks without this

View File

@ -1,5 +1,6 @@
using Content.Shared.Access.Systems;
using Content.Shared.Shipyard;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
@ -10,6 +11,7 @@ public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
private readonly AccessReaderSystem _access;
@ -25,7 +27,7 @@ public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface
{
base.Open();
_menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access);
_menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access, _whitelistSystem);
_menu.OpenCentered();
_menu.OnClose += Close;
_menu.OnPurchased += Purchase;

View File

@ -2,6 +2,7 @@ using Content.Client.UserInterface.Controls;
using Content.Shared.Access.Systems;
using Content.Shared.Shipyard;
using Content.Shared.Shipyard.Prototypes;
using Content.Shared.Whitelist;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Player;
@ -25,7 +26,7 @@ public sealed partial class ShipyardConsoleMenu : FancyWindow
public Entity<ShipyardConsoleComponent> Console;
private string? _category;
public ShipyardConsoleMenu(EntityUid console, IPrototypeManager proto, IEntityManager entMan, IPlayerManager player, AccessReaderSystem access)
public ShipyardConsoleMenu(EntityUid console, IPrototypeManager proto, IEntityManager entMan, IPlayerManager player, AccessReaderSystem access, EntityWhitelistSystem whitelist)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
@ -37,7 +38,7 @@ public sealed partial class ShipyardConsoleMenu : FancyWindow
// don't include ships that aren't allowed by whitelist, server won't accept them anyway
foreach (var vessel in proto.EnumeratePrototypes<VesselPrototype>())
{
if (vessel.Whitelist?.IsValid(console, entMan) != false)
if(whitelist.IsWhitelistPass(vessel.Whitelist, console))
_vessels.Add(vessel);
}
_vessels.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase));

View File

@ -56,8 +56,8 @@ public sealed class DoAfterOverlay : Overlay
// If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid.
const float scale = 1f;
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3.CreateRotation(-rotation);
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
var curTime = _timing.CurTime;
@ -91,9 +91,9 @@ public sealed class DoAfterOverlay : Overlay
? curTime - _meta.GetPauseTime(uid, meta)
: curTime;
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition);
var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
handle.SetTransform(matty);
var offset = 0f;
@ -151,7 +151,7 @@ public sealed class DoAfterOverlay : Overlay
}
handle.UseShader(null);
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
}
public Color GetProgressColor(float progress, float alpha = 1f)

View File

@ -1,7 +1,9 @@
using Content.Shared.CCVar;
using Content.Shared.Drugs;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@ -10,6 +12,7 @@ namespace Content.Client.Drugs;
public sealed class RainbowOverlay : Overlay
{
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@ -75,6 +78,10 @@ public sealed class RainbowOverlay : Overlay
protected override void Draw(in OverlayDrawArgs args)
{
// TODO disable only the motion part or ike's idea (single static frame of the overlay)
if (_config.GetCVar(CCVars.ReducedMotion))
return;
if (ScreenTexture == null)
return;

View File

@ -153,7 +153,6 @@ namespace Content.Client.Entry
_parallaxManager.LoadDefaultParallax();
_overlayManager.AddOverlay(new SingularityOverlay());
_overlayManager.AddOverlay(new FlashOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
_chatManager.Initialize();
_clientPreferencesManager.Initialize();

View File

@ -48,7 +48,7 @@ public sealed class ExplosionOverlay : Overlay
DrawExplosion(drawHandle, args.WorldBounds, visuals, index, xforms, textures);
}
drawHandle.SetTransform(Matrix3.Identity);
drawHandle.SetTransform(Matrix3x2.Identity);
drawHandle.UseShader(null);
}
@ -78,7 +78,8 @@ public sealed class ExplosionOverlay : Overlay
if (visuals.SpaceTiles == null)
return;
gridBounds = Matrix3.Invert(visuals.SpaceMatrix).TransformBox(worldBounds).Enlarged(2);
Matrix3x2.Invert(visuals.SpaceMatrix, out var invSpace);
gridBounds = invSpace.TransformBox(worldBounds).Enlarged(2);
drawHandle.SetTransform(visuals.SpaceMatrix);
DrawTiles(drawHandle, gridBounds, index, visuals.SpaceTiles, visuals, visuals.SpaceTileSize, textures);

View File

@ -1,12 +1,11 @@
using System.Numerics;
using Content.Shared.Flash;
using Content.Shared.Flash.Components;
using Content.Shared.StatusEffect;
using Content.Client.Viewport;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using SixLabors.ImageSharp.PixelFormats;
@ -17,66 +16,87 @@ namespace Content.Client.Flash
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly StatusEffectsSystem _statusSys;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
private double _startTime = -1;
private double _lastsFor = 1;
private Texture? _screenshotTexture;
public float PercentComplete = 0.0f;
public Texture? ScreenshotTexture;
public FlashOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").Instance().Duplicate();
_shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").InstanceUnique();
_statusSys = _entityManager.System<StatusEffectsSystem>();
}
public void ReceiveFlash(double duration)
protected override void FrameUpdate(FrameEventArgs args)
{
var playerEntity = _playerManager.LocalEntity;
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<FlashedComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status))
return;
var curTime = _timing.CurTime;
var lastsFor = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds;
var timeDone = (float) (curTime - time.Value.Item1).TotalSeconds;
PercentComplete = timeDone / lastsFor;
}
public void ReceiveFlash()
{
if (_stateManager.CurrentState is IMainViewportState state)
{
// take a screenshot
// note that the callback takes a while and ScreenshotTexture will be null the first few Draws
state.Viewport.Viewport.Screenshot(image =>
{
var rgba32Image = image.CloneAs<Rgba32>(SixLabors.ImageSharp.Configuration.Default);
_screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image);
});
}
}
_startTime = _gameTiming.CurTime.TotalSeconds;
_lastsFor = duration;
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
return false;
if (args.Viewport.Eye != eyeComp.Eye)
return false;
return PercentComplete < 1.0f;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
return;
if (args.Viewport.Eye != eyeComp.Eye)
return;
var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor);
if (percentComplete >= 1.0f)
if (ScreenshotTexture == null)
return;
var worldHandle = args.WorldHandle;
_shader.SetParameter("percentComplete", PercentComplete);
worldHandle.UseShader(_shader);
_shader.SetParameter("percentComplete", percentComplete);
if (_screenshotTexture != null)
{
worldHandle.DrawTextureRectRegion(_screenshotTexture, args.WorldBounds);
}
worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds);
worldHandle.UseShader(null);
}
protected override void DisposeBehavior()
{
base.DisposeBehavior();
_screenshotTexture = null;
ScreenshotTexture = null;
PercentComplete = 1.0f;
}
}
}

View File

@ -1,62 +1,67 @@
using Content.Shared.Flash;
using Content.Shared.Flash.Components;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.GameStates;
using Robust.Shared.Timing;
using Robust.Shared.Player;
namespace Content.Client.Flash
namespace Content.Client.Flash;
public sealed class FlashSystem : SharedFlashSystem
{
public sealed class FlashSystem : SharedFlashSystem
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private FlashOverlay _overlay = default!;
public override void Initialize()
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
base.Initialize();
public override void Initialize()
SubscribeLocalEvent<FlashedComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<FlashedComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<FlashedComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<FlashedComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<FlashedComponent, StatusEffectAddedEvent>(OnStatusAdded);
_overlay = new();
}
private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPlayerAttachedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args)
{
_overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit args)
{
if (_player.LocalEntity == uid)
{
base.Initialize();
SubscribeLocalEvent<FlashableComponent, ComponentHandleState>(OnFlashableHandleState);
_overlayMan.AddOverlay(_overlay);
}
}
private void OnFlashableHandleState(EntityUid uid, FlashableComponent component, ref ComponentHandleState args)
private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShutdown args)
{
if (_player.LocalEntity == uid)
{
if (args.Current is not FlashableComponentState state)
return;
_overlay.PercentComplete = 1.0f;
_overlay.ScreenshotTexture = null;
_overlayMan.RemoveOverlay(_overlay);
}
}
// Yes, this code is awful. I'm just porting it to an entity system so don't blame me.
if (_playerManager.LocalEntity != uid)
{
return;
}
if (state.Time == default)
{
return;
}
// Few things here:
// 1. If a shorter duration flash is applied then don't do anything
// 2. If the client-side time is later than when the flash should've ended don't do anything
var currentTime = _gameTiming.CurTime.TotalSeconds;
var newEndTime = state.Time.TotalSeconds + state.Duration;
var currentEndTime = component.LastFlash.TotalSeconds + component.Duration;
if (currentEndTime > newEndTime)
{
return;
}
if (currentTime > newEndTime)
{
return;
}
component.LastFlash = state.Time;
component.Duration = state.Duration;
var overlay = _overlayManager.GetOverlay<FlashOverlay>();
overlay.ReceiveFlash(component.Duration);
private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args)
{
if (_player.LocalEntity == uid && args.Key == FlashedKey)
{
_overlay.ReceiveFlash();
}
}
}

View File

@ -1,4 +1,5 @@
using Content.Shared.FixedPoint;
using System.Numerics;
using Content.Shared.FixedPoint;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
@ -73,7 +74,7 @@ public sealed class PuddleOverlay : Overlay
}
}
drawHandle.SetTransform(Matrix3.Identity);
drawHandle.SetTransform(Matrix3x2.Identity);
}
private void DrawScreen(in OverlayDrawArgs args)
@ -99,7 +100,7 @@ public sealed class PuddleOverlay : Overlay
if (!gridBounds.Contains(centre))
continue;
var screenCenter = _eyeManager.WorldToScreen(matrix.Transform(centre));
var screenCenter = _eyeManager.WorldToScreen(Vector2.Transform(centre, matrix));
drawHandle.DrawString(_font, screenCenter, debugOverlayData.CurrentVolume.ToString(), Color.White);
}

View File

@ -4,10 +4,12 @@ using Content.Client.Lobby;
using Content.Client.RoundEnd;
using Content.Shared.GameTicking;
using Content.Shared.GameWindow;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.GameTicking.Managers
{
@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();
private Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> _jobsAvailable = new();
private Dictionary<NetEntity, string> _stationNames = new();
[ViewVariables] public bool AreWeReady { get; private set; }
@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
public event Action? InfoBlobUpdated;
public event Action? LobbyStatusUpdated;
public event Action? LobbyLateJoinStatusUpdated;
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>>>? LobbyJobsAvailableUpdated;
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>>? LobbyJobsAvailableUpdated;
public override void Initialize()
{
@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers
// reading the console. E.g., logs like this one could leak the nuke station/grid:
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
#if !DEBUG
_map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
EntityManager.System<SharedMapSystem>().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
#endif
}

View File

@ -11,6 +11,9 @@ public sealed class Box : BoxContainer, IDocumentTag
HorizontalExpand = true;
control = this;
if (args.TryGetValue("Margin", out var margin))
Margin = new Thickness(float.Parse(margin));
if (args.TryGetValue("Orientation", out var orientation))
Orientation = Enum.Parse<LayoutOrientation>(orientation);
else

View File

@ -0,0 +1,49 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Guidebook.Richtext;
[UsedImplicitly]
public sealed class ColorBox : PanelContainer, IDocumentTag
{
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
HorizontalExpand = true;
VerticalExpand = true;
control = this;
if (args.TryGetValue("Margin", out var margin))
Margin = new Thickness(float.Parse(margin));
if (args.TryGetValue("HorizontalAlignment", out var halign))
HorizontalAlignment = Enum.Parse<HAlignment>(halign);
else
HorizontalAlignment = HAlignment.Stretch;
if (args.TryGetValue("VerticalAlignment", out var valign))
VerticalAlignment = Enum.Parse<VAlignment>(valign);
else
VerticalAlignment = VAlignment.Stretch;
var styleBox = new StyleBoxFlat();
if (args.TryGetValue("Color", out var color))
styleBox.BackgroundColor = Color.FromHex(color);
if (args.TryGetValue("OutlineThickness", out var outlineThickness))
styleBox.BorderThickness = new Thickness(float.Parse(outlineThickness));
else
styleBox.BorderThickness = new Thickness(1);
if (args.TryGetValue("OutlineColor", out var outlineColor))
styleBox.BorderColor = Color.FromHex(outlineColor);
else
styleBox.BorderColor = Color.White;
PanelOverride = styleBox;
return true;
}
}

View File

@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.UserInterface.Controls;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Guidebook.Richtext;
[UsedImplicitly]
public sealed class Table : TableContainer, IDocumentTag
{
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
HorizontalExpand = true;
control = this;
if (!args.TryGetValue("Columns", out var columns) || !int.TryParse(columns, out var columnsCount))
{
Logger.Error("Guidebook tag \"Table\" does not specify required property \"Columns.\"");
control = null;
return false;
}
Columns = columnsCount;
return true;
}
}

View File

@ -244,7 +244,7 @@ namespace Content.Client.LateJoin
VerticalAlignment = VAlignment.Center
};
var jobIcon = _prototypeManager.Index<StatusIconPrototype>(prototype.Icon);
var jobIcon = _prototypeManager.Index(prototype.Icon);
icon.Texture = _sprites.Frame0(jobIcon.Icon);
jobSelector.AddChild(icon);
@ -290,7 +290,7 @@ namespace Content.Client.LateJoin
}
}
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> updatedJobs)
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> updatedJobs)
{
foreach (var stationEntries in updatedJobs)
{
@ -337,10 +337,10 @@ namespace Content.Client.LateJoin
public Label JobLabel { get; }
public string JobId { get; }
public string JobLocalisedName { get; }
public uint? Amount { get; private set; }
public int? Amount { get; private set; }
private bool _initialised = false;
public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
public JobButton(Label jobLabel, ProtoId<JobPrototype> jobId, string jobLocalisedName, int? amount)
{
JobLabel = jobLabel;
JobId = jobId;
@ -350,7 +350,7 @@ namespace Content.Client.LateJoin
_initialised = true;
}
public void RefreshLabel(uint? amount)
public void RefreshLabel(int? amount)
{
if (Amount == amount && _initialised)
{

View File

@ -207,8 +207,11 @@ namespace Content.Client.Light
public static Color GetCurrentRgbColor(TimeSpan curTime, TimeSpan offset, Entity<RgbLightControllerComponent> rgb)
{
var delta = (float)(curTime - offset).TotalSeconds;
var entOffset = Math.Abs(rgb.Owner.Id * 0.09817f);
var hue = (delta * rgb.Comp.CycleRate + entOffset) % 1;
return Color.FromHsv(new Vector4(
(float) (((curTime.TotalSeconds - offset.TotalSeconds) * rgb.Comp.CycleRate + Math.Abs(rgb.Owner.Id * 0.1)) % 1),
MathF.Abs(hue),
1.0f,
1.0f,
1.0f

View File

@ -302,7 +302,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
{
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
return _prototypeManager.Index<JobPrototype>(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
}
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)

View File

@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton
.LoadProfileEntity(humanoid, null, true);
var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
if (highPriorityJob != null)
if (highPriorityJob != default)
{
var jobName = prototypeManager.Index<JobPrototype>(highPriorityJob).LocalizedName;
var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
description = $"{description}\n{jobName}";
}
}

View File

@ -7,6 +7,7 @@ using Content.Client.Lobby.UI.Loadouts;
using Content.Client.Lobby.UI.Roles;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
@ -466,38 +467,96 @@ namespace Content.Client.Lobby.UI
var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
if (traits.Count > 0)
{
foreach (var trait in traits)
{
var selector = new TraitPreferenceSelector(trait);
if (Profile?.TraitPreferences.Contains(trait.ID) == true)
{
selector.Preference = true;
}
else
{
selector.Preference = false;
}
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, preference);
SetDirty();
};
TraitsList.AddChild(selector);
}
}
else
if (traits.Count < 1)
{
TraitsList.AddChild(new Label
{
// TODO: Localise
Text = "No traits available :(",
Text = Loc.GetString("humanoid-profile-editor-no-traits"),
FontColorOverride = Color.Gray,
});
return;
}
//Setup model
Dictionary<string, List<string>> model = new();
List<string> defaultTraits = new();
model.Add("default", defaultTraits);
foreach (var trait in traits)
{
if (trait.Category == null)
{
defaultTraits.Add(trait.ID);
continue;
}
if (!model.ContainsKey(trait.Category))
{
model.Add(trait.Category, new());
}
model[trait.Category].Add(trait.ID);
}
//Create UI view from model
foreach (var (categoryId, traitId) in model)
{
TraitCategoryPrototype? category = null;
if (categoryId != "default")
{
category = _prototypeManager.Index<TraitCategoryPrototype>(categoryId);
// Label
TraitsList.AddChild(new Label
{
Text = Loc.GetString(category.Name),
Margin = new Thickness(0, 10, 0, 0),
StyleClasses = { StyleBase.StyleClassLabelHeading },
});
}
List<TraitPreferenceSelector?> selectors = new();
var selectionCount = 0;
foreach (var traitProto in traitId)
{
var trait = _prototypeManager.Index<TraitPrototype>(traitProto);
var selector = new TraitPreferenceSelector(trait);
selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true;
if (selector.Preference)
selectionCount += trait.Cost;
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, categoryId, preference);
SetDirty();
RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
};
selectors.Add(selector);
}
// Selection counter
if (category is { MaxTraitPoints: >= 0 })
{
TraitsList.AddChild(new Label
{
Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)),
FontColorOverride = Color.Gray
});
}
foreach (var selector in selectors)
{
if (selector == null)
continue;
if (category is { MaxTraitPoints: >= 0 } &&
selector.Cost + selectionCount > category.MaxTraitPoints)
{
selector.Checkbox.Label.FontColorOverride = Color.Red;
}
TraitsList.AddChild(selector);
}
}
}
@ -562,7 +621,8 @@ namespace Content.Client.Lobby.UI
selector.Setup(items, title, 250, description);
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
if (!_requirements.CheckRoleTime(requirements, out var reason))
{
selector.LockRequirements(reason);
Profile = Profile?.WithAntagPreference(antag.ID, false);
@ -719,8 +779,17 @@ namespace Content.Client.Lobby.UI
_jobPriorities.Clear();
var firstCategory = true;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
// Get all displayed departments
var departments = new List<DepartmentPrototype>();
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.EditorHidden)
continue;
departments.Add(department);
}
departments.Sort(DepartmentUIComparer.Instance);
var items = new[]
{
@ -774,7 +843,7 @@ namespace Content.Client.Lobby.UI
JobList.AddChild(category);
}
var jobs = department.Roles.Select(jobId => _prototypeManager.Index<JobPrototype>(jobId))
var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId))
.Where(job => job.SetPreference)
.ToArray();
@ -797,7 +866,7 @@ namespace Content.Client.Lobby.UI
TextureScale = new Vector2(2, 2),
VerticalAlignment = VAlignment.Center
};
var jobIcon = _prototypeManager.Index<StatusIconPrototype>(job.Icon);
var jobIcon = _prototypeManager.Index(job.Icon);
icon.Texture = jobIcon.Icon.Frame0();
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
@ -821,13 +890,15 @@ namespace Content.Client.Lobby.UI
if (jobId == job.ID)
{
other.Select(selectedPrio);
continue;
}
else if (selectedJobPrio == JobPriority.High && (JobPriority) other.Selected == JobPriority.High)
{
// Lower any other high priorities to medium.
other.Select((int) JobPriority.Medium);
Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium);
}
if (selectedJobPrio != JobPriority.High || (JobPriority) other.Selected != JobPriority.High)
continue;
// Lower any other high priorities to medium.
other.Select((int)JobPriority.Medium);
Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium);
}
// TODO: Only reload on high change (either to or from).
@ -932,6 +1003,11 @@ namespace Content.Client.Lobby.UI
SetDirty();
ReloadPreview();
};
if (Profile is null)
return;
UpdateJobPriorities();
}
private void OnFlavorTextChange(string content)

View File

@ -2,6 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BoxContainer Name="Container"
Orientation="Horizontal">
<CheckBox Name="Checkbox"/>
<CheckBox Name="Checkbox" Access="Public"/>
</BoxContainer>
</Control>

View File

@ -9,6 +9,8 @@ namespace Content.Client.Lobby.UI.Roles;
[GenerateTypedNameReferences]
public sealed partial class TraitPreferenceSelector : Control
{
public int Cost;
public bool Preference
{
get => Checkbox.Pressed;
@ -20,7 +22,12 @@ public sealed partial class TraitPreferenceSelector : Control
public TraitPreferenceSelector(TraitPrototype trait)
{
RobustXamlLoader.Load(this);
Checkbox.Text = Loc.GetString(trait.Name);
var text = trait.Cost != 0 ? $"[{trait.Cost}] " : "";
text += Loc.GetString(trait.Name);
Cost = trait.Cost;
Checkbox.Text = text;
Checkbox.OnToggled += OnCheckBoxToggled;
if (trait.Description is { } desc)

View File

@ -101,7 +101,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
return;
StartDragging(gridUid, Transform(gridUid).InvWorldMatrix.Transform(mousePos.Position));
StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix));
}
if (!TryComp(_dragging, out TransformComponent? xform))
@ -116,7 +116,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem
return;
}
var localToWorld = xform.WorldMatrix.Transform(_localPosition);
var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix);
if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return;

View File

@ -2,94 +2,115 @@
using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
using Content.Shared.Buckle.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.StatusEffect;
using Content.Shared.Stunnable;
using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
public sealed class WaddleAnimationSystem : EntitySystem
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
base.Initialize();
SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned);
SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
}
private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
{
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
if (!_timing.IsFirstTimePredicted)
{
return;
}
if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
{
var stopped = new StoppedWaddlingEvent(entity);
RaiseLocalEvent(entity, ref stopped);
return;
}
// Only start waddling if we're not currently AND we're actually moving.
if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
return;
var started = new StartedWaddlingEvent(entity);
RaiseLocalEvent(entity, ref started);
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StartWaddling((GetEntity(msg.Entity), comp));
}
private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (_animation.HasRunningAnimation(uid, component.KeyName))
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StopWaddling((GetEntity(msg.Entity), comp));
}
private void StartWaddling(Entity<WaddleAnimationComponent> entity)
{
if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
if (!TryComp<InputMoverComponent>(uid, out var mover))
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
if (_gravity.IsWeightless(uid))
if (_gravity.IsWeightless(entity.Owner))
return;
if (!_actionBlocker.CanMove(uid, mover))
if (!_actionBlocker.CanMove(entity.Owner, mover))
return;
// Do nothing if buckled in
if (_buckle.IsBuckled(uid))
if (_buckle.IsBuckled(entity.Owner))
return;
// Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(uid))
if (_mobState.IsIncapacitated(entity.Owner))
return;
var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
component.LastStep = !component.LastStep;
component.IsCurrentlyWaddling = true;
private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
{
return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
}
private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
{
return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
}
private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
{
if (args.Key != entity.Comp.KeyName)
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
_animation.Stop(entity.Owner, entity.Comp.KeyName);
if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
return;
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
}
private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
{
entity.Comp.LastStep = !entity.Comp.LastStep;
var anim = new Animation()
{
@ -116,58 +137,13 @@ public sealed class WaddleAnimationSystem : EntitySystem
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
}
}
}
};
_animation.Play(uid, anim, component.KeyName);
}
private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
{
StopWaddling(uid, component);
}
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
{
var started = new StartedWaddlingEvent(uid);
RaiseLocalEvent(uid, ref started);
}
private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
{
StopWaddling(uid, component);
}
private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
{
StopWaddling(uid, component);
}
private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
{
StopWaddling(uid, component);
}
private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
{
if (!component.IsCurrentlyWaddling)
return;
_animation.Stop(uid, component.KeyName);
if (!TryComp<SpriteComponent>(uid, out var sprite))
{
return;
}
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
component.IsCurrentlyWaddling = false;
_animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
}

View File

@ -223,7 +223,7 @@ namespace Content.Client.NPC
foreach (var crumb in chunk.Value)
{
var crumbMapPos = worldMatrix.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates));
var crumbMapPos = Vector2.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates), worldMatrix);
var distance = (crumbMapPos - mouseWorldPos.Position).Length();
if (distance < nearestDistance)
@ -292,7 +292,7 @@ namespace Content.Client.NPC
foreach (var poly in tile)
{
if (poly.Box.Contains(invGridMatrix.Transform(mouseWorldPos.Position)))
if (poly.Box.Contains(Vector2.Transform(mouseWorldPos.Position, invGridMatrix)))
{
nearest = poly;
break;
@ -488,7 +488,7 @@ namespace Content.Client.NPC
if (neighborMap.MapId != args.MapId)
continue;
neighborPos = invMatrix.Transform(neighborMap.Position);
neighborPos = Vector2.Transform(neighborMap.Position, invMatrix);
}
else
{
@ -576,7 +576,7 @@ namespace Content.Client.NPC
}
}
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
}
}
}

View File

@ -199,7 +199,7 @@ namespace Content.Client.NodeContainer
}
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
_gridIndex.Clear();
}

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@ -43,7 +44,7 @@ public sealed partial class DogVisionOverlay : Overlay
var worldHandle = args.WorldHandle;
var viewport = args.WorldBounds;
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_dogVisionShader);
worldHandle.DrawRect(viewport, Color.White);
worldHandle.UseShader(null); // important - as of writing, construction overlay breaks without this

View File

@ -22,6 +22,7 @@ public sealed class TargetOutlineSystem : EntitySystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
private bool _enabled = false;
@ -137,7 +138,7 @@ public sealed class TargetOutlineSystem : EntitySystem
// check the entity whitelist
if (valid && Whitelist != null)
valid = Whitelist.IsValid(entity);
valid = _whitelistSystem.IsWhitelistPass(Whitelist, entity);
// and check the cancellable event
if (valid && ValidationEvent != null)

View File

@ -1,14 +1,17 @@
using System.Numerics;
using Content.Client.StatusIcon;
using Content.Client.UserInterface.Systems;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using static Robust.Shared.Maths.Color;
namespace Content.Client.Overlays;
@ -19,19 +22,27 @@ namespace Content.Client.Overlays;
public sealed class EntityHealthBarOverlay : Overlay
{
private readonly IEntityManager _entManager;
private readonly IPrototypeManager _prototype;
private readonly SharedTransformSystem _transform;
private readonly MobStateSystem _mobStateSystem;
private readonly MobThresholdSystem _mobThresholdSystem;
private readonly StatusIconSystem _statusIconSystem;
private readonly ProgressColorSystem _progressColor;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public HashSet<string> DamageContainers = new();
public ProtoId<StatusIconPrototype>? StatusIcon;
public EntityHealthBarOverlay(IEntityManager entManager)
public EntityHealthBarOverlay(IEntityManager entManager, IPrototypeManager prototype)
{
_entManager = entManager;
_prototype = prototype;
_transform = _entManager.System<SharedTransformSystem>();
_mobStateSystem = _entManager.System<MobStateSystem>();
_mobThresholdSystem = _entManager.System<MobThresholdSystem>();
_statusIconSystem = _entManager.System<StatusIconSystem>();
_progressColor = _entManager.System<ProgressColorSystem>();
}
@ -42,8 +53,9 @@ public sealed class EntityHealthBarOverlay : Overlay
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
const float scale = 1f;
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3.CreateRotation(-rotation);
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
_prototype.TryIndex(StatusIcon, out var statusIcon);
var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
while (query.MoveNext(out var uid,
@ -52,41 +64,33 @@ public sealed class EntityHealthBarOverlay : Overlay
out var damageableComponent,
out var spriteComponent))
{
if (_entManager.TryGetComponent<MetaDataComponent>(uid, out var metaDataComponent) &&
metaDataComponent.Flags.HasFlag(MetaDataFlags.InContainer))
{
if (statusIcon != null && !_statusIconSystem.IsVisible((uid, _entManager.GetComponent<MetaDataComponent>(uid)), statusIcon))
continue;
}
// We want the stealth user to still be able to see his health bar himself
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapID != args.MapId)
{
continue;
}
if (damageableComponent.DamageContainerID == null || !DamageContainers.Contains(damageableComponent.DamageContainerID))
{
continue;
}
// we use the status icon component bounds if specified otherwise use sprite
var bounds = _entManager.GetComponentOrNull<StatusIconComponent>(uid)?.Bounds ?? spriteComponent.Bounds;
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
if (!bounds.Translated(worldPos).Intersects(args.WorldAABB))
{
continue;
}
// we are all progressing towards death every day
if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress)
continue;
var worldPosition = _transform.GetWorldPosition(xform);
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
handle.SetTransform(matty);
@ -115,7 +119,7 @@ public sealed class EntityHealthBarOverlay : Overlay
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
}
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
}
/// <summary>

View File

@ -19,10 +19,10 @@ public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem<ShowCrimi
private void OnGetStatusIconsEvent(EntityUid uid, CriminalRecordComponent component, ref GetStatusIconsEvent ev)
{
if (!IsActive || ev.InContainer)
if (!IsActive)
return;
if (_prototype.TryIndex<StatusIconPrototype>(component.StatusIcon.Id, out var iconPrototype))
if (_prototype.TryIndex(component.StatusIcon, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype);
}
}

View File

@ -2,6 +2,8 @@ using Content.Shared.Inventory.Events;
using Content.Shared.Overlays;
using Robust.Client.Graphics;
using System.Linq;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
@ -11,6 +13,7 @@ namespace Content.Client.Overlays;
public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComponent>
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private EntityHealthBarOverlay _overlay = default!;
@ -18,16 +21,21 @@ public sealed class ShowHealthBarsSystem : EquipmentHudSystem<ShowHealthBarsComp
{
base.Initialize();
_overlay = new(EntityManager);
_overlay = new(EntityManager, _prototype);
}
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthBarsComponent> component)
{
base.UpdateInternal(component);
foreach (var damageContainerId in component.Components.SelectMany(x => x.DamageContainers))
foreach (var comp in component.Components)
{
_overlay.DamageContainers.Add(damageContainerId);
foreach (var damageContainerId in comp.DamageContainers)
{
_overlay.DamageContainers.Add(damageContainerId);
}
_overlay.StatusIcon = comp.HealthStatusIcon;
}
if (!_overlayMan.HasOverlay<EntityHealthBarOverlay>())

View File

@ -24,7 +24,6 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
base.Initialize();
SubscribeLocalEvent<DamageableComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
}
protected override void UpdateInternal(RefreshEquipmentHudEvent<ShowHealthIconsComponent> component)
@ -46,7 +45,7 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem<ShowHealthIconsCo
private void OnGetStatusIconsEvent(Entity<DamageableComponent> entity, ref GetStatusIconsEvent args)
{
if (!IsActive || args.InContainer)
if (!IsActive)
return;
var healthIcons = DecideHealthIcons(entity);

View File

@ -18,7 +18,7 @@ public sealed class ShowHungerIconsSystem : EquipmentHudSystem<ShowHungerIconsCo
private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent component, ref GetStatusIconsEvent ev)
{
if (!IsActive || ev.InContainer)
if (!IsActive)
return;
if (_hunger.TryGetStatusIconPrototype(component, out var iconPrototype))

View File

@ -25,7 +25,7 @@ public sealed class ShowJobIconsSystem : EquipmentHudSystem<ShowJobIconsComponen
private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent ev)
{
if (!IsActive || ev.InContainer)
if (!IsActive)
return;
var iconId = JobIconForNoId;

View File

@ -19,10 +19,10 @@ public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShiel
private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev)
{
if (!IsActive || ev.InContainer)
if (!IsActive)
return;
if (_prototype.TryIndex<StatusIconPrototype>(component.MindShieldStatusIcon.Id, out var iconPrototype))
if (_prototype.TryIndex(component.MindShieldStatusIcon, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype);
}
}

View File

@ -19,11 +19,10 @@ public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem<ShowSyndicateI
private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent component, ref GetStatusIconsEvent ev)
{
if (!IsActive || ev.InContainer)
if (!IsActive)
return;
if (_prototype.TryIndex<StatusIconPrototype>(component.SyndStatusIcon, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype);
}
}

View File

@ -18,7 +18,7 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsCo
private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent component, ref GetStatusIconsEvent ev)
{
if (!IsActive || ev.InContainer)
if (!IsActive)
return;
if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype))

View File

@ -7,7 +7,7 @@ namespace Content.Client.Overlays;
public sealed partial class StencilOverlay
{
private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix)
private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix)
{
var worldHandle = args.WorldHandle;
var renderScale = args.Viewport.RenderScale.X;
@ -16,7 +16,7 @@ public sealed partial class StencilOverlay
var length = zoom.X;
var bufferRange = MathF.Min(10f, rangeComp.Range);
var pixelCenter = invMatrix.Transform(rangeComp.Origin);
var pixelCenter = Vector2.Transform(rangeComp.Origin, invMatrix);
// Something something offset?
var vertical = args.Viewport.Size.Y;
@ -44,7 +44,7 @@ public sealed partial class StencilOverlay
worldHandle.DrawRect(localAABB, Color.White);
}, Color.Transparent);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
var curTime = _timing.RealTime;

View File

@ -10,7 +10,7 @@ public sealed partial class StencilOverlay
{
private List<Entity<MapGridComponent>> _grids = new();
private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix)
private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3x2 invMatrix)
{
var worldHandle = args.WorldHandle;
var mapId = args.MapId;
@ -32,7 +32,7 @@ public sealed partial class StencilOverlay
foreach (var grid in _grids)
{
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
Matrix3.Multiply(in matrix, in invMatrix, out var matty);
var matty = Matrix3x2.Multiply(matrix, invMatrix);
worldHandle.SetTransform(matty);
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
@ -52,7 +52,7 @@ public sealed partial class StencilOverlay
}, Color.Transparent);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
var curTime = _timing.RealTime;
@ -62,7 +62,7 @@ public sealed partial class StencilOverlay
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha));
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
worldHandle.UseShader(null);
}
}

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Content.Client.Parallax;
using Content.Client.Weather;
using Content.Shared.Salvage;
@ -72,6 +73,6 @@ public sealed partial class StencilOverlay : Overlay
}
args.WorldHandle.UseShader(null);
args.WorldHandle.SetTransform(Matrix3.Identity);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
}

View File

@ -50,7 +50,7 @@ public sealed partial class StampLabel : Label
base.Draw(handle);
// Restore a sane transform+shader
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
handle.UseShader(null);
}
}

View File

@ -53,7 +53,7 @@ public sealed partial class StampWidget : PanelContainer
base.Draw(handle);
// Restore a sane transform+shader
handle.SetTransform(Matrix3.Identity);
handle.SetTransform(Matrix3x2.Identity);
handle.UseShader(null);
}
}

View File

@ -218,7 +218,7 @@ public partial class NavMapControl : MapGridControl
// Convert to a world position
var unscaledPosition = (localPosition - MidPointVector) / MinimapScale;
var worldPosition = _transformSystem.GetWorldMatrix(_xform).Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset);
var worldPosition = Vector2.Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset, _transformSystem.GetWorldMatrix(_xform));
// Find closest tracked entity in range
var closestEntity = NetEntity.Invalid;
@ -401,7 +401,7 @@ public partial class NavMapControl : MapGridControl
if (mapPos.MapId != MapId.Nullspace)
{
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset;
position = ScalePosition(new Vector2(position.X, -position.Y));
handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color);
@ -422,7 +422,7 @@ public partial class NavMapControl : MapGridControl
if (mapPos.MapId != MapId.Nullspace)
{
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset;
position = ScalePosition(new Vector2(position.X, -position.Y));
var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale);

View File

@ -107,7 +107,13 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
if (player == null)
return true;
return CheckRoleTime(job.Requirements, out reason);
return CheckRoleTime(job, out reason);
}
public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
{
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
return CheckRoleTime(reqs, out reason);
}
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.Examine;
using Robust.Client.Graphics;
using Robust.Client.Player;
@ -55,7 +56,7 @@ public sealed class PopupOverlay : Overlay
if (args.ViewportControl == null)
return;
args.DrawingHandle.SetTransform(Matrix3.Identity);
args.DrawingHandle.SetTransform(Matrix3x2.Identity);
args.DrawingHandle.UseShader(_shader);
var scale = _configManager.GetCVar(CVars.DisplayUIScale);
@ -90,7 +91,7 @@ public sealed class PopupOverlay : Overlay
e => e == popup.InitialPos.EntityId || e == ourEntity, entMan: _entManager))
continue;
var pos = matrix.Transform(mapPos.Position);
var pos = Vector2.Transform(mapPos.Position, matrix);
_controller.DrawPopup(popup, worldHandle, pos, scale);
}
}

View File

@ -102,7 +102,8 @@ public sealed partial class PowerMonitoringWindow
button.ToolTip = Loc.GetString(name);
// Update power value
button.PowerValue.Text = Loc.GetString("power-monitoring-window-value", ("value", entry.PowerValue));
// Don't use SI prefixes, just give the number in W, so that it is readily apparent which consumer is using a lot of power.
button.PowerValue.Text = Loc.GetString("power-monitoring-window-button-value", ("value", Math.Round(entry.PowerValue).ToString("N0")));
}
private void UpdateEntrySourcesOrLoads(BoxContainer masterContainer, BoxContainer currentContainer, PowerMonitoringConsoleEntry[]? entries, SpriteSpecifier.Texture icon)
@ -480,6 +481,7 @@ public sealed class PowerMonitoringButton : Button
PowerValue = new Label()
{
HorizontalAlignment = HAlignment.Right,
Align = Label.AlignMode.Right,
SetWidth = 72f,
Margin = new Thickness(10, 0, 0, 0),
ClipText = true,

View File

@ -1,44 +1,37 @@
using Content.Shared.Antag;
using Content.Shared.Revolutionary.Components;
using Content.Shared.Ghost;
using Content.Shared.Revolutionary;
using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes;
namespace Content.Client.Revolutionary;
/// <summary>
/// Used for the client to get status icons from other revs.
/// </summary>
public sealed class RevolutionarySystem : EntitySystem
public sealed class RevolutionarySystem : SharedRevolutionarySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RevolutionaryComponent, CanDisplayStatusIconsEvent>(OnCanShowRevIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, CanDisplayStatusIconsEvent>(OnCanShowRevIcon);
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetHeadRevIcon);
}
/// <summary>
/// Determine whether a client should display the rev icon.
/// </summary>
private void OnCanShowRevIcon<T>(EntityUid uid, T comp, ref CanDisplayStatusIconsEvent args) where T : IAntagStatusIconComponent
private void GetRevIcon(Entity<RevolutionaryComponent> ent, ref GetStatusIconsEvent args)
{
args.Cancelled = !CanDisplayIcon(args.User, comp.IconVisibleToGhost);
if (HasComp<HeadRevolutionaryComponent>(ent))
return;
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype))
args.StatusIcons.Add(iconPrototype);
}
/// <summary>
/// The criteria that determine whether a client should see Rev/Head rev icons.
/// </summary>
private bool CanDisplayIcon(EntityUid? uid, bool visibleToGhost)
private void GetHeadRevIcon(Entity<HeadRevolutionaryComponent> ent, ref GetStatusIconsEvent args)
{
if (HasComp<HeadRevolutionaryComponent>(uid) || HasComp<RevolutionaryComponent>(uid))
return true;
if (visibleToGhost && HasComp<GhostComponent>(uid))
return true;
return HasComp<ShowRevIconsComponent>(uid);
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype))
args.StatusIcons.Add(iconPrototype);
}
}

View File

@ -30,13 +30,12 @@ public sealed class SSDIndicatorSystem : EntitySystem
{
if (component.IsSSD &&
_cfg.GetCVar(CCVars.ICShowSSDIndicator) &&
!args.InContainer &&
!_mobState.IsDead(uid) &&
!HasComp<ActiveNPCComponent>(uid) &&
TryComp<MindContainerComponent>(uid, out var mindContainer) &&
mindContainer.ShowExamineInfo)
{
args.StatusIcons.Add(_prototype.Index<StatusIconPrototype>(component.Icon));
args.StatusIcons.Add(_prototype.Index(component.Icon));
}
}
}

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.Shuttles.Events;
using Content.Shared.Shuttles.Systems;
using Robust.Client.Graphics;
@ -75,6 +76,6 @@ public sealed class EmergencyShuttleOverlay : Overlay
args.WorldHandle.SetTransform(xform.WorldMatrix);
args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100));
args.WorldHandle.SetTransform(Matrix3.Identity);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
}

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Shuttles.Components;
using Robust.Client.AutoGenerated;
@ -115,7 +116,7 @@ public partial class BaseShuttleControl : MapGridControl
}
}
protected void DrawGrid(DrawingHandleScreen handle, Matrix3 matrix, Entity<MapGridComponent> grid, Color color, float alpha = 0.01f)
protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 matrix, Entity<MapGridComponent> grid, Color color, float alpha = 0.01f)
{
var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp);
var minimapScale = MinimapScale;
@ -289,7 +290,7 @@ public partial class BaseShuttleControl : MapGridControl
public float MinimapScale;
public Vector2 MidPoint;
public Matrix3 Matrix;
public Matrix3x2 Matrix;
public List<Vector2> Vertices;
public Vector2[] ScaledVertices;
@ -297,7 +298,7 @@ public partial class BaseShuttleControl : MapGridControl
public void Execute(int index)
{
var vert = Vertices[index];
var adjustedVert = Matrix.Transform(vert);
var adjustedVert = Vector2.Transform(vert, Matrix);
adjustedVert = adjustedVert with { Y = -adjustedVert.Y };
var scaledVert = ScalePosition(adjustedVert, MinimapScale, MidPoint);

View File

@ -1,3 +1,4 @@
using System.Numerics;
using Content.Shared.Shuttles.BUIStates;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
@ -68,7 +69,7 @@ public sealed partial class NavScreen : BoxContainer
}
var (_, worldRot, worldMatrix) = _xformSystem.GetWorldPositionRotationMatrix(gridXform);
var worldPos = worldMatrix.Transform(gridBody.LocalCenter);
var worldPos = Vector2.Transform(gridBody.LocalCenter, worldMatrix);
// Get the positive reduced angle.
var displayRot = -worldRot.Reduced();

View File

@ -108,10 +108,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
var gridNent = EntManager.GetNetEntity(GridEntity);
var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value);
var ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner);
var dockMatrix = Matrix3.CreateTransform(_coordinates.Value.Position, Angle.Zero);
Matrix3.Multiply(dockMatrix, ourGridMatrix, out var offsetMatrix);
var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero);
var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix);
offsetMatrix = offsetMatrix.Invert();
Matrix3x2.Invert(worldFromDock, out var offsetMatrix);
// Draw nearby grids
var controlBounds = PixelSizeBox;
@ -137,7 +137,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
continue;
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
Matrix3.Multiply(in gridMatrix, in offsetMatrix, out var matty);
var matty = Matrix3x2.Multiply(gridMatrix, offsetMatrix);
var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp);
DrawGrid(handle, matty, grid, color);
@ -151,23 +151,23 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
if (ViewedDock == dock.Entity)
continue;
var position = matty.Transform(dock.Coordinates.Position);
var position = Vector2.Transform(dock.Coordinates.Position, matty);
var otherDockRotation = Matrix3.CreateRotation(dock.Angle);
var otherDockRotation = Matrix3Helpers.CreateRotation(dock.Angle);
var scaledPos = ScalePosition(position with {Y = -position.Y});
if (!controlBounds.Contains(scaledPos.Floored()))
continue;
// Draw the dock's collision
var collisionBL = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(-0.2f, -0.7f)));
var collisionBR = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(0.2f, -0.7f)));
var collisionTR = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(0.2f, -0.5f)));
var collisionTL = matty.Transform(dock.Coordinates.Position +
otherDockRotation.Transform(new Vector2(-0.2f, -0.5f)));
var collisionBL = Vector2.Transform(dock.Coordinates.Position +
Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), matty);
var collisionBR = Vector2.Transform(dock.Coordinates.Position +
Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), matty);
var collisionTR = Vector2.Transform(dock.Coordinates.Position +
Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), matty);
var collisionTL = Vector2.Transform(dock.Coordinates.Position +
Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), matty);
var verts = new[]
{
@ -195,10 +195,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockConnection);
// Draw the dock itself
var dockBL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f));
var dockBR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f));
var dockTR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f));
var dockTL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f));
var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), matty);
var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), matty);
var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), matty);
var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), matty);
verts = new[]
{
@ -308,14 +308,14 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
// Draw the dock's collision
var invertedPosition = Vector2.Zero;
invertedPosition.Y = -invertedPosition.Y;
var rotation = Matrix3.CreateRotation(-_angle.Value + MathF.PI);
var rotation = Matrix3Helpers.CreateRotation(-_angle.Value + MathF.PI);
var ourDockConnection = new UIBox2(
ScalePosition(rotation.Transform(new Vector2(-0.2f, -0.7f))),
ScalePosition(rotation.Transform(new Vector2(0.2f, -0.5f))));
ScalePosition(Vector2.Transform(new Vector2(-0.2f, -0.7f), rotation)),
ScalePosition(Vector2.Transform(new Vector2(0.2f, -0.5f), rotation)));
var ourDock = new UIBox2(
ScalePosition(rotation.Transform(new Vector2(-0.5f, 0.5f))),
ScalePosition(rotation.Transform(new Vector2(0.5f, -0.5f))));
ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)),
ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation)));
var dockColor = Color.Magenta;
var connectionColor = Color.Pink;

View File

@ -114,7 +114,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var beaconsOnly = EntManager.TryGetComponent(mapUid, out FTLDestinationComponent? destComp) &&
destComp.BeaconsOnly;
var mapTransform = Matrix3.CreateInverseTransform(Offset, Angle.Zero);
var mapTransform = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero);
if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _))
{
@ -203,7 +203,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
/// </summary>
/// <param name="mapObjects"></param>
/// <returns></returns>
private List<IMapObject> GetViewportMapObjects(Matrix3 matty, List<IMapObject> mapObjects)
private List<IMapObject> GetViewportMapObjects(Matrix3x2 matty, List<IMapObject> mapObjects)
{
var results = new List<IMapObject>();
var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale));
@ -217,7 +217,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var mapCoords = _shuttles.GetMapCoordinates(mapObj);
var relativePos = matty.Transform(mapCoords.Position);
var relativePos = Vector2.Transform(mapCoords.Position, matty);
relativePos = relativePos with { Y = -relativePos.Y };
var uiPosition = ScalePosition(relativePos);
@ -250,7 +250,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
DrawParallax(handle);
var viewedMapUid = _mapManager.GetMapEntityId(ViewingMap);
var matty = Matrix3.CreateInverseTransform(Offset, Angle.Zero);
var matty = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero);
var realTime = _timing.RealTime;
var viewBox = new Box2(Offset - WorldRangeVector, Offset + WorldRangeVector);
var viewportObjects = GetViewportMapObjects(matty, mapObjects);
@ -267,7 +267,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform);
gridPos = Maps.GetGridPosition((gridUid, gridPhysics), gridPos, gridRot);
var gridRelativePos = matty.Transform(gridPos);
var gridRelativePos = Vector2.Transform(gridPos, matty);
gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
var gridUiPos = ScalePosition(gridRelativePos);
@ -296,7 +296,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
continue;
}
var adjustedPos = matty.Transform(mapCoords.Position);
var adjustedPos = Vector2.Transform(mapCoords.Position, matty);
var localPos = ScalePosition(adjustedPos with { Y = -adjustedPos.Y});
handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor.WithAlpha(0.05f));
handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor, filled: false);
@ -319,7 +319,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
foreach (var (beaconName, coords, mapO) in GetBeacons(viewportObjects, matty, controlLocalBounds))
{
var localPos = matty.Transform(coords.Position);
var localPos = Vector2.Transform(coords.Position, matty);
localPos = localPos with { Y = -localPos.Y };
var beaconUiPos = ScalePosition(localPos);
var mapObject = GetMapObject(localPos, Angle.Zero, scale: 0.75f, scalePosition: true);
@ -360,7 +360,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(grid.Owner);
gridPos = Maps.GetGridPosition((grid, gridPhysics), gridPos, gridRot);
var gridRelativePos = matty.Transform(gridPos);
var gridRelativePos = Vector2.Transform(gridPos, matty);
gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
var gridUiPos = ScalePosition(gridRelativePos);
@ -439,7 +439,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
var color = ftlFree ? Color.LimeGreen : Color.Magenta;
var gridRelativePos = matty.Transform(gridPos);
var gridRelativePos = Vector2.Transform(gridPos, matty);
gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y };
var gridUiPos = ScalePosition(gridRelativePos);
@ -512,7 +512,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
/// <summary>
/// Returns the beacons that intersect the viewport.
/// </summary>
private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List<IMapObject> mapObjs, Matrix3 mapTransform, UIBox2i area)
private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List<IMapObject> mapObjs, Matrix3x2 mapTransform, UIBox2i area)
{
foreach (var mapO in mapObjs)
{
@ -520,7 +520,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
continue;
var beaconCoords = EntManager.GetCoordinates(beacon.Coordinates).ToMap(EntManager, _xformSystem);
var position = mapTransform.Transform(beaconCoords.Position);
var position = Vector2.Transform(beaconCoords.Position, mapTransform);
var localPos = ScalePosition(position with {Y = -position.Y});
// If beacon not on screen then ignore it.
@ -557,7 +557,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
return mapObj;
}
private bool TryGetBeacon(IEnumerable<IMapObject> mapObjects, Matrix3 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos)
private bool TryGetBeacon(IEnumerable<IMapObject> mapObjects, Matrix3x2 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos)
{
// In pixels
const float BeaconSnapRange = 32f;
@ -579,7 +579,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
if (!_shuttles.CanFTLBeacon(beaconObj.Coordinates))
continue;
var position = mapTransform.Transform(beaconCoords.Position);
var position = Vector2.Transform(beaconCoords.Position, mapTransform);
var localPos = ScalePosition(position with {Y = -position.Y});
// If beacon not on screen then ignore it.

View File

@ -137,10 +137,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
var offset = _coordinates.Value.Position;
var posMatrix = Matrix3.CreateTransform(offset, _rotation.Value);
var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value);
var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId);
Matrix3.Multiply(posMatrix, ourEntMatrix, out var ourWorldMatrix);
var ourWorldMatrixInvert = ourWorldMatrix.Invert();
var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert);
// Draw our grid in detail
var ourGridId = xform.GridUid;
@ -148,7 +148,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
fixturesQuery.HasComponent(ourGridId.Value))
{
var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value);
Matrix3.Multiply(in ourGridMatrix, in ourWorldMatrixInvert, out var matrix);
var matrix = Matrix3x2.Multiply(ourGridMatrix, ourWorldMatrixInvert);
var color = _shuttles.GetIFFColor(ourGridId.Value, self: true);
DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color);
@ -194,7 +194,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
continue;
var gridMatrix = _transform.GetWorldMatrix(gUid);
Matrix3.Multiply(in gridMatrix, in ourWorldMatrixInvert, out var matty);
var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
var color = _shuttles.GetIFFColor(grid, self: false, iff);
// Others default:
@ -207,7 +207,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
{
var gridBounds = grid.Comp.LocalAABB;
var gridCentre = matty.Transform(gridBody.LocalCenter);
var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
gridCentre.Y = -gridCentre.Y;
var distance = gridCentre.Length();
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
@ -242,7 +242,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
}
}
private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix)
private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matrix)
{
if (!ShowDocks)
return;
@ -255,7 +255,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
foreach (var state in docks)
{
var position = state.Coordinates.Position;
var uiPosition = matrix.Transform(position);
var uiPosition = Vector2.Transform(position, matrix);
if (uiPosition.Length() > (WorldRange * 2f) - DockScale)
continue;
@ -264,10 +264,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
var verts = new[]
{
matrix.Transform(position + new Vector2(-DockScale, -DockScale)),
matrix.Transform(position + new Vector2(DockScale, -DockScale)),
matrix.Transform(position + new Vector2(DockScale, DockScale)),
matrix.Transform(position + new Vector2(-DockScale, DockScale)),
Vector2.Transform(position + new Vector2(-DockScale, -DockScale), matrix),
Vector2.Transform(position + new Vector2(DockScale, -DockScale), matrix),
Vector2.Transform(position + new Vector2(DockScale, DockScale), matrix),
Vector2.Transform(position + new Vector2(-DockScale, DockScale), matrix),
};
for (var i = 0; i < verts.Length; i++)

View File

@ -39,13 +39,13 @@ public sealed class StatusIconOverlay : Overlay
var eyeRot = args.Viewport.Eye?.Rotation ?? default;
var xformQuery = _entity.GetEntityQuery<TransformComponent>();
var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1));
var rotationMatrix = Matrix3.CreateRotation(-eyeRot);
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(1, 1));
var rotationMatrix = Matrix3Helpers.CreateRotation(-eyeRot);
var query = _entity.AllEntityQueryEnumerator<StatusIconComponent, SpriteComponent, TransformComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta))
{
if (xform.MapID != args.MapId)
if (xform.MapID != args.MapId || !sprite.Visible)
continue;
var bounds = comp.Bounds ?? sprite.Bounds;
@ -59,9 +59,9 @@ public sealed class StatusIconOverlay : Overlay
if (icons.Count == 0)
continue;
var worldMatrix = Matrix3.CreateTranslation(worldPos);
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
var worldMatrix = Matrix3Helpers.CreateTranslation(worldPos);
var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
handle.SetTransform(matty);
var countL = 0;
@ -72,6 +72,8 @@ public sealed class StatusIconOverlay : Overlay
foreach (var proto in icons)
{
if (!_statusIcon.IsVisible((uid, meta), proto))
continue;
var curTime = _timing.RealTime;
var texture = _sprite.GetFrame(proto.Icon, curTime);

View File

@ -1,7 +1,11 @@
using Content.Shared.CCVar;
using Content.Shared.Ghost;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Stealth.Components;
using Content.Shared.Whitelist;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
namespace Content.Client.StatusIcon;
@ -13,6 +17,8 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
{
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
private bool _globalEnabled;
private bool _localEnabled;
@ -54,10 +60,34 @@ public sealed class StatusIconSystem : SharedStatusIconSystem
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
return list;
var inContainer = (meta.Flags & MetaDataFlags.InContainer) != 0;
var ev = new GetStatusIconsEvent(list, inContainer);
var ev = new GetStatusIconsEvent(list);
RaiseLocalEvent(uid, ref ev);
return ev.StatusIcons;
}
}
/// <summary>
/// For overlay to check if an entity can be seen.
/// </summary>
public bool IsVisible(Entity<MetaDataComponent> ent, StatusIconData data)
{
var viewer = _playerManager.LocalSession?.AttachedEntity;
// Always show our icons to our entity
if (viewer == ent.Owner)
return true;
if (data.VisibleToGhosts && HasComp<GhostComponent>(viewer))
return true;
if (data.HideInContainer && (ent.Comp.Flags & MetaDataFlags.InContainer) != 0)
return false;
if (data.HideOnStealth && TryComp<StealthComponent>(ent, out var stealth) && stealth.Enabled)
return false;
if (data.ShowTo != null && !_entityWhitelist.IsValid(data.ShowTo, viewer))
return false;
return true;
}
}

View File

@ -1,4 +1,5 @@
using Content.Client.Interactable.Components;
using Content.Client.StatusIcon;
using Content.Shared.Stealth;
using Content.Shared.Stealth.Components;
using Robust.Client.GameObjects;
@ -18,6 +19,7 @@ public sealed class StealthSystem : SharedStealthSystem
base.Initialize();
_shader = _protoMan.Index<ShaderPrototype>("Stealth").InstanceUnique();
SubscribeLocalEvent<StealthComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<StealthComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<StealthComponent, BeforePostShaderRenderEvent>(OnShaderRender);
@ -93,4 +95,3 @@ public sealed class StealthSystem : SharedStealthSystem
args.Sprite.Color = new Color(visibility, visibility, 1, 1);
}
}

View File

@ -1,4 +1,5 @@
using System.Linq;
using System.Linq;
using System.Numerics;
using Content.Client.Animations;
using Content.Shared.Hands;
using Content.Shared.Storage;
@ -149,7 +150,7 @@ public sealed class StorageSystem : SharedStorageSystem
}
var finalMapPos = finalCoords.ToMapPos(EntityManager, TransformSystem);
var finalPos = TransformSystem.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos);
var finalPos = Vector2.Transform(finalMapPos, TransformSystem.GetInvWorldMatrix(initialCoords.EntityId));
_entityPickupAnimation.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle);
}

View File

@ -1,6 +1,7 @@
using Content.Shared.Store;
using JetBrains.Annotations;
using System.Linq;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes;
namespace Content.Client.Store.Ui;
@ -13,9 +14,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
[ViewVariables]
private StoreMenu? _menu;
[ViewVariables]
private string _windowName = Loc.GetString("store-ui-default-title");
[ViewVariables]
private string _search = string.Empty;
@ -28,7 +26,9 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
protected override void Open()
{
_menu = new StoreMenu(_windowName);
_menu = new StoreMenu();
if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store))
_menu.Title = Loc.GetString(store.Name);
_menu.OpenCentered();
_menu.OnClose += Close;
@ -64,25 +64,15 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
{
base.UpdateState(state);
if (_menu == null)
return;
switch (state)
{
case StoreUpdateState msg:
_listings = msg.Listings;
_menu.UpdateBalance(msg.Balance);
_menu?.UpdateBalance(msg.Balance);
UpdateListingsWithSearchFilter();
_menu.SetFooterVisibility(msg.ShowFooter);
_menu.UpdateRefund(msg.AllowRefund);
break;
case StoreInitializeState msg:
_windowName = msg.Name;
if (_menu != null && _menu.Window != null)
{
_menu.Window.Title = msg.Name;
}
_menu?.SetFooterVisibility(msg.ShowFooter);
_menu?.UpdateRefund(msg.AllowRefund);
break;
}
}

View File

@ -32,7 +32,7 @@ public sealed partial class StoreMenu : DefaultWindow
private List<ListingData> _cachedListings = new();
public StoreMenu(string name)
public StoreMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
@ -40,9 +40,6 @@ public sealed partial class StoreMenu : DefaultWindow
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
RefundButton.OnButtonDown += OnRefundButtonDown;
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
if (Window != null)
Window.Title = name;
}
public void UpdateBalance(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)

View File

@ -65,7 +65,7 @@ public sealed class DirectionIcon : TextureRect
if (_rotation != null)
{
var offset = (-_rotation.Value).RotateVec(Size * UIScale / 2) - Size * UIScale / 2;
handle.SetTransform(Matrix3.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value));
handle.SetTransform(Matrix3Helpers.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value));
}
base.Draw(handle);

View File

@ -169,7 +169,7 @@ public partial class MapGridControl : LayoutContainer
var inversePos = (value - MidPointVector) / MinimapScale;
inversePos = inversePos with { Y = -inversePos.Y };
inversePos = Matrix3.CreateTransform(Offset, Angle.Zero).Transform(inversePos);
inversePos = Vector2.Transform(inversePos, Matrix3Helpers.CreateTransform(Offset, Angle.Zero));
return inversePos;
}

View File

@ -0,0 +1,285 @@
using System.Numerics;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.UserInterface.Controls;
// This control is not part of engine because I quickly wrote it in 2 hours at 2 AM and don't want to deal with
// API stabilization and/or figuring out relation to GridContainer.
// Grid layout is a complicated problem and I don't want to commit another half-baked thing into the engine.
// It's probably sufficient for its use case (RichTextLabel tables for rules/guidebook).
// Despite that, it's still better comment the shit half of you write on a regular basis.
//
// EMO: thank you PJB i was going to kill myself.
/// <summary>
/// Displays children in a tabular grid. Unlike <see cref="GridContainer"/>,
/// properly handles layout constraints so putting word-wrapping <see cref="RichTextLabel"/> in it should work.
/// </summary>
/// <remarks>
/// All children are automatically laid out in <see cref="Columns"/> columns.
/// The first control is in the top left, laid out per row from there.
/// </remarks>
[Virtual]
public class TableContainer : Container
{
private int _columns = 1;
/// <summary>
/// The absolute minimum width a column can be forced to.
/// </summary>
/// <remarks>
/// <para>
/// If a column *asks* for less width than this (small contents), it can still be smaller.
/// But if it asks for more it cannot go below this width.
/// </para>
/// </remarks>
public float MinForcedColumnWidth { get; set; } = 50;
// Scratch space used while calculating layout, cached to avoid regular allocations during layout pass.
private ColumnData[] _columnDataCache = [];
private RowData[] _rowDataCache = [];
/// <summary>
/// How many columns should be displayed.
/// </summary>
public int Columns
{
get => _columns;
set
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, 1, nameof(value));
_columns = value;
}
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
ResetCachedArrays();
// Do a first pass measuring all child controls as if they're given infinite space.
// This gives us a maximum width the columns want, which we use to proportion them later.
var columnIdx = 0;
foreach (var child in Children)
{
ref var column = ref _columnDataCache[columnIdx];
child.Measure(new Vector2(float.PositiveInfinity, float.PositiveInfinity));
column.MaxWidth = Math.Max(column.MaxWidth, child.DesiredSize.X);
columnIdx += 1;
if (columnIdx == _columns)
columnIdx = 0;
}
// Calculate Slack and MinWidth for all columns. Also calculate sums for all columns.
var totalMinWidth = 0f;
var totalMaxWidth = 0f;
var totalSlack = 0f;
for (var c = 0; c < _columns; c++)
{
ref var column = ref _columnDataCache[c];
column.MinWidth = Math.Min(column.MaxWidth, MinForcedColumnWidth);
column.Slack = column.MaxWidth - column.MinWidth;
totalMinWidth += column.MinWidth;
totalMaxWidth += column.MaxWidth;
totalSlack += column.Slack;
}
if (totalMaxWidth <= availableSize.X)
{
// We want less horizontal space than we're given. Huh, that's convenient.
// Just set assigned width to be however much they asked for.
// We could probably skip the second measure pass in this scenario,
// but that's just an optimization, so I don't care right now.
//
// There's probably a very clever way to make this behavior work with the else block of logic,
// just by fiddling with the math.
// I'm dumb, it's 4:30 AM. Yeah, I *started* at 2 AM.
for (var c = 0; c < _columns; c++)
{
ref var column = ref _columnDataCache[c];
column.AssignedWidth = column.MaxWidth;
}
}
else
{
// We don't have enough horizontal space,
// at least without causing *some* sort of word wrapping (assuming text contents).
//
// Assign horizontal space proportional to the wanted maximum size of the columns.
var assignableWidth = Math.Max(0, availableSize.X - totalMinWidth);
for (var c = 0; c < _columns; c++)
{
ref var column = ref _columnDataCache[c];
var slackRatio = column.Slack / totalSlack;
column.AssignedWidth = column.MinWidth + slackRatio * assignableWidth;
}
}
// Go over controls for a second measuring pass, this time giving them their assigned measure width.
// This will give us a height to slot into per-row data.
// We still measure assuming infinite vertical space.
// This control can't properly handle being constrained on the Y axis.
columnIdx = 0;
var rowIdx = 0;
foreach (var child in Children)
{
ref var column = ref _columnDataCache[columnIdx];
ref var row = ref _rowDataCache[rowIdx];
child.Measure(new Vector2(column.AssignedWidth, float.PositiveInfinity));
row.MeasuredHeight = Math.Max(row.MeasuredHeight, child.DesiredSize.Y);
columnIdx += 1;
if (columnIdx == _columns)
{
columnIdx = 0;
rowIdx += 1;
}
}
// Sum up height of all rows to get final measured table height.
var totalHeight = 0f;
for (var r = 0; r < _rowDataCache.Length; r++)
{
ref var row = ref _rowDataCache[r];
totalHeight += row.MeasuredHeight;
}
return new Vector2(Math.Min(availableSize.X, totalMaxWidth), totalHeight);
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
// TODO: Expand to fit given vertical space.
// Calculate MinWidth and Slack sums again from column data.
// We could've cached these from measure but whatever.
var totalMinWidth = 0f;
var totalSlack = 0f;
for (var c = 0; c < _columns; c++)
{
ref var column = ref _columnDataCache[c];
totalMinWidth += column.MinWidth;
totalSlack += column.Slack;
}
// Calculate new width based on final given size, also assign horizontal positions of all columns.
var assignableWidth = Math.Max(0, finalSize.X - totalMinWidth);
var xPos = 0f;
for (var c = 0; c < _columns; c++)
{
ref var column = ref _columnDataCache[c];
var slackRatio = column.Slack / totalSlack;
column.ArrangedWidth = column.MinWidth + slackRatio * assignableWidth;
column.ArrangedX = xPos;
xPos += column.ArrangedWidth;
}
// Do actual arrangement row-by-row.
var arrangeY = 0f;
for (var r = 0; r < _rowDataCache.Length; r++)
{
ref var row = ref _rowDataCache[r];
for (var c = 0; c < _columns; c++)
{
ref var column = ref _columnDataCache[c];
var index = c + r * _columns;
if (index >= ChildCount) // Quit early if we don't actually fill out the row.
break;
var child = GetChild(c + r * _columns);
child.Arrange(UIBox2.FromDimensions(column.ArrangedX, arrangeY, column.ArrangedWidth, row.MeasuredHeight));
}
arrangeY += row.MeasuredHeight;
}
return finalSize with { Y = arrangeY };
}
/// <summary>
/// Ensure cached array space is allocated to correct size and is reset to a clean slate.
/// </summary>
private void ResetCachedArrays()
{
// 1-argument Array.Clear() is not currently available in sandbox (added in .NET 6).
if (_columnDataCache.Length != _columns)
_columnDataCache = new ColumnData[_columns];
Array.Clear(_columnDataCache, 0, _columnDataCache.Length);
var rowCount = ChildCount / _columns;
if (ChildCount % _columns != 0)
rowCount += 1;
if (rowCount != _rowDataCache.Length)
_rowDataCache = new RowData[rowCount];
Array.Clear(_rowDataCache, 0, _rowDataCache.Length);
}
/// <summary>
/// Per-column data used during layout.
/// </summary>
private struct ColumnData
{
// Measure data.
/// <summary>
/// The maximum width any control in this column wants, if given infinite space.
/// Maximum of all controls on the column.
/// </summary>
public float MaxWidth;
/// <summary>
/// The minimum width this column may be given.
/// This is either <see cref="MaxWidth"/> or <see cref="TableContainer.MinForcedColumnWidth"/>.
/// </summary>
public float MinWidth;
/// <summary>
/// Difference between max and min width; how much this column can expand from its minimum.
/// </summary>
public float Slack;
/// <summary>
/// How much horizontal space this column was assigned at measure time.
/// </summary>
public float AssignedWidth;
// Arrange data.
/// <summary>
/// How much horizontal space this column was assigned at arrange time.
/// </summary>
public float ArrangedWidth;
/// <summary>
/// The horizontal position this column was assigned at arrange time.
/// </summary>
public float ArrangedX;
}
private struct RowData
{
// Measure data.
/// <summary>
/// How much height the tallest control on this row was measured at,
/// measuring for infinite vertical space but assigned column width.
/// </summary>
public float MeasuredHeight;
}
}

View File

@ -289,6 +289,10 @@ public sealed class ActionButton : Control, IEntityControl
{
if (_action.IconOn != null)
SetActionIcon(_spriteSys.Frame0(_action.IconOn));
else if (_action.Icon != null)
SetActionIcon(_spriteSys.Frame0(_action.Icon));
else
SetActionIcon(null);
if (_action.BackgroundOn != null)
_buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);

View File

@ -198,9 +198,12 @@ public sealed class AdminUIController : UIController,
args.Handle();
}
private void ObjectsTabEntryKeyBindDown(ObjectsTabEntry entry, GUIBoundKeyEventArgs args)
private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data)
{
var uid = entry.AssocEntity;
if (data is not ObjectsListData { Info: var info })
return;
var uid = info.Entity;
var function = args.Function;
if (function == EngineKeyFunctions.UIClick)

View File

@ -1,6 +1,5 @@
using Content.Shared.Atmos.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
namespace Content.Client.UserInterface.Systems.Atmos.GasTank
{
@ -30,7 +29,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
protected override void Open()
{
base.Open();
_window = new GasTankWindow(this);
_window = new GasTankWindow(this, EntMan.GetComponent<MetaDataComponent>(Owner).EntityName);
_window.OnClose += Close;
_window.OpenCentered();
}

View File

@ -10,201 +10,194 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.UserInterface.Systems.Atmos.GasTank
namespace Content.Client.UserInterface.Systems.Atmos.GasTank;
public sealed class GasTankWindow
: BaseWindow
{
public sealed class GasTankWindow
: BaseWindow
private readonly RichTextLabel _lblPressure;
private readonly FloatSpinBox _spbPressure;
private readonly RichTextLabel _lblInternals;
private readonly Button _btnInternals;
public GasTankWindow(GasTankBoundUserInterface owner, string uidName)
{
private GasTankBoundUserInterface _owner;
private readonly Label _lblName;
private readonly BoxContainer _topContainer;
private readonly Control _contentContainer;
Control contentContainer;
BoxContainer topContainer;
TextureButton btnClose;
var resourceCache = IoCManager.Resolve<IResourceCache>();
var rootContainer = new LayoutContainer { Name = "GasTankRoot" };
AddChild(rootContainer);
MouseFilter = MouseFilterMode.Stop;
private readonly IResourceCache _resourceCache = default!;
private readonly RichTextLabel _lblPressure;
private readonly FloatSpinBox _spbPressure;
private readonly RichTextLabel _lblInternals;
private readonly Button _btnInternals;
public GasTankWindow(GasTankBoundUserInterface owner)
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
{
TextureButton btnClose;
_resourceCache = IoCManager.Resolve<IResourceCache>();
_owner = owner;
var rootContainer = new LayoutContainer {Name = "GasTankRoot"};
AddChild(rootContainer);
Texture = panelTex,
Modulate = Color.FromHex("#25252A"),
};
MouseFilter = MouseFilterMode.Stop;
back.SetPatchMargin(StyleBox.Margin.All, 10);
var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
var topPanel = new PanelContainer
{
PanelOverride = back,
MouseFilter = MouseFilterMode.Pass
};
var bottomWrap = new LayoutContainer
{
Name = "BottomWrap"
};
rootContainer.AddChild(topPanel);
rootContainer.AddChild(bottomWrap);
LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide);
LayoutContainer.SetMarginBottom(topPanel, -85);
LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide);
LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both);
var topContainerWrap = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
Texture = panelTex,
Modulate = Color.FromHex("#25252A"),
};
back.SetPatchMargin(StyleBox.Margin.All, 10);
var topPanel = new PanelContainer
{
PanelOverride = back,
MouseFilter = MouseFilterMode.Pass
};
var bottomWrap = new LayoutContainer
{
Name = "BottomWrap"
};
rootContainer.AddChild(topPanel);
rootContainer.AddChild(bottomWrap);
LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide);
LayoutContainer.SetMarginBottom(topPanel, -85);
LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide);
LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both);
var topContainerWrap = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
(topContainer = new BoxContainer
{
(_topContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
}),
new Control {MinSize = new Vector2(0, 110)}
}
};
Orientation = LayoutOrientation.Vertical
}),
new Control {MinSize = new Vector2(0, 110)}
}
};
rootContainer.AddChild(topContainerWrap);
rootContainer.AddChild(topContainerWrap);
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
var topRow = new BoxContainer
var topRow = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Margin = new Thickness(4, 2, 12, 2),
Children =
{
(new Label
{
Text = uidName,
FontOverride = font,
FontColorOverride = StyleNano.NanoGold,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
HorizontalAlignment = HAlignment.Left,
Margin = new Thickness(0, 0, 20, 0),
}),
(btnClose = new TextureButton
{
StyleClasses = {DefaultWindow.StyleClassWindowCloseButton},
VerticalAlignment = VAlignment.Center
})
}
};
var middle = new PanelContainer
{
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#202025") },
Children =
{
(contentContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Margin = new Thickness(8, 4),
})
}
};
topContainer.AddChild(topRow);
topContainer.AddChild(new PanelContainer
{
MinSize = new Vector2(0, 2),
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#525252ff") }
});
topContainer.AddChild(middle);
topContainer.AddChild(new PanelContainer
{
MinSize = new Vector2(0, 2),
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#525252ff") }
});
_lblPressure = new RichTextLabel();
contentContainer.AddChild(_lblPressure);
//internals
_lblInternals = new RichTextLabel
{ MinSize = new Vector2(200, 0), VerticalAlignment = VAlignment.Center };
_btnInternals = new Button { Text = Loc.GetString("gas-tank-window-internals-toggle-button") };
contentContainer.AddChild(
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Margin = new Thickness(4, 2, 12, 2),
Children =
{
(_lblName = new Label
{
Text = Loc.GetString("gas-tank-window-label"),
FontOverride = font,
FontColorOverride = StyleNano.NanoGold,
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
HorizontalAlignment = HAlignment.Left,
Margin = new Thickness(0, 0, 20, 0),
}),
(btnClose = new TextureButton
{
StyleClasses = {DefaultWindow.StyleClassWindowCloseButton},
VerticalAlignment = VAlignment.Center
})
}
};
var middle = new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")},
Children =
{
(_contentContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Margin = new Thickness(8, 4),
})
}
};
_topContainer.AddChild(topRow);
_topContainer.AddChild(new PanelContainer
{
MinSize = new Vector2(0, 2),
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
});
_topContainer.AddChild(middle);
_topContainer.AddChild(new PanelContainer
{
MinSize = new Vector2(0, 2),
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
Margin = new Thickness(0, 7, 0, 0),
Children = { _lblInternals, _btnInternals }
});
_lblPressure = new RichTextLabel();
_contentContainer.AddChild(_lblPressure);
//internals
_lblInternals = new RichTextLabel
{MinSize = new Vector2(200, 0), VerticalAlignment = VAlignment.Center};
_btnInternals = new Button {Text = Loc.GetString("gas-tank-window-internals-toggle-button") };
_contentContainer.AddChild(
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Margin = new Thickness(0, 7, 0, 0),
Children = {_lblInternals, _btnInternals}
});
// Separator
_contentContainer.AddChild(new Control
{
MinSize = new Vector2(0, 10)
});
_contentContainer.AddChild(new Label
{
Text = Loc.GetString("gas-tank-window-output-pressure-label"),
Align = Label.AlignMode.Center
});
_spbPressure = new FloatSpinBox
{
IsValid = f => f >= 0 || f <= 3000,
Margin = new Thickness(25, 0, 25, 7)
};
_contentContainer.AddChild(_spbPressure);
// Handlers
_spbPressure.OnValueChanged += args =>
{
_owner.SetOutputPressure(args.Value);
};
_btnInternals.OnPressed += args =>
{
_owner.ToggleInternals();
};
btnClose.OnPressed += _ => Close();
}
public void UpdateState(GasTankBoundUserInterfaceState state)
// Separator
contentContainer.AddChild(new Control
{
_lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}")));
_btnInternals.Disabled = !state.CanConnectInternals;
_lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text",
("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
if (state.OutputPressure.HasValue)
{
_spbPressure.Value = state.OutputPressure.Value;
}
}
MinSize = new Vector2(0, 10)
});
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
contentContainer.AddChild(new Label
{
return DragMode.Move;
}
Text = Loc.GetString("gas-tank-window-output-pressure-label"),
Align = Label.AlignMode.Center
});
_spbPressure = new FloatSpinBox
{
IsValid = f => f >= 0 || f <= 3000,
Margin = new Thickness(25, 0, 25, 7)
};
contentContainer.AddChild(_spbPressure);
protected override bool HasPoint(Vector2 point)
// Handlers
_spbPressure.OnValueChanged += args =>
{
return false;
owner.SetOutputPressure(args.Value);
};
_btnInternals.OnPressed += args =>
{
owner.ToggleInternals();
};
btnClose.OnPressed += _ => Close();
}
public void UpdateState(GasTankBoundUserInterfaceState state)
{
_lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}")));
_btnInternals.Disabled = !state.CanConnectInternals;
_lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text",
("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
if (state.OutputPressure.HasValue)
{
_spbPressure.Value = state.OutputPressure.Value;
}
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
{
return DragMode.Move;
}
protected override bool HasPoint(Vector2 point)
{
return false;
}
}

View File

@ -123,7 +123,6 @@ namespace Content.Client.Verbs
if ((visibility & MenuVisibility.Invisible) == 0)
{
var spriteQuery = GetEntityQuery<SpriteComponent>();
var tagQuery = GetEntityQuery<TagComponent>();
for (var i = entities.Count - 1; i >= 0; i--)
{
@ -131,7 +130,7 @@ namespace Content.Client.Verbs
if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) ||
!spriteComponent.Visible ||
_tagSystem.HasTag(entity, "HideContextMenu", tagQuery))
_tagSystem.HasTag(entity, "HideContextMenu"))
{
entities.RemoveSwap(i);
}

View File

@ -277,8 +277,8 @@ namespace Content.Client.Viewport
EnsureViewportCreated();
var matrix = Matrix3.Invert(GetLocalToScreenMatrix());
coords = matrix.Transform(coords);
Matrix3x2.Invert(GetLocalToScreenMatrix(), out var matrix);
coords = Vector2.Transform(coords, matrix);
return _viewport!.LocalToWorld(coords);
}
@ -291,8 +291,8 @@ namespace Content.Client.Viewport
EnsureViewportCreated();
var matrix = Matrix3.Invert(GetLocalToScreenMatrix());
coords = matrix.Transform(coords);
Matrix3x2.Invert(GetLocalToScreenMatrix(), out var matrix);
coords = Vector2.Transform(coords, matrix);
var ev = new PixelToMapEvent(coords, this, _viewport!);
_entityManager.EventBus.RaiseEvent(EventSource.Local, ref ev);
@ -311,16 +311,16 @@ namespace Content.Client.Viewport
var matrix = GetLocalToScreenMatrix();
return matrix.Transform(vpLocal);
return Vector2.Transform(vpLocal, matrix);
}
public Matrix3 GetWorldToScreenMatrix()
public Matrix3x2 GetWorldToScreenMatrix()
{
EnsureViewportCreated();
return _viewport!.GetWorldToLocalMatrix() * GetLocalToScreenMatrix();
}
public Matrix3 GetLocalToScreenMatrix()
public Matrix3x2 GetLocalToScreenMatrix()
{
EnsureViewportCreated();
@ -329,9 +329,9 @@ namespace Content.Client.Viewport
if (scaleFactor.X == 0 || scaleFactor.Y == 0)
// Basically a nonsense scenario, at least make sure to return something that can be inverted.
return Matrix3.Identity;
return Matrix3x2.Identity;
return Matrix3.CreateTransform(GlobalPixelPosition + drawBox.TopLeft, 0, scaleFactor);
return Matrix3Helpers.CreateTransform(GlobalPixelPosition + drawBox.TopLeft, 0, scaleFactor);
}
private void EnsureViewportCreated()

View File

@ -81,7 +81,7 @@ public sealed partial class MeleeWeaponSystem
case WeaponArcAnimation.None:
var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform);
var worldPos = mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos);
var newLocalPos = TransformSystem.GetInvWorldMatrix(xform.ParentUid).Transform(worldPos);
var newLocalPos = Vector2.Transform(worldPos, TransformSystem.GetInvWorldMatrix(xform.ParentUid));
TransformSystem.SetLocalPositionNoLerp(animationUid, newLocalPos, xform);
if (arcComponent.Fadeout)
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);

View File

@ -1,4 +1,4 @@
using System.Numerics;
using System.Numerics;
using Content.Client.Resources;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@ -53,7 +53,7 @@ public abstract class BaseBulletRenderer : Control
{
// Scale rendering in this control by UIScale.
var currentTransform = handle.GetTransform();
handle.SetTransform(Matrix3.CreateScale(new Vector2(UIScale)) * currentTransform);
handle.SetTransform(Matrix3Helpers.CreateScale(new Vector2(UIScale)) * currentTransform);
var countPerRow = CountPerRow(Size.X);

View File

@ -1,21 +1,40 @@
using System.Linq;
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.Zombies;
public sealed class ZombieSystem : EntitySystem
public sealed class ZombieSystem : SharedZombieSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ZombieComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ZombieComponent, CanDisplayStatusIconsEvent>(OnCanDisplayStatusIcons);
SubscribeLocalEvent<InitialInfectedComponent, CanDisplayStatusIconsEvent>(OnCanDisplayStatusIcons);
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(GetZombieIcon);
SubscribeLocalEvent<InitialInfectedComponent, GetStatusIconsEvent>(GetInitialInfectedIcon);
}
private void GetZombieIcon(Entity<ZombieComponent> ent, ref GetStatusIconsEvent args)
{
var iconPrototype = _prototype.Index(ent.Comp.StatusIcon);
args.StatusIcons.Add(iconPrototype);
}
private void GetInitialInfectedIcon(Entity<InitialInfectedComponent> ent, ref GetStatusIconsEvent args)
{
if (HasComp<ZombieComponent>(ent))
return;
var iconPrototype = _prototype.Index(ent.Comp.StatusIcon);
args.StatusIcons.Add(iconPrototype);
}
private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args)
@ -31,29 +50,4 @@ public sealed class ZombieSystem : EntitySystem
sprite.LayerSetColor(i, component.SkinColor);
}
}
/// <summary>
/// Determines whether a player should be able to see the StatusIcon for zombies.
/// </summary>
private void OnCanDisplayStatusIcons(EntityUid uid, ZombieComponent component, ref CanDisplayStatusIconsEvent args)
{
if (HasComp<ZombieComponent>(args.User) || HasComp<InitialInfectedComponent>(args.User) || HasComp<ShowZombieIconsComponent>(args.User))
return;
if (component.IconVisibleToGhost && HasComp<GhostComponent>(args.User))
return;
args.Cancelled = true;
}
private void OnCanDisplayStatusIcons(EntityUid uid, InitialInfectedComponent component, ref CanDisplayStatusIconsEvent args)
{
if (HasComp<InitialInfectedComponent>(args.User) && !HasComp<ZombieComponent>(args.User))
return;
if (component.IconVisibleToGhost && HasComp<GhostComponent>(args.User))
return;
args.Cancelled = true;
}
}

View File

@ -7,6 +7,7 @@ using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.UnitTesting;
@ -133,27 +134,73 @@ public sealed partial class TestPair
}
/// <summary>
/// Helper method for enabling or disabling a antag role
/// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test.
/// </summary>
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
public async Task SetAntagPreference(ProtoId<AntagPrototype> id, bool value, NetUserId? user = null)
{
user ??= Client.User!.Value;
if (user is not {} userId)
return;
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
var prefs = prefMan.GetPreferences(userId);
var prefs = prefMan.GetPreferences(Client.User!.Value);
// what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable?
var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter;
// Automatic preference resetting only resets slot 0.
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value));
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
var newProfile = profile.WithAntagPreference(id, value);
_modifiedProfiles.Add(userId);
await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait());
}
await Server.WaitPost(() =>
/// <summary>
/// Set a user's job preferences. Modified preferences are automatically reset at the end of the test.
/// </summary>
public async Task SetJobPriority(ProtoId<JobPrototype> id, JobPriority value, NetUserId? user = null)
{
user ??= Client.User!.Value;
if (user is { } userId)
await SetJobPriorities(userId, (id, value));
}
/// <inheritdoc cref="SetJobPriority"/>
public async Task SetJobPriorities(params (ProtoId<JobPrototype>, JobPriority)[] priorities)
=> await SetJobPriorities(Client.User!.Value, priorities);
/// <inheritdoc cref="SetJobPriority"/>
public async Task SetJobPriorities(NetUserId user, params (ProtoId<JobPrototype>, JobPriority)[] priorities)
{
var highCount = priorities.Count(x => x.Item2 == JobPriority.High);
Assert.That(highCount, Is.LessThanOrEqualTo(1), "Cannot have more than one high priority job");
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
var prefs = prefMan.GetPreferences(user);
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(profile.JobPriorities);
// Automatic preference resetting only resets slot 0.
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
if (highCount != 0)
{
prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait();
});
foreach (var (key, priority) in dictionary)
{
if (priority == JobPriority.High)
dictionary[key] = JobPriority.Medium;
}
}
// And why the fuck does it always create a new preference and profile object instead of just reusing them?
var newPrefs = prefMan.GetPreferences(Client.User.Value);
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value));
foreach (var (job, priority) in priorities)
{
if (priority == JobPriority.Never)
dictionary.Remove(job);
else
dictionary[job] = priority;
}
var newProfile = profile.WithJobPriorities(dictionary);
_modifiedProfiles.Add(user);
await Server.WaitPost(() => prefMan.SetProfile(user, 0, newProfile).Wait());
}
}

View File

@ -2,10 +2,12 @@
using System.IO;
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.Preferences.Managers;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Server.Player;
using Robust.Shared.Exceptions;
@ -34,6 +36,11 @@ public sealed partial class TestPair : IAsyncDisposable
private async Task OnCleanDispose()
{
await Server.WaitIdleAsync();
await Client.WaitIdleAsync();
await ResetModifiedPreferences();
await Server.RemoveAllDummySessions();
if (TestMap != null)
{
await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid));
@ -79,6 +86,16 @@ public sealed partial class TestPair : IAsyncDisposable
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
}
private async Task ResetModifiedPreferences()
{
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
foreach (var user in _modifiedProfiles)
{
await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
}
_modifiedProfiles.Clear();
}
public async ValueTask CleanReturnAsync()
{
if (State != PairState.InUse)

View File

@ -26,6 +26,8 @@ public sealed partial class TestPair
public readonly List<string> TestHistory = new();
public PoolSettings Settings = default!;
public TestMapData? TestMap;
private List<NetUserId> _modifiedProfiles = new();
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
@ -37,7 +39,8 @@ public sealed partial class TestPair
client = Client;
}
public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault();
public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value);
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
public PoolTestLogHandler ServerLogHandler { get; private set; } = default!;

View File

@ -28,6 +28,7 @@ public static partial class PoolManager
(CCVars.EmergencyShuttleEnabled.Name, "false"),
(CCVars.ProcgenPreload.Name, "false"),
(CCVars.WorldgenEnabled.Name, "false"),
(CCVars.GatewayGeneratorEnabled.Name, "false"),
(CVars.ReplayClientRecordingEnabled.Name, "false"),
(CVars.ReplayServerRecordingEnabled.Name, "false"),
(CCVars.GameDummyTicker.Name, "true"),

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