Revert "Upstream sync (#1509)" (#1510)

This reverts commit a35c718734.
This commit is contained in:
Red
2025-07-08 11:59:51 +03:00
committed by GitHub
parent a35c718734
commit ae077c3971
935 changed files with 9384 additions and 19938 deletions

View File

@@ -27,7 +27,7 @@ jobs:
run: dotnet restore
- name: Build Project
run: dotnet build --no-restore
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Build DocFX
uses: nikeee/docfx-action@v1.0.0

View File

@@ -42,7 +42,7 @@ jobs:
run: dotnet restore
- name: Build Project
run: dotnet build Content.MapRenderer --configuration Release --no-restore /m
run: dotnet build Content.MapRenderer --configuration Release --no-restore /p:WarningsAsErrors=nullable /m
- name: Run Map Renderer
run: dotnet run --project Content.MapRenderer Dev

View File

@@ -42,7 +42,7 @@ jobs:
run: dotnet restore
- name: Build Project
run: dotnet build --configuration DebugOpt --no-restore /m
run: dotnet build --configuration DebugOpt --no-restore /p:WarningsAsErrors=nullable /m
- name: Run Content.Tests
run: dotnet test --no-build --configuration DebugOpt Content.Tests/Content.Tests.csproj -- NUnit.ConsoleOut=0

View File

@@ -2,7 +2,7 @@
on:
pull_request_target:
types: [review_requested, opened]
types: [review_requested]
jobs:
add_label:

View File

@@ -14,10 +14,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fail if we are attempting to run on the master branch
if: ${{GITHUB.REF_NAME == 'master' && github.repository == 'space-wizards/space-station-14'}}
run: exit 1
- name: Install dependencies
run: sudo apt-get install -y python3-paramiko python3-lxml

View File

@@ -27,7 +27,7 @@ If you believe someone is violating the code of conduct, we ask that you report
Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html).
## On Community Moderation
## On Comunity Moderation
Deviating from the Code of Conduct on the Github repository may result in moderative actions taken by project Maintainers. This can involve your content being edited or deleted, and may result in a temporary or permanent block from the repository.

View File

@@ -211,7 +211,7 @@ namespace Content.Client.Actions
else
{
var request = new RequestPerformActionEvent(GetNetEntity(action));
RaisePredictiveEvent(request);
EntityManager.RaisePredictiveEvent(request);
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Frozen;
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Systems;
@@ -25,7 +24,6 @@ internal sealed class AdminNameOverlay : Overlay
private readonly EntityLookupSystem _entityLookup;
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly SharedRoleSystem _roles;
private readonly IPrototypeManager _prototypeManager;
private readonly Font _font;
private readonly Font _fontBold;
private AdminOverlayAntagFormat _overlayFormat;
@@ -38,9 +36,8 @@ internal sealed class AdminNameOverlay : Overlay
private float _overlayMergeDistance;
//TODO make this adjustable via GUI?
private static readonly FrozenSet<ProtoId<RoleTypePrototype>> Filter =
new ProtoId<RoleTypePrototype>[] {"SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"}
.ToFrozenSet();
private readonly ProtoId<RoleTypePrototype>[] _filter =
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
@@ -52,8 +49,7 @@ internal sealed class AdminNameOverlay : Overlay
EntityLookupSystem entityLookup,
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config,
SharedRoleSystem roles,
IPrototypeManager prototypeManager)
SharedRoleSystem roles)
{
_system = system;
_entityManager = entityManager;
@@ -61,7 +57,6 @@ internal sealed class AdminNameOverlay : Overlay
_entityLookup = entityLookup;
_userInterfaceManager = userInterfaceManager;
_roles = roles;
_prototypeManager = prototypeManager;
ZIndex = 200;
// Setting these to a specific ttf would break the antag symbols
_font = resourceCache.NotoStack();
@@ -130,14 +125,6 @@ internal sealed class AdminNameOverlay : Overlay
foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList())
{
var playerInfo = info.Item1;
var rolePrototype = playerInfo.RoleProto == null
? null
: _prototypeManager.Index(playerInfo.RoleProto.Value);
var roleName = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var roleColor = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
var roleSymbol = rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol;
var aabb = info.Item2;
var entity = info.Item3;
var screenCoordinatesCenter = info.Item4;
@@ -222,7 +209,7 @@ internal sealed class AdminNameOverlay : Overlay
switch (_overlaySymbolStyle)
{
case AdminOverlayAntagSymbolStyle.Specific:
symbol = roleSymbol;
symbol = playerInfo.RoleProto.Symbol;
break;
case AdminOverlayAntagSymbolStyle.Basic:
symbol = Loc.GetString("player-tab-antag-prefix");
@@ -238,17 +225,17 @@ internal sealed class AdminNameOverlay : Overlay
switch (_overlayFormat)
{
case AdminOverlayAntagFormat.Roletype:
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? roleName.ToUpper()
color = playerInfo.RoleProto.Color;
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
text = _filter.Contains(playerInfo.RoleProto)
? Loc.GetString(playerInfo.RoleProto.Name).ToUpper()
: string.Empty;
break;
case AdminOverlayAntagFormat.Subtype:
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? _roles.GetRoleSubtypeLabel(roleName, playerInfo.Subtype).ToUpper()
color = playerInfo.RoleProto.Color;
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
text = _filter.Contains(playerInfo.RoleProto)
? _roles.GetRoleSubtypeLabel(playerInfo.RoleProto.Name, playerInfo.Subtype).ToUpper()
: string.Empty;
break;
default:
@@ -271,12 +258,4 @@ internal sealed class AdminNameOverlay : Overlay
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
}
}
private static bool IsFiltered(ProtoId<RoleTypePrototype>? roleProtoId)
{
if (roleProtoId == null)
return false;
return Filter.Contains(roleProtoId.Value);
}
}

View File

@@ -4,7 +4,6 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client.Administration.Systems
{
@@ -18,7 +17,6 @@ namespace Content.Client.Administration.Systems
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private AdminNameOverlay _adminNameOverlay = default!;
@@ -35,8 +33,7 @@ namespace Content.Client.Administration.Systems
_entityLookup,
_userInterfaceManager,
_configurationManager,
_roles,
_proto);
_roles);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}

View File

@@ -1,7 +1,7 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc ban-panel-title}" MinSize="410 500">
Title="{Loc ban-panel-title}" MinSize="350 500">
<BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" VerticalExpand="True">
<!-- Basic info -->

View File

@@ -1,14 +1,12 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using Content.Client.Administration.UI.CustomControls;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -33,21 +31,14 @@ public sealed partial class BanPanel : DefaultWindow
private uint Multiplier { get; set; }
private bool HasBanFlag { get; set; }
private TimeSpan? ButtonResetOn { get; set; }
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated.
// Role group name -> the role buttons themselves.
private readonly Dictionary<string, List<Button>> _roleCheckboxes = new();
private readonly List<CheckBox> _roleCheckboxes = new();
private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
private const string ExpandedArrow = "▼";
private const string ContractedArrow = "▶";
private enum TabNumbers
{
@@ -153,90 +144,47 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
var departmentJobs = _protoMan.EnumeratePrototypes<DepartmentPrototype>()
.OrderBy(x => x.Weight);
foreach (var proto in departmentJobs)
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
var roles = proto.Roles.Select(x => _protoMan.Index(x))
.OrderBy(x => x.ID);
CreateRoleGroup(proto.ID, proto.Color, roles);
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
}
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
.OrderBy(x => x.ID);
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
}
/// <summary>
/// Creates a "Role group" which stores information and logic for one "group" of roll bans.
/// For example, all antags are one group, logi is a group, medical is a group, etc...
/// </summary>
private void CreateRoleGroup<T>(string groupName, Color color, IEnumerable<T> roles) where T : class, IPrototype
private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
{
var outerContainer = new BoxContainer
{
Name = $"{groupName}GroupOuterBox",
Name = $"{roleName}GroupOuterBox",
HorizontalExpand = true,
VerticalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(4),
Margin = new Thickness(4)
};
// Stores stuff like ban all and expand buttons.
var roleGroupHeader = new BoxContainer
var departmentCheckbox = new CheckBox
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Name = $"{roleName}GroupCheckbox",
Text = roleName,
Modulate = color,
HorizontalAlignment = HAlignment.Left
};
// Stores the role checkboxes themselves.
var innerContainer = new GridContainer
outerContainer.AddChild(departmentCheckbox);
var innerContainer = new BoxContainer
{
Name = $"{groupName}GroupInnerBox",
Name = $"{roleName}GroupInnerBox",
HorizontalExpand = true,
Columns = 2,
Visible = false,
Margin = new Thickness(15, 5, 0, 5),
Orientation = BoxContainer.LayoutOrientation.Horizontal
};
var roleGroupCheckbox = CreateRoleGroupHeader(groupName, roleGroupHeader, color, innerContainer);
outerContainer.AddChild(roleGroupHeader);
// Add the roles themselves
foreach (var role in roles)
departmentCheckbox.OnToggled += args =>
{
AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox);
}
outerContainer.AddChild(innerContainer);
RolesContainer.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat
foreach (var child in innerContainer.Children)
{
BackgroundColor = color
}
});
RolesContainer.AddChild(outerContainer);
RolesContainer.AddChild(new HSeparator());
}
private Button CreateRoleGroupHeader(string groupName, BoxContainer header, Color color, GridContainer innerContainer)
{
var roleGroupCheckbox = new Button
{
Name = $"{groupName}GroupCheckbox",
Text = "Ban all",
Margin = new Thickness(0, 0, 5, 0),
ToggleMode = true,
};
// When this is toggled, toggle all buttons in this group so they match.
roleGroupCheckbox.OnToggled += args =>
{
foreach (var role in _roleCheckboxes[groupName])
{
role.Pressed = args.Pressed;
if (child is CheckBox c)
{
c.Pressed = args.Pressed;
}
}
if (args.Pressed)
@@ -251,12 +199,15 @@ public sealed partial class BanPanel : DefaultWindow
}
else
{
foreach (var roleButtons in _roleCheckboxes.Values)
foreach (var childContainer in RolesContainer.Children)
{
foreach (var button in roleButtons)
if (childContainer is Container)
{
if (button.Pressed)
return;
foreach (var child in childContainer.Children)
{
if (child is CheckBox { Pressed: true })
return;
}
}
}
@@ -269,72 +220,38 @@ public sealed partial class BanPanel : DefaultWindow
SeverityOption.SelectId((int) newSeverity);
}
};
var hideButton = new Button
outerContainer.AddChild(innerContainer);
foreach (var role in roleList)
{
Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow,
ToggleMode = true,
};
hideButton.OnPressed += args =>
AddRoleCheckbox(role, innerContainer, departmentCheckbox);
}
RolesContainer.AddChild(new PanelContainer
{
innerContainer.Visible = args.Button.Pressed;
((Button)args.Button).Text = args.Button.Pressed
? Loc.GetString("role-bans-contract-roles") + " " + ExpandedArrow
: Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow;
};
header.AddChild(new Label
{
Text = groupName,
Modulate = color,
Margin = new Thickness(0, 0, 5, 0),
PanelOverride = new StyleBoxFlat
{
BackgroundColor = color
}
});
header.AddChild(roleGroupCheckbox);
header.AddChild(hideButton);
return roleGroupCheckbox;
RolesContainer.AddChild(outerContainer);
RolesContainer.AddChild(new HSeparator());
}
/// <summary>
/// Adds a checkbutton specifically for one "role" in a "group"
/// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
/// </summary>
private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
private void AddRoleCheckbox(string role, Control container, CheckBox header)
{
var roleCheckboxContainer = new BoxContainer();
var roleCheckButton = new Button
var roleCheckbox = new CheckBox
{
Name = $"{role}RoleCheckbox",
Text = role,
ToggleMode = true,
Text = role
};
roleCheckButton.OnToggled += args =>
roleCheckbox.OnToggled += args =>
{
// Checks the role group checkbox if all the children are pressed
if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
roleGroupCheckbox.Pressed = args.Pressed;
if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed))
header.Pressed = args.Pressed;
else
roleGroupCheckbox.Pressed = false;
header.Pressed = false;
};
// This is adding the icon before the role name
// Yeah, this is sus, but having to split the functions up and stuff is worse imo.
if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
{
var jobIconTexture = new TextureRect
{
Texture = _entMan.System<SpriteSystem>().Frame0(iconProto.Icon),
TextureScale = new Vector2(2.5f, 2.5f),
Stretch = TextureRect.StretchMode.KeepCentered,
Margin = new Thickness(5, 0, 0, 0),
};
roleCheckboxContainer.AddChild(jobIconTexture);
}
roleCheckboxContainer.AddChild(roleCheckButton);
roleGroupInnerContainer.AddChild(roleCheckboxContainer);
_roleCheckboxes.TryAdd(group, []);
_roleCheckboxes[group].Add(roleCheckButton);
container.AddChild(roleCheckbox);
_roleCheckboxes.Add(roleCheckbox);
}
public void UpdateBanFlag(bool newFlag)
@@ -552,13 +469,7 @@ public sealed partial class BanPanel : DefaultWindow
if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty");
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
{
if (button is { Pressed: true, Text: not null })
{
rolesList.Add(button.Text);
}
}
rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
if (rolesList.Count == 0)
{

View File

@@ -49,7 +49,6 @@
<Control HorizontalExpand="True"/>
<Label Name="Count" Access="Public"/>
<Control HorizontalExpand="True"/>
<Button Name="ExportLogs" Access="Public" Text="{Loc admin-logs-export}"/>
<Button Name="PopOutButton" Access="Public" Text="{Loc admin-logs-pop-out}"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">

View File

@@ -1,6 +1,4 @@
using System.IO;
using System.Linq;
using Content.Client.Administration.UI.CustomControls;
using System.Linq;
using Content.Client.Eui;
using Content.Shared.Administration.Logs;
using Content.Shared.Eui;
@@ -17,16 +15,6 @@ public sealed class AdminLogsEui : BaseEui
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IFileDialogManager _dialogManager = default!;
[Dependency] private readonly ILogManager _log = default!;
private const char CsvSeparator = ',';
private const string CsvQuote = "\"";
private const string CsvHeader = "Date,ID,PlayerID,Severity,Type,Message";
private ISawmill _sawmill;
private bool _currentlyExportingLogs = false;
public AdminLogsEui()
{
@@ -38,9 +26,6 @@ public sealed class AdminLogsEui : BaseEui
LogsControl.RefreshButton.OnPressed += _ => RequestLogs();
LogsControl.NextButton.OnPressed += _ => NextLogs();
LogsControl.PopOutButton.OnPressed += _ => PopOut();
LogsControl.ExportLogs.OnPressed += _ => ExportLogs();
_sawmill = _log.GetSawmill("admin.logs.ui");
}
private WindowRoot? Root { get; set; }
@@ -89,71 +74,6 @@ public sealed class AdminLogsEui : BaseEui
SendMessage(request);
}
private async void ExportLogs()
{
if (_currentlyExportingLogs)
return;
_currentlyExportingLogs = true;
LogsControl.ExportLogs.Disabled = true;
var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("csv")));
if (file == null)
return;
try
{
// Buffer is set to 4KB for performance reasons. As the average export of 1000 logs is ~200KB
await using var writer = new StreamWriter(file.Value.fileStream, bufferSize: 4096);
await writer.WriteLineAsync(CsvHeader);
foreach (var child in LogsControl.LogsContainer.Children)
{
if (child is not AdminLogLabel logLabel || !child.Visible)
continue;
var log = logLabel.Log;
// Date
// I swear to god if someone adds ,s or "s to the other fields...
await writer.WriteAsync(log.Date.ToString("s", System.Globalization.CultureInfo.InvariantCulture));
await writer.WriteAsync(CsvSeparator);
// ID
await writer.WriteAsync(log.Id.ToString());
await writer.WriteAsync(CsvSeparator);
// PlayerID
var players = log.Players;
for (var i = 0; i < players.Length; i++)
{
await writer.WriteAsync(players[i] + (i == players.Length - 1 ? "" : " "));
}
await writer.WriteAsync(CsvSeparator);
// Severity
await writer.WriteAsync(log.Impact.ToString());
await writer.WriteAsync(CsvSeparator);
// Type
await writer.WriteAsync(log.Type.ToString());
await writer.WriteAsync(CsvSeparator);
// Message
await writer.WriteAsync(CsvQuote);
await writer.WriteAsync(log.Message.Replace(CsvQuote, CsvQuote + CsvQuote));
await writer.WriteAsync(CsvQuote);
await writer.WriteLineAsync();
}
}
catch (Exception exc)
{
_sawmill.Error($"Error when exporting admin log:\n{exc.StackTrace}");
}
finally
{
await file.Value.fileStream.DisposeAsync();
_currentlyExportingLogs = false;
LogsControl.ExportLogs.Disabled = false;
}
}
private void PopOut()
{
if (LogsWindow == null)

View File

@@ -132,8 +132,8 @@ public sealed partial class ObjectsTab : Control
entry.OnTeleport += TeleportTo;
entry.OnDelete += Delete;
button.ToolTip = $"{info.Name}, {info.Entity}";
button.AddChild(entry);
button.StyleClasses.Clear();
}
private bool DataFilterCondition(string filter, ListData listData)

View File

@@ -172,7 +172,6 @@ public sealed partial class PlayerTab : Control
_playerTabSymbolSetting);
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
button.StyleClasses.Clear();
}
/// <summary>

View File

