Merge branch 'master' of https://github.com/space-wizards/space-station-14 into moss-seniorcargo

Merged master into branch
This commit is contained in:
Hitlinemoss
2025-06-28 15:25:23 -04:00
1210 changed files with 19683 additions and 10901 deletions

View File

@@ -27,7 +27,7 @@ jobs:
run: dotnet restore
- name: Build Project
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
run: dotnet build --no-restore
- 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 /p:WarningsAsErrors=nullable /m
run: dotnet build Content.MapRenderer --configuration Release --no-restore /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 /p:WarningsAsErrors=nullable /m
run: dotnet build --configuration DebugOpt --no-restore /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]
types: [review_requested, opened]
jobs:
add_label:

View File

@@ -14,6 +14,10 @@ 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 Comunity Moderation
## On Community 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));
EntityManager.RaisePredictiveEvent(request);
RaisePredictiveEvent(request);
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Frozen;
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Systems;
@@ -24,6 +25,7 @@ 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;
@@ -36,8 +38,9 @@ internal sealed class AdminNameOverlay : Overlay
private float _overlayMergeDistance;
//TODO make this adjustable via GUI?
private readonly ProtoId<RoleTypePrototype>[] _filter =
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
private static readonly FrozenSet<ProtoId<RoleTypePrototype>> Filter =
new ProtoId<RoleTypePrototype>[] {"SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"}
.ToFrozenSet();
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
@@ -49,7 +52,8 @@ internal sealed class AdminNameOverlay : Overlay
EntityLookupSystem entityLookup,
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config,
SharedRoleSystem roles)
SharedRoleSystem roles,
IPrototypeManager prototypeManager)
{
_system = system;
_entityManager = entityManager;
@@ -57,6 +61,7 @@ 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();
@@ -125,6 +130,14 @@ 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;
@@ -209,7 +222,7 @@ internal sealed class AdminNameOverlay : Overlay
switch (_overlaySymbolStyle)
{
case AdminOverlayAntagSymbolStyle.Specific:
symbol = playerInfo.RoleProto.Symbol;
symbol = roleSymbol;
break;
case AdminOverlayAntagSymbolStyle.Basic:
symbol = Loc.GetString("player-tab-antag-prefix");
@@ -225,17 +238,17 @@ internal sealed class AdminNameOverlay : Overlay
switch (_overlayFormat)
{
case AdminOverlayAntagFormat.Roletype:
color = playerInfo.RoleProto.Color;
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
text = _filter.Contains(playerInfo.RoleProto)
? Loc.GetString(playerInfo.RoleProto.Name).ToUpper()
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? roleName.ToUpper()
: string.Empty;
break;
case AdminOverlayAntagFormat.Subtype:
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()
color = roleColor;
symbol = IsFiltered(playerInfo.RoleProto) ? symbol : string.Empty;
text = IsFiltered(playerInfo.RoleProto)
? _roles.GetRoleSubtypeLabel(roleName, playerInfo.Subtype).ToUpper()
: string.Empty;
break;
default:
@@ -258,4 +271,12 @@ 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,6 +4,7 @@ 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
{
@@ -17,6 +18,7 @@ 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!;
@@ -33,7 +35,8 @@ namespace Content.Client.Administration.Systems
_entityLookup,
_userInterfaceManager,
_configurationManager,
_roles);
_roles,
_proto);
_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="350 500">
Title="{Loc ban-panel-title}" MinSize="410 500">
<BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" VerticalExpand="True">
<!-- Basic info -->

View File

@@ -1,12 +1,14 @@
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;
@@ -31,14 +33,21 @@ 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.
private readonly List<CheckBox> _roleCheckboxes = new();
// Role group name -> the role buttons themselves.
private readonly Dictionary<string, List<Button>> _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
{
@@ -144,47 +153,90 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
var departmentJobs = _protoMan.EnumeratePrototypes<DepartmentPrototype>()
.OrderBy(x => x.Weight);
foreach (var proto in departmentJobs)
{
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
var roles = proto.Roles.Select(x => _protoMan.Index(x))
.OrderBy(x => x.ID);
CreateRoleGroup(proto.ID, proto.Color, roles);
}
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
.OrderBy(x => x.ID);
CreateRoleGroup("Antagonist", Color.Red, antagRoles);
}
private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
/// <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
{
var outerContainer = new BoxContainer
{
Name = $"{roleName}GroupOuterBox",
Name = $"{groupName}GroupOuterBox",
HorizontalExpand = true,
VerticalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(4)
Margin = new Thickness(4),
};
var departmentCheckbox = new CheckBox
// Stores stuff like ban all and expand buttons.
var roleGroupHeader = new BoxContainer
{
Name = $"{roleName}GroupCheckbox",
Text = roleName,
Modulate = color,
HorizontalAlignment = HAlignment.Left
Orientation = BoxContainer.LayoutOrientation.Horizontal,
};
outerContainer.AddChild(departmentCheckbox);
var innerContainer = new BoxContainer
// Stores the role checkboxes themselves.
var innerContainer = new GridContainer
{
Name = $"{roleName}GroupInnerBox",
Name = $"{groupName}GroupInnerBox",
HorizontalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Horizontal
Columns = 2,
Visible = false,
Margin = new Thickness(15, 5, 0, 5),
};
departmentCheckbox.OnToggled += args =>
var roleGroupCheckbox = CreateRoleGroupHeader(groupName, roleGroupHeader, color, innerContainer);
outerContainer.AddChild(roleGroupHeader);
// Add the roles themselves
foreach (var role in roles)
{
foreach (var child in innerContainer.Children)
AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox);
}
outerContainer.AddChild(innerContainer);
RolesContainer.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat
{
if (child is CheckBox c)
{
c.Pressed = args.Pressed;
}
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 (args.Pressed)
@@ -199,15 +251,12 @@ public sealed partial class BanPanel : DefaultWindow
}
else
{
foreach (var childContainer in RolesContainer.Children)
foreach (var roleButtons in _roleCheckboxes.Values)
{
if (childContainer is Container)
foreach (var button in roleButtons)
{
foreach (var child in childContainer.Children)
{
if (child is CheckBox { Pressed: true })
return;
}
if (button.Pressed)
return;
}
}
@@ -220,38 +269,72 @@ public sealed partial class BanPanel : DefaultWindow
SeverityOption.SelectId((int) newSeverity);
}
};
outerContainer.AddChild(innerContainer);
foreach (var role in roleList)
var hideButton = new Button
{
AddRoleCheckbox(role, innerContainer, departmentCheckbox);
}
RolesContainer.AddChild(new PanelContainer
Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow,
ToggleMode = true,
};
hideButton.OnPressed += args =>
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = color
}
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),
});
RolesContainer.AddChild(outerContainer);
RolesContainer.AddChild(new HSeparator());
header.AddChild(roleGroupCheckbox);
header.AddChild(hideButton);
return roleGroupCheckbox;
}
private void AddRoleCheckbox(string role, Control container, CheckBox header)
/// <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)
{
var roleCheckbox = new CheckBox
var roleCheckboxContainer = new BoxContainer();
var roleCheckButton = new Button
{
Name = $"{role}RoleCheckbox",
Text = role
Text = role,
ToggleMode = true,
};
roleCheckbox.OnToggled += args =>
roleCheckButton.OnToggled += args =>
{
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;
// Checks the role group checkbox if all the children are pressed
if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
roleGroupCheckbox.Pressed = args.Pressed;
else
header.Pressed = false;
roleGroupCheckbox.Pressed = false;
};
container.AddChild(roleCheckbox);
_roleCheckboxes.Add(roleCheckbox);
// 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);
}
public void UpdateBanFlag(bool newFlag)
@@ -469,7 +552,13 @@ public sealed partial class BanPanel : DefaultWindow
if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty");
rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
{
if (button is { Pressed: true, Text: not null })
{
rolesList.Add(button.Text);
}
}
if (rolesList.Count == 0)
{

View File

@@ -49,6 +49,7 @@
<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,4 +1,6 @@
using System.Linq;
using System.IO;
using System.Linq;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Eui;
using Content.Shared.Administration.Logs;
using Content.Shared.Eui;
@@ -15,6 +17,12 @@ 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 ISawmill _sawmill;
private bool _currentlyExportingLogs = false;
public AdminLogsEui()
{
@@ -26,6 +34,9 @@ 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; }
@@ -74,6 +85,66 @@ 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
{
await using var writer = new StreamWriter(file.Value.fileStream);
foreach (var child in LogsControl.LogsContainer.Children)
{
if (child is not AdminLogLabel logLabel || !child.Visible)
continue;
var log = logLabel.Log;
// 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(',');
await writer.WriteAsync(log.Id.ToString());
await writer.WriteAsync(',');
await writer.WriteAsync(log.Impact.ToString());
await writer.WriteAsync(',');
// Message
await writer.WriteAsync('"');
await writer.WriteAsync(log.Message.Replace("\"", "\"\""));
await writer.WriteAsync('"');
// End of message
await writer.WriteAsync(',');
var players = log.Players;
for (var i = 0; i < players.Length; i++)
{
await writer.WriteAsync(players[i] + (i == players.Length - 1 ? "" : " "));
}
await writer.WriteAsync(',');
await writer.WriteAsync(log.Type.ToString());
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,6 +172,7 @@ 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,9 +1,11 @@
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;
@@ -11,6 +13,7 @@ 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,
@@ -23,6 +26,8 @@ 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");
@@ -57,19 +62,19 @@ public sealed partial class PlayerTabEntry : PanelContainer
break;
default:
case AdminPlayerTabSymbolOption.Specific:
symbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
symbol = player.Antag ? rolePrototype?.Symbol ?? RoleTypePrototype.FallbackSymbol : string.Empty;
break;
}
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
if (player.Antag && colorAntags)
CharacterLabel.FontColorOverride = player.RoleProto.Color;
CharacterLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
var roletype = RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
var subtype = roles.GetRoleSubtypeLabel(player.RoleProto.Name, player.Subtype);
var roletype = RoleTypeLabel.Text = Loc.GetString(rolePrototype?.Name ?? RoleTypePrototype.FallbackName);
var subtype = roles.GetRoleSubtypeLabel(rolePrototype?.Name ?? RoleTypePrototype.FallbackName, player.Subtype);
switch (roleSetting)
{
case AdminPlayerTabRoleTypeOption.RoleTypeSubtype:
@@ -92,7 +97,7 @@ public sealed partial class PlayerTabEntry : PanelContainer
}
if (colorRoles)
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
RoleTypeLabel.FontColorOverride = rolePrototype?.Color ?? RoleTypePrototype.FallbackColor;
BackgroundColorPanel.PanelOverride = styleBoxFlat;
OverallPlaytimeLabel.Text = player.PlaytimeString;
}

View File

@@ -0,0 +1,90 @@
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,11 +11,14 @@ public record struct UpdateAlertSpriteEvent
{
public Entity<SpriteComponent> SpriteViewEnt;
public EntityUid ViewerEnt;
public AlertPrototype Alert;
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, AlertPrototype alert)
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, EntityUid viewerEnt, AlertPrototype alert)
{
SpriteViewEnt = spriteViewEnt;
ViewerEnt = viewerEnt;
Alert = alert;
}
}

