Merge branch 'master' of https://github.com/space-wizards/space-station-14 into moss-seniorcargo
Merged master into branch
This commit is contained in:
2
.github/workflows/build-docfx.yml
vendored
2
.github/workflows/build-docfx.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/build-map-renderer.yml
vendored
2
.github/workflows/build-map-renderer.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/build-test-debug.yml
vendored
2
.github/workflows/build-test-debug.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/labeler-needsreview.yml
vendored
2
.github/workflows/labeler-needsreview.yml
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [review_requested]
|
||||
types: [review_requested, opened]
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Content.Client.Actions
|
||||
else
|
||||
{
|
||||
var request = new RequestPerformActionEvent(GetNetEntity(action));
|
||||
EntityManager.RaisePredictiveEvent(request);
|
||||
RaisePredictiveEvent(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
90
Content.Client/Alerts/GenericCounterAlertSystem.cs
Normal file
90
Content.Client/Alerts/GenericCounterAlertSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
144
Content.Client/Light/AmbientOcclusionOverlay.cs
Normal file
144
Content.Client/Light/AmbientOcclusionOverlay.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
StyleClasses="ButtonBig" MinWidth="137" />
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
<RichTextLabel Name="PlaytimeComment" Visible="False" Access="Public" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<!-- Voting Popups -->
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
108
Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
Normal file
108
Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
Normal 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 '^'
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs
Normal file
50
Content.Client/StatusEffectNew/ClientStatusEffectsSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>>();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
12
Content.IntegrationTests/ExternalTestContext.cs
Normal file
12
Content.IntegrationTests/ExternalTestContext.cs
Normal 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;
|
||||
}
|
||||
13
Content.IntegrationTests/ITestContextLike.cs
Normal file
13
Content.IntegrationTests/ITestContextLike.cs
Normal 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; }
|
||||
}
|
||||
|
||||
12
Content.IntegrationTests/NUnitTestContextWrap.cs
Normal file
12
Content.IntegrationTests/NUnitTestContextWrap.cs
Normal 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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) });
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>());
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user