@@ -1,11 +1,9 @@
using Content.Shared.Administration;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
@@ -13,7 +11,6 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab;
public sealed partial class PlayerTabEntry : PanelContainer
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
public PlayerTabEntry(
PlayerInfo player,
@@ -26,8 +23,6 @@ public sealed partial class PlayerTabEntry : PanelContainer
RobustXamlLoader.Load(this);
var roles = _entMan.System<SharedRoleSystem>();
var rolePrototype = player.RoleProto == null ? null : _prototype.Index(player.RoleProto.Value);
UsernameLabel.Text = player.Username;
if (!player.Connected)
UsernameLabel.StyleClasses.Add("Disabled");
@@ -62,19 +57,19 @@ public sealed partial class PlayerTabEntry : PanelContainer
break;
default:
case AdminPlayerTabSymbolOption.Specific:
symbol = player.Antag ? rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol : string.Empty;
symbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
break;
}
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
if (player.Antag && colorAntags)
CharacterLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
CharacterLabel.FontColorOverride = player.RoleProto.Color;
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
var roletype = RoleTypeLabel.Text = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var subtype = roles.GetRoleSubtypeLabel(rolePrototype?.Name ?? RoleTypePrototype.FallbackName, player.Subtype);
var roletype = RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
var subtype = roles.GetRoleSubtypeLabel(player.RoleProto.Name, player.Subtype);
switch (roleSetting)
{
case AdminPlayerTabRoleTypeOption.RoleTypeSubtype:
@@ -97,7 +92,7 @@ public sealed partial class PlayerTabEntry : PanelContainer
}
if (colorRoles)
RoleTypeLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
BackgroundColorPanel.PanelOverride = styleBoxFlat;
OverallPlaytimeLabel.Text = player.PlaytimeString;
}

View File

@@ -1,90 +0,0 @@
using System.Numerics;
using Content.Shared.Alert.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
namespace Content.Client.Alerts;
/// <summary>
/// This handles <see cref="GenericCounterAlertComponent"/>
/// </summary>
public sealed class GenericCounterAlertSystem : EntitySystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<GenericCounterAlertComponent, UpdateAlertSpriteEvent>(OnUpdateAlertSprite);
}
private void OnUpdateAlertSprite(Entity<GenericCounterAlertComponent> ent, ref UpdateAlertSpriteEvent args)
{
var sprite = args.SpriteViewEnt.Comp;
var ev = new GetGenericAlertCounterAmountEvent(args.Alert);
RaiseLocalEvent(args.ViewerEnt, ref ev);
if (!ev.Handled)
return;
// It cannot be null if its handled, but good to check to avoid ugly null ignores.
if (ev.Amount == null)
return;
// How many digits can we display
var maxDigitCount = GetMaxDigitCount((ent, ent, sprite));
// Clamp it to a positive number that we can actually display in full (no rollover to 0)
var amount = (int) Math.Clamp(ev.Amount.Value, 0, Math.Pow(10, maxDigitCount) - 1);
// This is super wack but ig it works?
var digitCount = ent.Comp.HideLeadingZeroes
? amount.ToString().Length
: maxDigitCount;
if (ent.Comp.HideLeadingZeroes)
{
for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
{
if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
continue;
_sprite.LayerSetVisible(ent.Owner, layer, i <= digitCount - 1);
}
}
// ReSharper disable once PossibleLossOfFraction
var baseOffset = (ent.Comp.AlertSize.X - digitCount * ent.Comp.GlyphWidth) / 2 * (1f / EyeManager.PixelsPerMeter);
for (var i = 0; i < ent.Comp.DigitKeys.Count; i++)
{
if (!_sprite.LayerMapTryGet(ent.Owner, ent.Comp.DigitKeys[i], out var layer, false))
continue;
var result = amount / (int) Math.Pow(10, i) % 10;
_sprite.LayerSetRsiState(ent.Owner, layer, result.ToString());
if (ent.Comp.CenterGlyph)
{
var offset = baseOffset + (digitCount - 1 - i) * ent.Comp.GlyphWidth * (1f / EyeManager.PixelsPerMeter);
_sprite.LayerSetOffset(ent.Owner, layer, new Vector2(offset, 0));
}
}
}
/// <summary>
/// Gets the number of digits that we can display.
/// </summary>
/// <returns>The number of digits.</returns>
private int GetMaxDigitCount(Entity<GenericCounterAlertComponent, SpriteComponent> ent)
{
for (var i = ent.Comp1.DigitKeys.Count - 1; i >= 0; i--)
{
if (_sprite.LayerExists((ent.Owner, ent.Comp2), ent.Comp1.DigitKeys[i]))
return i + 1;
}
return 0;
}
}

View File

@@ -11,14 +11,11 @@ public record struct UpdateAlertSpriteEvent
{
public Entity<SpriteComponent> SpriteViewEnt;
public EntityUid ViewerEnt;
public AlertPrototype Alert;
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, EntityUid viewerEnt, AlertPrototype alert)
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, AlertPrototype alert)
{
SpriteViewEnt = spriteViewEnt;
ViewerEnt = viewerEnt;
Alert = alert;
}
}

View File

@@ -1,31 +0,0 @@
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping.Binary.Components;
namespace Content.Client.Atmos.EntitySystems;
/// <summary>
/// Represents the client system responsible for managing and updating the gas pressure regulator interface.
/// Inherits from the shared system <see cref="SharedGasPressureRegulatorSystem"/>.
/// </summary>
public sealed partial class GasPressureRegulatorSystem : SharedGasPressureRegulatorSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasPressureRegulatorComponent, AfterAutoHandleStateEvent>(OnValveUpdate);
}
private void OnValveUpdate(Entity<GasPressureRegulatorComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateUi(ent);
}
protected override void UpdateUi(Entity<GasPressureRegulatorComponent> ent)
{
if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, GasPressureRegulatorUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -1,58 +0,0 @@
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Localizations;
using Robust.Client.UserInterface;
namespace Content.Client.Atmos.UI;
public sealed class GasPressureRegulatorBoundUserInterface(EntityUid owner, Enum uiKey)
: BoundUserInterface(owner, uiKey)
{
private GasPressureRegulatorWindow? _window;
protected override void Open()
{
base.Open();
_window = this.CreateWindow<GasPressureRegulatorWindow>();
_window.SetEntity(Owner);
_window.ThresholdPressureChanged += OnThresholdChanged;
if (EntMan.TryGetComponent(Owner, out GasPressureRegulatorComponent? comp))
_window.SetThresholdPressureInput(comp.Threshold);
Update();
}
public override void Update()
{
if (_window == null)
return;
_window.Title = Identity.Name(Owner, EntMan);
if (!EntMan.TryGetComponent(Owner, out GasPressureRegulatorComponent? comp))
return;
_window.SetThresholdPressureLabel(comp.Threshold);
_window.UpdateInfo(comp.InletPressure, comp.OutletPressure, comp.FlowRate);
}
private void OnThresholdChanged(string newThreshold)
{
var sentThreshold = 0f;
if (UserInputParser.TryFloat(newThreshold, out var parsedNewThreshold) && parsedNewThreshold >= 0 &&
!float.IsInfinity(parsedNewThreshold))
{
sentThreshold = parsedNewThreshold;
}
// Autofill to zero if the user inputs an invalid value.
_window?.SetThresholdPressureInput(sentThreshold);
SendPredictedMessage(new GasPressureRegulatorChangeThresholdMessage(sentThreshold));
}
}

View File

@@ -1,96 +0,0 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="345 380"
MinSize="345 380"
Title="{Loc gas-pressure-regulator-ui-title}"
Resizable="False">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Vertical" Margin="0 10 0 10">
<BoxContainer Orientation="Vertical" Align="Center">
<Label Text="{Loc gas-pressure-regulator-ui-outlet}" Align="Center" StyleClasses="LabelKeyText" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Label Name="OutletPressureLabel" Text="N/A" Margin="0 0 4 0" />
<Label Text="{Loc gas-pressure-regulator-ui-pressure-unit}" />
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Align="Center">
<BoxContainer Orientation="Vertical" Align="Center" HorizontalExpand="True">
<Label Text="{Loc gas-pressure-regulator-ui-target}" Align="Right" StyleClasses="LabelKeyText" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Right">
<Label Name="TargetPressureLabel" Margin="0 0 4 0" />
<Label Text="{Loc gas-pressure-regulator-ui-pressure-unit}" />
</BoxContainer>
</BoxContainer>
<ProgressBar Name="ToTargetBar" MaxValue="1" SetSize="5 75" Margin="10" Vertical="True" />
<SpriteView Name="EntityView" SetSize="64 64" Scale="3 3" OverrideDirection="North" Margin="0" />
<ProgressBar Name="FlowRateBar" MaxValue="1" SetSize="5 75" Margin="10" Vertical="True" />
<BoxContainer Orientation="Vertical" Align="Center" HorizontalExpand="True">
<Label Text="{Loc gas-pressure-regulator-ui-flow}" StyleClasses="LabelKeyText" />
<BoxContainer Orientation="Horizontal">
<Label Name="CurrentFlowLabel" Text="N/A" Margin="0 0 4 0" />
<Label Text="{Loc gas-pressure-regulator-ui-flow-rate-unit}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" Align="Center" Margin="1">
<Label Text="{Loc gas-pressure-regulator-ui-inlet}" Align="Center" StyleClasses="LabelKeyText" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Label Name="InletPressureLabel" Text="N/A" Margin="0 0 4 0" />
<Label Text="{Loc gas-pressure-regulator-ui-pressure-unit}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Controls to Set Pressure -->
<controls:StripeBack Name="SetPressureStripeBack" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="10 10 10 10">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<LineEdit Name="ThresholdInput" HorizontalExpand="True" MinSize="70 0" />
<Button Name="SetThresholdButton" Text="{Loc gas-pressure-regulator-ui-set-threshold}"
Disabled="True" Margin="5 0 0 0" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
<Button Name="Subtract1000Button" Text="{Loc gas-pressure-regulator-ui-subtract-1000}"
HorizontalExpand="True" Margin="0 2 2 0"
StyleClasses="OpenBoth" />
<Button Name="Subtract100Button" Text="{Loc gas-pressure-regulator-ui-subtract-100}"
HorizontalExpand="True" Margin="0 2 2 0"
StyleClasses="OpenBoth" />
<Button Name="Subtract10Button" Text="{Loc gas-pressure-regulator-ui-subtract-10}"
HorizontalExpand="True" Margin="0 2 2 0"
StyleClasses="OpenBoth" />
<Button Name="Add10Button" Text="{Loc gas-pressure-regulator-ui-add-10}" HorizontalExpand="True"
Margin="0 2 2 0"
StyleClasses="OpenBoth" />
<Button Name="Add100Button" Text="{Loc gas-pressure-regulator-ui-add-100}"
HorizontalExpand="True" Margin="0 2 2 0"
StyleClasses="OpenBoth" />
<Button Name="Add1000Button" Text="{Loc gas-pressure-regulator-ui-add-1000}"
HorizontalExpand="True" Margin="0 2 2 0"
StyleClasses="OpenBoth" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 5 0 0">
<Button Name="ZeroThresholdButton" Text="{Loc gas-pressure-regulator-ui-zero-threshold}"
HorizontalExpand="True" Margin="0 0 5 0" />
<Button Name="SetToCurrentPressureButton"
Text="{Loc gas-pressure-regulator-ui-set-to-current-pressure}" HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</controls:StripeBack>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -1,129 +0,0 @@
using System.Globalization;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client.Atmos.UI;
/// <summary>
/// Client-side UI for controlling a pressure regulator.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class GasPressureRegulatorWindow : FancyWindow
{
private float _flowRate;
public GasPressureRegulatorWindow()
{
RobustXamlLoader.Load(this);
ThresholdInput.OnTextChanged += _ => SetThresholdButton.Disabled = false;
SetThresholdButton.OnPressed += _ =>
{
ThresholdPressureChanged?.Invoke(ThresholdInput.Text ??= "");
SetThresholdButton.Disabled = true;
};
SetToCurrentPressureButton.OnPressed += _ =>
{
if (InletPressureLabel.Text != null)
{
ThresholdInput.Text = InletPressureLabel.Text;
}
SetThresholdButton.Disabled = false;
};
ZeroThresholdButton.OnPressed += _ =>
{
ThresholdInput.Text = "0";
SetThresholdButton.Disabled = false;
};
Add1000Button.OnPressed += _ => AdjustThreshold(1000);
Add100Button.OnPressed += _ => AdjustThreshold(100);
Add10Button.OnPressed += _ => AdjustThreshold(10);
Subtract10Button.OnPressed += _ => AdjustThreshold(-10);
Subtract100Button.OnPressed += _ => AdjustThreshold(-100);
Subtract1000Button.OnPressed += _ => AdjustThreshold(-1000);
return;
void AdjustThreshold(float adjustment)
{
if (float.TryParse(ThresholdInput.Text, out var currentValue))
{
ThresholdInput.Text = (currentValue + adjustment).ToString(CultureInfo.CurrentCulture);
SetThresholdButton.Disabled = false;
}
}
}
public event Action<string>? ThresholdPressureChanged;
/// <summary>
/// Sets the current threshold pressure label. This is not setting the threshold input box.
/// </summary>
/// <param name="threshold"> Threshold to set.</param>
public void SetThresholdPressureLabel(float threshold)
{
TargetPressureLabel.Text = threshold.ToString(CultureInfo.CurrentCulture);
}
/// <summary>
/// Sets the threshold pressure input field with the given value.
/// When the client opens the UI the field will be autofilled with the current threshold pressure.
/// </summary>
/// <param name="input">The threshold pressure value to autofill into the input field.</param>
public void SetThresholdPressureInput(float input)
{
ThresholdInput.Text = input.ToString(CultureInfo.CurrentCulture);
}
/// <summary>
/// Sets the entity to be visible in the UI.
/// </summary>
/// <param name="entity"></param>
public void SetEntity(EntityUid entity)
{
EntityView.SetEntity(entity);
}
/// <summary>
/// Updates the UI for the labels.
/// </summary>
/// <param name="inletPressure">The current pressure at the valve's inlet.</param>
/// <param name="outletPressure">The current pressure at the valve's outlet.</param>
/// <param name="flowRate">The current flow rate through the valve.</param>
public void UpdateInfo(float inletPressure, float outletPressure, float flowRate)
{
if (float.TryParse(TargetPressureLabel.Text, out var parsedfloat))
ToTargetBar.Value = inletPressure / parsedfloat;
InletPressureLabel.Text = float.Round(inletPressure).ToString(CultureInfo.CurrentCulture);
OutletPressureLabel.Text = float.Round(outletPressure).ToString(CultureInfo.CurrentCulture);
CurrentFlowLabel.Text = float.IsNaN(flowRate) ? "0" : flowRate.ToString(CultureInfo.CurrentCulture);
_flowRate = flowRate;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
// Defines the flow rate at which the progress bar fills in one second.
// If the flow rate is >50 L/s, the bar will take <1 second to fill.
// If the flow rate is <50 L/s, the bar will take >1 second to fill.
const int barFillPerSecond = 50;
var maxValue = FlowRateBar.MaxValue;
// Increment the progress bar value based on elapsed time
FlowRateBar.Value += (_flowRate / barFillPerSecond) * args.DeltaSeconds;
// Reset the progress bar when it is fully filled
if (FlowRateBar.Value >= maxValue)
{
FlowRateBar.Value = 0f;
}
}
}

View File

@@ -168,7 +168,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
_targetTime = _gameTiming.CurTime + TimeSpan.FromSeconds(_cooldown);
var player = _playerManager.LocalEntity;
if (!TryComp(player, out TransformComponent? xform))
if (!EntityManager.TryGetComponent(player, out TransformComponent? xform))
{
ClearSounds();
return;

View File

@@ -1,5 +0,0 @@
using Content.Shared.Body.Systems;
namespace Content.Client.Body.Systems;
public sealed class BloodstreamSystem : SharedBloodstreamSystem;

View File

@@ -5,27 +5,32 @@ using Robust.Shared.Containers;
namespace Content.Client.Commands;
public sealed class HideMechanismsCommand : LocalizedEntityCommands
public sealed class HideMechanismsCommand : LocalizedCommands
{
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Command => "hidemechanisms";
public override string Description => LocalizationManager.GetString($"cmd-{Command}-desc", ("showMechanismsCommand", ShowMechanismsCommand.CommandName));
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
var containerSys = _entityManager.System<SharedContainerSystem>();
var spriteSys = _entityManager.System<SpriteSystem>();
var query = _entityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out _, out var sprite))
{
_spriteSystem.SetContainerOccluded((uid, sprite), false);
spriteSys.SetContainerOccluded((uid, sprite), false);
var tempParent = uid;
while (_containerSystem.TryGetContainingContainer((tempParent, null, null), out var container))
while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
{
if (!container.ShowContents)
{
_spriteSystem.SetContainerOccluded((uid, sprite), true);
spriteSys.SetContainerOccluded((uid, sprite), true);
break;
}

View File

@@ -1,46 +1,57 @@
using Content.Shared.Damage.Prototypes;
using Content.Shared.Overlays;
using Robust.Client.Player;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
using System.Linq;
namespace Content.Client.Commands;
public sealed class ShowHealthBarsCommand : LocalizedEntityCommands
public sealed class ShowHealthBarsCommand : LocalizedCommands
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Command => "showhealthbars";
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player;
var player = _playerManager.LocalSession;
if (player == null)
{
shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command"));
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-not-player"));
return;
}
if (player.AttachedEntity is not { } playerEntity)
var playerEntity = player?.AttachedEntity;
if (!playerEntity.HasValue)
{
shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity"));
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-no-entity"));
return;
}
if (!EntityManager.HasComponent<ShowHealthBarsComponent>(playerEntity))
if (!_entityManager.HasComponent<ShowHealthBarsComponent>(playerEntity))
{
var showHealthBarsComponent = new ShowHealthBarsComponent
{
DamageContainers = args.Select(arg => new ProtoId<DamageContainerPrototype>(arg)).ToList(),
HealthStatusIcon = null,
NetSyncEnabled = false,
NetSyncEnabled = false
};
EntityManager.AddComponent(playerEntity, showHealthBarsComponent, true);
_entityManager.AddComponent(playerEntity.Value, showHealthBarsComponent, true);
shell.WriteLine(Loc.GetString("cmd-showhealthbars-notify-enabled", ("args", string.Join(", ", args))));
shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-enabled", ("args", string.Join(", ", args))));
return;
}
else
{
_entityManager.RemoveComponentDeferred<ShowHealthBarsComponent>(playerEntity.Value);
shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-disabled"));
}
EntityManager.RemoveComponentDeferred<ShowHealthBarsComponent>(playerEntity);
shell.WriteLine(Loc.GetString("cmd-showhealthbars-notify-disabled"));
return;
}
}

View File

@@ -4,19 +4,24 @@ using Robust.Shared.Console;
namespace Content.Client.Commands;
public sealed class ShowMechanismsCommand : LocalizedEntityCommands
public sealed class ShowMechanismsCommand : LocalizedCommands
{
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
public override string Command => "showmechanisms";
public const string CommandName = "showmechanisms";
public override string Command => CommandName;
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
var spriteSys = _entManager.System<SpriteSystem>();
var query = _entManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out _, out var sprite))
{
_spriteSystem.SetContainerOccluded((uid, sprite), false);
spriteSys.SetContainerOccluded((uid, sprite), false);
}
}
}