View File

@@ -2,16 +2,16 @@ using Robust.Shared.Console;
namespace Content.Client.Audio;
public sealed class AmbientOverlayCommand : IConsoleCommand
public sealed class AmbientOverlayCommand : LocalizedEntityCommands
{
public string Command => "showambient";
public string Description => "Shows all AmbientSoundComponents in the viewport";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AmbientSoundSystem>();
system.OverlayEnabled ^= true;
[Dependency] private readonly AmbientSoundSystem _ambient = default!;
shell.WriteLine($"Ambient sound overlay set to {system.OverlayEnabled}");
public override string Command => "showambient";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_ambient.OverlayEnabled ^= true;
shell.WriteLine(Loc.GetString($"cmd-showambient-status", ("status", _ambient.OverlayEnabled)));
}
}

View File

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

View File

@@ -15,8 +15,8 @@ namespace Content.Client.Changelog
[GenerateTypedNameReferences]
public sealed partial class ChangelogWindow : FancyWindow
{
[Dependency] private readonly ChangelogManager _changelog = default!;
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly ChangelogManager _changelog = default!;
public ChangelogWindow()
{
@@ -112,15 +112,15 @@ namespace Content.Client.Changelog
}
[UsedImplicitly, AnyCommand]
public sealed class ChangelogCommand : IConsoleCommand
public sealed class ChangelogCommand : LocalizedCommands
{
public string Command => "changelog";
public string Description => "Opens the changelog";
public string Help => "Usage: changelog";
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override string Command => "changelog";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<ChangelogUIController>().OpenWindow();
_uiManager.GetUIController<ChangelogUIController>().OpenWindow();
}
}
}

View File