View File

@@ -286,14 +286,14 @@ namespace Content.Client.Construction
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))
return false;
ghost = Spawn("constructionghost", loc);
var comp = Comp<ConstructionGhostComponent>(ghost.Value);
ghost = EntityManager.SpawnEntity("constructionghost", loc);
var comp = EntityManager.GetComponent<ConstructionGhostComponent>(ghost.Value);
comp.Prototype = prototype;
comp.GhostId = ghost.GetHashCode();
Comp<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
_ghosts.Add(comp.GhostId, ghost.Value);
var sprite = Comp<SpriteComponent>(ghost.Value);
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
_sprite.SetColor((ghost.Value, sprite), new Color(48, 255, 48, 128));
if (targetProto.TryGetComponent(out IconComponent? icon, EntityManager.ComponentFactory))
@@ -306,7 +306,7 @@ namespace Content.Client.Construction
else if (targetProto.Components.TryGetValue("Sprite", out _))
{
var dummy = EntityManager.SpawnEntity(targetProtoId, MapCoordinates.Nullspace);
var targetSprite = EnsureComp<SpriteComponent>(dummy);
var targetSprite = EntityManager.EnsureComponent<SpriteComponent>(dummy);
EntityManager.System<AppearanceSystem>().OnChangeData(dummy, targetSprite);
for (var i = 0; i < targetSprite.AllLayers.Count(); i++)
@@ -325,7 +325,7 @@ namespace Content.Client.Construction
_sprite.LayerSetVisible((ghost.Value, sprite), i, true);
}
Del(dummy);
EntityManager.DeleteEntity(dummy);
}
else
return false;
@@ -367,7 +367,7 @@ namespace Content.Client.Construction
{
foreach (var ghost in _ghosts)
{
if (Comp<TransformComponent>(ghost.Value).Coordinates.Equals(loc))
if (EntityManager.GetComponent<TransformComponent>(ghost.Value).Coordinates.Equals(loc))
return true;
}
@@ -384,7 +384,7 @@ namespace Content.Client.Construction
throw new ArgumentException($"Can't start construction for a ghost with no prototype. Ghost id: {ghostId}");
}
var transform = Comp<TransformComponent>(ghostId);
var transform = EntityManager.GetComponent<TransformComponent>(ghostId);
var msg = new TryStartStructureConstructionMessage(GetNetCoordinates(transform.Coordinates), ghostComp.Prototype.ID, transform.LocalRotation, ghostId.GetHashCode());
RaiseNetworkEvent(msg);
}
@@ -405,7 +405,7 @@ namespace Content.Client.Construction
if (!_ghosts.TryGetValue(ghostId, out var ghost))
return;
QueueDel(ghost);
EntityManager.QueueDeleteEntity(ghost);
_ghosts.Remove(ghostId);
}
@@ -416,7 +416,7 @@ namespace Content.Client.Construction
{
foreach (var ghost in _ghosts.Values)
{
QueueDel(ghost);
EntityManager.QueueDeleteEntity(ghost);
}
_ghosts.Clear();

View File

@@ -7,7 +7,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Client\</OutputPath>
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
<WarningsAsErrors>RA0032;nullable</WarningsAsErrors>
<WarningsAsErrors>nullable</WarningsAsErrors>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;Tools;DebugOpt</Configurations>
<Platforms>AnyCPU</Platforms>

View File

@@ -1,5 +1,5 @@
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@@ -15,7 +15,6 @@ public sealed class DrowsinessOverlay : Overlay
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly SharedStatusEffectsSystem _statusEffects = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
@@ -30,9 +29,6 @@ public sealed class DrowsinessOverlay : Overlay
public DrowsinessOverlay()
{
IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
_drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
}
@@ -43,11 +39,17 @@ public sealed class DrowsinessOverlay : Overlay
if (playerEntity == null)
return;
if (!_statusEffects.TryGetEffectsEndTimeWithComp<DrowsinessStatusEffectComponent>(playerEntity, out var endTime))
if (!_entityManager.HasComponent<DrowsinessComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
endTime ??= TimeSpan.MaxValue;
var timeLeft = (float)(endTime - _timing.CurTime).Value.TotalSeconds;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
if (!statusSys.TryGetTime(playerEntity.Value, SharedDrowsinessSystem.DrowsinessKey, out var time, status))
return;
var curTime = _timing.CurTime;
var timeLeft = (float)(time.Value.Item2 - curTime).TotalSeconds;
CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
}

View File

@@ -1,5 +1,4 @@
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -10,7 +9,6 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
private DrowsinessOverlay _overlay = default!;
@@ -18,44 +16,35 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
{
base.Initialize();
SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectAppliedEvent>(OnDrowsinessApply);
SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectRemovedEvent>(OnDrowsinessShutdown);
SubscribeLocalEvent<DrowsinessComponent, ComponentInit>(OnDrowsinessInit);
SubscribeLocalEvent<DrowsinessComponent, ComponentShutdown>(OnDrowsinessShutdown);
SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerAttachedEvent>>(OnStatusEffectPlayerAttached);
SubscribeLocalEvent<DrowsinessStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerDetachedEvent>>(OnStatusEffectPlayerDetached);
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DrowsinessComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnDrowsinessApply(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
{
if (_player.LocalEntity == args.Target)
_overlayMan.AddOverlay(_overlay);
}
private void OnDrowsinessShutdown(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
{
if (_player.LocalEntity != args.Target)
return;
if (!_statusEffects.HasEffectComp<DrowsinessStatusEffectComponent>(_player.LocalEntity.Value))
{
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}
private void OnStatusEffectPlayerAttached(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectRelayedEvent<LocalPlayerAttachedEvent> args)
private void OnPlayerAttached(EntityUid uid, DrowsinessComponent component, LocalPlayerAttachedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnStatusEffectPlayerDetached(Entity<DrowsinessStatusEffectComponent> ent, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
private void OnPlayerDetached(EntityUid uid, DrowsinessComponent component, LocalPlayerDetachedEvent args)
{
if (_player.LocalEntity is null)
return;
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
if (!_statusEffects.HasEffectComp<DrowsinessStatusEffectComponent>(_player.LocalEntity.Value))
private void OnDrowsinessInit(EntityUid uid, DrowsinessComponent component, ComponentInit args)
{
if (_player.LocalEntity == uid)
_overlayMan.AddOverlay(_overlay);
}
private void OnDrowsinessShutdown(EntityUid uid, DrowsinessComponent component, ComponentShutdown args)
{
if (_player.LocalEntity == uid)
{
_overlay.CurrentPower = 0;
_overlayMan.RemoveOverlay(_overlay);

View File

@@ -1,5 +1,4 @@
using Content.Shared.Drugs;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -18,47 +17,49 @@ public sealed class DrugOverlaySystem : EntitySystem
private RainbowOverlay _overlay = default!;
public static string RainbowKey = "SeeingRainbows";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectAppliedEvent>(OnApplied);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectRemovedEvent>(OnRemoved);
SubscribeLocalEvent<SeeingRainbowsComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SeeingRainbowsComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerAttachedEvent>>(OnPlayerAttached);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerDetachedEvent>>(OnPlayerDetached);
SubscribeLocalEvent<SeeingRainbowsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<SeeingRainbowsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
_overlay = new();
}
private void OnRemoved(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
private void OnPlayerAttached(EntityUid uid, SeeingRainbowsComponent component, LocalPlayerAttachedEvent args)
{
if (_player.LocalEntity != args.Target)
return;
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, SeeingRainbowsComponent component, LocalPlayerDetachedEvent args)
{
_overlay.Intoxication = 0;
_overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnApplied(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
private void OnInit(EntityUid uid, SeeingRainbowsComponent component, ComponentInit args)
{
if (_player.LocalEntity != args.Target)
return;
_overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect
_overlayMan.AddOverlay(_overlay);
if (_player.LocalEntity == uid)
{
_overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect
_overlayMan.AddOverlay(_overlay);
}
}
private void OnPlayerAttached(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectRelayedEvent<LocalPlayerAttachedEvent> args)
private void OnShutdown(EntityUid uid, SeeingRainbowsComponent component, ComponentShutdown args)
{
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
{
_overlay.Intoxication = 0;
_overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
if (_player.LocalEntity == uid)
{
_overlay.Intoxication = 0;
_overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}
}

View File

@@ -1,6 +1,6 @@
using Content.Shared.CCVar;
using Content.Shared.Drugs;
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
@@ -17,8 +17,6 @@ public sealed class RainbowOverlay : Overlay
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly SharedStatusEffectsSystem _statusEffects = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
@@ -39,8 +37,6 @@ public sealed class RainbowOverlay : Overlay
{
IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
_rainbowShader = _prototypeManager.Index<ShaderPrototype>("Rainbow").InstanceUnique();
_config.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true);
}
@@ -58,13 +54,18 @@ public sealed class RainbowOverlay : Overlay
if (playerEntity == null)
return;
if (!_statusEffects.TryGetEffectsEndTimeWithComp<SeeingRainbowsStatusEffectComponent>(playerEntity, out var endTime))
if (!_entityManager.HasComponent<SeeingRainbowsComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
return;
endTime ??= TimeSpan.MaxValue;
var timeLeft = (float)(endTime - _timing.CurTime).Value.TotalSeconds;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
if (!statusSys.TryGetTime(playerEntity.Value, DrugOverlaySystem.RainbowKey, out var time, status))
return;
var timeLeft = (float)(time.Value.Item2 - time.Value.Item1).TotalSeconds;
TimeTicker += args.DeltaSeconds;
if (timeLeft - TimeTicker > timeLeft / 16f)
{
Intoxication += (timeLeft - Intoxication) * args.DeltaSeconds / 16f;

View File

@@ -110,7 +110,7 @@ namespace Content.Client.Examine
{
var entity = args.EntityUid;
if (!args.EntityUid.IsValid() || !Exists(entity))
if (!args.EntityUid.IsValid() || !EntityManager.EntityExists(entity))
{
return false;
}
@@ -225,7 +225,7 @@ namespace Content.Client.Examine
vBox.AddChild(hBox);
if (HasComp<SpriteComponent>(target))
if (EntityManager.HasComponent<SpriteComponent>(target))
{
var spriteView = new SpriteView
{

View File

@@ -61,7 +61,7 @@ public sealed partial class TriggerSystem
private void OnProximityInit(EntityUid uid, TriggerOnProximityComponent component, ComponentInit args)
{
EnsureComp<AnimationPlayerComponent>(uid);
EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
}
private void OnProxAppChange(EntityUid uid, TriggerOnProximityComponent component, ref AppearanceChangeEvent args)

View File

@@ -1,5 +1,6 @@
using Content.Shared.Flash;
using Content.Shared.Flash.Components;
using Content.Shared.StatusEffect;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Utility;
namespace Content.Client.Guidebook.Richtext;
[UsedImplicitly]
public sealed class KeyBindTag : IMarkupTagHandler
public sealed class KeyBindTag : IMarkupTag
{
[Dependency] private readonly IInputManager _inputManager = default!;

View File

@@ -9,7 +9,7 @@ namespace Content.Client.Guidebook.RichText;
/// In order to be accessed by this tag, the desired field/property must
/// be tagged with <see cref="Shared.Guidebook.GuidebookDataAttribute"/>.
/// </summary>
public sealed class ProtodataTag : IMarkupTagHandler
public sealed class ProtodataTag : IMarkupTag
{
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IEntityManager _entMan = default!;

View File

@@ -10,14 +10,16 @@ using Content.Client.UserInterface.ControlExtensions;
namespace Content.Client.Guidebook.RichText;
[UsedImplicitly]
public sealed class TextLinkTag : IMarkupTagHandler
public sealed class TextLinkTag : IMarkupTag
{
public static Color LinkColor => Color.CornflowerBlue;
public string Name => "textlink";
public Control? Control;
/// <inheritdoc/>
public bool TryCreateControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
public bool TryGetControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
{
if (!node.Value.TryGetString(out var text)
|| !node.Attributes.TryGetValue("link", out var linkParameter)
@@ -36,21 +38,22 @@ public sealed class TextLinkTag : IMarkupTagHandler
label.OnMouseEntered += _ => label.FontColorOverride = Color.LightSkyBlue;
label.OnMouseExited += _ => label.FontColorOverride = Color.CornflowerBlue;
label.OnKeyBindDown += args => OnKeybindDown(args, link, label);
label.OnKeyBindDown += args => OnKeybindDown(args, link);
control = label;
Control = label;
return true;
}
private void OnKeybindDown(GUIBoundKeyEventArgs args, string link, Control? control)
private void OnKeybindDown(GUIBoundKeyEventArgs args, string link)
{
if (args.Function != EngineKeyFunctions.UIClick)
return;
if (control == null)
if (Control == null)
return;
if (control.TryGetParentHandler<ILinkClickHandler>(out var handler))
if (Control.TryGetParentHandler<ILinkClickHandler>(out var handler))
handler.HandleClick(link);
else
Logger.Warning("Warning! No valid ILinkClickHandler found.");

View File

@@ -16,6 +16,7 @@ using Robust.Client.UserInterface;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Hands.Systems
@@ -26,13 +27,16 @@ namespace Content.Client.Hands.Systems
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly StrippableSystem _stripSys = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly ExamineSystem _examine = default!;
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
public event Action<string, HandLocation>? OnPlayerAddHand;
public event Action<string>? OnPlayerRemoveHand;
public event Action<string?>? OnPlayerSetActiveHand;
public event Action<Entity<HandsComponent>>? OnPlayerHandsAdded;
public event Action<HandsComponent>? OnPlayerHandsAdded;
public event Action? OnPlayerHandsRemoved;
public event Action<string, EntityUid>? OnPlayerItemAdded;
public event Action<string, EntityUid>? OnPlayerItemRemoved;
@@ -54,28 +58,67 @@ namespace Content.Client.Hands.Systems
}
#region StateHandling
private void HandleComponentState(Entity<HandsComponent> ent, ref ComponentHandleState args)
private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args)
{
if (args.Current is not HandsComponentState state)
return;
var newHands = state.Hands.Keys.Except(ent.Comp.Hands.Keys); // hands that were added between states
var oldHands = ent.Comp.Hands.Keys.Except(state.Hands.Keys); // hands that were removed between states
foreach (var handId in oldHands)
var handsModified = component.Hands.Count != state.Hands.Count;
// we need to check that, even if we have the same amount, that the individual hands didn't change.
if (!handsModified)
{
RemoveHand(ent.AsNullable(), handId);
foreach (var hand in component.Hands.Values)
{
if (state.Hands.Contains(hand))
continue;
handsModified = true;
break;
}
}
foreach (var handId in state.SortedHands.Intersect(newHands))
var manager = EnsureComp<ContainerManagerComponent>(uid);
if (handsModified)
{
AddHand(ent.AsNullable(), handId, state.Hands[handId]);
List<Hand> addedHands = new();
foreach (var hand in state.Hands)
{
if (component.Hands.ContainsKey(hand.Name))
continue;
var container = _containerSystem.EnsureContainer<ContainerSlot>(uid, hand.Name, manager);
var newHand = new Hand(hand.Name, hand.Location, container);
component.Hands.Add(hand.Name, newHand);
addedHands.Add(newHand);
}
foreach (var name in component.Hands.Keys)
{
if (!state.HandNames.Contains(name))
{
RemoveHand(uid, name, component);
}
}
component.SortedHands.Clear();
component.SortedHands.AddRange(state.HandNames);
var sorted = addedHands.OrderBy(hand => component.SortedHands.IndexOf(hand.Name));
foreach (var hand in sorted)
{
AddHand(uid, hand, component);
}
}
ent.Comp.SortedHands = new (state.SortedHands);
SetActiveHand(ent.AsNullable(), state.ActiveHandId);
_stripSys.UpdateUi(uid);
_stripSys.UpdateUi(ent);
if (component.ActiveHand == null && state.ActiveHand == null)
return; //edge case
if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name)
{
SetActiveHand(uid, component.Hands[state.ActiveHand!], component);
}
}
#endregion
@@ -86,77 +129,72 @@ namespace Content.Client.Hands.Systems
return;
}
OnPlayerHandsAdded?.Invoke(hands.Value);
OnPlayerHandsAdded?.Invoke(hands);
}
public override void DoDrop(Entity<HandsComponent?> ent,
string handId,
bool doDropInteraction = true,
bool log = true)
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
{
base.DoDrop(ent, handId, doDropInteraction, log);
base.DoDrop(uid, hand, doDropInteraction, hands, log);
if (TryGetHeldItem(ent, handId, out var held) && TryComp(held, out SpriteComponent? sprite))
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
sprite.RenderOrder = EntityManager.CurrentTick.Value;
}
public EntityUid? GetActiveHandEntity()
{
return TryGetPlayerHands(out var hands) ? GetActiveItem(hands.Value.AsNullable()) : null;
return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null;
}
/// <summary>
/// Get the hands component of the local player
/// </summary>
public bool TryGetPlayerHands([NotNullWhen(true)] out Entity<HandsComponent>? hands)
public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands)
{
var player = _playerManager.LocalEntity;
hands = null;
if (player == null || !TryComp<HandsComponent>(player.Value, out var handsComp))
return false;
hands = (player.Value, handsComp);
return true;
return player != null && TryComp(player.Value, out hands);
}
/// <summary>
/// Called when a user clicked on their hands GUI
/// </summary>
public void UIHandClick(Entity<HandsComponent> ent, string handName)
public void UIHandClick(HandsComponent hands, string handName)
{
var hands = ent.Comp;
if (hands.ActiveHandId == null)
if (!hands.Hands.TryGetValue(handName, out var pressedHand))
return;
var pressedEntity = GetHeldItem(ent.AsNullable(), handName);
var activeEntity = GetActiveItem(ent.AsNullable());
if (hands.ActiveHand == null)
return;
if (handName == hands.ActiveHandId && activeEntity != null)
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = hands.ActiveHand.HeldEntity;
if (pressedHand == hands.ActiveHand && activeEntity != null)
{
// use item in hand
// it will always be attack_self() in my heart.
RaisePredictiveEvent(new RequestUseInHandEvent());
EntityManager.RaisePredictiveEvent(new RequestUseInHandEvent());
return;
}
if (handName != hands.ActiveHandId && pressedEntity == null)
if (pressedHand != hands.ActiveHand && pressedEntity == null)
{
// change active hand
RaisePredictiveEvent(new RequestSetHandEvent(handName));
EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName));
return;
}
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity != null)
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null)
{
// use active item on held item
RaisePredictiveEvent(new RequestHandInteractUsingEvent(handName));
EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name));
return;
}
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity == null)
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null)
{
// move the item to the active hand
RaisePredictiveEvent(new RequestMoveHandItemEvent(handName));
EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name));
}
}
@@ -166,18 +204,19 @@ namespace Content.Client.Hands.Systems
/// </summary>
public void UIHandActivate(string handName)
{
RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
EntityManager.RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
}
public void UIInventoryExamine(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
{
return;
}
_examine.DoExamine(heldEntity.Value);
_examine.DoExamine(entity);
}
/// <summary>
@@ -187,12 +226,13 @@ namespace Content.Client.Hands.Systems
public void UIHandOpenContextMenu(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
{
return;
}
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(heldEntity.Value);
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
}
public void UIHandAltActivateItem(string handName)
@@ -206,67 +246,60 @@ namespace Content.Client.Hands.Systems
{
base.HandleEntityInserted(uid, hands, args);
if (!hands.Hands.ContainsKey(args.Container.ID))
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
return;
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
UpdateHandVisuals(uid, args.Entity, hand);
_stripSys.UpdateUi(uid);
if (uid != _playerManager.LocalEntity)
return;
OnPlayerItemAdded?.Invoke(args.Container.ID, args.Entity);
OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
if (HasComp<VirtualItemComponent>(args.Entity))
OnPlayerHandBlocked?.Invoke(args.Container.ID);
OnPlayerHandBlocked?.Invoke(hand.Name);
}
protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
{
base.HandleEntityRemoved(uid, hands, args);
if (!hands.Hands.ContainsKey(args.Container.ID))
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
return;
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
UpdateHandVisuals(uid, args.Entity, hand);
_stripSys.UpdateUi(uid);
if (uid != _playerManager.LocalEntity)
return;
OnPlayerItemRemoved?.Invoke(args.Container.ID, args.Entity);
OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
if (HasComp<VirtualItemComponent>(args.Entity))
OnPlayerHandUnblocked?.Invoke(args.Container.ID);
OnPlayerHandUnblocked?.Invoke(hand.Name);
}
/// <summary>
/// Update the players sprite with new in-hand visuals.
/// </summary>
private void UpdateHandVisuals(Entity<HandsComponent?, SpriteComponent?> ent, EntityUid held, string handId)
private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null)
{
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false))
return;
var handComp = ent.Comp1;
var sprite = ent.Comp2;
if (!TryGetHand((ent, handComp), handId, out var hand))
if (!Resolve(uid, ref handComp, ref sprite, false))
return;
// visual update might involve changes to the entity's effective sprite -> need to update hands GUI.
if (ent == _playerManager.LocalEntity)
OnPlayerItemAdded?.Invoke(handId, held);
if (uid == _playerManager.LocalEntity)
OnPlayerItemAdded?.Invoke(hand.Name, held);
if (!handComp.ShowInHands)
return;
// Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this
// may eventually bloat the player with lots of layers.
if (handComp.RevealedLayers.TryGetValue(hand.Value.Location, out var revealedLayers))
if (handComp.RevealedLayers.TryGetValue(hand.Location, out var revealedLayers))
{
foreach (var key in revealedLayers)
{
_sprite.RemoveLayer((ent, sprite), key);
_sprite.RemoveLayer((uid, sprite), key);
}
revealedLayers.Clear();
@@ -274,22 +307,22 @@ namespace Content.Client.Hands.Systems
else
{
revealedLayers = new();
handComp.RevealedLayers[hand.Value.Location] = revealedLayers;
handComp.RevealedLayers[hand.Location] = revealedLayers;
}
if (HandIsEmpty((ent, handComp), handId))
if (hand.HeldEntity == null)
{
// the held item was removed.
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
return;
}
var ev = new GetInhandVisualsEvent(ent, hand.Value.Location);
var ev = new GetInhandVisualsEvent(uid, hand.Location);
RaiseLocalEvent(held, ev);
if (ev.Layers.Count == 0)
{
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
return;
}
@@ -302,7 +335,7 @@ namespace Content.Client.Hands.Systems
continue;
}
var index = _sprite.LayerMapReserve((ent, sprite), key);
var index = _sprite.LayerMapReserve((uid, sprite), key);
// In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
if (layerData.RsiPath == null
@@ -310,34 +343,35 @@ namespace Content.Client.Hands.Systems
&& sprite[index].Rsi == null)
{
if (TryComp<ItemComponent>(held, out var itemComponent) && itemComponent.RsiPath != null)
_sprite.LayerSetRsi((ent, sprite), index, new ResPath(itemComponent.RsiPath));
_sprite.LayerSetRsi((uid, sprite), index, new ResPath(itemComponent.RsiPath));
else if (TryComp(held, out SpriteComponent? clothingSprite))
_sprite.LayerSetRsi((ent, sprite), index, clothingSprite.BaseRSI);
_sprite.LayerSetRsi((uid, sprite), index, clothingSprite.BaseRSI);
}
_sprite.LayerSetData((ent, sprite), index, layerData);
_sprite.LayerSetData((uid, sprite), index, layerData);
// Add displacement maps
var displacement = hand.Value.Location switch
var displacement = hand.Location switch
{
HandLocation.Left => handComp.LeftHandDisplacement,
HandLocation.Right => handComp.RightHandDisplacement,
_ => handComp.HandDisplacement
};
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (ent, sprite), index, key, out var displacementKey))
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (uid, sprite), index, key, out var displacementKey))
revealedLayers.Add(displacementKey);
}
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
}
private void OnVisualsChanged(EntityUid uid, HandsComponent component, VisualsChangedEvent args)
{
// update hands visuals if this item is in a hand (rather then inventory or other container).
if (!component.Hands.ContainsKey(args.ContainerId))
return;
UpdateHandVisuals((uid, component), GetEntity(args.Item), args.ContainerId);
if (component.Hands.TryGetValue(args.ContainerId, out var hand))
{
UpdateHandVisuals(uid, GetEntity(args.Item), hand, component);
}
}
#endregion
@@ -345,7 +379,7 @@ namespace Content.Client.Hands.Systems
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, LocalPlayerAttachedEvent args)
{
OnPlayerHandsAdded?.Invoke((uid, component));
OnPlayerHandsAdded?.Invoke(component);
}
private void HandlePlayerDetached(EntityUid uid, HandsComponent component, LocalPlayerDetachedEvent args)
@@ -356,7 +390,7 @@ namespace Content.Client.Hands.Systems
private void OnHandsStartup(EntityUid uid, HandsComponent component, ComponentStartup args)
{
if (_playerManager.LocalEntity == uid)
OnPlayerHandsAdded?.Invoke((uid, component));
OnPlayerHandsAdded?.Invoke(component);
}
private void OnHandsShutdown(EntityUid uid, HandsComponent component, ComponentShutdown args)
@@ -366,6 +400,36 @@ namespace Content.Client.Hands.Systems
}
#endregion
private void AddHand(EntityUid uid, Hand newHand, HandsComponent? handsComp = null)
{
AddHand(uid, newHand.Name, newHand.Location, handsComp);
}
public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null)
{
base.AddHand(uid, handName, handLocation, handsComp);
if (uid == _playerManager.LocalEntity)
OnPlayerAddHand?.Invoke(handName, handLocation);
if (handsComp == null)
return;
if (handsComp.ActiveHand == null)
SetActiveHand(uid, handsComp.Hands[handName], handsComp);
}
public override void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
{
if (uid == _playerManager.LocalEntity && handsComp != null &&
handsComp.Hands.ContainsKey(handName) && uid ==
_playerManager.LocalEntity)
{
OnPlayerRemoveHand?.Invoke(handName);
}
base.RemoveHand(uid, handName, handsComp);
}
private void OnHandActivated(Entity<HandsComponent>? ent)
{
if (ent is not { } hand)
@@ -374,7 +438,13 @@ namespace Content.Client.Hands.Systems
if (_playerManager.LocalEntity != hand.Owner)
return;
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHandId);
if (hand.Comp.ActiveHand == null)
{
OnPlayerSetActiveHand?.Invoke(null);
return;
}
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHand.Name);
}
}
}