@@ -5,8 +5,9 @@ using Content.Shared.Chemistry.EntitySystems;
namespace Content.Client.Chemistry.EntitySystems;
public sealed class HypospraySystem : SharedHypospraySystem
public sealed class HyposprayStatusControlSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
public override void Initialize()
{
base.Initialize();

View File

@@ -5,32 +5,27 @@ using Robust.Shared.Containers;
namespace Content.Client.Commands;
public sealed class HideMechanismsCommand : LocalizedCommands
public sealed class HideMechanismsCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = 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 containerSys = _entityManager.System<SharedContainerSystem>();
var spriteSys = _entityManager.System<SpriteSystem>();
var query = _entityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out _, out var sprite))
{
spriteSys.SetContainerOccluded((uid, sprite), false);
_spriteSystem.SetContainerOccluded((uid, sprite), false);
var tempParent = uid;
while (containerSys.TryGetContainingContainer((tempParent, null, null), out var container))
while (_containerSystem.TryGetContainingContainer((tempParent, null, null), out var container))
{
if (!container.ShowContents)
{
spriteSys.SetContainerOccluded((uid, sprite), true);
_spriteSystem.SetContainerOccluded((uid, sprite), true);
break;
}

View File

@@ -1,57 +1,46 @@
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 : LocalizedCommands
public sealed class ShowHealthBarsCommand : LocalizedEntityCommands
{
[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 = _playerManager.LocalSession;
var player = shell.Player;
if (player == null)
{
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-not-player"));
shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command"));
return;
}
var playerEntity = player?.AttachedEntity;
if (!playerEntity.HasValue)
if (player.AttachedEntity is not { } playerEntity)
{
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error-no-entity"));
shell.WriteError(Loc.GetString("shell-must-be-attached-to-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.Value, showHealthBarsComponent, true);
EntityManager.AddComponent(playerEntity, showHealthBarsComponent, true);
shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-enabled", ("args", string.Join(", ", args))));
shell.WriteLine(Loc.GetString("cmd-showhealthbars-notify-enabled", ("args", string.Join(", ", args))));
return;
}
else
{
_entityManager.RemoveComponentDeferred<ShowHealthBarsComponent>(playerEntity.Value);
shell.WriteLine(LocalizationManager.GetString($"cmd-{Command}-notify-disabled"));
}
return;
EntityManager.RemoveComponentDeferred<ShowHealthBarsComponent>(playerEntity);
shell.WriteLine(Loc.GetString("cmd-showhealthbars-notify-disabled"));
}
}

View File

@@ -4,24 +4,19 @@ using Robust.Shared.Console;
namespace Content.Client.Commands;
public sealed class ShowMechanismsCommand : LocalizedCommands
public sealed class ShowMechanismsCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
public const string CommandName = "showmechanisms";
public override string Command => CommandName;
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
public override string Command => "showmechanisms";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var spriteSys = _entManager.System<SpriteSystem>();
var query = _entManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
var query = EntityManager.AllEntityQueryEnumerator<OrganComponent, SpriteComponent>();
while (query.MoveNext(out var uid, out _, out var sprite))
{
spriteSys.SetContainerOccluded((uid, sprite), false);
_spriteSystem.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 = EntityManager.SpawnEntity("constructionghost", loc);
var comp = EntityManager.GetComponent<ConstructionGhostComponent>(ghost.Value);
ghost = Spawn("constructionghost", loc);
var comp = Comp<ConstructionGhostComponent>(ghost.Value);
comp.Prototype = prototype;
comp.GhostId = ghost.GetHashCode();
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
Comp<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
_ghosts.Add(comp.GhostId, ghost.Value);
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
var sprite = Comp<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 = EntityManager.EnsureComponent<SpriteComponent>(dummy);
var targetSprite = EnsureComp<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);
}
EntityManager.DeleteEntity(dummy);
Del(dummy);
}
else
return false;
@@ -367,7 +367,7 @@ namespace Content.Client.Construction
{
foreach (var ghost in _ghosts)
{
if (EntityManager.GetComponent<TransformComponent>(ghost.Value).Coordinates.Equals(loc))
if (Comp<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 = EntityManager.GetComponent<TransformComponent>(ghostId);
var transform = Comp<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;
EntityManager.QueueDeleteEntity(ghost);
QueueDel(ghost);
_ghosts.Remove(ghostId);
}
@@ -416,7 +416,7 @@ namespace Content.Client.Construction
{
foreach (var ghost in _ghosts.Values)
{
EntityManager.QueueDeleteEntity(ghost);
QueueDel(ghost);
}
_ghosts.Clear();

View File

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

View File

@@ -1,17 +1,15 @@
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Content.Client.Decals;
public sealed class ToggleDecalCommand : IConsoleCommand
public sealed class ToggleDecalCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _e = default!;
[Dependency] private readonly DecalSystem _decal = default!;
public string Command => "toggledecals";
public string Description => "Toggles decaloverlay";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override string Command => "toggledecals";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_e.System<DecalSystem>().ToggleOverlay();
_decal.ToggleOverlay();
}
}

View File

@@ -1,5 +1,5 @@
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@@ -15,6 +15,7 @@ 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;
@@ -29,6 +30,9 @@ public sealed class DrowsinessOverlay : Overlay
public DrowsinessOverlay()
{
IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
_drowsinessShader = _prototypeManager.Index<ShaderPrototype>("Drowsiness").InstanceUnique();
}
@@ -39,17 +43,11 @@ public sealed class DrowsinessOverlay : Overlay
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<DrowsinessComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
if (!_statusEffects.TryGetEffectsEndTimeWithComp<DrowsinessStatusEffectComponent>(playerEntity, out var endTime))
return;
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;
endTime ??= TimeSpan.MaxValue;
var timeLeft = (float)(endTime - _timing.CurTime).Value.TotalSeconds;
CurrentPower += 8f * (0.5f * timeLeft - CurrentPower) * args.DeltaSeconds / (timeLeft + 1);
}

View File

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

View File

@@ -1,4 +1,5 @@
using Content.Shared.Drugs;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -17,49 +18,47 @@ public sealed class DrugOverlaySystem : EntitySystem
private RainbowOverlay _overlay = default!;
public static string RainbowKey = "SeeingRainbows";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SeeingRainbowsComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SeeingRainbowsComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectAppliedEvent>(OnApplied);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectRemovedEvent>(OnRemoved);
SubscribeLocalEvent<SeeingRainbowsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<SeeingRainbowsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerAttachedEvent>>(OnPlayerAttached);
SubscribeLocalEvent<SeeingRainbowsStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerDetachedEvent>>(OnPlayerDetached);
_overlay = new();
}
private void OnPlayerAttached(EntityUid uid, SeeingRainbowsComponent component, LocalPlayerAttachedEvent args)
private void OnRemoved(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
{
_overlayMan.AddOverlay(_overlay);
}
if (_player.LocalEntity != args.Target)
return;
private void OnPlayerDetached(EntityUid uid, SeeingRainbowsComponent component, LocalPlayerDetachedEvent args)
{
_overlay.Intoxication = 0;
_overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnInit(EntityUid uid, SeeingRainbowsComponent component, ComponentInit args)
private void OnApplied(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
{
if (_player.LocalEntity == uid)
{
_overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect
_overlayMan.AddOverlay(_overlay);
}
if (_player.LocalEntity != args.Target)
return;
_overlay.Phase = _random.NextFloat(MathF.Tau); // random starting phase for movement effect
_overlayMan.AddOverlay(_overlay);
}
private void OnShutdown(EntityUid uid, SeeingRainbowsComponent component, ComponentShutdown args)
private void OnPlayerAttached(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectRelayedEvent<LocalPlayerAttachedEvent> args)
{
if (_player.LocalEntity == uid)
{
_overlay.Intoxication = 0;
_overlay.TimeTicker = 0;
_overlayMan.RemoveOverlay(_overlay);
}
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(Entity<SeeingRainbowsStatusEffectComponent> ent, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
{
_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.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Configuration;
@@ -17,6 +17,8 @@ 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;
@@ -37,6 +39,8 @@ 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);
}
@@ -54,18 +58,13 @@ public sealed class RainbowOverlay : Overlay
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<SeeingRainbowsComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
if (!_statusEffects.TryGetEffectsEndTimeWithComp<SeeingRainbowsStatusEffectComponent>(playerEntity, out var endTime))
return;
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;
endTime ??= TimeSpan.MaxValue;
var timeLeft = (float)(endTime - _timing.CurTime).Value.TotalSeconds;
TimeTicker += args.DeltaSeconds;
if (timeLeft - TimeTicker > timeLeft / 16f)
{
Intoxication += (timeLeft - Intoxication) * args.DeltaSeconds / 16f;

View File

@@ -14,6 +14,7 @@ using Content.Client.Lobby;
using Content.Client.MainMenu;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Playtime;
using Content.Client.Radiation.Overlays;
using Content.Client.Replay;
using Content.Client.Screenshot;
@@ -74,6 +75,7 @@ namespace Content.Client.Entry
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ClientsidePlaytimeTrackingManager _clientsidePlaytimeManager = default!;
public override void Init()
{
@@ -136,6 +138,7 @@ namespace Content.Client.Entry
_extendedDisconnectInformation.Initialize();
_jobRequirements.Initialize();
_playbackMan.Initialize();
_clientsidePlaytimeManager.Initialize();
//AUTOSCALING default Setup!
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);

View File

@@ -110,7 +110,7 @@ namespace Content.Client.Examine
{
var entity = args.EntityUid;
if (!args.EntityUid.IsValid() || !EntityManager.EntityExists(entity))
if (!args.EntityUid.IsValid() || !Exists(entity))
{
return false;
}
@@ -225,7 +225,7 @@ namespace Content.Client.Examine
vBox.AddChild(hBox);
if (EntityManager.HasComponent<SpriteComponent>(target))
if (HasComp<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)
{
EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
EnsureComp<AnimationPlayerComponent>(uid);
}
private void OnProxAppChange(EntityUid uid, TriggerOnProximityComponent component, ref AppearanceChangeEvent args)

View File

@@ -1,6 +1,5 @@
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

@@ -2,25 +2,17 @@
namespace Content.Client.Ghost.Commands;
public sealed class ToggleGhostVisibilityCommand : IConsoleCommand
public sealed class ToggleGhostVisibilityCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntitySystemManager _entSysMan = default!;
[Dependency] private readonly GhostSystem _ghost = default!;
public string Command => "toggleghostvisibility";
public string Description => "Toggles ghost visibility on the client.";
public string Help => "toggleghostvisibility [bool]";
public override string Command => "toggleghostvisibility";
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var ghostSystem = _entSysMan.GetEntitySystem<GhostSystem>();
if (args.Length != 0 && bool.TryParse(args[0], out var visibility))
{
ghostSystem.ToggleGhostVisibility(visibility);
}
_ghost.ToggleGhostVisibility(visibility);
else
{
ghostSystem.ToggleGhostVisibility();
}
_ghost.ToggleGhostVisibility();
}
}

View File

@@ -4,28 +4,27 @@ using Robust.Shared.Console;
namespace Content.Client.Ghost;
public sealed class GhostToggleSelfVisibility : IConsoleCommand
public sealed class GhostToggleSelfVisibility : LocalizedEntityCommands
{
public string Command => "toggleselfghost";
public string Description => "Toggles seeing your own ghost.";
public string Help => "toggleselfghost";
public void Execute(IConsoleShell shell, string argStr, string[] args)
[Dependency] private readonly SpriteSystem _sprite = default!;
public override string Command => "toggleselfghost";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var attachedEntity = shell.Player?.AttachedEntity;
if (!attachedEntity.HasValue)
return;
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.HasComponent<GhostComponent>(attachedEntity))
if (!EntityManager.HasComponent<GhostComponent>(attachedEntity))
{
shell.WriteError("Entity must be a ghost.");
shell.WriteError(Loc.GetString($"cmd-toggleselfghost-must-be-ghost"));
return;
}
if (!entityManager.TryGetComponent(attachedEntity, out SpriteComponent? spriteComponent))
if (!EntityManager.TryGetComponent(attachedEntity, out SpriteComponent? spriteComponent))
return;
var spriteSys = entityManager.System<SpriteSystem>();
spriteSys.SetVisible((attachedEntity.Value, spriteComponent), !spriteComponent.Visible);
_sprite.SetVisible((attachedEntity.Value, spriteComponent), !spriteComponent.Visible);
}
}

View File

@@ -16,7 +16,6 @@ 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
@@ -27,16 +26,13 @@ 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<HandsComponent>? OnPlayerHandsAdded;
public event Action<Entity<HandsComponent>>? OnPlayerHandsAdded;
public event Action? OnPlayerHandsRemoved;
public event Action<string, EntityUid>? OnPlayerItemAdded;
public event Action<string, EntityUid>? OnPlayerItemRemoved;
@@ -58,67 +54,28 @@ namespace Content.Client.Hands.Systems
}
#region StateHandling
private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args)
private void HandleComponentState(Entity<HandsComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not HandsComponentState state)
return;
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)
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)
{
foreach (var hand in component.Hands.Values)
{
if (state.Hands.Contains(hand))
continue;
handsModified = true;
break;
}
RemoveHand(ent.AsNullable(), handId);
}
var manager = EnsureComp<ContainerManagerComponent>(uid);
if (handsModified)
foreach (var handId in state.SortedHands.Intersect(newHands))
{
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);
}
AddHand(ent.AsNullable(), handId, state.Hands[handId]);
}
ent.Comp.SortedHands = new (state.SortedHands);
_stripSys.UpdateUi(uid);
SetActiveHand(ent.AsNullable(), state.ActiveHandId);
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);
}
_stripSys.UpdateUi(ent);
}
#endregion
@@ -129,72 +86,77 @@ namespace Content.Client.Hands.Systems
return;
}
OnPlayerHandsAdded?.Invoke(hands);
OnPlayerHandsAdded?.Invoke(hands.Value);
}
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
public override void DoDrop(Entity<HandsComponent?> ent,
string handId,
bool doDropInteraction = true,
bool log = true)
{
base.DoDrop(uid, hand, doDropInteraction, hands, log);
base.DoDrop(ent, handId, doDropInteraction, log);
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
if (TryGetHeldItem(ent, handId, out var held) && TryComp(held, out SpriteComponent? sprite))
sprite.RenderOrder = EntityManager.CurrentTick.Value;
}
public EntityUid? GetActiveHandEntity()
{
return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null;
return TryGetPlayerHands(out var hands) ? GetActiveItem(hands.Value.AsNullable()) : null;
}
/// <summary>
/// Get the hands component of the local player
/// </summary>
public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands)
public bool TryGetPlayerHands([NotNullWhen(true)] out Entity<HandsComponent>? hands)
{
var player = _playerManager.LocalEntity;
hands = null;
return player != null && TryComp(player.Value, out hands);
if (player == null || !TryComp<HandsComponent>(player.Value, out var handsComp))
return false;
hands = (player.Value, handsComp);
return true;
}
/// <summary>
/// Called when a user clicked on their hands GUI
/// </summary>
public void UIHandClick(HandsComponent hands, string handName)
public void UIHandClick(Entity<HandsComponent> ent, string handName)
{
if (!hands.Hands.TryGetValue(handName, out var pressedHand))
var hands = ent.Comp;
if (hands.ActiveHandId == null)
return;
if (hands.ActiveHand == null)
return;
var pressedEntity = GetHeldItem(ent.AsNullable(), handName);
var activeEntity = GetActiveItem(ent.AsNullable());
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = hands.ActiveHand.HeldEntity;
if (pressedHand == hands.ActiveHand && activeEntity != null)
if (handName == hands.ActiveHandId && activeEntity != null)
{
// use item in hand
// it will always be attack_self() in my heart.
EntityManager.RaisePredictiveEvent(new RequestUseInHandEvent());
RaisePredictiveEvent(new RequestUseInHandEvent());
return;
}
if (pressedHand != hands.ActiveHand && pressedEntity == null)
if (handName != hands.ActiveHandId && pressedEntity == null)
{
// change active hand
EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName));
RaisePredictiveEvent(new RequestSetHandEvent(handName));
return;
}
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null)
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity != null)
{
// use active item on held item
EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name));
RaisePredictiveEvent(new RequestHandInteractUsingEvent(handName));
return;
}
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null)
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity == null)
{
// move the item to the active hand
EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name));
RaisePredictiveEvent(new RequestMoveHandItemEvent(handName));
}
}
@@ -204,19 +166,18 @@ namespace Content.Client.Hands.Systems
/// </summary>
public void UIHandActivate(string handName)
{
EntityManager.RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
}
public void UIInventoryExamine(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
{
return;
}
_examine.DoExamine(entity);
_examine.DoExamine(heldEntity.Value);
}
/// <summary>
@@ -226,13 +187,12 @@ namespace Content.Client.Hands.Systems
public void UIHandOpenContextMenu(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
{
return;
}
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(heldEntity.Value);
}
public void UIHandAltActivateItem(string handName)
@@ -246,60 +206,67 @@ namespace Content.Client.Hands.Systems
{
base.HandleEntityInserted(uid, hands, args);
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
if (!hands.Hands.ContainsKey(args.Container.ID))
return;
UpdateHandVisuals(uid, args.Entity, hand);
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
_stripSys.UpdateUi(uid);
if (uid != _playerManager.LocalEntity)
return;
OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
OnPlayerItemAdded?.Invoke(args.Container.ID, args.Entity);
if (HasComp<VirtualItemComponent>(args.Entity))
OnPlayerHandBlocked?.Invoke(hand.Name);
OnPlayerHandBlocked?.Invoke(args.Container.ID);
}
protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
{
base.HandleEntityRemoved(uid, hands, args);
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
if (!hands.Hands.ContainsKey(args.Container.ID))
return;
UpdateHandVisuals(uid, args.Entity, hand);
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
_stripSys.UpdateUi(uid);
if (uid != _playerManager.LocalEntity)
return;
OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
OnPlayerItemRemoved?.Invoke(args.Container.ID, args.Entity);
if (HasComp<VirtualItemComponent>(args.Entity))
OnPlayerHandUnblocked?.Invoke(hand.Name);
OnPlayerHandUnblocked?.Invoke(args.Container.ID);
}
/// <summary>
/// Update the players sprite with new in-hand visuals.
/// </summary>
private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null)
private void UpdateHandVisuals(Entity<HandsComponent?, SpriteComponent?> ent, EntityUid held, string handId)
{
if (!Resolve(uid, ref handComp, ref sprite, false))
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))
return;
// visual update might involve changes to the entity's effective sprite -> need to update hands GUI.
if (uid == _playerManager.LocalEntity)
OnPlayerItemAdded?.Invoke(hand.Name, held);
if (ent == _playerManager.LocalEntity)
OnPlayerItemAdded?.Invoke(handId, 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.Location, out var revealedLayers))
if (handComp.RevealedLayers.TryGetValue(hand.Value.Location, out var revealedLayers))
{
foreach (var key in revealedLayers)
{
_sprite.RemoveLayer((uid, sprite), key);
_sprite.RemoveLayer((ent, sprite), key);
}
revealedLayers.Clear();
@@ -307,22 +274,22 @@ namespace Content.Client.Hands.Systems
else
{
revealedLayers = new();
handComp.RevealedLayers[hand.Location] = revealedLayers;
handComp.RevealedLayers[hand.Value.Location] = revealedLayers;
}
if (hand.HeldEntity == null)
if (HandIsEmpty((ent, handComp), handId))
{
// the held item was removed.
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
return;
}
var ev = new GetInhandVisualsEvent(uid, hand.Location);
var ev = new GetInhandVisualsEvent(ent, hand.Value.Location);
RaiseLocalEvent(held, ev);
if (ev.Layers.Count == 0)
{
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
return;
}
@@ -335,7 +302,7 @@ namespace Content.Client.Hands.Systems
continue;
}
var index = _sprite.LayerMapReserve((uid, sprite), key);
var index = _sprite.LayerMapReserve((ent, 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
@@ -343,35 +310,34 @@ namespace Content.Client.Hands.Systems
&& sprite[index].Rsi == null)
{
if (TryComp<ItemComponent>(held, out var itemComponent) && itemComponent.RsiPath != null)
_sprite.LayerSetRsi((uid, sprite), index, new ResPath(itemComponent.RsiPath));
_sprite.LayerSetRsi((ent, sprite), index, new ResPath(itemComponent.RsiPath));
else if (TryComp(held, out SpriteComponent? clothingSprite))
_sprite.LayerSetRsi((uid, sprite), index, clothingSprite.BaseRSI);
_sprite.LayerSetRsi((ent, sprite), index, clothingSprite.BaseRSI);
}
_sprite.LayerSetData((uid, sprite), index, layerData);
_sprite.LayerSetData((ent, sprite), index, layerData);
// Add displacement maps
var displacement = hand.Location switch
var displacement = hand.Value.Location switch
{
HandLocation.Left => handComp.LeftHandDisplacement,
HandLocation.Right => handComp.RightHandDisplacement,
_ => handComp.HandDisplacement
};
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (uid, sprite), index, key, out var displacementKey))
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (ent, sprite), index, key, out var displacementKey))
revealedLayers.Add(displacementKey);
}
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, 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.TryGetValue(args.ContainerId, out var hand))
{
UpdateHandVisuals(uid, GetEntity(args.Item), hand, component);
}
if (!component.Hands.ContainsKey(args.ContainerId))
return;
UpdateHandVisuals((uid, component), GetEntity(args.Item), args.ContainerId);
}
#endregion
@@ -379,7 +345,7 @@ namespace Content.Client.Hands.Systems
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, LocalPlayerAttachedEvent args)
{
OnPlayerHandsAdded?.Invoke(component);
OnPlayerHandsAdded?.Invoke((uid, component));
}
private void HandlePlayerDetached(EntityUid uid, HandsComponent component, LocalPlayerDetachedEvent args)
@@ -390,7 +356,7 @@ namespace Content.Client.Hands.Systems
private void OnHandsStartup(EntityUid uid, HandsComponent component, ComponentStartup args)
{
if (_playerManager.LocalEntity == uid)
OnPlayerHandsAdded?.Invoke(component);
OnPlayerHandsAdded?.Invoke((uid, component));
}
private void OnHandsShutdown(EntityUid uid, HandsComponent component, ComponentShutdown args)
@@ -400,36 +366,6 @@ 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)
@@ -438,13 +374,7 @@ namespace Content.Client.Hands.Systems
if (_playerManager.LocalEntity != hand.Owner)
return;
if (hand.Comp.ActiveHand == null)
{
OnPlayerSetActiveHand?.Invoke(null);
return;
}
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHand.Name);
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHandId);
}
}
}

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 != "DisplacedStencilDraw")
if (_sprite.TryGetLayer((hologram, hologramSprite), i, out var layer, false) && layer.ShaderPrototype != "DisplacedDraw")
hologramSprite.LayerSetShader(i, "unshaded");
}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
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;
@@ -34,6 +35,7 @@ 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;
@@ -65,6 +67,7 @@ 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>();
@@ -120,28 +123,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 hand in handsComp.Hands.Values)
foreach (var (id, hand) in handsComp.Hands)
{
if (hand.Location != HandLocation.Right)
continue;
AddHandButton(hand);
AddHandButton((Owner, handsComp), id, hand);
}
foreach (var hand in handsComp.Hands.Values)
foreach (var (id, hand) in handsComp.Hands)
{
if (hand.Location != HandLocation.Middle)
continue;
AddHandButton(hand);
AddHandButton((Owner, handsComp), id, hand);
}
foreach (var hand in handsComp.Hands.Values)
foreach (var (id, hand) in handsComp.Hands)
{
if (hand.Location != HandLocation.Left)
continue;
AddHandButton(hand);
AddHandButton((Owner, handsComp), id, hand);
}
}
@@ -177,20 +180,21 @@ namespace Content.Client.Inventory
_strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize);
}
private void AddHandButton(Hand hand)
private void AddHandButton(Entity<HandsComponent> ent, string handId, Hand hand)
{
var button = new HandButton(hand.Name, hand.Location);
var button = new HandButton(handId, hand.Location);
button.Pressed += SlotPressed;
if (EntMan.TryGetComponent<VirtualItemComponent>(hand.HeldEntity, out var virt))
var heldEntity = _hands.GetHeldItem(ent.AsNullable(), handId);
if (EntMan.TryGetComponent<VirtualItemComponent>(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, hand.HeldEntity);
UpdateEntityIcon(button, heldEntity);
_strippingMenu!.HandsContainer.AddChild(button);
LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation));
_handCount++;