View File

@@ -107,7 +107,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
// Remove shading from all layers (except displacement maps)
for (var i = 0; i < hologramSprite.AllLayers.Count(); i++)
{
if (_sprite.TryGetLayer((hologram, hologramSprite), i, out var layer, false) && layer.ShaderPrototype != "DisplacedDraw")
if (_sprite.TryGetLayer((hologram, hologramSprite), i, out var layer, false) && layer.ShaderPrototype != "DisplacedStencilDraw")
hologramSprite.LayerSetShader(i, "unshaded");
}

View File

@@ -27,7 +27,6 @@ public sealed class EyeColorPicker : Control
AddChild(vBox);
vBox.AddChild(_colorSelectors = new ColorSelectorSliders());
_colorSelectors.SelectorType = ColorSelectorSliders.ColorSelectorType.Hsv; // defaults color selector to HSV
_colorSelectors.OnColorChanged += ColorValueChanged;
}

View File

@@ -36,7 +36,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
private void OnCvarChanged(bool value)
{
var humanoidQuery = AllEntityQuery<HumanoidAppearanceComponent, SpriteComponent>();
var humanoidQuery = EntityManager.AllEntityQueryEnumerator<HumanoidAppearanceComponent, SpriteComponent>();
while (humanoidQuery.MoveNext(out var uid, out var humanoidComp, out var spriteComp))
{
UpdateSprite((uid, humanoidComp, spriteComp));

View File

@@ -416,7 +416,6 @@ public sealed partial class MarkingPicker : Control
CMarkingColors.AddChild(colorContainer);
ColorSelectorSliders colorSelector = new ColorSelectorSliders();
colorSelector.SelectorType = ColorSelectorSliders.ColorSelectorType.Hsv; // defaults color selector to HSV
colorSliders.Add(colorSelector);
colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" });

View File

@@ -15,7 +15,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly SpriteSystem _sprite;
/// <summary>
/// What happens if a marking is selected.
/// It will send the 'slot' (marking index)
@@ -231,7 +231,6 @@ public sealed partial class SingleMarkingPicker : BoxContainer
HorizontalExpand = true
};
selector.Color = marking.MarkingColors[i];
selector.SelectorType = ColorSelectorSliders.ColorSelectorType.Hsv; // defaults color selector to HSV
var colorIndex = i;
selector.OnColorChanged += color =>

View File

@@ -102,8 +102,6 @@ public static class MidiParser
// 0x03 is TrackName,
// 0x04 is InstrumentName
// This string can potentially contain control characters, including 0x00 which can cause problems if it ends up in database entries via admin logs
// we sanitize TrackName and InstrumentName after they have been send to the server
var text = Encoding.ASCII.GetString(metaData, 0, (int)metaLength);
switch (metaType)
{

View File

@@ -194,12 +194,12 @@ namespace Content.Client.Inventory
public void UIInventoryActivate(string slot)
{
RaisePredictiveEvent(new UseSlotNetworkMessage(slot));
EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot));
}
public void UIInventoryStorageActivate(string slot)
{
RaisePredictiveEvent(new OpenSlotStorageNetworkMessage(slot));
EntityManager.RaisePredictiveEvent(new OpenSlotStorageNetworkMessage(slot));
}
public void UIInventoryExamine(string slot, EntityUid uid)
@@ -223,7 +223,7 @@ namespace Content.Client.Inventory
if (!TryGetSlotEntity(uid, slot, out var item))
return;
RaisePredictiveEvent(
EntityManager.RaisePredictiveEvent(
new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: false));
}
@@ -232,7 +232,7 @@ namespace Content.Client.Inventory
if (!TryGetSlotEntity(uid, slot, out var item))
return;
RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true));
EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(GetNetEntity(item.Value), altInteract: true));
}
protected override void UpdateInventoryTemplate(Entity<InventoryComponent> ent)

View File

@@ -1,7 +1,6 @@
using System.Linq;
using System.Numerics;
using Content.Client.Examine;
using Content.Client.Hands.Systems;
using Content.Client.Strip;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
@@ -35,7 +34,6 @@ namespace Content.Client.Inventory
[Dependency] private readonly IUserInterfaceManager _ui = default!;
private readonly ExamineSystem _examine;
private readonly HandsSystem _hands;
private readonly InventorySystem _inv;
private readonly SharedCuffableSystem _cuffable;
private readonly StrippableSystem _strippable;
@@ -67,7 +65,6 @@ namespace Content.Client.Inventory
public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_examine = EntMan.System<ExamineSystem>();
_hands = EntMan.System<HandsSystem>();
_inv = EntMan.System<InventorySystem>();
_cuffable = EntMan.System<SharedCuffableSystem>();
_strippable = EntMan.System<StrippableSystem>();
@@ -123,28 +120,28 @@ namespace Content.Client.Inventory
{
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
// and not gui hands... which are different...
foreach (var (id, hand) in handsComp.Hands)
foreach (var hand in handsComp.Hands.Values)
{
if (hand.Location != HandLocation.Right)
continue;
AddHandButton((Owner, handsComp), id, hand);
AddHandButton(hand);
}
foreach (var (id, hand) in handsComp.Hands)
foreach (var hand in handsComp.Hands.Values)
{
if (hand.Location != HandLocation.Middle)
continue;
AddHandButton((Owner, handsComp), id, hand);
AddHandButton(hand);
}
foreach (var (id, hand) in handsComp.Hands)
foreach (var hand in handsComp.Hands.Values)
{
if (hand.Location != HandLocation.Left)
continue;
AddHandButton((Owner, handsComp), id, hand);
AddHandButton(hand);
}
}
@@ -180,21 +177,20 @@ namespace Content.Client.Inventory
_strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize);
}
private void AddHandButton(Entity<HandsComponent> ent, string handId, Hand hand)
private void AddHandButton(Hand hand)
{
var button = new HandButton(handId, hand.Location);
var button = new HandButton(hand.Name, hand.Location);
button.Pressed += SlotPressed;
var heldEntity = _hands.GetHeldItem(ent.AsNullable(), handId);
if (EntMan.TryGetComponent<VirtualItemComponent>(heldEntity, out var virt))
if (EntMan.TryGetComponent<VirtualItemComponent>(hand.HeldEntity, out var virt))
{
button.Blocked = true;
if (EntMan.TryGetComponent<CuffableComponent>(Owner, out var cuff) && _cuffable.GetAllCuffs(cuff).Contains(virt.BlockingEntity))
button.BlockedRect.MouseFilter = MouseFilterMode.Ignore;
}
UpdateEntityIcon(button, heldEntity);
UpdateEntityIcon(button, hand.HeldEntity);
_strippingMenu!.HandsContainer.AddChild(button);
LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation));
_handCount++;

View File

@@ -223,14 +223,13 @@ public sealed partial class LatheMenu : DefaultWindow
/// Populates the build queue list with all queued items
/// </summary>
/// <param name="queue"></param>
public void PopulateQueueList(IReadOnlyCollection<ProtoId<LatheRecipePrototype>> queue)
public void PopulateQueueList(List<LatheRecipePrototype> queue)
{
QueueList.DisposeAllChildren();
var idx = 1;
foreach (var recipeProto in queue)
foreach (var recipe in queue)
{
var recipe = _prototypeManager.Index(recipeProto);
var queuedRecipeBox = new BoxContainer();
queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
@@ -244,14 +243,12 @@ public sealed partial class LatheMenu : DefaultWindow
}
}
public void SetQueueInfo(ProtoId<LatheRecipePrototype>? recipeProto)
public void SetQueueInfo(LatheRecipePrototype? recipe)
{
FabricatingContainer.Visible = recipeProto != null;
if (recipeProto == null)
FabricatingContainer.Visible = recipe != null;
if (recipe == null)
return;
var recipe = _prototypeManager.Index(recipeProto.Value);
FabricatingDisplayContainer.Children.Clear();
FabricatingDisplayContainer.AddChild(GetRecipeDisplayControl(recipe));

View File

@@ -1,144 +0,0 @@
using System.Numerics;
using Content.Shared.CCVar;
using Content.Shared.Maps;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Light;
/// <summary>
/// Applies ambient-occlusion to the viewport.
/// </summary>
public sealed class AmbientOcclusionOverlay : Overlay
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
private IRenderTexture? _aoTarget;
private IRenderTexture? _aoBlurBuffer;
// Couldn't figure out a way to avoid this so if you can then please do.
private IRenderTexture? _aoStencilTarget;
public AmbientOcclusionOverlay()
{
IoCManager.InjectDependencies(this);
ZIndex = AfterLightTargetOverlay.ContentZIndex + 1;
}
protected override void Draw(in OverlayDrawArgs args)
{
/*
* tl;dr
* - we draw a black square on each "ambient occlusion" entity.
* - we blur this.
* - We apply it to the viewport.
*
* We do this while ignoring lighting because it will wash out the actual effect.
* In 3D ambient occlusion is more complicated due top having to calculate normals but in 2D
* we don't have a concept of depth / corners necessarily.
*/
var viewport = args.Viewport;
var mapId = args.MapId;
var worldBounds = args.WorldBounds;
var worldHandle = args.WorldHandle;
var color = Color.FromHex(_cfgManager.GetCVar(CCVars.AmbientOcclusionColor));
var distance = _cfgManager.GetCVar(CCVars.AmbientOcclusionDistance);
//var color = Color.Red;
var target = viewport.RenderTarget;
var lightScale = target.Size / (Vector2) viewport.Size;
var scale = viewport.RenderScale / (Vector2.One / lightScale);
var maps = _entManager.System<SharedMapSystem>();
var lookups = _entManager.System<EntityLookupSystem>();
var query = _entManager.System<OccluderSystem>();
var xformSystem = _entManager.System<SharedTransformSystem>();
var turfSystem = _entManager.System<TurfSystem>();
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
if (_aoTarget?.Texture.Size != target.Size)
{
_aoTarget?.Dispose();
_aoTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-target");
}
if (_aoBlurBuffer?.Texture.Size != target.Size)
{
_aoBlurBuffer?.Dispose();
_aoBlurBuffer = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-blur-target");
}
if (_aoStencilTarget?.Texture.Size != target.Size)
{
_aoStencilTarget?.Dispose();
_aoStencilTarget = _clyde.CreateRenderTarget(target.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "ambient-occlusion-stencil-target");
}
// Draw the texture data to the texture.
args.WorldHandle.RenderInRenderTarget(_aoTarget,
() =>
{
worldHandle.UseShader(_proto.Index<ShaderPrototype>("unshaded").Instance());
var invMatrix = _aoTarget.GetWorldToLocalMatrix(viewport.Eye!, scale);
foreach (var entry in query.QueryAabb(mapId, worldBounds))
{
DebugTools.Assert(entry.Component.Enabled);
var matrix = xformSystem.GetWorldMatrix(entry.Transform);
var localMatrix = Matrix3x2.Multiply(matrix, invMatrix);
worldHandle.SetTransform(localMatrix);
// 4 pixels
worldHandle.DrawRect(Box2.UnitCentered.Enlarged(distance / EyeManager.PixelsPerMeter), Color.White);
}
}, Color.Transparent);
_clyde.BlurRenderTarget(viewport, _aoTarget, _aoBlurBuffer, viewport.Eye!, 14f);
// Need to do stencilling after blur as it will nuke it.
// Draw stencil for the grid so we don't draw in space.
args.WorldHandle.RenderInRenderTarget(_aoStencilTarget,
() =>
{
// Don't want lighting affecting it.
worldHandle.UseShader(_proto.Index<ShaderPrototype>("unshaded").Instance());
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
var transform = xformSystem.GetWorldMatrix(grid.Owner);
var worldToTextureMatrix = Matrix3x2.Multiply(transform, invMatrix);
var tiles = maps.GetTilesEnumerator(grid.Owner, grid, worldBounds);
worldHandle.SetTransform(worldToTextureMatrix);
while (tiles.MoveNext(out var tileRef))
{
if (turfSystem.IsSpace(tileRef))
continue;
var bounds = lookups.GetLocalBounds(tileRef, grid.TileSize);
worldHandle.DrawRect(bounds, Color.White);
}
}
}, Color.Transparent);
// Draw the stencil texture to depth buffer.
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilMask").Instance());
worldHandle.DrawTextureRect(_aoStencilTarget!.Texture, worldBounds);
// Draw the Blurred AO texture finally.
worldHandle.UseShader(_proto.Index<ShaderPrototype>("StencilEqualDraw").Instance());
worldHandle.DrawTextureRect(_aoTarget!.Texture, worldBounds, color);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
args.WorldHandle.UseShader(null);
}
}

View File

@@ -63,7 +63,7 @@ public sealed class LightBehaviorSystem : EntitySystem
/// </summary>
private void CopyLightSettings(Entity<LightBehaviourComponent> entity, string property)
{
if (TryComp(entity, out PointLightComponent? light))
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
{
var propertyValue = AnimationHelper.GetAnimatableProperty(light, property);
if (propertyValue != null)
@@ -73,7 +73,7 @@ public sealed class LightBehaviorSystem : EntitySystem
}
else
{
Log.Warning($"{Comp<MetaDataComponent>(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
Log.Warning($"{EntityManager.GetComponent<MetaDataComponent>(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
}
}
@@ -84,7 +84,7 @@ public sealed class LightBehaviorSystem : EntitySystem
/// </summary>
public void StartLightBehaviour(Entity<LightBehaviourComponent> entity, string id = "")
{
if (!TryComp(entity, out AnimationPlayerComponent? animation))
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
{
return;
}
@@ -113,7 +113,7 @@ public sealed class LightBehaviorSystem : EntitySystem
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
public void StopLightBehaviour(Entity<LightBehaviourComponent> entity, string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
{
if (!TryComp(entity, out AnimationPlayerComponent? animation))
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
{
return;
}
@@ -143,7 +143,7 @@ public sealed class LightBehaviorSystem : EntitySystem
comp.Animations.Remove(container);
}
if (resetToOriginalSettings && TryComp(entity, out PointLightComponent? light))
if (resetToOriginalSettings && EntityManager.TryGetComponent(entity, out PointLightComponent? light))
{
foreach (var (property, value) in comp.OriginalPropertyValues)
{
@@ -161,7 +161,7 @@ public sealed class LightBehaviorSystem : EntitySystem
public bool HasRunningBehaviours(Entity<LightBehaviourComponent> entity)
{
//var uid = Owner;
if (!TryComp(entity, out AnimationPlayerComponent? animation))
if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
{
return false;
}

View File

@@ -1,51 +1,17 @@
using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
namespace Content.Client.Light.EntitySystems;
public sealed class PlanetLightSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!;
/// <summary>
/// Enables / disables the ambient occlusion overlay.
/// </summary>
public bool AmbientOcclusion
{
get => _ambientOcclusion;
set
{
if (_ambientOcclusion == value)
return;
_ambientOcclusion = value;
if (value)
{
_overlayMan.AddOverlay(new AmbientOcclusionOverlay());
}
else
{
_overlayMan.RemoveOverlay<AmbientOcclusionOverlay>();
}
}
}
private bool _ambientOcclusion;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
_cfgManager.OnValueChanged(CCVars.AmbientOcclusion, val =>
{
AmbientOcclusion = val;
}, true);
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));

View File

@@ -62,8 +62,6 @@ public sealed class RoofOverlay : Overlay
worldHandle.RenderInRenderTarget(target,
() =>
{
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
for (var i = 0; i < _grids.Count; i++)
{
var grid = _grids[i];
@@ -71,6 +69,8 @@ public sealed class RoofOverlay : Overlay
if (!_entManager.TryGetComponent(grid.Owner, out ImplicitRoofComponent? roof))
continue;
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
@@ -94,13 +94,13 @@ public sealed class RoofOverlay : Overlay
worldHandle.RenderInRenderTarget(target,
() =>
{
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
foreach (var grid in _grids)
{
if (!_entManager.TryGetComponent(grid.Owner, out RoofComponent? roof))
continue;
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);

View File

@@ -241,7 +241,6 @@ namespace Content.Client.Lobby.UI
};
RgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
_rgbSkinColorSelector.SelectorType = ColorSelectorSliders.ColorSelectorType.Hsv; // defaults color selector to HSV
_rgbSkinColorSelector.OnColorChanged += _ =>
{
OnSkinColorOnValueChanged();

View File

@@ -144,8 +144,8 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
{
subList.AddChild(proto);
}
var itemName = firstElement.Text ?? "";
UpdateSubGroupSelectedInfo(firstElement, itemName, subList);
UpdateToggleColor(toggle, subList);
}
else
{
@@ -164,14 +164,12 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
};
toggle.Text = subContainer.Visible ? OpenedGroupMark : ClosedGroupMark;
toggle.Pressed = subContainer.Visible;
toggle.OnPressed += _ =>
{
var willOpen = !subContainer.Visible;
subContainer.Visible = willOpen;
toggle.Text = willOpen ? OpenedGroupMark : ClosedGroupMark;
toggle.Pressed = willOpen;
_openedGroups[kvp.Key] = willOpen;
};
@@ -180,16 +178,15 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
return toggle;
}
private void UpdateSubGroupSelectedInfo(LoadoutContainer loadout, string itemName, BoxContainer subList)
private void UpdateToggleColor(Button toggle, BoxContainer subList)
{
var countSubSelected = subList.Children
var anyActive = subList.Children
.OfType<LoadoutContainer>()
.Count(c => c.Select.Pressed);
.Any(c => c.Select.Pressed);
if (countSubSelected > 0)
{
loadout.Text = Loc.GetString("loadouts-count-items-in-group", ("item", itemName), ("count", countSubSelected));
}
toggle.Modulate = anyActive
? Color.Green
: Color.White;
}
/// <summary>

View File

@@ -793,7 +793,7 @@ public sealed class MappingState : GameplayStateBase
if (_mapMan.TryFindGridAt(mapPos, out var gridUid, out var grid) &&
_entityManager.System<SharedMapSystem>().TryGetTileRef(gridUid, grid, coords, out var tileRef) &&
_allPrototypesDict.TryGetValue(_entityManager.System<TurfSystem>().GetContentTileDefinition(tileRef), out button))
_allPrototypesDict.TryGetValue(tileRef.GetContentTileDefinition(), out button))
{
OnSelected(button);
return true;

View File

@@ -33,7 +33,7 @@ public sealed class MarkerSystem : EntitySystem
private void UpdateVisibility(EntityUid uid)
{
if (TryComp(uid, out SpriteComponent? sprite))
if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
{
_sprite.SetVisible((uid, sprite), MarkersVisible);
}

View File

@@ -1,5 +1,4 @@
using System.Linq;
using System.Numerics;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceNetwork;
@@ -15,8 +14,6 @@ namespace Content.Client.NetworkConfigurator;
[GenerateTypedNameReferences]
public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private const string PanelBgColor = "#202023";
private readonly LinksRender _links;
@@ -36,7 +33,6 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
public NetworkConfiguratorLinkMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var footerStyleBox = new StyleBoxFlat()
{
@@ -65,7 +61,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
ButtonContainerRight.RemoveAllChildren();
_sources.Clear();
_sources.AddRange(linkState.Sources.Select(s => _prototypeManager.Index(s)));
_sources.AddRange(linkState.Sources);
_links.SourceButtons.Clear();
var i = 0;
foreach (var source in _sources)
@@ -77,7 +73,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
}
_sinks.Clear();
_sinks.AddRange(linkState.Sinks.Select(s => _prototypeManager.Index(s)));
_sinks.AddRange(linkState.Sinks);
_links.SinkButtons.Clear();
i = 0;
foreach (var sink in _sinks)

View File

@@ -14,7 +14,6 @@
<ui:OptionDropDown Name="DropDownLightingQuality" Title="{Loc 'ui-options-lighting-label'}" />
<CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
<CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
<CheckBox Name="AmbientOcclusionCheckBox" Text="{Loc 'ui-options-ambient-occlusion'}" />
<!-- Interface -->
<Label Text="{Loc 'ui-options-interface-label'}" StyleClasses="LabelKeyText"/>

View File

@@ -20,7 +20,6 @@ public sealed partial class GraphicsTab : Control
RobustXamlLoader.Load(this);
Control.AddOptionCheckBox(CVars.DisplayVSync, VSyncCheckBox);
Control.AddOptionCheckBox(CCVars.AmbientOcclusion, AmbientOcclusionCheckBox);
Control.AddOption(new OptionFullscreen(Control, _cfg, FullscreenCheckBox));
Control.AddOption(new OptionLightingQuality(Control, _cfg, DropDownLightingQuality));

View File

@@ -64,7 +64,7 @@ public sealed class OrbitVisualsSystem : EntitySystem
{
base.FrameUpdate(frameTime);
var query = EntityQueryEnumerator<OrbitVisualsComponent, SpriteComponent>();
var query = EntityManager.EntityQueryEnumerator<OrbitVisualsComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out var orbit, out var sprite))
{
var progress = (float)(_timing.CurTime.TotalSeconds / orbit.OrbitLength) % 1;

View File

@@ -48,14 +48,17 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
if (!_sprite.LayerExists((uid, args.Sprite), PowerCellVisualLayers.Unshaded))
return;
if (!_appearance.TryGetData<byte>(uid, PowerCellVisuals.ChargeLevel, out var level, args.Component))
level = 0;
if (_appearance.TryGetData<byte>(uid, PowerCellVisuals.ChargeLevel, out var level, args.Component))
{
if (level == 0)
{
_sprite.LayerSetVisible((uid, args.Sprite), PowerCellVisualLayers.Unshaded, false);
return;
}
var positiveCharge = level > 0;
_sprite.LayerSetVisible((uid, args.Sprite), PowerCellVisualLayers.Unshaded, positiveCharge);
if (positiveCharge)
_sprite.LayerSetVisible((uid, args.Sprite), PowerCellVisualLayers.Unshaded, false);
_sprite.LayerSetRsiState((uid, args.Sprite), PowerCellVisualLayers.Unshaded, $"o{level}");
}
}
private enum PowerCellVisualLayers : byte

View File

@@ -1,6 +1,5 @@
using System.Numerics;
using Content.Client.Gameplay;
using Content.Client.Hands.Systems;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.RCD.Components;
@@ -18,7 +17,6 @@ public sealed class AlignRCDConstruction : PlacementMode
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly SharedMapSystem _mapSystem;
private readonly HandsSystem _handsSystem;
private readonly RCDSystem _rcdSystem;
private readonly SharedTransformSystem _transformSystem;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -36,7 +34,6 @@ public sealed class AlignRCDConstruction : PlacementMode
{
IoCManager.InjectDependencies(this);
_mapSystem = _entityManager.System<SharedMapSystem>();
_handsSystem = _entityManager.System<HandsSystem>();
_rcdSystem = _entityManager.System<RCDSystem>();
_transformSystem = _entityManager.System<SharedTransformSystem>();
@@ -91,9 +88,11 @@ public sealed class AlignRCDConstruction : PlacementMode
}
// Determine if player is carrying an RCD in their active hand
if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity))
if (!_entityManager.TryGetComponent<HandsComponent>(player, out var hands))
return false;
var heldEntity = hands.ActiveHand?.HeldEntity;
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
return false;

View File

@@ -1,7 +1,8 @@
using Content.Client.Hands.Systems;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.RCD;
using Content.Shared.RCD.Components;
using Content.Shared.RCD.Systems;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Shared.Enums;
@@ -9,18 +10,13 @@ using Robust.Shared.Prototypes;
namespace Content.Client.RCD;
/// <summary>
/// System for handling structure ghost placement in places where RCD can create objects.
/// </summary>
public sealed class RCDConstructionGhostSystem : EntitySystem
{
private const string PlacementMode = nameof(AlignRCDConstruction);
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly HandsSystem _hands = default!;
private string _placementMode = typeof(AlignRCDConstruction).Name;
private Direction _placementDirection = default;
public override void Update(float frameTime)
@@ -37,10 +33,12 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
return;
// Determine if player is carrying an RCD in their active hand
if (_playerManager.LocalSession?.AttachedEntity is not { } player)
var player = _playerManager.LocalSession?.AttachedEntity;
if (!TryComp<HandsComponent>(player, out var hands))
return;
var heldEntity = _hands.GetActiveItem(player);
var heldEntity = hands.ActiveHand?.HeldEntity;
if (!TryComp<RCDComponent>(heldEntity, out var rcd))
{
@@ -67,7 +65,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
var newObjInfo = new PlacementInformation
{
MobUid = heldEntity.Value,
PlacementOption = PlacementMode,
PlacementOption = _placementMode,
EntityType = prototype.Prototype,
Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange),
IsTile = (prototype.Mode == RcdMode.ConstructTile),

View File

@@ -1,6 +1,4 @@
using Content.Client.Alerts;
using Content.Shared.Alert;
using Content.Shared.Alert.Components;
using Content.Shared.Revenant;
using Content.Shared.Revenant.Components;
using Robust.Client.GameObjects;
@@ -17,7 +15,7 @@ public sealed class RevenantSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<RevenantComponent, GetGenericAlertCounterAmountEvent>(OnGetCounterAmount);
SubscribeLocalEvent<RevenantComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
}
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
@@ -42,14 +40,14 @@ public sealed class RevenantSystem : EntitySystem
}
}
private void OnGetCounterAmount(Entity<RevenantComponent> ent, ref GetGenericAlertCounterAmountEvent args)
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
{
if (args.Handled)
if (args.Alert.ID != ent.Comp.EssenceAlert)
return;
if (ent.Comp.EssenceAlert != args.Alert)
return;
args.Amount = ent.Comp.Essence.Int();
var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999);
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}");
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}");
_sprite.LayerSetRsiState(args.SpriteViewEnt.AsNullable(), RevenantVisualLayers.Digit3, $"{essence % 10}");
}
}

View File

@@ -90,7 +90,7 @@ namespace Content.Client.Sandbox
// Try copy entity.
if (uid.IsValid()
&& TryComp(uid, out MetaDataComponent? comp)
&& EntityManager.TryGetComponent(uid, out MetaDataComponent? comp)
&& !comp.EntityDeleted)
{
if (comp.EntityPrototype == null || comp.EntityPrototype.HideSpawnMenu || comp.EntityPrototype.Abstract)

View File

@@ -25,29 +25,6 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
private TimeSpan _retentionTime;
private readonly Dictionary<int, SensorData> _sensorData = new();
/// <summary>
/// <para>A shared array used to store vertices for drawing graphs in <see cref="GraphView"/>.
/// Prevents excessive allocations by reusing the same array across multiple graph views.</para>
/// <para>This effectively makes it so that each <see cref="SensorMonitoringWindow"/> has its own pooled array.</para>
/// </summary>
private Vector2[] _sharedVertices = [];
/// <summary>
/// Retrieves a shared array of vertices, ensuring that it has at least the requested size.
/// Assigns a new array of the requested size if the current shared array is smaller than the requested size.
/// </summary>
/// <param name="requestedSize">The minimum number of vertices required in the shared array.</param>
/// <returns>An array of <see cref="System.Numerics.Vector2"/> representing the shared vertices.</returns>
/// <remarks>This does not prevent other threads from accessing the same shared pool.</remarks>
public Vector2[] GetSharedVertices(int requestedSize)
{
if (_sharedVertices.Length < requestedSize)
{
_sharedVertices = new Vector2[requestedSize];
}
return _sharedVertices;
}
public SensorMonitoringWindow()
{
RobustXamlLoader.Load(this);
@@ -168,7 +145,7 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
}
});
Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f, this) { MinHeight = 150 });
Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f) { MinHeight = 150 });
Asdf.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
}
}
@@ -220,37 +197,31 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
private readonly TimeSpan _startTime;
private readonly TimeSpan _curTime;
private readonly float _maxY;
private readonly SensorMonitoringWindow _parentWindow;
public GraphView(Queue<SensorSample> samples,
TimeSpan startTime,
TimeSpan curTime,
float maxY,
SensorMonitoringWindow parentWindow)
public GraphView(Queue<SensorSample> samples, TimeSpan startTime, TimeSpan curTime, float maxY)
{
_samples = samples;
_startTime = startTime;
_curTime = curTime;
_maxY = maxY;
RectClipContent = true;
_parentWindow = parentWindow;
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
var window = (float)(_curTime - _startTime).TotalSeconds;
var window = (float) (_curTime - _startTime).TotalSeconds;
// TODO: omg this is terrible don't fucking hardcode this size to something uncached huge omfg.
var vertices = new Vector2[25000];
var countVtx = 0;
var lastPoint = new Vector2(float.NaN, float.NaN);
var requiredVertices = 6 * (_samples.Count - 1);
var vertices = _parentWindow.GetSharedVertices(requiredVertices);
foreach (var (time, sample) in _samples)
{
var relTime = (float)(time - _startTime).TotalSeconds;
var relTime = (float) (time - _startTime).TotalSeconds;
var posY = PixelHeight - (sample / _maxY) * PixelHeight;
var posX = (relTime / window) * PixelWidth;
@@ -271,9 +242,8 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
lastPoint = newPoint;
}
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList,
vertices.AsSpan(0, countVtx),
Color.White.WithAlpha(0.1f));
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, vertices.AsSpan(0, countVtx), Color.White.WithAlpha(0.1f));
}
}
}