View File

@@ -12,6 +12,7 @@ using Content.Client.Launcher;
using Content.Client.Mapping;
using Content.Client.Parallax.Managers;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Playtime;
using Content.Client.Replay;
using Content.Client.Screenshot;
using Content.Client.Stylesheets;
@@ -60,6 +61,7 @@ namespace Content.Client.IoC
collection.Register<PlayerRateLimitManager>();
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
collection.Register<TitleWindowManager>();
collection.Register<ClientsidePlaytimeTrackingManager>();
}
}
}

View File

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

View File

@@ -0,0 +1,144 @@
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 (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
if (TryComp(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($"{EntityManager.GetComponent<MetaDataComponent>(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
Log.Warning($"{Comp<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 (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
if (!TryComp(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 (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
if (!TryComp(entity, out AnimationPlayerComponent? animation))
{
return;
}
@@ -143,7 +143,7 @@ public sealed class LightBehaviorSystem : EntitySystem
comp.Animations.Remove(container);
}
if (resetToOriginalSettings && EntityManager.TryGetComponent(entity, out PointLightComponent? light))
if (resetToOriginalSettings && TryComp(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 (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation))
if (!TryComp(entity, out AnimationPlayerComponent? animation))
{
return false;
}

View File

@@ -1,17 +1,51 @@
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,6 +62,8 @@ 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];
@@ -69,8 +71,6 @@ 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

@@ -3,6 +3,7 @@ using Content.Client.GameTicking.Managers;
using Content.Client.LateJoin;
using Content.Client.Lobby.UI;
using Content.Client.Message;
using Content.Client.Playtime;
using Content.Client.UserInterface.Systems.Chat;
using Content.Client.Voting;
using Content.Shared.CCVar;
@@ -26,6 +27,7 @@ namespace Content.Client.Lobby
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
[Dependency] private readonly ClientsidePlaytimeTrackingManager _playtimeTracking = default!;
private ClientGameTicker _gameTicker = default!;
private ContentAudioSystem _contentAudioSystem = default!;
@@ -195,6 +197,26 @@ namespace Content.Client.Lobby
{
Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
}
var minutesToday = _playtimeTracking.PlaytimeMinutesToday;
if (minutesToday > 60)
{
Lobby!.PlaytimeComment.Visible = true;
var hoursToday = Math.Round(minutesToday / 60f, 1);
var chosenString = minutesToday switch
{
< 180 => "lobby-state-playtime-comment-normal",
< 360 => "lobby-state-playtime-comment-concerning",
< 720 => "lobby-state-playtime-comment-grasstouchless",
_ => "lobby-state-playtime-comment-selfdestructive"
};
Lobby.PlaytimeComment.SetMarkup(Loc.GetString(chosenString, ("hours", hoursToday)));
}
else
Lobby!.PlaytimeComment.Visible = false;
}
private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)

View File

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

View File

@@ -41,6 +41,7 @@
StyleClasses="ButtonBig" MinWidth="137" />
</BoxContainer>
</controls:StripeBack>
<RichTextLabel Name="PlaytimeComment" Visible="False" Access="Public" HorizontalAlignment="Center" />
</BoxContainer>
</PanelContainer>
<!-- Voting Popups -->

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(tileRef.GetContentTileDefinition(), out button))
_allPrototypesDict.TryGetValue(_entityManager.System<TurfSystem>().GetContentTileDefinition(tileRef), out button))
{
OnSelected(button);
return true;

View File

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

View File

@@ -3,14 +3,14 @@ using Robust.Shared.Console;
namespace Content.Client.NPC;
public sealed class ShowHTNCommand : IConsoleCommand
public sealed class ShowHtnCommand : LocalizedEntityCommands
{
public string Command => "showhtn";
public string Description => "Shows the current status for HTN NPCs";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
[Dependency] private readonly HTNSystem _htnSystem = default!;
public override string Command => "showhtn";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var npcs = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<HTNSystem>();
npcs.EnableOverlay ^= true;
_htnSystem.EnableOverlay ^= true;
}
}

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using System.Linq;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceNetwork;
@@ -14,6 +15,8 @@ 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;
@@ -33,6 +36,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
public NetworkConfiguratorLinkMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var footerStyleBox = new StyleBoxFlat()
{
@@ -61,7 +65,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
ButtonContainerRight.RemoveAllChildren();
_sources.Clear();
_sources.AddRange(linkState.Sources);
_sources.AddRange(linkState.Sources.Select(s => _prototypeManager.Index(s)));
_links.SourceButtons.Clear();
var i = 0;
foreach (var source in _sources)
@@ -73,7 +77,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
}
_sinks.Clear();
_sinks.AddRange(linkState.Sinks);
_sinks.AddRange(linkState.Sinks.Select(s => _prototypeManager.Index(s)));
_links.SinkButtons.Clear();
i = 0;
foreach (var sink in _sinks)

View File

@@ -137,15 +137,14 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
}
}
public sealed class ClearAllNetworkLinkOverlays : IConsoleCommand
public sealed class ClearAllNetworkLinkOverlays : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _e = default!;
[Dependency] private readonly NetworkConfiguratorSystem _network = default!;
public string Command => "clearnetworklinkoverlays";
public string Description => "Clear all network link overlays.";
public string Help => Command;
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override string Command => "clearnetworklinkoverlays";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_e.System<NetworkConfiguratorSystem>().ClearAllOverlays();
_network.ClearAllOverlays();
}
}

View File

@@ -1,48 +1,39 @@
using Content.Client.Administration.Managers;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Client.NodeContainer
{
public sealed class NodeVisCommand : IConsoleCommand
public sealed class NodeVisCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _e = default!;
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly NodeGroupSystem _nodeSystem = default!;
public string Command => "nodevis";
public string Description => "Toggles node group visualization";
public string Help => "";
public override string Command => "nodevis";
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var adminMan = IoCManager.Resolve<IClientAdminManager>();
if (!adminMan.HasFlag(AdminFlags.Debug))
if (!_adminManager.HasFlag(AdminFlags.Debug))
{
shell.WriteError("You need +DEBUG for this command");
shell.WriteError(Loc.GetString($"shell-missing-required-permission", ("perm", "+DEBUG")));
return;
}
var sys = _e.System<NodeGroupSystem>();
sys.SetVisEnabled(!sys.VisEnabled);
_nodeSystem.SetVisEnabled(!_nodeSystem.VisEnabled);
}
}
public sealed class NodeVisFilterCommand : IConsoleCommand
public sealed class NodeVisFilterCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _e = default!;
[Dependency] private readonly NodeGroupSystem _nodeSystem = default!;
public string Command => "nodevisfilter";
public string Description => "Toggles showing a specific group on nodevis";
public string Help => "Usage: nodevis [filter]\nOmit filter to list currently masked-off";
public override string Command => "nodevisfilter";
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var sys = _e.System<NodeGroupSystem>();
if (args.Length == 0)
{
foreach (var filtered in sys.Filtered)
foreach (var filtered in _nodeSystem.Filtered)
{
shell.WriteLine(filtered);
}
@@ -50,10 +41,8 @@ namespace Content.Client.NodeContainer
else
{
var filter = args[0];
if (!sys.Filtered.Add(filter))
{
sys.Filtered.Remove(filter);
}
if (!_nodeSystem.Filtered.Add(filter))
_nodeSystem.Filtered.Remove(filter);
}
}
}