View File

@@ -1,50 +0,0 @@
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components;
using Robust.Shared.Collections;
using Robust.Shared.GameStates;
namespace Content.Client.StatusEffectNew;
/// <inheritdoc/>
public sealed partial class ClientStatusEffectsSystem : SharedStatusEffectsSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentHandleState>(OnHandleState);
}
private void OnHandleState(Entity<StatusEffectContainerComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not StatusEffectContainerComponentState state)
return;
var toRemove = new ValueList<EntityUid>();
foreach (var effect in ent.Comp.ActiveStatusEffects)
{
if (state.ActiveStatusEffects.Contains(GetNetEntity(effect)))
continue;
toRemove.Add(effect);
}
foreach (var effect in toRemove)
{
ent.Comp.ActiveStatusEffects.Remove(effect);
var ev = new StatusEffectRemovedEvent(ent);
RaiseLocalEvent(effect, ref ev);
}
foreach (var effect in state.ActiveStatusEffects)
{
var effectUid = GetEntity(effect);
if (ent.Comp.ActiveStatusEffects.Contains(effectUid))
continue;
ent.Comp.ActiveStatusEffects.Add(effectUid);
var ev = new StatusEffectAppliedEvent(ent);
RaiseLocalEvent(effectUid, ref ev);
}
}
}

View File

@@ -68,15 +68,11 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem
foreach (var hand in _hands.EnumerateHands(player.Value))
{
if (!_hands.TryGetHeldItem(player.Value, hand, out var heldEntity))
continue;
if (!scannerQuery.TryGetComponent(heldEntity, out var heldScanner) || !heldScanner.Enabled)
if (!scannerQuery.TryGetComponent(hand.HeldEntity, out var heldScanner) || !heldScanner.Enabled)
continue;
range = MathF.Max(heldScanner.Range, range);
canSee = true;
break;
}
inRange = new HashSet<Entity<SubFloorHideComponent>>();

View File

@@ -131,7 +131,7 @@ namespace Content.Client.Tabletop
// Get the camera entity that the server has created for us
var camera = GetEntity(msg.CameraUid);
if (!TryComp<EyeComponent>(camera, out var eyeComponent))
if (!EntityManager.TryGetComponent<EyeComponent>(camera, out var eyeComponent))
{
// If there is no eye, print error and do not open any window
Log.Error("Camera entity does not have eye component!");
@@ -258,7 +258,7 @@ namespace Content.Client.Tabletop
private void StopDragging(bool broadcast = true)
{
// Set the dragging player on the component to noone
if (broadcast && _draggedEntity != null && HasComp<TabletopDraggableComponent>(_draggedEntity.Value))
if (broadcast && _draggedEntity != null && EntityManager.HasComponent<TabletopDraggableComponent>(_draggedEntity.Value))
{
RaisePredictiveEvent(new TabletopMoveEvent(GetNetEntity(_draggedEntity.Value), Transforms.GetMapCoordinates(_draggedEntity.Value), GetNetEntity(_table!.Value)));
RaisePredictiveEvent(new TabletopDraggingPlayerChangedEvent(GetNetEntity(_draggedEntity.Value), false));

View File

@@ -98,8 +98,7 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
if (!EntityManager.TryGetComponent<SpriteComponent>(spriteViewEnt, out var sprite))
return;
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), player, alert);
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), alert);
EntityManager.EventBus.RaiseLocalEvent(player, ref ev);
EntityManager.EventBus.RaiseLocalEvent(spriteViewEnt, ref ev);
}
}

View File

@@ -57,15 +57,10 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
_sprite = _entityManager.System<SpriteSystem>();
TooltipSupplier = SupplyTooltip;
Alert = alert;
HorizontalAlignment = HAlignment.Left;
_severity = severity;
_icon = new SpriteView
{
Scale = new Vector2(2, 2),
MaxSize = new Vector2(64, 64),
Stretch = SpriteView.StretchMode.None,
HorizontalAlignment = HAlignment.Left
Scale = new Vector2(2, 2)
};
SetupIcon();

View File

@@ -207,9 +207,7 @@ public sealed class GasTankWindow
_btnInternals.Disabled = !canConnectInternals;
_lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text",
("status", Loc.GetString(internalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
if (!_spbPressure.HasKeyboardFocus())
// Don't update release pressure if we're currently editing it
_spbPressure.Value = outputPressure;
_spbPressure.Value = outputPressure;
}
protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -7,7 +7,6 @@ using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Timing;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
@@ -29,7 +28,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
private readonly Dictionary<string, int> _handContainerIndices = new();
private readonly Dictionary<string, HandButton> _handLookup = new();
private HandsComponent? _playerHandsComponent;
private HandButton? _activeHand;
private HandButton? _activeHand = null;
// We only have two item status controls (left and right hand),
// but we may have more than two hands.
@@ -39,7 +38,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
private HandButton? _statusHandLeft;
private HandButton? _statusHandRight;
private int _backupSuffix; //this is used when autogenerating container names if they don't have names
private int _backupSuffix = 0; //this is used when autogenerating container names if they don't have names
private HotbarGui? HandsGui => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
@@ -49,7 +48,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
_handsSystem.OnPlayerItemAdded += OnItemAdded;
_handsSystem.OnPlayerItemRemoved += OnItemRemoved;
_handsSystem.OnPlayerSetActiveHand += SetActiveHand;
_handsSystem.OnPlayerRemoveHand += OnRemoveHand;
_handsSystem.OnPlayerRemoveHand += RemoveHand;
_handsSystem.OnPlayerHandsAdded += LoadPlayerHands;
_handsSystem.OnPlayerHandsRemoved += UnloadPlayerHands;
_handsSystem.OnPlayerHandBlocked += HandBlocked;
@@ -62,35 +61,28 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
_handsSystem.OnPlayerItemAdded -= OnItemAdded;
_handsSystem.OnPlayerItemRemoved -= OnItemRemoved;
_handsSystem.OnPlayerSetActiveHand -= SetActiveHand;
_handsSystem.OnPlayerRemoveHand -= OnRemoveHand;
_handsSystem.OnPlayerRemoveHand -= RemoveHand;
_handsSystem.OnPlayerHandsAdded -= LoadPlayerHands;
_handsSystem.OnPlayerHandsRemoved -= UnloadPlayerHands;
_handsSystem.OnPlayerHandBlocked -= HandBlocked;
_handsSystem.OnPlayerHandUnblocked -= HandUnblocked;
}
private void OnAddHand(Entity<HandsComponent> entity, string name, HandLocation location)
private void OnAddHand(string name, HandLocation location)
{
if (entity.Owner != _player.LocalEntity)
return;
AddHand(name, location);
}
private void OnRemoveHand(Entity<HandsComponent> entity, string name)
{
if (entity.Owner != _player.LocalEntity)
return;
RemoveHand(name);
}
private void HandPressed(GUIBoundKeyEventArgs args, SlotControl hand)
{
if (!_handsSystem.TryGetPlayerHands(out var hands))
if (_playerHandsComponent == null)
{
return;
}
if (args.Function == EngineKeyFunctions.UIClick)
{
_handsSystem.UIHandClick(hands.Value, hand.SlotName);
_handsSystem.UIHandClick(_playerHandsComponent, hand.SlotName);
args.Handle();
}
else if (args.Function == EngineKeyFunctions.UseSecondary)
@@ -130,33 +122,33 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
}
}
private void LoadPlayerHands(Entity<HandsComponent> handsComp)
private void LoadPlayerHands(HandsComponent handsComp)
{
DebugTools.Assert(_playerHandsComponent == null);
if (HandsGui != null)
HandsGui.Visible = true;
_playerHandsComponent = handsComp;
foreach (var (name, hand) in handsComp.Comp.Hands)
foreach (var (name, hand) in handsComp.Hands)
{
var handButton = AddHand(name, hand.Location);
if (_handsSystem.TryGetHeldItem(handsComp.AsNullable(), name, out var held) &&
_entities.TryGetComponent(held, out VirtualItemComponent? virt))
if (_entities.TryGetComponent(hand.HeldEntity, out VirtualItemComponent? virt))
{
handButton.SetEntity(virt.BlockingEntity);
handButton.Blocked = true;
}
else
{
handButton.SetEntity(held);
handButton.SetEntity(hand.HeldEntity);
handButton.Blocked = false;
}
}
if (handsComp.Comp.ActiveHandId == null)
var activeHand = handsComp.ActiveHand;
if (activeHand == null)
return;
SetActiveHand(handsComp.Comp.ActiveHandId);
SetActiveHand(activeHand.Name);
}
private void HandBlocked(string handName)
@@ -268,21 +260,19 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
if (HandsGui != null &&
_playerHandsComponent != null &&
_player.LocalSession?.AttachedEntity is { } playerEntity &&
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), handName, out var hand))
_handsSystem.TryGetHand(playerEntity, handName, out var hand, _playerHandsComponent))
{
var heldEnt = _handsSystem.GetHeldItem((playerEntity, _playerHandsComponent), handName);
var foldedLocation = hand.Value.Location.GetUILocation();
var foldedLocation = hand.Location.GetUILocation();
if (foldedLocation == HandUILocation.Left)
{
_statusHandLeft = handControl;
HandsGui.UpdatePanelEntityLeft(heldEnt);
HandsGui.UpdatePanelEntityLeft(hand.HeldEntity);
}
else
{
// Middle or right
_statusHandRight = handControl;
HandsGui.UpdatePanelEntityRight(heldEnt);
HandsGui.UpdatePanelEntityRight(hand.HeldEntity);
}
HandsGui.SetHighlightHand(foldedLocation);
@@ -302,7 +292,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
button.Pressed += HandPressed;
if (!_handLookup.TryAdd(handName, button))
return _handLookup[handName];
throw new Exception("Tried to add hand with duplicate name to UI. Name:" + handName);
if (HandsGui != null)
{
@@ -372,7 +362,6 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
RemoveHand(handName, out _);
}
[PublicAPI]
private bool RemoveHand(string handName, out HandButton? handButton)
{
if (!_handLookup.TryGetValue(handName, out handButton))
@@ -388,7 +377,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
_statusHandRight = null;
_handLookup.Remove(handName);
handButton.Orphan();
handButton.Dispose();
UpdateVisibleStatusPanels();
return true;
}

View File

@@ -329,8 +329,9 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
var player = _playerUid;
if (!control.MouseIsHovering ||
player == null ||
!_handsSystem.TryGetActiveItem(player.Value, out var held) ||
_playerInventory == null ||
!_entities.TryGetComponent<HandsComponent>(player, out var hands) ||
hands.ActiveHandEntity is not { } held ||
!_entities.TryGetComponent(held, out SpriteComponent? sprite) ||
!_inventorySystem.TryGetSlotContainer(player.Value, control.SlotName, out var container, out var slotDef))
{
@@ -341,12 +342,12 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
// Set green / red overlay at 50% transparency
var hoverEntity = _entities.SpawnEntity("hoverentity", MapCoordinates.Nullspace);
var hoverSprite = _entities.GetComponent<SpriteComponent>(hoverEntity);
var fits = _inventorySystem.CanEquip(player.Value, held.Value, control.SlotName, out _, slotDef) &&
_container.CanInsert(held.Value, container);
var fits = _inventorySystem.CanEquip(player.Value, held, control.SlotName, out _, slotDef) &&
_container.CanInsert(held, container);
if (!fits && _entities.TryGetComponent<StorageComponent>(container.ContainedEntity, out var storage))
{
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held.Value, out _, storage);
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held, out _, storage);
}
else if (!fits && _entities.TryGetComponent<ItemSlotsComponent>(container.ContainedEntity, out var itemSlots))
{
@@ -356,14 +357,14 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
if (!slot.InsertOnInteract)
continue;
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held.Value, null, slot))
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held, null, slot))
continue;
fits = true;
break;
}
}
_sprite.CopySprite((held.Value, sprite), (hoverEntity, hoverSprite));
_sprite.CopySprite((held, sprite), (hoverEntity, hoverSprite));
_sprite.SetColor((hoverEntity, hoverSprite), fits ? new Color(0, 255, 0, 127) : new Color(255, 0, 0, 127));
control.HoverSpriteView.SetEntity(hoverEntity);

View File

@@ -225,7 +225,7 @@ namespace Content.Client.Verbs
// is this a client exclusive (gui) verb?
ExecuteVerb(verb, user, GetEntity(target));
else
RaisePredictiveEvent(new ExecuteVerbEvent(target, verb));
EntityManager.RaisePredictiveEvent(new ExecuteVerbEvent(target, verb));
}
private void HandleVerbResponse(VerbsResponseEvent msg)

View File