View File

@@ -14,6 +14,7 @@
<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,6 +20,7 @@ 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 = EntityManager.EntityQueryEnumerator<OrbitVisualsComponent, SpriteComponent>();
var query = 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

@@ -0,0 +1,108 @@
using Content.Shared.CCVar;
using Robust.Client.Player;
using Robust.Shared.Network;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
namespace Content.Client.Playtime;
/// <summary>
/// Keeps track of how long the player has played today.
/// </summary>
/// <remarks>
/// <para>
/// Playtime is treated as any time in which the player is attached to an entity.
/// This notably excludes scenarios like the lobby.
/// </para>
/// </remarks>
public sealed class ClientsidePlaytimeTrackingManager
{
[Dependency] private readonly IClientNetManager _clientNetManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private ISawmill _sawmill = default!;
private const string InternalDateFormat = "yyyy-MM-dd";
[ViewVariables]
private TimeSpan? _mobAttachmentTime;
/// <summary>
/// The total amount of time played today, in minutes.
/// </summary>
[ViewVariables]
public float PlaytimeMinutesToday
{
get
{
var cvarValue = _configurationManager.GetCVar(CCVars.PlaytimeMinutesToday);
if (_mobAttachmentTime == null)
return cvarValue;
return cvarValue + (float)(_gameTiming.RealTime - _mobAttachmentTime.Value).TotalMinutes;
}
}
public void Initialize()
{
_sawmill = _logManager.GetSawmill("clientplaytime");
_clientNetManager.Connected += OnConnected;
// The downside to relying on playerattached and playerdetached is that unsaved playtime won't be saved in the event of a crash
// But then again, the config doesn't get saved in the event of a crash, either, so /shrug
// Playerdetached gets called on quit, though, so at least that's covered.
_playerManager.LocalPlayerAttached += OnPlayerAttached;
_playerManager.LocalPlayerDetached += OnPlayerDetached;
}
private void OnConnected(object? sender, NetChannelArgs args)
{
var datatimey = DateTime.Now;
_sawmill.Info($"Current day: {datatimey.Day} Current Date: {datatimey.Date.ToString(InternalDateFormat)}");
var recordedDateString = _configurationManager.GetCVar(CCVars.PlaytimeLastConnectDate);
var formattedDate = datatimey.Date.ToString(InternalDateFormat);
if (formattedDate == recordedDateString)
return;
_configurationManager.SetCVar(CCVars.PlaytimeMinutesToday, 0);
_configurationManager.SetCVar(CCVars.PlaytimeLastConnectDate, formattedDate);
}
private void OnPlayerAttached(EntityUid entity)
{
_mobAttachmentTime = _gameTiming.RealTime;
}
private void OnPlayerDetached(EntityUid entity)
{
if (_mobAttachmentTime == null)
return;
var newTimeValue = PlaytimeMinutesToday;
_mobAttachmentTime = null;
var timeDiffMinutes = newTimeValue - _configurationManager.GetCVar(CCVars.PlaytimeMinutesToday);
if (timeDiffMinutes < 0)
{
_sawmill.Error("Time differential on player detachment somehow less than zero!");
return;
}
// At less than 1 minute of time diff, there's not much point, and saving regardless will brick tests
// The reason this isn't checking for 0 is because TotalMinutes is fractional, rather than solely whole minutes
if (timeDiffMinutes < 1)
return;
_configurationManager.SetCVar(CCVars.PlaytimeMinutesToday, newTimeValue);
_sawmill.Info($"Recorded {timeDiffMinutes} minutes of living playtime!");
_configurationManager.SaveToFile(); // We don't like that we have to save the entire config just to store playtime stats '^'
}
}

View File

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

View File

@@ -1,5 +1,6 @@
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;
@@ -17,6 +18,7 @@ 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!;
@@ -34,6 +36,7 @@ public sealed class AlignRCDConstruction : PlacementMode
{
IoCManager.InjectDependencies(this);
_mapSystem = _entityManager.System<SharedMapSystem>();
_handsSystem = _entityManager.System<HandsSystem>();
_rcdSystem = _entityManager.System<RCDSystem>();
_transformSystem = _entityManager.System<SharedTransformSystem>();
@@ -88,11 +91,9 @@ public sealed class AlignRCDConstruction : PlacementMode
}
// Determine if player is carrying an RCD in their active hand
if (!_entityManager.TryGetComponent<HandsComponent>(player, out var hands))
if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity))
return false;
var heldEntity = hands.ActiveHand?.HeldEntity;
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
return false;

View File

@@ -1,8 +1,7 @@
using Content.Shared.Hands.Components;
using Content.Client.Hands.Systems;
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;
@@ -10,13 +9,18 @@ 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!;
private string _placementMode = typeof(AlignRCDConstruction).Name;
[Dependency] private readonly HandsSystem _hands = default!;
private Direction _placementDirection = default;
public override void Update(float frameTime)
@@ -33,12 +37,10 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
return;
// Determine if player is carrying an RCD in their active hand
var player = _playerManager.LocalSession?.AttachedEntity;
if (!TryComp<HandsComponent>(player, out var hands))
if (_playerManager.LocalSession?.AttachedEntity is not { } player)
return;
var heldEntity = hands.ActiveHand?.HeldEntity;
var heldEntity = _hands.GetActiveItem(player);
if (!TryComp<RCDComponent>(heldEntity, out var rcd))
{
@@ -65,7 +67,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,4 +1,6 @@
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;
@@ -15,7 +17,7 @@ public sealed class RevenantSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<RevenantComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
SubscribeLocalEvent<RevenantComponent, GetGenericAlertCounterAmountEvent>(OnGetCounterAmount);
}
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
@@ -40,14 +42,14 @@ public sealed class RevenantSystem : EntitySystem
}
}
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
private void OnGetCounterAmount(Entity<RevenantComponent> ent, ref GetGenericAlertCounterAmountEvent args)
{
if (args.Alert.ID != ent.Comp.EssenceAlert)
if (args.Handled)
return;
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}");
if (ent.Comp.EssenceAlert != args.Alert)
return;
args.Amount = ent.Comp.Essence.Int();
}
}

View File

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

View File

@@ -25,6 +25,29 @@ 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);
@@ -145,7 +168,7 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
}
});
Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f) { MinHeight = 150 });
Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f, this) { MinHeight = 150 });
Asdf.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
}
}
@@ -197,31 +220,37 @@ 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)
public GraphView(Queue<SensorSample> samples,
TimeSpan startTime,
TimeSpan curTime,
float maxY,
SensorMonitoringWindow parentWindow)
{
_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;
// TODO: omg this is terrible don't fucking hardcode this size to something uncached huge omfg.
var vertices = new Vector2[25000];
var window = (float)(_curTime - _startTime).TotalSeconds;
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;
@@ -242,8 +271,9 @@ 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

@@ -0,0 +1,50 @@
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,11 +68,15 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem
foreach (var hand in _hands.EnumerateHands(player.Value))
{
if (!scannerQuery.TryGetComponent(hand.HeldEntity, out var heldScanner) || !heldScanner.Enabled)
if (!_hands.TryGetHeldItem(player.Value, hand, out var heldEntity))
continue;
if (!scannerQuery.TryGetComponent(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 (!EntityManager.TryGetComponent<EyeComponent>(camera, out var eyeComponent))
if (!TryComp<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 && EntityManager.HasComponent<TabletopDraggableComponent>(_draggedEntity.Value))
if (broadcast && _draggedEntity != null && HasComp<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,7 +98,8 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
if (!EntityManager.TryGetComponent<SpriteComponent>(spriteViewEnt, out var sprite))
return;
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), alert);
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), player, alert);
EntityManager.EventBus.RaiseLocalEvent(player, ref ev);
EntityManager.EventBus.RaiseLocalEvent(spriteViewEnt, ref ev);
}
}

View File

@@ -57,10 +57,15 @@ 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)
Scale = new Vector2(2, 2),
MaxSize = new Vector2(64, 64),
Stretch = SpriteView.StretchMode.None,
HorizontalAlignment = HAlignment.Left
};
SetupIcon();

View File