@@ -178,7 +178,7 @@ public sealed partial class GunSystem : SharedGunSystem
if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down && !gun.BurstActivated)
{
if (gun.ShotCounter != 0)
RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });
return;
}
@@ -190,7 +190,7 @@ public sealed partial class GunSystem : SharedGunSystem
if (mousePos.MapId == MapId.Nullspace)
{
if (gun.ShotCounter != 0)
RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });
return;
}
@@ -204,7 +204,7 @@ public sealed partial class GunSystem : SharedGunSystem
Log.Debug($"Sending shoot request tick {Timing.CurTick} / {Timing.CurTime}");
RaisePredictiveEvent(new RequestShootEvent
EntityManager.RaisePredictiveEvent(new RequestShootEvent
{
Target = target,
Coordinates = GetNetCoordinates(coordinates),

View File

@@ -1,12 +0,0 @@
using System.IO;
namespace Content.IntegrationTests;
/// <summary>
/// Generic implementation of <see cref="ITestContextLike"/> for usage outside of actual tests.
/// </summary>
public sealed class ExternalTestContext(string name, TextWriter writer) : ITestContextLike
{
public string FullName => name;
public TextWriter Out => writer;
}

View File

@@ -1,13 +0,0 @@
using System.IO;
namespace Content.IntegrationTests;
/// <summary>
/// Something that looks like a <see cref="TestContext"/>, for passing to integration tests.
/// </summary>
public interface ITestContextLike
{
string FullName { get; }
TextWriter Out { get; }
}

View File

@@ -1,12 +0,0 @@
using System.IO;
namespace Content.IntegrationTests;
/// <summary>
/// Canonical implementation of <see cref="ITestContextLike"/> for usage in actual NUnit tests.
/// </summary>
public sealed class NUnitTestContextWrap(TestContext context, TextWriter writer) : ITestContextLike
{
public string FullName => context.Test.FullName;
public TextWriter Out => writer;
}

View File

@@ -13,7 +13,6 @@ using Robust.Server.Player;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Pair;
@@ -85,7 +84,6 @@ public sealed partial class TestPair : IAsyncDisposable
var returnTime = Watch.Elapsed;
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
State = PairState.Ready;
}
private async Task ResetModifiedPreferences()
@@ -106,7 +104,7 @@ public sealed partial class TestPair : IAsyncDisposable
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Return of pair {Id} started");
State = PairState.CleanDisposed;
await OnCleanDispose();
DebugTools.Assert(State is PairState.Dead or PairState.Ready);
State = PairState.Ready;
PoolManager.NoCheckReturn(this);
ClearContext();
}

View File

@@ -182,29 +182,24 @@ public static partial class PoolManager
/// </summary>
/// <param name="poolSettings">See <see cref="PoolSettings"/></param>
/// <returns></returns>
public static async Task<TestPair> GetServerClient(
PoolSettings? poolSettings = null,
ITestContextLike? testContext = null)
public static async Task<TestPair> GetServerClient(PoolSettings? poolSettings = null)
{
return await GetServerClientPair(
poolSettings ?? new PoolSettings(),
testContext ?? new NUnitTestContextWrap(TestContext.CurrentContext, TestContext.Out));
return await GetServerClientPair(poolSettings ?? new PoolSettings());
}
private static string GetDefaultTestName(ITestContextLike testContext)
private static string GetDefaultTestName(TestContext testContext)
{
return testContext.FullName.Replace("Content.IntegrationTests.Tests.", "");
return testContext.Test.FullName.Replace("Content.IntegrationTests.Tests.", "");
}
private static async Task<TestPair> GetServerClientPair(
PoolSettings poolSettings,
ITestContextLike testContext)
private static async Task<TestPair> GetServerClientPair(PoolSettings poolSettings)
{
if (!_initialized)
throw new InvalidOperationException($"Pool manager has not been initialized");
// Trust issues with the AsyncLocal that backs this.
var testOut = testContext.Out;
var testContext = TestContext.CurrentContext;
var testOut = TestContext.Out;
DieIfPoolFailure();
var currentTestName = poolSettings.TestName ?? GetDefaultTestName(testContext);

View File

@@ -1,65 +0,0 @@
#nullable enable
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Actions;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.RetractableItemAction;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Actions;
public sealed class RetractableItemActionTest : InteractionTest
{
private static readonly EntProtoId ArmBladeActionProtoId = "ActionRetractableItemArmBlade";
/// <summary>
/// Gives the player the arm blade action, then activates it and makes sure they are given the blade.
/// Afterwards, uses the action again to retract the blade and makes sure their hand is empty.
/// </summary>
[Test]
public async Task ArmBladeActivateDeactivateTest()
{
var actionsSystem = Server.System<SharedActionsSystem>();
var handsSystem = Server.System<SharedHandsSystem>();
var playerUid = SEntMan.GetEntity(Player);
await Server.WaitAssertion(() =>
{
// Make sure the player's hand starts empty
var heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) at start of test.");
// Inspect the action prototype to find the item it spawns
var armBladeActionProto = ProtoMan.Index(ArmBladeActionProtoId);
// Find the component
Assert.That(armBladeActionProto.TryGetComponent<RetractableItemActionComponent>(out var actionComp, SEntMan.ComponentFactory));
// Get the item protoId from the component
var spawnedProtoId = actionComp!.SpawnedPrototype;
// Add the action to the player
var actionUid = actionsSystem.AddAction(playerUid, ArmBladeActionProtoId);
// Make sure the player has the action now
Assert.That(actionUid, Is.Not.Null, "Failed to add action to player.");
var actionEnt = actionsSystem.GetAction(actionUid);
// Make sure the player's hand is still empty
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) after adding action.");
// Activate the arm blade
actionsSystem.PerformAction(ToServer(Player), actionEnt!.Value);
// Make sure the player is now holding the expected item
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Not.Null, $"Expected player to be holding {spawnedProtoId} but was holding nothing.");
AssertPrototype(spawnedProtoId, SEntMan.GetNetEntity(heldItem));
// Use the action again to retract the arm blade
actionsSystem.PerformAction(ToServer(Player), actionEnt.Value);
// Make sure the player's hand is empty again
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Null, $"Player is still holding an item ({SEntMan.ToPrettyString(heldItem)}) after second use.");
});
}
}

View File

@@ -293,9 +293,9 @@ namespace Content.IntegrationTests.Tests.Buckle
Assert.That(buckle.Buckled);
// With items in all hands
foreach (var hand in hands.Hands.Keys)
foreach (var hand in hands.Hands.Values)
{
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Not.Null);
Assert.That(hand.HeldEntity, Is.Not.Null);
}
var bodySystem = entityManager.System<BodySystem>();
@@ -316,9 +316,9 @@ namespace Content.IntegrationTests.Tests.Buckle
Assert.That(buckle.Buckled);
// Now with no item in any hand
foreach (var hand in hands.Hands.Keys)
foreach (var hand in hands.Hands.Values)
{
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Null);
Assert.That(hand.HeldEntity, Is.Null);
}
buckleSystem.Unbuckle(human, human);

View File

@@ -1,6 +1,7 @@
using Content.Client.Chemistry.UI;
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Chemistry;
using Content.Server.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
namespace Content.IntegrationTests.Tests.Chemistry;
@@ -18,7 +19,7 @@ public sealed class DispenserTest : InteractionTest
// Insert beaker
await InteractUsing("Beaker");
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
// Open BUI
await Interact();
@@ -28,18 +29,18 @@ public sealed class DispenserTest : InteractionTest
await SendBui(ReagentDispenserUiKey.Key, ev);
// Beaker is back in the player's hands
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
// Re-insert the beaker
await Interact();
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
// Re-eject using the button directly instead of sending a BUI event. This test is really just a test of the
// bui/window helper methods.
await ClickControl<ReagentDispenserWindow>(nameof(ReagentDispenserWindow.EjectButton));
await RunTicks(5);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
}
}

View File

@@ -1,46 +0,0 @@
using Content.Shared.Cloning;
namespace Content.IntegrationTests.Tests.Cloning;
public sealed class CloningSettingsPrototypeTest
{
/// <summary>
/// Checks that the components named in every <see cref="CloningSettingsPrototype"/> are valid components known to the server.
/// This is used instead of <see cref="ComponentNameSerializer"/> because we only care if the components are registered with the server,
/// and instead of a <see cref="ComponentRegistry"/> because we only need component names.
/// </summary>
[Test]
public async Task ValidatePrototypes()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var protoMan = server.ProtoMan;
var compFactory = server.EntMan.ComponentFactory;
await server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
var protos = protoMan.EnumeratePrototypes<CloningSettingsPrototype>();
foreach (var proto in protos)
{
foreach (var compName in proto.Components)
{
Assert.That(compFactory.TryGetRegistration(compName, out _),
$"Failed to find a component named {compName} for {nameof(CloningSettingsPrototype)} \"{proto.ID}\""
);
}
foreach (var eventCompName in proto.EventComponents)
{
Assert.That(compFactory.TryGetRegistration(eventCompName, out _),
$"Failed to find a component named {eventCompName} for {nameof(CloningSettingsPrototype)} \"{proto.ID}\""
);
}
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -1,72 +0,0 @@
#nullable enable
using System.Linq;
using Content.Server.Objectives;
using Content.Shared.Mind;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Content.IntegrationTests.Tests.Commands;
public sealed class ObjectiveCommandsTest
{
private const string ObjectiveProtoId = "MindCommandsTestObjective";
private const string DummyUsername = "MindCommandsTestUser";
[TestPrototypes]
private const string Prototypes = $"""
- type: entity
id: {ObjectiveProtoId}
components:
- type: Objective
difficulty: 1
issuer: objective-issuer-syndicate
icon:
sprite: error.rsi
state: error
- type: DieCondition
""";
/// <summary>
/// Creates a dummy session, and assigns it a mind, then
/// tests using <c>addobjective</c>, <c>lsobjectives</c>,
/// and <c>rmobjective</c> on it.
/// </summary>
[Test]
public async Task AddListRemoveObjectiveTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entMan = server.EntMan;
var playerMan = server.ResolveDependency<ISharedPlayerManager>();
var mindSys = server.System<SharedMindSystem>();
var objectivesSystem = server.System<ObjectivesSystem>();
await server.AddDummySession(DummyUsername);
await server.WaitRunTicks(5);
var playerSession = playerMan.Sessions.Single();
Entity<MindComponent>? mindEnt = null;
await server.WaitPost(() =>
{
mindEnt = mindSys.CreateMind(playerSession.UserId);
});
Assert.That(mindEnt, Is.Not.Null);
var mindComp = mindEnt.Value.Comp;
Assert.That(mindComp.Objectives, Is.Empty, "Dummy player started with objectives.");
await pair.WaitCommand($"addobjective {playerSession.Name} {ObjectiveProtoId}");
Assert.That(mindComp.Objectives, Has.Count.EqualTo(1), "addobjective failed to increase Objectives count.");
await pair.WaitCommand($"lsobjectives {playerSession.Name}");
await pair.WaitCommand($"rmobjective {playerSession.Name} 0");
Assert.That(mindComp.Objectives, Is.Empty, "rmobjective failed to remove objective");
await pair.CleanReturnAsync();
}
}

View File

@@ -266,7 +266,7 @@ public sealed class SuicideCommandTests
await server.WaitPost(() =>
{
var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
Assert.That(executionComponent, Is.Not.EqualTo(null));
});
@@ -338,7 +338,7 @@ public sealed class SuicideCommandTests
await server.WaitPost(() =>
{
var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
Assert.That(executionComponent, Is.Not.EqualTo(null));
});

View File

@@ -13,10 +13,10 @@ public sealed class WallConstruction : InteractionTest
{
await StartConstruction(Wall);
await InteractUsing(Steel, 2);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
ClientAssertPrototype(Girder, Target);
await InteractUsing(Steel, 2);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
AssertPrototype(WallSolid);
}

View File

@@ -28,7 +28,7 @@ namespace Content.IntegrationTests.Tests.Disposal
SubscribeLocalEvent<DoInsertDisposalUnitEvent>(ev =>
{
var (_, toInsert, unit) = ev;
var insertTransform = Comp<TransformComponent>(toInsert);
var insertTransform = EntityManager.GetComponent<TransformComponent>(toInsert);
// Not in a tube yet
Assert.That(insertTransform.ParentUid, Is.EqualTo(unit));
}, after: new[] { typeof(SharedDisposalUnitSystem) });

View File

@@ -53,20 +53,20 @@ public sealed class HandTests
var xform = entMan.GetComponent<TransformComponent>(player);
item = entMan.SpawnEntity("Crowbar", tSys.GetMapCoordinates(player, xform: xform));
hands = entMan.GetComponent<HandsComponent>(player);
sys.TryPickup(player, item, hands.ActiveHandId!);
sys.TryPickup(player, item, hands.ActiveHand!);
});
// run ticks here is important, as errors may happen within the container system's frame update methods.
await pair.RunTicksSync(5);
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
await server.WaitPost(() =>
{
sys.TryDrop(player, item);
sys.TryDrop(player, item, null!);
});
await pair.RunTicksSync(5);
Assert.That(sys.GetActiveItem((player, hands)), Is.Null);
Assert.That(hands.ActiveHandEntity, Is.Null);
await server.WaitPost(() => mapSystem.DeleteMap(data.MapId));
await pair.CleanReturnAsync();
@@ -105,10 +105,10 @@ public sealed class HandTests
player = playerMan.Sessions.First().AttachedEntity!.Value;
tSys.PlaceNextTo(player, item);
hands = entMan.GetComponent<HandsComponent>(player);
sys.TryPickup(player, item, hands.ActiveHandId!);
sys.TryPickup(player, item, hands.ActiveHand!);
});
await pair.RunTicksSync(5);
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
// Open then close the box to place the player, who is holding the crowbar, inside of it
var storage = server.System<EntityStorageSystem>();
@@ -125,12 +125,12 @@ public sealed class HandTests
// with the item not being in the player's hands
await server.WaitPost(() =>
{
sys.TryDrop(player, item);
sys.TryDrop(player, item, null!);
});
await pair.RunTicksSync(5);
var xform = entMan.GetComponent<TransformComponent>(player);
var itemXform = entMan.GetComponent<TransformComponent>(item);
Assert.That(sys.GetActiveItem((player, hands)), Is.Not.EqualTo(item));
Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item));
Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform)));
await server.WaitPost(() => mapSystem.DeleteMap(map.MapId));

View File

@@ -120,18 +120,18 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task DeleteHeldEntity()
{
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is { } held)
if (Hands.ActiveHandEntity is { } held)
{
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryDrop((SEntMan.GetEntity(Player), Hands), null, false, true));
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), null, false, true, Hands));
SEntMan.DeleteEntity(held);
SLogger.Debug($"Deleting held entity");
});
}
await RunTicks(1);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
}
/// <summary>
@@ -152,7 +152,7 @@ public abstract partial class InteractionTest
/// <param name="enableToggleable">Whether or not to automatically enable any toggleable items</param>
protected async Task<NetEntity> PlaceInHands(EntitySpecifier entity, bool enableToggleable = true)
{
if (Hands.ActiveHandId == null)
if (Hands.ActiveHand == null)
{
Assert.Fail("No active hand");
return default;
@@ -169,7 +169,7 @@ public abstract partial class InteractionTest
{
var playerEnt = SEntMan.GetEntity(Player);
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHandId, false, false, false, Hands));
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands));
// turn on welders
if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
@@ -179,7 +179,7 @@ public abstract partial class InteractionTest
});
await RunTicks(1);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(item));
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item));
if (enableToggleable && itemToggle != null)
Assert.That(itemToggle.Activated);
@@ -193,7 +193,7 @@ public abstract partial class InteractionTest
{
entity ??= Target;
if (Hands.ActiveHandId == null)
if (Hands.ActiveHand == null)
{
Assert.Fail("No active hand");
return;
@@ -212,11 +212,11 @@ public abstract partial class InteractionTest
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryPickup(ToServer(Player), uid.Value, Hands.ActiveHandId, false, false, false, Hands, item));
Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, Hands, item));
});
await RunTicks(1);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(uid));
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(uid));
}
/// <summary>
@@ -224,7 +224,7 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task Drop()
{
if (HandSys.GetActiveItem((ToServer(Player), Hands)) == null)
if (Hands.ActiveHandEntity == null)
{
Assert.Fail("Not holding any entity to drop");
return;
@@ -232,11 +232,11 @@ public abstract partial class InteractionTest
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryDrop((ToServer(Player), Hands)));
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), handsComp: Hands));
});
await RunTicks(1);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
}
#region Interact
@@ -246,7 +246,7 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task UseInHand()
{
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is not { } target)
if (Hands.ActiveHandEntity is not { } target)
{
Assert.Fail("Not holding any entity");
return;

View File

@@ -1,12 +1,15 @@
#nullable enable
using System.Linq;
using System.Numerics;
using Content.Client.Construction;
using Content.Client.Examine;
using Content.Client.Gameplay;
using Content.IntegrationTests.Pair;
using Content.Server.Body.Systems;
using Content.Server.Hands.Systems;
using Content.Server.Stack;
using Content.Server.Tools;
using Content.Shared.Body.Part;
using Content.Shared.DoAfter;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
@@ -132,13 +135,10 @@ public abstract partial class InteractionTest
- type: entity
id: InteractionTestMob
components:
- type: Body
prototype: Aghost
- type: DoAfter
- type: Hands
hands:
hand_right: # only one hand, so that they do not accidentally pick up deconstruction products
location: Right
sortedHands:
- hand_right
- type: ComplexInteraction
- type: MindContainer
- type: Stripping
@@ -230,6 +230,20 @@ public abstract partial class InteractionTest
SEntMan.DeleteEntity(old.Value);
});
// Ensure that the player only has one hand, so that they do not accidentally pick up deconstruction products
await Server.WaitPost(() =>
{
// I lost an hour of my life trying to track down how the hell interaction tests were breaking
// so greatz to this. Just make your own body prototype!
var bodySystem = SEntMan.System<BodySystem>();
var hands = bodySystem.GetBodyChildrenOfType(SEntMan.GetEntity(Player), BodyPartType.Hand).ToArray();
for (var i = 1; i < hands.Length; i++)
{
SEntMan.DeleteEntity(hands[i].Id);
}
});
// Change UI state to in-game.
var state = Client.ResolveDependency<IStateManager>();
await Client.WaitPost(() => state.RequestStateChange<GameplayState>());

View File

@@ -410,7 +410,7 @@ namespace Content.IntegrationTests.Tests.Networking
{
var uid = GetEntity(message.Uid);
var component = Comp<PredictionTestComponent>(uid);
var component = EntityManager.GetComponent<PredictionTestComponent>(uid);
var old = component.Foo;
if (Allow)
{

View File

@@ -17,7 +17,7 @@ public sealed class TileConstructionTests : InteractionTest
await SetTile(null);
await InteractUsing(Rod);
await AssertTile(Lattice);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
await InteractUsing(Cut);
await AssertTile(null);
await AssertEntityLookup((Rod, 1));
@@ -49,7 +49,7 @@ public sealed class TileConstructionTests : InteractionTest
AssertGridCount(1);
// Cut lattice
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
await InteractUsing(Cut);
await AssertTile(null);
AssertGridCount(0);
@@ -83,13 +83,13 @@ public sealed class TileConstructionTests : InteractionTest
// Lattice -> Plating
await InteractUsing(FloorItem);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
await AssertTile(Plating);
AssertGridCount(1);
// Plating -> Tile
await InteractUsing(FloorItem);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
Assert.That(Hands.ActiveHandEntity, Is.Null);
await AssertTile(Floor);
AssertGridCount(1);

View File

@@ -13,7 +13,6 @@ public sealed class CommandLineArguments
public string OutputPath { get; set; } = DirectoryExtensions.MapImages().FullName;
public bool ArgumentsAreFileNames { get; set; } = false;
public bool ShowMarkers { get; set; } = false;
public bool OutputParallax { get; set; } = false;
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArguments? parsed)
{
@@ -71,17 +70,7 @@ public sealed class CommandLineArguments
PrintHelp();
return false;
case "--parallax":
parsed.OutputParallax = true;
break;
default:
if (argument.StartsWith('-'))
{
Console.WriteLine($"Unknown argument: {argument}");
return false;
}
parsed.Maps.Add(argument);
break;
}
@@ -106,6 +95,7 @@ Options:
Defaults to: png
--viewer
Causes the map renderer to create the map.json files required for use with the map viewer.
Also puts the maps in the required directory structure.
-o / --output <output path>
Changes the path the rendered maps will get saved to.
Defaults to Resources/MapImages
@@ -114,8 +104,6 @@ Options:
Example: Content.MapRenderer -f /Maps/box.yml /Maps/bagel.yml
-m / --markers
Show hidden markers on map render. Defaults to false.
--parallax
Output images and data used for map viewer parallax.
-h / --help
Displays this help text");
}

View File

@@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json.Serialization;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
using Robust.Shared.Maths;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.MapRenderer;
@@ -45,31 +43,31 @@ public sealed class LayerGroup
public GroupSource Source { get; set; } = new();
public List<Layer> Layers { get; set; } = new();
public static LayerGroup DefaultParallax(IResourceManager resourceManager, ParallaxOutput output)
public static LayerGroup DefaultParallax()
{
return new LayerGroup
{
Scale = new Position(0.1f, 0.1f),
Source = new GroupSource
{
Url = output.ReferenceResourceFile(resourceManager, new ResPath("/Textures/Parallaxes/layer1.png")),
Extent = new Extent(6000, 4000),
Url = "https://i.imgur.com/3YO8KRd.png",
Extent = new Extent(6000, 4000)
},
Layers = new List<Layer>
{
new()
{
Url = output.ReferenceResourceFile(resourceManager, new ResPath("/Textures/Parallaxes/layer1.png")),
Url = "https://i.imgur.com/IannmmK.png"
},
new()
{
Url = output.ReferenceResourceFile(resourceManager, new ResPath("/Textures/Parallaxes/layer2.png")),
Url = "https://i.imgur.com/T3W6JsE.png",
Composition = "lighter",
ParallaxScale = new Position(0.2f, 0.2f)
},
new()
{
Url = output.ReferenceResourceFile(resourceManager, new ResPath("/Textures/Parallaxes/layer3.png")),
Url = "https://i.imgur.com/T3W6JsE.png",
Composition = "lighter",
ParallaxScale = new Position(0.3f, 0.3f)
}
@@ -93,13 +91,9 @@ public sealed class Layer
public readonly struct Extent
{
[JsonInclude]
public readonly float X1;
[JsonInclude]
public readonly float Y1;
[JsonInclude]
public readonly float X2;
[JsonInclude]
public readonly float Y2;
public Extent()
@@ -129,9 +123,7 @@ public readonly struct Extent
public readonly struct Position
{
[JsonInclude]
public readonly float X;
[JsonInclude]
public readonly float Y;
public Position(float x, float y)

View File

@@ -1,158 +1,149 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.IO;
using System.Threading.Tasks;
using Content.Client.Markers;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.GameTicking;
using Robust.Client.GameObjects;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace Content.MapRenderer.Painters
{
public sealed class MapPainter : IAsyncDisposable
public sealed class MapPainter
{
private readonly RenderMap _map;
private readonly ITestContextLike _testContextLike;
private TestPair? _pair;
private Entity<MapGridComponent>[] _grids = [];
public MapPainter(RenderMap map, ITestContextLike testContextLike)
public static async IAsyncEnumerable<RenderedGridImage<Rgba32>> Paint(string map,
bool mapIsFilename = false,
bool showMarkers = false)
{
_map = map;
_testContextLike = testContextLike;
}
var stopwatch = new Stopwatch();
stopwatch.Start();
public async Task Initialize()
{
var stopwatch = RStopwatch.StartNew();
var poolSettings = new PoolSettings
await using var pair = await PoolManager.GetServerClient(new PoolSettings
{
DummyTicker = false,
Connected = true,
Destructive = true,
Fresh = true,
// Seriously whoever made MapPainter use GameMapPrototype I wish you step on a lego one time.
Map = _map is RenderMapPrototype prototype ? prototype.Prototype : PoolManager.TestMap,
};
_pair = await PoolManager.GetServerClient(poolSettings, _testContextLike);
Map = mapIsFilename ? "Empty" : map,
});
var server = pair.Server;
var client = pair.Client;
Console.WriteLine($"Loaded client and server in {(int)stopwatch.Elapsed.TotalMilliseconds} ms");
if (_map is RenderMapFile mapFile)
stopwatch.Restart();
var cEntityManager = client.ResolveDependency<IClientEntityManager>();
var cPlayerManager = client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
await client.WaitPost(() =>
{
using var stream = File.OpenRead(mapFile.FileName);
await _pair.Server.WaitPost(() =>
if (cEntityManager.TryGetComponent(cPlayerManager.LocalEntity, out SpriteComponent? sprite))
{
var loadOptions = new MapLoadOptions
{
// Accept loading both maps and grids without caring about what the input file truly is.
DeserializationOptions =
{
LogOrphanedGrids = false,
},
};
if (!_pair.Server.System<MapLoaderSystem>().TryLoadGeneric(stream, mapFile.FileName, out var loadResult, loadOptions))
throw new IOException($"File {mapFile.FileName} could not be read");
_grids = loadResult.Grids.ToArray();
});
}
}
public async Task SetupView(bool showMarkers)
{
if (_pair == null)
throw new InvalidOperationException("Instance not initialized!");
await _pair.Client.WaitPost(() =>
{
if (_pair.Client.EntMan.TryGetComponent(_pair.Client.PlayerMan.LocalEntity, out SpriteComponent? sprite))
{
_pair.Client.System<SpriteSystem>()
.SetVisible((_pair.Client.PlayerMan.LocalEntity.Value, sprite), false);
cEntityManager.System<SpriteSystem>().SetVisible((cPlayerManager.LocalEntity.Value, sprite), false);
}
});
if (showMarkers)
{
await _pair.Client.WaitPost(() =>
{
_pair.Client.System<MarkerSystem>().MarkersVisible = true;
});
}
}
public async Task<MapViewerData> GenerateMapViewerData(ParallaxOutput? parallaxOutput)
{
if (_pair == null)
throw new InvalidOperationException("Instance not initialized!");
var mapShort = _map.ShortName;
string fullName;
if (_map is RenderMapPrototype prototype)
{
fullName = _pair.Server.ProtoMan.Index(prototype.Prototype).MapName;
}
else
{
fullName = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(mapShort);
}
var mapViewerData = new MapViewerData
{
Id = mapShort,
Name = fullName,
};
if (parallaxOutput != null)
{
await _pair.Client.WaitPost(() =>
{
var res = _pair.Client.InstanceDependencyCollection.Resolve<IResourceManager>();
mapViewerData.ParallaxLayers.Add(LayerGroup.DefaultParallax(res, parallaxOutput));
});
}
return mapViewerData;
}
public async IAsyncEnumerable<RenderedGridImage<Rgba32>> Paint()
{
if (_pair == null)
throw new InvalidOperationException("Instance not initialized!");
var client = _pair.Client;
var server = _pair.Server;
await pair.WaitClientCommand("showmarkers");
var sEntityManager = server.ResolveDependency<IServerEntityManager>();
var sPlayerManager = server.ResolveDependency<IPlayerManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var mapLoader = entityManager.System<MapLoaderSystem>();
var mapSys = entityManager.System<SharedMapSystem>();
var deps = server.ResolveDependency<IEntitySystemManager>().DependencyCollection;
await _pair.RunTicksSync(10);
Entity<MapGridComponent>[] grids = [];
if (mapIsFilename)
{
var resPath = new ResPath(map);
if (!mapLoader.TryReadFile(resPath, out var data))
throw new IOException($"File {map} could not be read");
var ev = new BeforeEntityReadEvent();
server.EntMan.EventBus.RaiseEvent(EventSource.Local, ev);
var deserializer = new EntityDeserializer(deps,
data,
DeserializationOptions.Default,
ev.RenamedPrototypes,
ev.DeletedPrototypes);
if (!deserializer.TryProcessData())
{
throw new IOException($"Failed to process entity data in {map}");
}
if (deserializer.Result.Category == FileCategory.Unknown && deserializer.Result.Version < 7)
{
var mapCount = 0;
var gridCount = 0;
foreach (var (entId, ent) in deserializer.YamlEntities)
{
if (ent.Components != null && ent.Components.ContainsKey("MapGrid"))
{
gridCount++;
}
if (ent.Components != null && ent.Components.ContainsKey("Map"))
{
mapCount++;
}
}
if (mapCount == 1)
deserializer.Result.Category = FileCategory.Map;
else if (mapCount == 0 && gridCount == 1)
deserializer.Result.Category = FileCategory.Grid;
}
switch (deserializer.Result.Category)
{
case FileCategory.Map:
await server.WaitPost(() =>
{
if (mapLoader.TryLoadMap(resPath, out _, out var loadedGrids))
{
grids = loadedGrids.ToArray();
}
});
break;
case FileCategory.Grid:
await server.WaitPost(() =>
{
if (mapLoader.TryLoadGrid(resPath, out _, out var loadedGrids))
{
grids = [(Entity<MapGridComponent>)loadedGrids];
}
});
break;
default:
throw new IOException($"Unknown category {deserializer.Result.Category}");
}
}
await pair.RunTicksSync(10);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
var sMapManager = server.ResolveDependency<IMapManager>();
@@ -171,23 +162,23 @@ namespace Content.MapRenderer.Painters
sEntityManager.DeleteEntity(playerEntity.Value);
}
if (_map is RenderMapPrototype)
if (!mapIsFilename)
{
var mapId = sEntityManager.System<GameTicker>().DefaultMap;
_grids = sMapManager.GetAllGrids(mapId).ToArray();
grids = sMapManager.GetAllGrids(mapId).ToArray();
}
foreach (var (uid, _) in _grids)
foreach (var (uid, _) in grids)
{
var gridXform = xformQuery.GetComponent(uid);
xformSystem.SetWorldRotation(gridXform, Angle.Zero);
}
});
await _pair.RunTicksSync(10);
await pair.RunTicksSync(10);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
foreach (var (uid, grid) in _grids)
foreach (var (uid, grid) in grids)
{
var tiles = mapSys.GetAllTiles(uid, grid).ToList();
if (tiles.Count == 0)
@@ -228,20 +219,16 @@ namespace Content.MapRenderer.Painters
yield return renderedImage;
}
}
public async Task CleanReturnAsync()
{
if (_pair == null)
throw new InvalidOperationException("Instance not initialized!");
await _pair.CleanReturnAsync();
}
public async ValueTask DisposeAsync()
{
if (_pair != null)
await _pair.DisposeAsync();
// We don't care if it fails as we have already saved the images.
try
{
await pair.CleanReturnAsync();
}
catch
{
// ignored
}
}
}
}

View File

@@ -1,41 +0,0 @@
using System.Collections.Generic;
using System.IO;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
namespace Content.MapRenderer;
/// <summary>
/// Helper class for collecting the files used for parallax output
/// </summary>
public sealed class ParallaxOutput
{
public const string OutputDirectory = "_parallax";
public readonly HashSet<ResPath> FilesToCopy = [];
private readonly string _outputPath;
/// <summary>
/// Helper class for collecting the files used for parallax output
/// </summary>
public ParallaxOutput(string outputPath)
{
_outputPath = outputPath;
Directory.CreateDirectory(Path.Combine(_outputPath, OutputDirectory));
}
public string ReferenceResourceFile(IResourceManager resourceManager, ResPath path)
{
var fileName = Path.Combine(OutputDirectory, path.Filename);
if (FilesToCopy.Add(path))
{
using var file = resourceManager.ContentFileRead(path);
using var target = File.Create(Path.Combine(_outputPath, fileName));
file.CopyTo(target);
}
return fileName;
}
}

View File

@@ -3,11 +3,12 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Content.IntegrationTests;
using Content.MapRenderer.Painters;
using Content.Server.Maps;
using Newtonsoft.Json;
using Robust.Shared.Prototypes;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Webp;
@@ -20,19 +21,20 @@ namespace Content.MapRenderer
private static readonly Func<string, string> ChosenMapIdNotIntMessage = id => $"The chosen id is not a valid integer: {id}";
private static readonly Func<int, string> NoMapFoundWithIdMessage = id => $"No map found with chosen id: {id}";
private static readonly MapPainter MapPainter = new();
internal static async Task Main(string[] args)
{
if (!CommandLineArguments.TryParse(args, out var arguments))
return;
var testContext = new ExternalTestContext("Content.MapRenderer", Console.Out);
PoolManager.Startup();
if (arguments.Maps.Count == 0)
{
Console.WriteLine("Didn't specify any maps to paint! Loading the map list...");
await using var pair = await PoolManager.GetServerClient(testContext: testContext);
await using var pair = await PoolManager.GetServerClient();
var mapIds = pair.Server
.ResolveDependency<IPrototypeManager>()
.EnumeratePrototypes<GameMapPrototype>()
@@ -102,118 +104,45 @@ namespace Content.MapRenderer
Console.WriteLine($"Selected maps: {string.Join(", ", selectedMapPrototypes)}");
}
var maps = new List<RenderMap>();
if (arguments.ArgumentsAreFileNames)
{
Console.WriteLine("Retrieving maps by file names...");
//
// Handle legacy command line processing:
// Ideally, people pass file names that are relative to the process working directory.
// i.e. regular command-line behavior.
//
// However, the map renderer was originally written to only handle gameMap prototypes,
// so it would actually go through the list of prototypes and match file name arguments
// via a *very* coarse check.
//
// So if we have any input filenames that don't exist... we run the old behavior.
// Yes by the way this means a typo means spinning up an entire integration pool pair
// before the map renderer can report a proper failure.
//
// Note that this legacy processing is very important! The map server currently relies on it,
// because it wants to work with file names, but we *need* to resolve the input to a prototype
// to properly export viewer JSON data.
//
var lookupPrototypeFiles = new List<string>();
foreach (var map in arguments.Maps)
{
if (File.Exists(map))
{
maps.Add(new RenderMapFile { FileName = map });
}
else
{
lookupPrototypeFiles.Add(map);
}
}
if (lookupPrototypeFiles.Count > 0)
{
Console.Write($"Following map files did not exist on disk directly, searching through prototypes: {string.Join(", ", lookupPrototypeFiles)}");
await using var pair = await PoolManager.GetServerClient();
var mapPrototypes = pair.Server
.ResolveDependency<IPrototypeManager>()
.EnumeratePrototypes<GameMapPrototype>()
.ToArray();
foreach (var toFind in lookupPrototypeFiles)
{
foreach (var mapPrototype in mapPrototypes)
{
if (mapPrototype.MapPath.Filename == toFind)
{
maps.Add(new RenderMapPrototype { Prototype = mapPrototype, });
Console.WriteLine($"Found matching map prototype: {mapPrototype.MapName}");
goto found;
}
}
await Console.Error.WriteLineAsync($"Found no map prototype for file '{toFind}'!");
found: ;
}
}
}
else
{
foreach (var map in arguments.Maps)
{
maps.Add(new RenderMapPrototype { Prototype = map });
}
}
await Run(arguments, maps, testContext);
await Run(arguments);
PoolManager.Shutdown();
}
private static async Task Run(
CommandLineArguments arguments,
List<RenderMap> toRender,
ExternalTestContext testContext)
private static async Task Run(CommandLineArguments arguments)
{
Console.WriteLine($"Creating images for {toRender.Count} maps");
var parallaxOutput = arguments.OutputParallax ? new ParallaxOutput(arguments.OutputPath) : null;
Console.WriteLine($"Creating images for {arguments.Maps.Count} maps");
var mapNames = new List<string>();
foreach (var map in toRender)
foreach (var map in arguments.Maps)
{
Console.WriteLine($"Painting map {map}");
await using var painter = new MapPainter(map, testContext);
await painter.Initialize();
await painter.SetupView(showMarkers: arguments.ShowMarkers);
var mapViewerData = new MapViewerData
{
Id = map,
Name = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(map)
};
var mapViewerData = await painter.GenerateMapViewerData(parallaxOutput);
var mapShort = map.ShortName;
var directory = Path.Combine(arguments.OutputPath, mapShort);
mapNames.Add(mapShort);
mapViewerData.ParallaxLayers.Add(LayerGroup.DefaultParallax());
var directory = Path.Combine(arguments.OutputPath, Path.GetFileNameWithoutExtension(map));
var i = 0;
try
{
await foreach (var renderedGrid in painter.Paint())
await foreach (var renderedGrid in MapPainter.Paint(map,
arguments.ArgumentsAreFileNames,
arguments.ShowMarkers))
{
var grid = renderedGrid.Image;
Directory.CreateDirectory(directory);
var savePath = $"{directory}{Path.DirectorySeparatorChar}{mapShort}-{i}.{arguments.Format}";
var fileName = Path.GetFileNameWithoutExtension(map);
var savePath = $"{directory}{Path.DirectorySeparatorChar}{fileName}-{i}.{arguments.Format}";
Console.WriteLine($"Writing grid of size {grid.Width}x{grid.Height} to {savePath}");
@@ -238,7 +167,9 @@ namespace Content.MapRenderer
grid.Dispose();
mapViewerData.Grids.Add(new GridLayer(renderedGrid, Path.Combine(mapShort, Path.GetFileName(savePath))));
mapViewerData.Grids.Add(new GridLayer(renderedGrid, Path.Combine(map, Path.GetFileName(savePath))));
mapNames.Add(fileName);
i++;
}
}
@@ -251,17 +182,8 @@ namespace Content.MapRenderer
if (arguments.ExportViewerJson)
{
var json = JsonSerializer.Serialize(mapViewerData);
await File.WriteAllTextAsync(Path.Combine(directory, "map.json"), json);
}
try
{
await painter.CleanReturnAsync();
}
catch (Exception e)
{
Console.WriteLine($"Exception while shutting down painter: {e}");
var json = JsonConvert.SerializeObject(mapViewerData);
await File.WriteAllTextAsync(Path.Combine(arguments.OutputPath, map, "map.json"), json);
}
}

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