@@ -207,7 +207,9 @@ 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"))));
_spbPressure.Value = outputPressure;
if (!_spbPressure.HasKeyboardFocus())
// Don't update release pressure if we're currently editing it
_spbPressure.Value = outputPressure;
}
protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -19,8 +19,11 @@ namespace Content.Client.UserInterface.Systems.Chat.Widgets;
[Virtual]
public partial class ChatBox : UIWidget
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly ILogManager _log = default!;
private readonly ISawmill _sawmill;
private readonly ChatUIController _controller;
private readonly IEntityManager _entManager;
public bool Main { get; set; }
@@ -29,7 +32,7 @@ public partial class ChatBox : UIWidget
public ChatBox()
{
RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>();
_sawmill = _log.GetSawmill("chat");
ChatInput.Input.OnTextEntered += OnTextEntered;
ChatInput.Input.OnKeyBindDown += OnInputKeyBindDown;
@@ -52,7 +55,7 @@ public partial class ChatBox : UIWidget
private void OnMessageAdded(ChatMessage msg)
{
Logger.DebugS("chat", $"{msg.Channel}: {msg.Message}");
_sawmill.Debug($"{msg.Channel}: {msg.Message}");
if (!ChatInput.FilterButton.Popup.IsActive(msg.Channel))
{
return;

View File

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

View File

@@ -329,9 +329,8 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
var player = _playerUid;
if (!control.MouseIsHovering ||
_playerInventory == null ||
!_entities.TryGetComponent<HandsComponent>(player, out var hands) ||
hands.ActiveHandEntity is not { } held ||
player == null ||
!_handsSystem.TryGetActiveItem(player.Value, out var held) ||
!_entities.TryGetComponent(held, out SpriteComponent? sprite) ||
!_inventorySystem.TryGetSlotContainer(player.Value, control.SlotName, out var container, out var slotDef))
{
@@ -342,12 +341,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, control.SlotName, out _, slotDef) &&
_container.CanInsert(held, container);
var fits = _inventorySystem.CanEquip(player.Value, held.Value, control.SlotName, out _, slotDef) &&
_container.CanInsert(held.Value, container);
if (!fits && _entities.TryGetComponent<StorageComponent>(container.ContainedEntity, out var storage))
{
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held, out _, storage);
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held.Value, out _, storage);
}
else if (!fits && _entities.TryGetComponent<ItemSlotsComponent>(container.ContainedEntity, out var itemSlots))
{
@@ -357,14 +356,14 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
if (!slot.InsertOnInteract)
continue;
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held, null, slot))
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held.Value, null, slot))
continue;
fits = true;
break;
}
}
_sprite.CopySprite((held, sprite), (hoverEntity, hoverSprite));
_sprite.CopySprite((held.Value, 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,6 +225,10 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
return;
// Do not rotate items unless we are either dragging them or hovering over a storage window.
if (DraggingGhost is null && UIManager.CurrentlyHovered is not StorageWindow)
return;
//clamp it to a cardinal.
DraggingRotation = (DraggingRotation + Math.PI / 2f).GetCardinalDir().ToAngle();
if (DraggingGhost != null)

View File

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

View File

@@ -274,13 +274,11 @@ namespace Content.Client.Voting.UI
}
[UsedImplicitly, AnyCommand]
public sealed class VoteMenuCommand : IConsoleCommand
public sealed class VoteMenuCommand : LocalizedCommands
{
public string Command => "votemenu";
public string Description => Loc.GetString("ui-vote-menu-command-description");
public string Help => Loc.GetString("ui-vote-menu-command-help-text");
public override string Command => "votemenu";
public void Execute(IConsoleShell shell, string argStr, string[] args)
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
new VoteCallMenu().OpenCentered();
}

View File

@@ -1,18 +1,18 @@
using Content.Client.Weapons.Ranged.Systems;
using Robust.Shared.Console;
namespace Content.Client.Weapons.Ranged;
namespace Content.Client.Weapons.Ranged.Commands;
public sealed class ShowSpreadCommand : IConsoleCommand
public sealed class ShowSpreadCommand : LocalizedEntityCommands
{
public string Command => "showgunspread";
public string Description => $"Shows gun spread overlay for debugging";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GunSystem>();
system.SpreadOverlay ^= true;
[Dependency] private readonly GunSystem _gunSystem = default!;
shell.WriteLine($"Set spread overlay to {system.SpreadOverlay}");
public override string Command => "showgunspread";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_gunSystem.SpreadOverlay ^= true;
shell.WriteLine(Loc.GetString($"cmd-showgunspread-status", ("status", _gunSystem.SpreadOverlay)));
}
}

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)
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });
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)
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) });
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}");
EntityManager.RaisePredictiveEvent(new RequestShootEvent
RaisePredictiveEvent(new RequestShootEvent
{
Target = target,
Coordinates = GetNetCoordinates(coordinates),

View File

@@ -0,0 +1,12 @@
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

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,12 @@
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,6 +13,7 @@ using Robust.Server.Player;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.IntegrationTests.Pair;
@@ -84,6 +85,7 @@ 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()
@@ -104,7 +106,7 @@ public sealed partial class TestPair : IAsyncDisposable
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Return of pair {Id} started");
State = PairState.CleanDisposed;
await OnCleanDispose();
State = PairState.Ready;
DebugTools.Assert(State is PairState.Dead or PairState.Ready);
PoolManager.NoCheckReturn(this);
ClearContext();
}

View File

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

View File

@@ -0,0 +1,65 @@
#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.Values)
foreach (var hand in hands.Hands.Keys)
{
Assert.That(hand.HeldEntity, Is.Not.Null);
Assert.That(handsSys.GetHeldItem((human, hands), hand), 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.Values)
foreach (var hand in hands.Hands.Keys)
{
Assert.That(hand.HeldEntity, Is.Null);
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Null);
}
buckleSystem.Unbuckle(human, human);

View File

@@ -1,7 +1,6 @@
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;
@@ -19,7 +18,7 @@ public sealed class DispenserTest : InteractionTest
// Insert beaker
await InteractUsing("Beaker");
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
// Open BUI
await Interact();
@@ -29,18 +28,18 @@ public sealed class DispenserTest : InteractionTest
await SendBui(ReagentDispenserUiKey.Key, ev);
// Beaker is back in the player's hands
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
// Re-insert the beaker
await Interact();
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), 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(Hands.ActiveHandEntity, Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
}
}

View File

@@ -267,7 +267,7 @@ public sealed class SuicideCommandTests
await server.WaitPost(() =>
{
var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
Assert.That(executionComponent, Is.Not.EqualTo(null));
});
@@ -342,7 +342,7 @@ public sealed class SuicideCommandTests
await server.WaitPost(() =>
{
var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
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(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
ClientAssertPrototype(Girder, Target);
await InteractUsing(Steel, 2);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
AssertPrototype(WallSolid);
}

View File

@@ -28,7 +28,7 @@ namespace Content.IntegrationTests.Tests.Disposal
SubscribeLocalEvent<DoInsertDisposalUnitEvent>(ev =>
{
var (_, toInsert, unit) = ev;
var insertTransform = EntityManager.GetComponent<TransformComponent>(toInsert);
var insertTransform = Comp<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.ActiveHand!);
sys.TryPickup(player, item, hands.ActiveHandId!);
});
// run ticks here is important, as errors may happen within the container system's frame update methods.
await pair.RunTicksSync(5);
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
await server.WaitPost(() =>
{
sys.TryDrop(player, item, null!);
sys.TryDrop(player, item);
});
await pair.RunTicksSync(5);
Assert.That(hands.ActiveHandEntity, Is.Null);
Assert.That(sys.GetActiveItem((player, hands)), 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.ActiveHand!);
sys.TryPickup(player, item, hands.ActiveHandId!);
});
await pair.RunTicksSync(5);
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
Assert.That(sys.GetActiveItem((player, hands)), 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, null!);
sys.TryDrop(player, item);
});
await pair.RunTicksSync(5);
var xform = entMan.GetComponent<TransformComponent>(player);
var itemXform = entMan.GetComponent<TransformComponent>(item);
Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item));
Assert.That(sys.GetActiveItem((player, hands)), 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 (Hands.ActiveHandEntity is { } held)
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is { } held)
{
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), null, false, true, Hands));
Assert.That(HandSys.TryDrop((SEntMan.GetEntity(Player), Hands), null, false, true));
SEntMan.DeleteEntity(held);
SLogger.Debug($"Deleting held entity");
});
}
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), 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.ActiveHand == null)
if (Hands.ActiveHandId == 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.ActiveHand, false, false, Hands));
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHandId, false, 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(Hands.ActiveHandEntity, Is.EqualTo(item));
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(item));
if (enableToggleable && itemToggle != null)
Assert.That(itemToggle.Activated);
@@ -193,7 +193,7 @@ public abstract partial class InteractionTest
{
entity ??= Target;
if (Hands.ActiveHand == null)
if (Hands.ActiveHandId == null)
{
Assert.Fail("No active hand");
return;
@@ -212,11 +212,11 @@ public abstract partial class InteractionTest
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, Hands, item));
Assert.That(HandSys.TryPickup(ToServer(Player), uid.Value, Hands.ActiveHandId, false, false, false, Hands, item));
});
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(uid));
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(uid));
}
/// <summary>
@@ -224,7 +224,7 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task Drop()
{
if (Hands.ActiveHandEntity == null)
if (HandSys.GetActiveItem((ToServer(Player), Hands)) == 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(SEntMan.GetEntity(Player), handsComp: Hands));
Assert.That(HandSys.TryDrop((ToServer(Player), Hands)));
});
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
}
#region Interact
@@ -246,7 +246,7 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task UseInHand()
{
if (Hands.ActiveHandEntity is not { } target)
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is not { } target)
{
Assert.Fail("Not holding any entity");
return;

View File

@@ -1,15 +1,12 @@
#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;
@@ -135,10 +132,13 @@ 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,20 +230,6 @@ 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 = EntityManager.GetComponent<PredictionTestComponent>(uid);
var component = Comp<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(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), 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(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), 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(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
await AssertTile(Plating);
AssertGridCount(1);
// Plating -> Tile
await InteractUsing(FloorItem);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
await AssertTile(Floor);
AssertGridCount(1);

View File

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

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