Merge remote-tracking branch 'upstream/stable' into ed-10-06-2025-upstream-sync

# Conflicts:
#	.github/CODEOWNERS
#	Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs
#	Content.Server/Chat/Managers/ChatManager.cs
#	Content.Server/Connection/ConnectionManager.cs
#	Content.Shared/Actions/SharedActionsSystem.cs
#	Content.Shared/Lock/LockSystem.cs
This commit is contained in:
Ed
2025-06-10 11:33:34 +03:00
1246 changed files with 48618 additions and 34899 deletions

40
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,40 @@
# Space Station 14 Code of Conduct
Space Station 14's staff and community is made up volunteers from all over the world, working on every aspect of the project - including development, teaching, and hosting integral tools.
Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to all levels of the project, from commenters to contributors to staff.
This isnt an exhaustive list of things that you cant do. Rather, take it in the spirit in which its intended - a guide to make it easier to enrich all of us and the technical communities in which we participate.
This code of conduct applies specifically to the Github repositories and its spaces managed by the Space Station 14 project or Space Wizards Federation. Some spaces, such as the Space Station 14 Discord or the official Wizard's Den game servers, have their own rules but are in spirit equal to what may be found in here.
If you believe someone is violating the code of conduct, we ask that you report it by contacting a Maintainer, Project Manager or Wizard staff member through [Discord](https://discord.ss14.io/), [the forums](https://forum.spacestation14.com/), or emailing [telecommunications@spacestation14.com](mailto:telecommunications@spacestation14.com).
- **Be friendly and patient.**
- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
- **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and contributors, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. We have contributors of all skill levels, some even making their first foray into a new field with this project, so keep that in mind when discussing someone's work.
- **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. Its important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Space Station 14 community should be respectful when dealing with other members as well as with people outside the Space Station 14 community. Assume contributions to the project, even those that do not end up being included, are made in good faith.
- **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
- Violent threats or language directed against another person.
- Discriminatory jokes and language.
- Posting sexually explicit or violent material.
- Posting (or threatening to post) other people's personally identifying information ("doxing").
- Personal insults, especially those using racist or sexist terms.
- Unwelcome sexual attention.
- Advocating for, or encouraging, any of the above behavior.
- Repeated harassment of others. In general, if someone asks you to stop, then stop.
- **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and Space Station 14 is no exception. It is important that we resolve disagreements and differing views constructively. Remember that were different. The strength of Space Station 14 comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesnt mean that theyre wrong. Dont forget that it is human to make mistakes and blaming each other doesnt get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html).
## On Comunity Moderation
Deviating from the Code of Conduct on the Github repository may result in moderative actions taken by project Maintainers. This can involve your content being edited or deleted, and may result in a temporary or permanent block from the repository.
This is to ensure Space Station 14 is a healthy community in which contributors feel encouraged and empowered to contribute, and to give you as a member of this community a chance to reflect on how you are interacting with it. While outright offensive and bigoted content will *always* be unacceptable on the repository, Maintainers are at liberty to take moderative actions against more ambiguous content that fail to provide constructive criticism, or that provides constructive criticism in a non-constructive manner. Examples of this include using hyperbole, bringing up PRs/changes unrelated to the discussion at hand, hostile tone, off-topic comments, creating PRs/Issues for the sole purpose of causing discussions, skirting the line of acceptable behavior, etc. Disagreeing with content or each other is fine and appreciated, but only as long as it's done with respect and in a constructive manner.
Maintainers are expected to adhere to the guidelines as listed in the [Github Moderation Guidelines](https://docs.spacestation14.com/en/general-development/github-moderation-guidelines.html), though may deviate should they feel it's in the best interest of the community. If you believe you had an action incorrectly applied against you, you are encouraged to contact staff via [Discord](https://discord.ss14.io/) or [the forums](https://forum.spacestation14.com/), [appeal your Github ban](https://forum.spacestation14.com/c/ban-appeals/appeals-github/38), or make a [staff complaint](https://forum.spacestation14.com/t/staff-complaint-instructions-and-info/31).
## Attribution
This Code of Conduct is an edited version of the [Django Code of Conduct](https://www.djangoproject.com/conduct/), licensed under CC BY 3.0, for the Space Station 14 Github repository.

View File

@@ -47,7 +47,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown();
}
public static readonly string[] MapsSource = { "Empty", "Satlern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
public static readonly string[] MapsSource = { "Empty", "Saltern", "Box", "Bagel", "Dev", "CentComm", "Core", "TestTeg", "Packed", "Omega", "Reach", "Meta", "Marathon", "MeteorArena", "Fland", "Oasis", "Convex"};
[ParamsSource(nameof(MapsSource))]
public string Map;

View File

@@ -0,0 +1,26 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Orientation="Horizontal"
Margin="10 10 10 10"
VerticalExpand="True"
HorizontalExpand="True"
MinHeight="70">
<!-- Access groups -->
<BoxContainer Name="AccessGroupList" Access="Public" Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="0.5" Margin="0 0 10 0">
<!-- Populated with C# code -->
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" VerticalExpand="True" Margin="0 0 0 0" SetWidth="2">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#FFFFFF" />
</PanelContainer.PanelOverride>
</PanelContainer>
<!-- Access levels -->
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="10 0 0 0">
<BoxContainer Name="AccessLevelChecklist" Access="Public" Orientation="Vertical" HorizontalAlignment="Left">
<!-- Populated with C# code -->
</BoxContainer>
</ScrollContainer>
</BoxContainer>

View File

@@ -0,0 +1,449 @@
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Access;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Linq;
using System.Numerics;
namespace Content.Client.Access.UI;
[GenerateTypedNameReferences]
public sealed partial class GroupedAccessLevelChecklist : BoxContainer
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private bool _isMonotone;
private string? _labelStyleClass;
// Access data
private HashSet<ProtoId<AccessGroupPrototype>> _accessGroups = new();
private HashSet<ProtoId<AccessLevelPrototype>> _accessLevels = new();
private HashSet<ProtoId<AccessLevelPrototype>> _activeAccessLevels = new();
// Button groups
private readonly ButtonGroup _accessGroupsButtons = new();
// Temp values
private int _accessGroupTabIndex = 0;
private bool _canInteract = false;
private List<AccessLevelPrototype> _accessLevelsForTab = new();
private readonly List<AccessLevelEntry> _accessLevelEntries = new();
private readonly Dictionary<AccessGroupPrototype, List<AccessLevelPrototype>> _groupedAccessLevels = new();
// Events
public event Action<HashSet<ProtoId<AccessLevelPrototype>>, bool>? OnAccessLevelsChangedEvent;
/// <summary>
/// Creates a UI control for changing access levels.
/// Access levels are organized under a list of tabs by their associated access group.
/// </summary>
public GroupedAccessLevelChecklist()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
private void ArrangeAccessControls()
{
// Create a list of known access groups with which to populate the UI
_groupedAccessLevels.Clear();
foreach (var accessGroup in _accessGroups)
{
if (!_protoManager.TryIndex(accessGroup, out var accessGroupProto))
continue;
_groupedAccessLevels.Add(accessGroupProto, new());
}
// Ensure that the 'general' access group is added to handle
// misc. access levels that aren't associated with any group
if (_protoManager.TryIndex<AccessGroupPrototype>("General", out var generalAccessProto))
_groupedAccessLevels.TryAdd(generalAccessProto, new());
// Assign known access levels with their associated groups
foreach (var accessLevel in _accessLevels)
{
if (!_protoManager.TryIndex(accessLevel, out var accessLevelProto))
continue;
var assigned = false;
foreach (var (accessGroup, accessLevels) in _groupedAccessLevels)
{
if (!accessGroup.Tags.Contains(accessLevelProto.ID))
continue;
assigned = true;
_groupedAccessLevels[accessGroup].Add(accessLevelProto);
}
if (!assigned && generalAccessProto != null)
_groupedAccessLevels[generalAccessProto].Add(accessLevelProto);
}
// Remove access groups that have no assigned access levels
foreach (var (group, accessLevels) in _groupedAccessLevels)
{
if (accessLevels.Count == 0)
_groupedAccessLevels.Remove(group);
}
}
private bool TryRebuildAccessGroupControls()
{
AccessGroupList.DisposeAllChildren();
AccessLevelChecklist.DisposeAllChildren();
// No access level prototypes were assigned to any of the access level groups.
// Either the turret controller has no assigned access levels or their names were invalid.
if (_groupedAccessLevels.Count == 0)
return false;
// Reorder the access groups alphabetically
var orderedAccessGroups = _groupedAccessLevels.Keys.OrderBy(x => x.GetAccessGroupName()).ToList();
// Add group access buttons to the UI
foreach (var accessGroup in orderedAccessGroups)
{
var accessGroupButton = CreateAccessGroupButton();
// Button styling
if (_groupedAccessLevels.Count > 1)
{
if (AccessGroupList.ChildCount == 0)
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenLeft);
else if (_groupedAccessLevels.Count > 1 && AccessGroupList.ChildCount == (_groupedAccessLevels.Count - 1))
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenRight);
else
accessGroupButton.AddStyleClass(StyleBase.ButtonOpenBoth);
}
accessGroupButton.Pressed = _accessGroupTabIndex == orderedAccessGroups.IndexOf(accessGroup);
// Label text and styling
if (_labelStyleClass != null)
accessGroupButton.Label.SetOnlyStyleClass(_labelStyleClass);
var accessLevelPrototypes = _groupedAccessLevels[accessGroup];
var prefix = accessLevelPrototypes.All(x => _activeAccessLevels.Contains(x))
? "»"
: accessLevelPrototypes.Any(x => _activeAccessLevels.Contains(x))
? ""
: " ";
var text = Loc.GetString(
"turret-controls-window-access-group-label",
("prefix", prefix),
("label", accessGroup.GetAccessGroupName())
);
accessGroupButton.Text = text;
// Button events
accessGroupButton.OnPressed += _ => OnAccessGroupChanged(accessGroupButton.GetPositionInParent());
AccessGroupList.AddChild(accessGroupButton);
}
// Adjust the current tab index so it remains in range
if (_accessGroupTabIndex >= _groupedAccessLevels.Count)
_accessGroupTabIndex = _groupedAccessLevels.Count - 1;
return true;
}
/// <summary>
/// Rebuilds the checkbox list for the access level controls.
/// </summary>
public void RebuildAccessLevelsControls()
{
AccessLevelChecklist.DisposeAllChildren();
_accessLevelEntries.Clear();
// No access level prototypes were assigned to any of the access level groups
// Either turret controller has no assigned access levels, or their names were invalid
if (_groupedAccessLevels.Count == 0)
return;
// Reorder the access groups alphabetically
var orderedAccessGroups = _groupedAccessLevels.Keys.OrderBy(x => x.GetAccessGroupName()).ToList();
// Get the access levels associated with the current tab
var selectedAccessGroupTabProto = orderedAccessGroups[_accessGroupTabIndex];
_accessLevelsForTab = _groupedAccessLevels[selectedAccessGroupTabProto];
_accessLevelsForTab = _accessLevelsForTab.OrderBy(x => x.GetAccessLevelName()).ToList();
// Add an 'all' checkbox as the first child of the list if it has more than one access level
// Toggling this checkbox on will mark all other boxes below it on/off
var allCheckBox = CreateAccessLevelCheckbox();
allCheckBox.Text = Loc.GetString("turret-controls-window-all-checkbox");
if (_labelStyleClass != null)
allCheckBox.Label.SetOnlyStyleClass(_labelStyleClass);
// Add the 'all' checkbox events
allCheckBox.OnPressed += args =>
{
SetCheckBoxPressedState(_accessLevelEntries, allCheckBox.Pressed);
var accessLevels = new HashSet<ProtoId<AccessLevelPrototype>>();
foreach (var accessLevel in _accessLevelsForTab)
{
accessLevels.Add(accessLevel);
}
OnAccessLevelsChangedEvent?.Invoke(accessLevels, allCheckBox.Pressed);
};
AccessLevelChecklist.AddChild(allCheckBox);
// Hide the 'all' checkbox if the tab has only one access level
var allCheckBoxVisible = _accessLevelsForTab.Count > 1;
allCheckBox.Visible = allCheckBoxVisible;
allCheckBox.Disabled = !_canInteract;
// Add any remaining missing access level buttons to the UI
foreach (var accessLevel in _accessLevelsForTab)
{
// Create the entry
var accessLevelEntry = new AccessLevelEntry(_isMonotone);
accessLevelEntry.AccessLevel = accessLevel;
accessLevelEntry.CheckBox.Text = accessLevel.GetAccessLevelName();
accessLevelEntry.CheckBox.Pressed = _activeAccessLevels.Contains(accessLevel);
accessLevelEntry.CheckBox.Disabled = !_canInteract;
if (_labelStyleClass != null)
accessLevelEntry.CheckBox.Label.SetOnlyStyleClass(_labelStyleClass);
// Set the checkbox linkage lines
var isEndOfList = _accessLevelsForTab.IndexOf(accessLevel) == (_accessLevelsForTab.Count - 1);
var lines = new List<(Vector2, Vector2)>
{
(new Vector2(0.5f, 0f), new Vector2(0.5f, isEndOfList ? 0.5f : 1f)),
(new Vector2(0.5f, 0.5f), new Vector2(1f, 0.5f)),
};
accessLevelEntry.UpdateCheckBoxLink(lines);
accessLevelEntry.CheckBoxLink.Visible = allCheckBoxVisible;
accessLevelEntry.CheckBoxLink.Modulate = !_canInteract ? Color.Gray : Color.White;
// Add checkbox events
accessLevelEntry.CheckBox.OnPressed += args =>
{
// If the checkbox and its siblings are checked, check the 'all' checkbox too
allCheckBox.Pressed = AreAllCheckBoxesPressed(_accessLevelEntries.Select(x => x.CheckBox));
OnAccessLevelsChangedEvent?.Invoke([accessLevelEntry.AccessLevel], accessLevelEntry.CheckBox.Pressed);
};
AccessLevelChecklist.AddChild(accessLevelEntry);
_accessLevelEntries.Add(accessLevelEntry);
}
// Press the 'all' checkbox if all others are pressed
allCheckBox.Pressed = AreAllCheckBoxesPressed(_accessLevelEntries.Select(x => x.CheckBox));
}
private bool AreAllCheckBoxesPressed(IEnumerable<CheckBox> checkBoxes)
{
foreach (var checkBox in checkBoxes)
{
if (!checkBox.Pressed)
return false;
}
return true;
}
private void SetCheckBoxPressedState(List<AccessLevelEntry> accessLevelEntries, bool pressed)
{
foreach (var accessLevelEntry in accessLevelEntries)
{
accessLevelEntry.CheckBox.Pressed = pressed;
}
}
/// <summary>
/// Provides the UI with a list of access groups using which list of tabs should be populated.
/// </summary>
public void SetAccessGroups(HashSet<ProtoId<AccessGroupPrototype>> accessGroups)
{
_accessGroups = accessGroups;
ArrangeAccessControls();
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
/// <summary>
/// Provides the UI with a list of access levels with which it can populate the currently selected tab.
/// </summary>
public void SetAccessLevels(HashSet<ProtoId<AccessLevelPrototype>> accessLevels)
{
_accessLevels = accessLevels;
ArrangeAccessControls();
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
/// <summary>
/// Sets which access level checkboxes should be marked on the UI.
/// </summary>
public void SetActiveAccessLevels(HashSet<ProtoId<AccessLevelPrototype>> activeAccessLevels)
{
_activeAccessLevels = activeAccessLevels;
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
/// <summary>
/// Sets whether the local player can interact with the checkboxes.
/// </summary>
public void SetLocalPlayerAccessibility(bool canInteract)
{
_canInteract = canInteract;
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
/// <summary>
/// Sets whether the UI should use monotone buttons and checkboxes.
/// </summary>
public void SetMonotone(bool monotone)
{
_isMonotone = monotone;
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
/// <summary>
/// Applies the specified style to the labels on the UI buttons and checkboxes.
/// </summary>
public void SetLabelStyleClass(string? styleClass)
{
_labelStyleClass = styleClass;
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
private void OnAccessGroupChanged(int newTabIndex)
{
if (newTabIndex == _accessGroupTabIndex)
return;
_accessGroupTabIndex = newTabIndex;
if (TryRebuildAccessGroupControls())
RebuildAccessLevelsControls();
}
private Button CreateAccessGroupButton()
{
var button = _isMonotone ? new MonotoneButton() : new Button();
button.ToggleMode = true;
button.Group = _accessGroupsButtons;
button.Label.HorizontalAlignment = HAlignment.Left;
return button;
}
private CheckBox CreateAccessLevelCheckbox()
{
var checkbox = _isMonotone ? new MonotoneCheckBox() : new CheckBox();
checkbox.Margin = new Thickness(0, 0, 0, 3);
checkbox.ToggleMode = true;
checkbox.ReservesSpace = false;
return checkbox;
}
private sealed class AccessLevelEntry : BoxContainer
{
public ProtoId<AccessLevelPrototype> AccessLevel;
public readonly CheckBox CheckBox;
public readonly LineRenderer CheckBoxLink;
public AccessLevelEntry(bool monotone)
{
HorizontalExpand = true;
CheckBoxLink = new LineRenderer
{
SetWidth = 22,
VerticalExpand = true,
Margin = new Thickness(0, -1),
ReservesSpace = false,
};
AddChild(CheckBoxLink);
CheckBox = monotone ? new MonotoneCheckBox() : new CheckBox();
CheckBox.ToggleMode = true;
CheckBox.Margin = new Thickness(0f, 0f, 0f, 3f);
AddChild(CheckBox);
}
public void UpdateCheckBoxLink(List<(Vector2, Vector2)> lines)
{
CheckBoxLink.Lines = lines;
}
}
private sealed class LineRenderer : Control
{
/// <summary>
/// List of lines to render (their start and end x-y coordinates).
/// Position (0,0) is the top left corner of the control and
/// position (1,1) is the bottom right corner.
/// </summary>
/// <remarks>
/// The color of the lines is inherited from the control.
/// </remarks>
public List<(Vector2, Vector2)> Lines;
public LineRenderer()
{
Lines = new List<(Vector2, Vector2)>();
}
public LineRenderer(List<(Vector2, Vector2)> lines)
{
Lines = lines;
}
protected override void Draw(DrawingHandleScreen handle)
{
foreach (var line in Lines)
{
var start = PixelPosition +
new Vector2(PixelWidth * line.Item1.X, PixelHeight * line.Item1.Y);
var end = PixelPosition +
new Vector2(PixelWidth * line.Item2.X, PixelHeight * line.Item2.Y);
handle.DrawLine(start, end, ActualModulateSelf);
}
}
}
}

View File

@@ -1,3 +1,6 @@
using Content.Shared.Actions.Components;
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
namespace Content.Client.Actions;
/// <summary>
@@ -7,3 +10,17 @@ public sealed class FillActionSlotEvent : EntityEventArgs
{
public EntityUid? Action;
}
/// <summary>
/// Client-side event used to attempt to trigger a targeted action.
/// This only gets raised if the has <see cref="TargetActionComponent">.
/// Handlers must set <c>Handled</c> to true, then if the action has been performed,
/// i.e. a target is found, then FoundTarget must be set to true.
/// </summary>
[ByRefEvent]
public record struct ActionTargetAttemptEvent(
PointerInputCmdArgs Input,
Entity<ActionsComponent> User,
ActionComponent Action,
bool Handled = false,
bool FoundTarget = false);

View File

@@ -1,18 +1,23 @@
using System.IO;
using System.Linq;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Mapping;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.GameStates;
using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -25,6 +30,7 @@ namespace Content.Client.Actions
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceManager _resources = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
@@ -38,131 +44,67 @@ namespace Content.Client.Actions
public event Action<List<SlotAssignment>>? AssignSlot;
private readonly List<EntityUid> _removed = new();
private readonly List<(EntityUid, BaseActionComponent?)> _added = new();
private readonly List<Entity<ActionComponent>> _added = new();
public static readonly EntProtoId MappingEntityAction = "BaseMappingEntityAction";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActionsComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ActionsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<ActionsComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<InstantActionComponent, ComponentHandleState>(OnInstantHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ComponentHandleState>(OnEntityTargetHandleState);
SubscribeLocalEvent<WorldTargetActionComponent, ComponentHandleState>(OnWorldTargetHandleState);
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
SubscribeLocalEvent<ActionComponent, AfterAutoHandleStateEvent>(OnActionAutoHandleState);
SubscribeLocalEvent<EntityTargetActionComponent, ActionTargetAttemptEvent>(OnEntityTargetAttempt);
SubscribeLocalEvent<WorldTargetActionComponent, ActionTargetAttemptEvent>(OnWorldTargetAttempt);
}
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
{
if (args.Current is not InstantActionComponentState state)
return;
BaseHandleState<InstantActionComponent>(uid, component, state);
private void OnActionAutoHandleState(Entity<ActionComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateAction(ent);
}
private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args)
public override void UpdateAction(Entity<ActionComponent> ent)
{
if (args.Current is not EntityTargetActionComponentState state)
return;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityTargetActionComponent>(uid, component, state);
}
private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args)
{
if (args.Current is not WorldTargetActionComponentState state)
return;
BaseHandleState<WorldTargetActionComponent>(uid, component, state);
}
private void OnEntityWorldTargetHandleState(EntityUid uid,
EntityWorldTargetActionComponent component,
ref ComponentHandleState args)
{
if (args.Current is not EntityWorldTargetActionComponentState state)
return;
component.Whitelist = state.Whitelist;
component.CanTargetSelf = state.CanTargetSelf;
BaseHandleState<EntityWorldTargetActionComponent>(uid, component, state);
}
private void BaseHandleState<T>(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent
{
// TODO ACTIONS use auto comp states
component.Icon = state.Icon;
component.IconOn = state.IconOn;
component.IconColor = state.IconColor;
component.OriginalIconColor = state.OriginalIconColor;
component.DisabledIconColor = state.DisabledIconColor;
component.Keywords.Clear();
component.Keywords.UnionWith(state.Keywords);
component.Enabled = state.Enabled;
component.Toggled = state.Toggled;
component.Cooldown = state.Cooldown;
component.UseDelay = state.UseDelay;
component.Container = EnsureEntity<T>(state.Container, uid);
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
component.CheckCanInteract = state.CheckCanInteract;
component.CheckConsciousness = state.CheckConsciousness;
component.ClientExclusive = state.ClientExclusive;
component.Priority = state.Priority;
component.AttachedEntity = EnsureEntity<T>(state.AttachedEntity, uid);
component.RaiseOnUser = state.RaiseOnUser;
component.RaiseOnAction = state.RaiseOnAction;
component.AutoPopulate = state.AutoPopulate;
component.Temporary = state.Temporary;
component.ItemIconStyle = state.ItemIconStyle;
component.Sound = state.Sound;
UpdateAction(uid, component);
}
public override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null)
{
if (!ResolveActionData(actionId, ref action))
return;
// TODO: Decouple this.
action.IconColor = _sharedCharges.GetCurrentCharges(actionId.Value) == 0 ? action.DisabledIconColor : action.OriginalIconColor;
base.UpdateAction(actionId, action);
if (_playerManager.LocalEntity != action.AttachedEntity)
ent.Comp.IconColor = _sharedCharges.GetCurrentCharges(ent.Owner) == 0 ? ent.Comp.DisabledIconColor : ent.Comp.OriginalIconColor;
base.UpdateAction(ent);
if (_playerManager.LocalEntity != ent.Comp.AttachedEntity)
return;
ActionsUpdated?.Invoke();
}
private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args)
private void OnHandleState(Entity<ActionsComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not ActionsComponentState state)
return;
var (uid, comp) = ent;
_added.Clear();
_removed.Clear();
var stateEnts = EnsureEntitySet<ActionsComponent>(state.Actions, uid);
foreach (var act in component.Actions)
foreach (var act in comp.Actions)
{
if (!stateEnts.Contains(act) && !IsClientSide(act))
_removed.Add(act);
}
component.Actions.ExceptWith(_removed);
comp.Actions.ExceptWith(_removed);
foreach (var actionId in stateEnts)
{
if (!actionId.IsValid())
continue;
if (!component.Actions.Add(actionId))
if (!comp.Actions.Add(actionId))
continue;
TryGetActionData(actionId, out var action);
_added.Add((actionId, action));
if (GetAction(actionId) is {} action)
_added.Add(action);
}
if (_playerManager.LocalEntity != uid)
@@ -177,47 +119,46 @@ namespace Content.Client.Actions
foreach (var action in _added)
{
OnActionAdded?.Invoke(action.Item1);
OnActionAdded?.Invoke(action);
}
ActionsUpdated?.Invoke();
}
public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b)
public static int ActionComparer(Entity<ActionComponent> a, Entity<ActionComponent> b)
{
var priorityA = a.Item2?.Priority ?? 0;
var priorityB = b.Item2?.Priority ?? 0;
var priorityA = a.Comp?.Priority ?? 0;
var priorityB = b.Comp?.Priority ?? 0;
if (priorityA != priorityB)
return priorityA - priorityB;
priorityA = a.Item2?.Container?.Id ?? 0;
priorityB = b.Item2?.Container?.Id ?? 0;
priorityA = a.Comp?.Container?.Id ?? 0;
priorityB = b.Comp?.Container?.Id ?? 0;
return priorityA - priorityB;
}
protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp,
BaseActionComponent action)
protected override void ActionAdded(Entity<ActionsComponent> performer, Entity<ActionComponent> action)
{
if (_playerManager.LocalEntity != performer)
if (_playerManager.LocalEntity != performer.Owner)
return;
OnActionAdded?.Invoke(actionId);
OnActionAdded?.Invoke(action);
ActionsUpdated?.Invoke();
}
protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
protected override void ActionRemoved(Entity<ActionsComponent> performer, Entity<ActionComponent> action)
{
if (_playerManager.LocalEntity != performer)
if (_playerManager.LocalEntity != performer.Owner)
return;
OnActionRemoved?.Invoke(actionId);
OnActionRemoved?.Invoke(action);
ActionsUpdated?.Invoke();
}
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
public IEnumerable<Entity<ActionComponent>> GetClientActions()
{
if (_playerManager.LocalEntity is not { } user)
return Enumerable.Empty<(EntityUid, BaseActionComponent)>();
return Enumerable.Empty<Entity<ActionComponent>>();
return GetActions(user);
}
@@ -254,24 +195,23 @@ namespace Content.Client.Actions
CommandBinds.Unregister<ActionsSystem>();
}
public void TriggerAction(EntityUid actionId, BaseActionComponent action)
public void TriggerAction(Entity<ActionComponent> action)
{
if (_playerManager.LocalEntity is not { } user ||
!TryComp(user, out ActionsComponent? actions))
{
return;
}
if (action is not InstantActionComponent instantAction)
if (_playerManager.LocalEntity is not { } user)
return;
if (action.ClientExclusive)
// TODO: unhardcode this somehow
if (!HasComp<InstantActionComponent>(action))
return;
if (action.Comp.ClientExclusive)
{
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
PerformAction(user, action);
}
else
{
var request = new RequestPerformActionEvent(GetNetEntity(actionId));
var request = new RequestPerformActionEvent(GetNetEntity(action));
EntityManager.RaisePredictiveEvent(request);
}
}
@@ -295,39 +235,137 @@ namespace Content.Client.Actions
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
return;
var actions = EnsureComp<ActionsComponent>(user);
ClearAssignments?.Invoke();
var assignments = new List<SlotAssignment>();
foreach (var entry in sequence.Sequence)
{
if (entry is not MappingDataNode map)
continue;
if (!map.TryGet("action", out var actionNode))
continue;
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
var actionId = Spawn();
AddComp(actionId, action);
AddActionDirect(user, actionId);
if (map.TryGet<ValueDataNode>("name", out var nameNode))
_metaData.SetEntityName(actionId, nameNode.Value);
if (!map.TryGet("assignments", out var assignmentNode))
continue;
var nodeAssignments = _serialization.Read<List<(byte Hotbar, byte Slot)>>(assignmentNode, notNullableOverride: true);
foreach (var index in nodeAssignments)
var actionId = EntityUid.Invalid;
if (map.TryGet<ValueDataNode>("action", out var actionNode))
{
var assignment = new SlotAssignment(index.Hotbar, index.Slot, actionId);
assignments.Add(assignment);
var id = new EntProtoId(actionNode.Value);
actionId = Spawn(id);
}
else if (map.TryGet<ValueDataNode>("entity", out var entityNode))
{
var id = new EntProtoId(entityNode.Value);
var proto = _proto.Index(id);
actionId = Spawn(MappingEntityAction);
SetIcon(actionId, new SpriteSpecifier.EntityPrototype(id));
SetEvent(actionId, new StartPlacementActionEvent()
{
PlacementOption = "SnapgridCenter",
EntityType = id
});
_metaData.SetEntityName(actionId, proto.Name);
}
else if (map.TryGet<ValueDataNode>("tileId", out var tileNode))
{
var id = new ProtoId<ContentTileDefinition>(tileNode.Value);
var proto = _proto.Index(id);
actionId = Spawn(MappingEntityAction);
if (proto.Sprite is {} sprite)
SetIcon(actionId, new SpriteSpecifier.Texture(sprite));
SetEvent(actionId, new StartPlacementActionEvent()
{
PlacementOption = "AlignTileAny",
TileId = id
});
_metaData.SetEntityName(actionId, Loc.GetString(proto.Name));
}
else
{
Log.Error($"Mapping actions from {path} had unknown action data!");
continue;
}
AddActionDirect((user, actions), actionId);
}
}
private void OnWorldTargetAttempt(Entity<WorldTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
{
if (args.Handled)
return;
args.Handled = true;
var (uid, comp) = ent;
var action = args.Action;
var coords = args.Input.Coordinates;
var user = args.User;
if (!ValidateWorldTarget(user, coords, ent))
return;
// optionally send the clicked entity too, if it matches its whitelist etc
// this is the actual entity-world targeting magic
EntityUid? targetEnt = null;
if (TryComp<EntityTargetActionComponent>(ent, out var entity) &&
args.Input.EntityUid != null &&
ValidateEntityTarget(user, args.Input.EntityUid, (uid, entity)))
{
targetEnt = args.Input.EntityUid;
}
AssignSlot?.Invoke(assignments);
if (action.ClientExclusive)
{
// TODO: abstract away from single event or maybe just RaiseLocalEvent?
if (comp.Event is {} ev)
{
ev.Target = coords;
ev.Entity = targetEnt;
}
PerformAction((user, user.Comp), (uid, action));
}
else
RaisePredictiveEvent(new RequestPerformActionEvent(GetNetEntity(uid), GetNetEntity(targetEnt), GetNetCoordinates(coords)));
args.FoundTarget = true;
}
private void OnEntityTargetAttempt(Entity<EntityTargetActionComponent> ent, ref ActionTargetAttemptEvent args)
{
if (args.Handled || args.Input.EntityUid is not { Valid: true } entity)
return;
// let world target component handle it
var (uid, comp) = ent;
if (comp.Event is not {} ev)
{
DebugTools.Assert(HasComp<WorldTargetActionComponent>(ent), $"Action {ToPrettyString(ent)} requires WorldTargetActionComponent for entity-world targeting");
return;
}
args.Handled = true;
var action = args.Action;
var user = args.User;
if (!ValidateEntityTarget(user, entity, ent))
return;
if (action.ClientExclusive)
{
ev.Target = entity;
PerformAction((user, user.Comp), (uid, action));
}
else
{
RaisePredictiveEvent(new RequestPerformActionEvent(GetNetEntity(uid), GetNetEntity(entity)));
}
args.FoundTarget = true;
}
public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId);

View File

@@ -1,13 +1,13 @@
<Control
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Margin="4"
MinSize="50 50">
<GridContainer
Columns="3">
<cc:CommandButton Command="startround" Text="{Loc administration-ui-round-tab-start-round}" />
<cc:CommandButton Command="endround" Text="{Loc administration-ui-round-tab-end-round}" />
<cc:CommandButton Command="restartround" Text="{Loc administration-ui-round-tab-restart-round}" />
<cc:CommandButton Command="restartroundnow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
<controls:ConfirmButton Name="StartRound" Text="{Loc administration-ui-round-tab-start-round}" />
<controls:ConfirmButton Name="EndRound" Text="{Loc administration-ui-round-tab-end-round}" />
<controls:ConfirmButton Name="RestartRound" Text="{Loc administration-ui-round-tab-restart-round}" />
<controls:ConfirmButton Name="RestartRoundNow" Text="{Loc administration-ui-round-tab-restart-round-now}" />
</GridContainer>
</Control>

View File

@@ -1,10 +1,24 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs
{
[GenerateTypedNameReferences]
public sealed partial class RoundTab : Control
{
[Dependency] private readonly IClientConsoleHost _console = default!;
public RoundTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
StartRound.OnPressed += _ => _console.ExecuteCommand("startround");
EndRound.OnPressed += _ => _console.ExecuteCommand("endround");
RestartRound.OnPressed += _ => _console.ExecuteCommand("restartround");
RestartRoundNow.OnPressed += _ => _console.ExecuteCommand("restartroundnow");
}
}
}

View File

@@ -1,11 +1,12 @@
<Control
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Margin="4"
MinSize="50 50">
<GridContainer
Columns="4" >
<cc:CommandButton Command="shutdown" Text="{Loc server-shutdown}" />
<controls:ConfirmButton Name="ServerShutdownButton" Text="{Loc server-shutdown}" />
<cc:CommandButton Name="SetOocButton" Command="setooc" Text="{Loc server-ooc-toggle}" ToggleMode="True" />
<cc:CommandButton Name="SetLoocButton" Command="setlooc" Text="{Loc server-looc-toggle}" ToggleMode="True" />
</GridContainer>

View File

@@ -1,5 +1,6 @@
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
@@ -10,6 +11,7 @@ namespace Content.Client.Administration.UI.Tabs
public sealed partial class ServerTab : Control
{
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
public ServerTab()
{
@@ -18,6 +20,8 @@ namespace Content.Client.Administration.UI.Tabs
_config.OnValueChanged(CCVars.OocEnabled, OocEnabledChanged, true);
_config.OnValueChanged(CCVars.LoocEnabled, LoocEnabledChanged, true);
ServerShutdownButton.OnPressed += _ => _console.ExecuteCommand("shutdown");
}
private void OocEnabledChanged(bool value)

View File

@@ -0,0 +1,203 @@
using Content.Client.Construction;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Construction.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.Placement.Modes;
using Robust.Client.Utility;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Numerics;
using static Robust.Client.Placement.PlacementManager;
namespace Content.Client.Atmos;
/// <summary>
/// Allows users to place atmos pipes on different layers depending on how the mouse cursor is positioned within a grid tile.
/// </summary>
/// <remarks>
/// This placement mode is not on the engine because it is content specific.
/// </remarks>
public sealed class AlignAtmosPipeLayers : SnapgridCenter
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _transformSystem;
private readonly SharedAtmosPipeLayersSystem _pipeLayersSystem;
private readonly SpriteSystem _spriteSystem;
private const float SearchBoxSize = 2f;
private EntityCoordinates _unalignedMouseCoords = default;
private const float MouseDeadzoneRadius = 0.25f;
private Color _guideColor = new Color(0, 0, 0.5785f);
private const float GuideRadius = 0.1f;
private const float GuideOffset = 0.21875f;
public AlignAtmosPipeLayers(PlacementManager pMan) : base(pMan)
{
IoCManager.InjectDependencies(this);
_mapSystem = _entityManager.System<SharedMapSystem>();
_transformSystem = _entityManager.System<SharedTransformSystem>();
_pipeLayersSystem = _entityManager.System<SharedAtmosPipeLayersSystem>();
_spriteSystem = _entityManager.System<SpriteSystem>();
}
/// <inheritdoc/>
public override void Render(in OverlayDrawArgs args)
{
var gridUid = _entityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
if (gridUid == null || Grid == null)
return;
// Draw guide circles for each pipe layer if we are not in line/grid placing mode
if (pManager.PlacementType == PlacementTypes.None)
{
var gridRotation = _transformSystem.GetWorldRotation(gridUid.Value);
var worldPosition = _mapSystem.LocalToWorld(gridUid.Value, Grid, MouseCoords.Position);
var direction = (_eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
var multi = (direction == Direction.North || direction == Direction.South) ? -1f : 1f;
args.WorldHandle.DrawCircle(worldPosition, GuideRadius, _guideColor);
args.WorldHandle.DrawCircle(worldPosition + gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
args.WorldHandle.DrawCircle(worldPosition - gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
}
base.Render(args);
}
/// <inheritdoc/>
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
base.AlignPlacementMode(mouseScreen);
// Exit early if we are in line/grid placing mode
if (pManager.PlacementType != PlacementTypes.None)
return;
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
var gridId = _transformSystem.GetGrid(MouseCoords);
if (!_entityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
return;
var gridRotation = _transformSystem.GetWorldRotation(gridId.Value);
CurrentTile = _mapSystem.GetTileRef(gridId.Value, mapGrid, MouseCoords);
float tileSize = mapGrid.TileSize;
GridDistancing = tileSize;
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2 + pManager.PlacementOffset.X,
CurrentTile.Y + tileSize / 2 + pManager.PlacementOffset.Y));
// Calculate the position of the mouse cursor with respect to the center of the tile to determine which layer to use
var mouseCoordsDiff = _unalignedMouseCoords.Position - MouseCoords.Position;
var layer = AtmosPipeLayer.Primary;
if (mouseCoordsDiff.Length() > MouseDeadzoneRadius)
{
// Determine the direction of the mouse is relative to the center of the tile, adjusting for the player eye and grid rotation
var direction = (new Angle(mouseCoordsDiff) + _eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
layer = (direction == Direction.North || direction == Direction.East) ? AtmosPipeLayer.Secondary : AtmosPipeLayer.Tertiary;
}
// Update the construction menu placer
if (pManager.Hijack != null)
UpdateHijackedPlacer(layer, mouseScreen);
// Otherwise update the debug placer
else
UpdatePlacer(layer);
}
private void UpdateHijackedPlacer(AtmosPipeLayer layer, ScreenCoordinates mouseScreen)
{
// Try to get alternative prototypes from the construction prototype
var constructionSystem = (pManager.Hijack as ConstructionPlacementHijack)?.CurrentConstructionSystem;
var altPrototypes = (pManager.Hijack as ConstructionPlacementHijack)?.CurrentPrototype?.AlternativePrototypes;
if (constructionSystem == null || altPrototypes == null || (int)layer >= altPrototypes.Length)
return;
var newProtoId = altPrototypes[(int)layer];
if (!_protoManager.TryIndex(newProtoId, out var newProto))
return;
if (newProto.Type != ConstructionType.Structure)
{
pManager.Clear();
return;
}
if (newProto.ID == (pManager.Hijack as ConstructionPlacementHijack)?.CurrentPrototype?.ID)
return;
// Start placing
pManager.BeginPlacing(new PlacementInformation()
{
IsTile = false,
PlacementOption = newProto.PlacementMode,
}, new ConstructionPlacementHijack(constructionSystem, newProto));
if (pManager.CurrentMode is AlignAtmosPipeLayers { } newMode)
newMode.RefreshGrid(mouseScreen);
// Update construction guide
constructionSystem.GetGuide(newProto);
}
private void UpdatePlacer(AtmosPipeLayer layer)
{
// Try to get alternative prototypes from the entity atmos pipe layer component
if (pManager.CurrentPermission?.EntityType == null)
return;
if (!_protoManager.TryIndex<EntityPrototype>(pManager.CurrentPermission.EntityType, out var currentProto))
return;
if (!currentProto.TryGetComponent<AtmosPipeLayersComponent>(out var atmosPipeLayers, _entityManager.ComponentFactory))
return;
if (!_pipeLayersSystem.TryGetAlternativePrototype(atmosPipeLayers, layer, out var newProtoId))
return;
if (_protoManager.TryIndex<EntityPrototype>(newProtoId, out var newProto))
{
// Update the placed prototype
pManager.CurrentPermission.EntityType = newProtoId;
// Update the appearance of the ghost sprite
if (newProto.TryGetComponent<SpriteComponent>(out var sprite, _entityManager.ComponentFactory))
{
var textures = new List<IDirectionalTextureProvider>();
foreach (var spriteLayer in sprite.AllLayers)
{
if (spriteLayer.ActualRsi?.Path != null && spriteLayer.RsiState.Name != null)
textures.Add(_spriteSystem.RsiStateLike(new SpriteSpecifier.Rsi(spriteLayer.ActualRsi.Path, spriteLayer.RsiState.Name)));
}
pManager.CurrentTextures = textures;
}
}
}
private void RefreshGrid(ScreenCoordinates mouseScreen)
{
base.AlignPlacementMode(mouseScreen);
}
}

View File

@@ -17,6 +17,10 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
public int? FocusNetId = null;
private const int ChunkSize = 4;
private const float ScaleModifier = 4f;
private readonly float[] _layerFraction = { 0.5f, 0.75f, 0.25f };
private const float LineThickness = 0.05f;
private readonly Color _basePipeNetColor = Color.LightGray;
private readonly Color _unfocusedPipeNetColor = Color.DimGray;
@@ -95,23 +99,23 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
foreach (var chunkedLine in atmosPipeNetwork)
{
var leftTop = ScalePosition(new Vector2
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - LineThickness,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - LineThickness)
- offset);
var rightTop = ScalePosition(new Vector2
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + LineThickness,
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - LineThickness)
- offset);
var leftBottom = ScalePosition(new Vector2
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - LineThickness,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + LineThickness)
- offset);
var rightBottom = ScalePosition(new Vector2
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + LineThickness,
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + LineThickness)
- offset);
if (!pipeVertexUVs.TryGetValue(chunkedLine.Color, out var pipeVertexUV))
@@ -142,7 +146,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
if (chunks == null || grid == null)
return decodedOutput;
// Clear stale look up table values
// Clear stale look up table values
_horizLines.Clear();
_horizLinesReversed.Clear();
_vertLines.Clear();
@@ -158,7 +162,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
{
var list = new List<AtmosMonitoringConsoleLine>();
foreach (var ((netId, hexColor), atmosPipeData) in chunk.AtmosPipeData)
foreach (var ((netId, layer, hexColor), atmosPipeData) in chunk.AtmosPipeData)
{
// Determine the correct coloration for the pipe
var color = Color.FromHex(hexColor) * _basePipeNetColor;
@@ -191,6 +195,9 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
_vertLinesReversed[color] = vertLinesReversed;
}
var layerFraction = _layerFraction[(int)layer];
var origin = new Vector2(grid.TileSize * layerFraction, -grid.TileSize * layerFraction);
// Loop over the chunk
for (var tileIdx = 0; tileIdx < ChunkSize * ChunkSize; tileIdx++)
{
@@ -208,21 +215,22 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
// Calculate the draw point offsets
var vertLineOrigin = (atmosPipeData & northMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 1f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * layerFraction, -grid.TileSize * 1f) : origin;
var vertLineTerminus = (atmosPipeData & southMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * layerFraction, -grid.TileSize * 0f) : origin;
var horizLineOrigin = (atmosPipeData & eastMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 1f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * 1f, -grid.TileSize * layerFraction) : origin;
var horizLineTerminus = (atmosPipeData & westMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
new Vector2(grid.TileSize * 0f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
new Vector2(grid.TileSize * 0f, -grid.TileSize * layerFraction) : origin;
// Since we can have pipe lines that have a length of a half tile,
// double the vectors and convert to vector2i so we can merge them
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, 2), ConvertVector2ToVector2i(tile + horizLineTerminus, 2), horizLines, horizLinesReversed);
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, 2), ConvertVector2ToVector2i(tile + vertLineTerminus, 2), vertLines, vertLinesReversed);
// Scale up the vectors and convert to vector2i so we can merge them
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, ScaleModifier),
ConvertVector2ToVector2i(tile + horizLineTerminus, ScaleModifier), horizLines, horizLinesReversed);
AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, ScaleModifier),
ConvertVector2ToVector2i(tile + vertLineTerminus, ScaleModifier), vertLines, vertLinesReversed);
}
}
}
@@ -235,7 +243,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
foreach (var (origin, terminal) in horizLines)
decodedOutput.Add(new AtmosMonitoringConsoleLine
(ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
(ConvertVector2iToVector2(origin, 1f / ScaleModifier), ConvertVector2iToVector2(terminal, 1f / ScaleModifier), sRGB));
}
foreach (var (color, vertLines) in _vertLines)
@@ -245,7 +253,7 @@ public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
foreach (var (origin, terminal) in vertLines)
decodedOutput.Add(new AtmosMonitoringConsoleLine
(ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
(ConvertVector2iToVector2(origin, 1f / ScaleModifier), ConvertVector2iToVector2(terminal, 1f / ScaleModifier), sRGB));
}
return decodedOutput;

View File

@@ -15,7 +15,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
private void OnHandleState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentHandleState args)
{
Dictionary<Vector2i, Dictionary<(int, string), ulong>> modifiedChunks;
Dictionary<Vector2i, Dictionary<AtmosMonitoringConsoleSubnet, ulong>> modifiedChunks;
Dictionary<NetEntity, AtmosDeviceNavMapData> atmosDevices;
switch (args.Current)
@@ -54,7 +54,7 @@ public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleS
foreach (var (origin, chunk) in modifiedChunks)
{
var newChunk = new AtmosPipeChunk(origin);
newChunk.AtmosPipeData = new Dictionary<(int, string), ulong>(chunk);
newChunk.AtmosPipeData = new Dictionary<AtmosMonitoringConsoleSubnet, ulong>(chunk);
component.AtmosPipeChunks[origin] = newChunk;
}

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
namespace Content.Client.Atmos.Consoles;
@@ -33,6 +34,8 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
private ProtoId<NavMapBlipPrototype> _navMapConsoleProtoId = "NavMapConsole";
private ProtoId<NavMapBlipPrototype> _gasPipeSensorProtoId = "GasPipeSensor";
private readonly Vector2[] _pipeLayerOffsets = { new Vector2(0f, 0f), new Vector2(0.25f, 0.25f), new Vector2(-0.25f, -0.25f) };
public AtmosMonitoringConsoleWindow(AtmosMonitoringConsoleBoundUserInterface userInterface, EntityUid? owner)
{
RobustXamlLoader.Load(this);
@@ -53,7 +56,7 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
consoleCoords = xform.Coordinates;
NavMap.MapUid = xform.GridUid;
// Assign station name
// Assign station name
if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
stationName = stationMetaData.EntityName;
@@ -238,6 +241,10 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
var blinks = proto.Blinks || _focusEntity == metaData.NetEntity;
var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
if (proto.Placement == NavMapBlipPlacement.Offset && metaData.PipeLayer > 0)
coords = coords.Offset(_pipeLayerOffsets[(int)metaData.PipeLayer]);
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(new SpriteSpecifier.Texture(texture)), color, blinks, proto.Selectable, proto.Scale);
NavMap.TrackedEntities[metaData.NetEntity] = blip;
}

View File

@@ -1,6 +1,7 @@
using Content.Client.SubFloor;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -8,7 +9,7 @@ using Robust.Client.GameObjects;
namespace Content.Client.Atmos.EntitySystems;
[UsedImplicitly]
public sealed class AtmosPipeAppearanceSystem : EntitySystem
public sealed partial class AtmosPipeAppearanceSystem : SharedAtmosPipeAppearanceSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
@@ -26,26 +27,37 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
if (!TryComp(uid, out SpriteComponent? sprite))
return;
var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out _);
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
{
var layer = _sprite.LayerMapReserve((uid, sprite), layerKey);
_sprite.LayerSetRsi((uid, sprite), layer, component.Sprite.RsiPath);
_sprite.LayerSetRsiState((uid, sprite), layer, component.Sprite.RsiState);
_sprite.LayerSetDirOffset((uid, sprite), layer, ToOffset(layerKey));
for (byte i = 0; i < numberOfPipeLayers; i++)
{
var layerName = layerKey.ToString() + i.ToString();
var layer = _sprite.LayerMapReserve((uid, sprite), layerName);
_sprite.LayerSetRsi((uid, sprite), layer, component.Sprite[i].RsiPath);
_sprite.LayerSetRsiState((uid, sprite), layer, component.Sprite[i].RsiState);
_sprite.LayerSetDirOffset((uid, sprite), layer, ToOffset(layerKey));
}
}
}
private void HideAllPipeConnection(Entity<SpriteComponent> entity)
private void HideAllPipeConnection(Entity<SpriteComponent> entity, AtmosPipeLayersComponent? atmosPipeLayers, int numberOfPipeLayers)
{
var sprite = entity.Comp;
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
{
if (!_sprite.LayerMapTryGet(entity.AsNullable(), layerKey, out var key, false))
continue;
for (byte i = 0; i < numberOfPipeLayers; i++)
{
var layerName = layerKey.ToString() + i.ToString();
var layer = sprite[key];
layer.Visible = false;
if (!_sprite.LayerMapTryGet(entity.AsNullable(), layerName, out var key, false))
continue;
var layer = sprite[key];
layer.Visible = false;
}
}
}
@@ -61,33 +73,45 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
return;
}
if (!_appearance.TryGetData<PipeDirection>(uid, PipeVisuals.VisualState, out var worldConnectedDirections, args.Component))
var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out var atmosPipeLayers);
if (!_appearance.TryGetData<int>(uid, PipeVisuals.VisualState, out var worldConnectedDirections, args.Component))
{
HideAllPipeConnection((uid, args.Sprite));
HideAllPipeConnection((uid, args.Sprite), atmosPipeLayers, numberOfPipeLayers);
return;
}
if (!_appearance.TryGetData<Color>(uid, PipeColorVisuals.Color, out var color, args.Component))
color = Color.White;
// transform connected directions to local-coordinates
var connectedDirections = worldConnectedDirections.RotatePipeDirection(-Transform(uid).LocalRotation);
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
for (byte i = 0; i < numberOfPipeLayers; i++)
{
if (!_sprite.LayerMapTryGet((uid, args.Sprite), layerKey, out var key, false))
continue;
// Extract the cardinal pipe orientations for the current pipe layer
// '15' is the four bit mask that is used to extract the pipe orientations of interest from 'worldConnectedDirections'
// Fun fact: a collection of four bits is called a 'nibble'! They aren't natively supported :(
var pipeLayerConnectedDirections = (PipeDirection)(15 & (worldConnectedDirections >> (PipeDirectionHelpers.PipeDirections * i)));
var layer = args.Sprite[key];
var dir = (PipeDirection)layerKey;
var visible = connectedDirections.HasDirection(dir);
// Transform the connected directions to local-coordinates
var connectedDirections = pipeLayerConnectedDirections.RotatePipeDirection(-Transform(uid).LocalRotation);
layer.Visible &= visible;
foreach (var layerKey in Enum.GetValues<PipeConnectionLayer>())
{
var layerName = layerKey.ToString() + i.ToString();
if (!visible)
continue;
if (!_sprite.LayerMapTryGet((uid, args.Sprite), layerName, out var key, false))
continue;
layer.Color = color;
var layer = args.Sprite[key];
var dir = (PipeDirection)layerKey;
var visible = connectedDirections.HasDirection(dir);
layer.Visible &= visible;
if (!visible)
continue;
layer.Color = color;
}
}
}

View File

@@ -0,0 +1,56 @@
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
namespace Content.Client.Atmos.EntitySystems;
/// <summary>
/// The system responsible for updating the appearance of layered gas pipe
/// </summary>
public sealed partial class AtmosPipeLayersSystem : SharedAtmosPipeLayersSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IReflectionManager _reflection = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AtmosPipeLayersComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAppearanceChange(Entity<AtmosPipeLayersComponent> ent, ref AppearanceChangeEvent ev)
{
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (_appearance.TryGetData<string>(ent, AtmosPipeLayerVisuals.Sprite, out var spriteRsi) &&
_resourceCache.TryGetResource(SpriteSpecifierSerializer.TextureRoot / spriteRsi, out RSIResource? resource))
{
_sprite.SetBaseRsi((ent, sprite), resource.RSI);
}
if (_appearance.TryGetData<Dictionary<string, string>>(ent, AtmosPipeLayerVisuals.SpriteLayers, out var pipeState))
{
foreach (var (layerKey, rsiPath) in pipeState)
{
if (TryParseKey(layerKey, out var @enum))
_sprite.LayerSetRsi((ent, sprite), @enum, new ResPath(rsiPath));
else
_sprite.LayerSetRsi((ent, sprite), layerKey, new ResPath(rsiPath));
}
}
}
private bool TryParseKey(string keyString, [NotNullWhen(true)] out Enum? @enum)
{
return _reflection.TryParseEnumReference(keyString, out @enum);
}
}

View File

@@ -11,7 +11,6 @@ namespace Content.Client.Atmos.EntitySystems;
/// </summary>
public sealed class FireVisualizerSystem : VisualizerSystem<FireVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly PointLightSystem _lights = default!;
public override void Initialize()
@@ -33,9 +32,9 @@ public sealed class FireVisualizerSystem : VisualizerSystem<FireVisualsComponent
// Need LayerMapTryGet because Init fails if there's no existing sprite / appearancecomp
// which means in some setups (most frequently no AppearanceComp) the layer never exists.
if (TryComp<SpriteComponent>(uid, out var sprite) &&
_sprite.LayerMapTryGet((uid, sprite), FireVisualLayers.Fire, out var layer, false))
SpriteSystem.LayerMapTryGet((uid, sprite), FireVisualLayers.Fire, out var layer, false))
{
_sprite.RemoveLayer((uid, sprite), layer);
SpriteSystem.RemoveLayer((uid, sprite), layer);
}
}
@@ -44,11 +43,11 @@ public sealed class FireVisualizerSystem : VisualizerSystem<FireVisualsComponent
if (!TryComp<SpriteComponent>(uid, out var sprite) || !TryComp(uid, out AppearanceComponent? appearance))
return;
_sprite.LayerMapReserve((uid, sprite), FireVisualLayers.Fire);
_sprite.LayerSetVisible((uid, sprite), FireVisualLayers.Fire, false);
SpriteSystem.LayerMapReserve((uid, sprite), FireVisualLayers.Fire);
SpriteSystem.LayerSetVisible((uid, sprite), FireVisualLayers.Fire, false);
sprite.LayerSetShader(FireVisualLayers.Fire, "unshaded");
if (component.Sprite != null)
_sprite.LayerSetRsi((uid, sprite), FireVisualLayers.Fire, new ResPath(component.Sprite));
SpriteSystem.LayerSetRsi((uid, sprite), FireVisualLayers.Fire, new ResPath(component.Sprite));
UpdateAppearance(uid, component, sprite, appearance);
}
@@ -61,12 +60,12 @@ public sealed class FireVisualizerSystem : VisualizerSystem<FireVisualsComponent
private void UpdateAppearance(EntityUid uid, FireVisualsComponent component, SpriteComponent sprite, AppearanceComponent appearance)
{
if (!_sprite.LayerMapTryGet((uid, sprite), FireVisualLayers.Fire, out var index, false))
if (!SpriteSystem.LayerMapTryGet((uid, sprite), FireVisualLayers.Fire, out var index, false))
return;
AppearanceSystem.TryGetData<bool>(uid, FireVisuals.OnFire, out var onFire, appearance);
AppearanceSystem.TryGetData<float>(uid, FireVisuals.FireStacks, out var fireStacks, appearance);
_sprite.LayerSetVisible((uid, sprite), index, onFire);
SpriteSystem.LayerSetVisible((uid, sprite), index, onFire);
if (!onFire)
{
@@ -80,9 +79,9 @@ public sealed class FireVisualizerSystem : VisualizerSystem<FireVisualsComponent
}
if (fireStacks > component.FireStackAlternateState && !string.IsNullOrEmpty(component.AlternateState))
_sprite.LayerSetRsiState((uid, sprite), index, component.AlternateState);
SpriteSystem.LayerSetRsiState((uid, sprite), index, component.AlternateState);
else
_sprite.LayerSetRsiState((uid, sprite), index, component.NormalState);
SpriteSystem.LayerSetRsiState((uid, sprite), index, component.NormalState);
component.LightEntity ??= Spawn(null, new EntityCoordinates(uid, default));
var light = EnsureComp<PointLightComponent>(component.LightEntity.Value);

View File

@@ -7,11 +7,9 @@ namespace Content.Client.Atmos.Monitor;
public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmableVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, AtmosAlarmableVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null || !_sprite.LayerMapTryGet((uid, args.Sprite), component.LayerMap, out var layer, false))
if (args.Sprite == null || !SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.LayerMap, out var layer, false))
return;
if (!args.AppearanceData.TryGetValue(PowerDeviceVisuals.Powered, out var poweredObject) ||
@@ -24,8 +22,8 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
{
foreach (var visLayer in component.HideOnDepowered)
{
if (_sprite.LayerMapTryGet((uid, args.Sprite), visLayer, out var powerVisibilityLayer, false))
_sprite.LayerSetVisible((uid, args.Sprite), powerVisibilityLayer, powered);
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), visLayer, out var powerVisibilityLayer, false))
SpriteSystem.LayerSetVisible((uid, args.Sprite), powerVisibilityLayer, powered);
}
}
@@ -33,8 +31,8 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
{
foreach (var (setLayer, powerState) in component.SetOnDepowered)
{
if (_sprite.LayerMapTryGet((uid, args.Sprite), setLayer, out var setStateLayer, false))
_sprite.LayerSetRsiState((uid, args.Sprite), setStateLayer, new RSI.StateId(powerState));
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), setLayer, out var setStateLayer, false))
SpriteSystem.LayerSetRsiState((uid, args.Sprite), setStateLayer, new RSI.StateId(powerState));
}
}
@@ -43,7 +41,7 @@ public sealed class AtmosAlarmableVisualsSystem : VisualizerSystem<AtmosAlarmabl
&& powered
&& component.AlarmStates.TryGetValue(alarmType, out var state))
{
_sprite.LayerSetRsiState((uid, args.Sprite), layer, new RSI.StateId(state));
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layer, new RSI.StateId(state));
}
}
}

View File

@@ -136,6 +136,7 @@ namespace Content.Client.Atmos.UI
else
{
// oh shit of fuck its more than 4 this ui isn't gonna look pretty anymore
CDeviceMixes.RemoveAllChildren();
for (var i = 1; i < msg.NodeGasMixes.Length; i++)
{
GenerateGasDisplay(msg.NodeGasMixes[i], CDeviceMixes);

View File

@@ -9,8 +9,6 @@ namespace Content.Client.Atmos.Visualizers;
/// </summary>
public sealed class PortableScrubberSystem : VisualizerSystem<PortableScrubberVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, PortableScrubberVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
@@ -20,15 +18,15 @@ public sealed class PortableScrubberSystem : VisualizerSystem<PortableScrubberVi
&& AppearanceSystem.TryGetData<bool>(uid, PortableScrubberVisuals.IsRunning, out var isRunning, args.Component))
{
var runningState = isRunning ? component.RunningState : component.IdleState;
_sprite.LayerSetRsiState((uid, args.Sprite), PortableScrubberVisualLayers.IsRunning, runningState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PortableScrubberVisualLayers.IsRunning, runningState);
var fullState = isFull ? component.FullState : component.ReadyState;
_sprite.LayerSetRsiState((uid, args.Sprite), PowerDeviceVisualLayers.Powered, fullState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PowerDeviceVisualLayers.Powered, fullState);
}
if (AppearanceSystem.TryGetData<bool>(uid, PortableScrubberVisuals.IsDraining, out var isDraining, args.Component))
{
_sprite.LayerSetVisible((uid, args.Sprite), PortableScrubberVisualLayers.IsDraining, isDraining);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PortableScrubberVisualLayers.IsDraining, isDraining);
}
}
}

View File

@@ -7,8 +7,6 @@ namespace Content.Client.Botany;
public sealed class PlantHolderVisualizerSystem : VisualizerSystem<PlantHolderVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
@@ -20,8 +18,8 @@ public sealed class PlantHolderVisualizerSystem : VisualizerSystem<PlantHolderVi
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
_sprite.LayerMapReserve((uid, sprite), PlantHolderLayers.Plant);
_sprite.LayerSetVisible((uid, sprite), PlantHolderLayers.Plant, false);
SpriteSystem.LayerMapReserve((uid, sprite), PlantHolderLayers.Plant);
SpriteSystem.LayerSetVisible((uid, sprite), PlantHolderLayers.Plant, false);
}
protected override void OnAppearanceChange(EntityUid uid, PlantHolderVisualsComponent component, ref AppearanceChangeEvent args)
@@ -34,12 +32,12 @@ public sealed class PlantHolderVisualizerSystem : VisualizerSystem<PlantHolderVi
{
var valid = !string.IsNullOrWhiteSpace(state);
_sprite.LayerSetVisible((uid, args.Sprite), PlantHolderLayers.Plant, valid);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PlantHolderLayers.Plant, valid);
if (valid)
{
_sprite.LayerSetRsi((uid, args.Sprite), PlantHolderLayers.Plant, new ResPath(rsi));
_sprite.LayerSetRsiState((uid, args.Sprite), PlantHolderLayers.Plant, state);
SpriteSystem.LayerSetRsi((uid, args.Sprite), PlantHolderLayers.Plant, new ResPath(rsi));
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PlantHolderLayers.Plant, state);
}
}
}

View File

@@ -25,16 +25,14 @@ public sealed class ChargesSystem : SharedChargesSystem
while (query.MoveNext(out var uid, out var recharge, out var charges))
{
BaseActionComponent? actionComp = null;
if (!_actions.ResolveActionData(uid, ref actionComp, logError: false))
if (_actions.GetAction(uid, false) is not {} action)
continue;
var current = GetCurrentCharges((uid, charges, recharge));
if (!_lastCharges.TryGetValue(uid, out var last) || current != last)
{
_actions.UpdateAction(uid, actionComp);
_actions.UpdateAction(action);
}
_tempLastCharges[uid] = current;

View File

@@ -9,7 +9,6 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorComponent component, ref AppearanceChangeEvent args)
{
@@ -34,24 +33,24 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
return;
}
var layerExists = _sprite.LayerMapTryGet((uid, args.Sprite), TypingIndicatorLayers.Base, out var layer, false);
var layerExists = SpriteSystem.LayerMapTryGet((uid, args.Sprite), TypingIndicatorLayers.Base, out var layer, false);
if (!layerExists)
layer = _sprite.LayerMapReserve((uid, args.Sprite), TypingIndicatorLayers.Base);
layer = SpriteSystem.LayerMapReserve((uid, args.Sprite), TypingIndicatorLayers.Base);
_sprite.LayerSetRsi((uid, args.Sprite), layer, proto.SpritePath);
_sprite.LayerSetRsiState((uid, args.Sprite), layer, proto.TypingState);
SpriteSystem.LayerSetRsi((uid, args.Sprite), layer, proto.SpritePath);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layer, proto.TypingState);
args.Sprite.LayerSetShader(layer, proto.Shader);
_sprite.LayerSetOffset((uid, args.Sprite), layer, proto.Offset);
SpriteSystem.LayerSetOffset((uid, args.Sprite), layer, proto.Offset);
AppearanceSystem.TryGetData<TypingIndicatorState>(uid, TypingIndicatorVisuals.State, out var state);
_sprite.LayerSetVisible((uid, args.Sprite), layer, state != TypingIndicatorState.None);
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, state != TypingIndicatorState.None);
switch (state)
{
case TypingIndicatorState.Idle:
_sprite.LayerSetRsiState((uid, args.Sprite), layer, proto.IdleState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layer, proto.IdleState);
break;
case TypingIndicatorState.Typing:
_sprite.LayerSetRsiState((uid, args.Sprite), layer, proto.TypingState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layer, proto.TypingState);
break;
}
}

View File

@@ -11,7 +11,6 @@ namespace Content.Client.Chemistry.Visualizers;
public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent>
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -72,7 +71,7 @@ public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent
return;
if (TryComp<SpriteComponent>(uid, out var sprite))
_sprite.SetVisible((uid, sprite), false);
SpriteSystem.SetVisible((uid, sprite), false);
}
}

View File

@@ -16,7 +16,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ItemSystem _itemSystem = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -50,7 +49,7 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
if (args.Sprite == null)
return;
if (!_sprite.LayerMapTryGet((uid, args.Sprite), component.Layer, out var fillLayer, false))
if (!SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.Layer, out var fillLayer, false))
return;
var maxFillLevels = component.MaxFillLevels;
@@ -68,9 +67,9 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
}
if (component.Metamorphic)
{
if (_sprite.LayerMapTryGet((uid, args.Sprite), component.BaseLayer, out var baseLayer, false))
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.BaseLayer, out var baseLayer, false))
{
var hasOverlay = _sprite.LayerMapTryGet((uid, args.Sprite), component.OverlayLayer, out var overlayLayer, false);
var hasOverlay = SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.OverlayLayer, out var overlayLayer, false);
if (AppearanceSystem.TryGetData<string>(uid, SolutionContainerVisuals.BaseOverride,
out var baseOverride,
@@ -80,35 +79,35 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
if (reagentProto?.MetamorphicSprite is { } sprite)
{
_sprite.LayerSetSprite((uid, args.Sprite), baseLayer, sprite);
SpriteSystem.LayerSetSprite((uid, args.Sprite), baseLayer, sprite);
if (reagentProto.MetamorphicMaxFillLevels > 0)
{
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, true);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, true);
maxFillLevels = reagentProto.MetamorphicMaxFillLevels;
fillBaseName = reagentProto.MetamorphicFillBaseName;
changeColor = reagentProto.MetamorphicChangeColor;
fillSprite = sprite;
}
else
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, false);
if (hasOverlay)
_sprite.LayerSetVisible((uid, args.Sprite), overlayLayer, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), overlayLayer, false);
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, true);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, true);
if (hasOverlay)
_sprite.LayerSetVisible((uid, args.Sprite), overlayLayer, true);
SpriteSystem.LayerSetVisible((uid, args.Sprite), overlayLayer, true);
if (component.MetamorphicDefaultSprite != null)
_sprite.LayerSetSprite((uid, args.Sprite), baseLayer, component.MetamorphicDefaultSprite);
SpriteSystem.LayerSetSprite((uid, args.Sprite), baseLayer, component.MetamorphicDefaultSprite);
}
}
}
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, true);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, true);
}
var closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, maxFillLevels + 1);
@@ -120,25 +119,25 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
var stateName = fillBaseName + closestFillSprite;
if (fillSprite != null)
_sprite.LayerSetSprite((uid, args.Sprite), fillLayer, fillSprite);
_sprite.LayerSetRsiState((uid, args.Sprite), fillLayer, stateName);
SpriteSystem.LayerSetSprite((uid, args.Sprite), fillLayer, fillSprite);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), fillLayer, stateName);
if (changeColor && AppearanceSystem.TryGetData<Color>(uid, SolutionContainerVisuals.Color, out var color, args.Component))
_sprite.LayerSetColor((uid, args.Sprite), fillLayer, color);
SpriteSystem.LayerSetColor((uid, args.Sprite), fillLayer, color);
else
_sprite.LayerSetColor((uid, args.Sprite), fillLayer, Color.White);
SpriteSystem.LayerSetColor((uid, args.Sprite), fillLayer, Color.White);
}
else
{
if (component.EmptySpriteName == null)
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, false);
else
{
_sprite.LayerSetRsiState((uid, args.Sprite), fillLayer, component.EmptySpriteName);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), fillLayer, component.EmptySpriteName);
if (changeColor)
_sprite.LayerSetColor((uid, args.Sprite), fillLayer, component.EmptySpriteColor);
SpriteSystem.LayerSetColor((uid, args.Sprite), fillLayer, component.EmptySpriteColor);
else
_sprite.LayerSetColor((uid, args.Sprite), fillLayer, Color.White);
SpriteSystem.LayerSetColor((uid, args.Sprite), fillLayer, Color.White);
}
}

View File

@@ -13,16 +13,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
private static readonly SlotFlags[] IgnoredSlots =
{
SlotFlags.All,
SlotFlags.PREVENTEQUIP,
SlotFlags.NONE
};
private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray();
private readonly Dictionary<SlotFlags, List<string>> _data = new();
public override void Initialize()
{
base.Initialize();
@@ -61,49 +51,4 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
borderColor.AccentVColor = otherBorderColor.AccentVColor;
}
}
/// <summary>
/// Get a list of valid chameleon targets for these slots.
/// </summary>
public IEnumerable<string> GetValidTargets(SlotFlags slot)
{
var set = new HashSet<string>();
foreach (var availableSlot in _data.Keys)
{
if (slot.HasFlag(availableSlot))
{
set.UnionWith(_data[availableSlot]);
}
}
return set;
}
private void PrepareAllVariants()
{
_data.Clear();
var prototypes = _proto.EnumeratePrototypes<EntityPrototype>();
foreach (var proto in prototypes)
{
// check if this is valid clothing
if (!IsValidTarget(proto))
continue;
if (!proto.TryGetComponent(out ClothingComponent? item, Factory))
continue;
// sort item by their slot flags
// one item can be placed in several buckets
foreach (var slot in Slots)
{
if (!item.Slots.HasFlag(slot))
continue;
if (!_data.ContainsKey(slot))
{
_data.Add(slot, new List<string>());
}
_data[slot].Add(proto.ID);
}
}
}
}

View File

@@ -42,7 +42,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
var targets = _chameleon.GetValidTargets(st.Slot);
if (st.RequiredTag != null)
{
var newTargets = new List<string>();
var newTargets = new List<EntProtoId>();
foreach (var target in targets)
{
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))

View File

@@ -19,8 +19,8 @@ public sealed partial class ChameleonMenu : DefaultWindow
private readonly SpriteSystem _sprite;
public event Action<string>? OnIdSelected;
private IEnumerable<string> _possibleIds = Enumerable.Empty<string>();
private string? _selectedId;
private IEnumerable<EntProtoId> _possibleIds = [];
private EntProtoId? _selectedId;
private string _searchFilter = "";
public ChameleonMenu()
@@ -32,7 +32,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
Search.OnTextChanged += OnSearchEntered;
}
public void UpdateState(IEnumerable<string> possibleIds, string? selectedId)
public void UpdateState(IEnumerable<EntProtoId> possibleIds, string? selectedId)
{
_possibleIds = possibleIds;
_selectedId = selectedId;
@@ -57,7 +57,7 @@ public sealed partial class ChameleonMenu : DefaultWindow
if (!_prototypeManager.TryIndex(id, out EntityPrototype? proto))
continue;
var lowId = id.ToLowerInvariant();
var lowId = id.Id.ToLowerInvariant();
var lowName = proto.Name.ToLowerInvariant();
if (!lowId.Contains(searchFilterLow) && !lowName.Contains(_searchFilter))
continue;

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Shared.Construction.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Placement;
@@ -13,6 +13,9 @@ namespace Content.Client.Construction
private readonly ConstructionSystem _constructionSystem;
private readonly ConstructionPrototype? _prototype;
public ConstructionSystem? CurrentConstructionSystem { get { return _constructionSystem; } }
public ConstructionPrototype? CurrentPrototype { get { return _prototype; } }
public override bool CanRotate { get; }
public ConstructionPlacementHijack(ConstructionSystem constructionSystem, ConstructionPrototype? prototype)

View File

@@ -131,7 +131,7 @@ namespace Content.Client.ContextMenu.UI
{
if (!Menus.TryPeek(out var topMenu))
{
Logger.Error("Context Menu: Mouse entered menu without any open menus?");
Log.Error("Context Menu: Mouse entered menu without any open menus?");
return;
}
@@ -181,7 +181,7 @@ namespace Content.Client.ContextMenu.UI
{
if (!Menus.TryPeek(out var topMenu))
{
Logger.Error("Context Menu: Attempting to open sub menu without any open menus?");
Log.Error("Context Menu: Attempting to open sub menu without any open menus?");
return;
}

View File

@@ -306,7 +306,7 @@ namespace Content.Client.ContextMenu.UI
// find the element associated with this entity
if (!Elements.TryGetValue(entity, out var element))
{
Logger.Error($"Attempted to remove unknown entity from the entity menu: {_entityManager.GetComponent<MetaDataComponent>(entity).EntityName} ({entity})");
Log.Error($"Attempted to remove unknown entity from the entity menu: {_entityManager.GetComponent<MetaDataComponent>(entity).EntityName} ({entity})");
return;
}

View File

@@ -27,7 +27,6 @@ namespace Content.Client.Damage;
public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponent>
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -229,7 +228,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
// the layer key just doesn't exist, we skip it.
foreach (var key in damageVisComp.TargetLayers)
{
if (!_sprite.LayerMapTryGet((entity, spriteComponent), key, out var index, false))
if (!SpriteSystem.LayerMapTryGet((entity, spriteComponent), key, out var index, false))
{
Log.Warning($"Layer at key {key} was invalid for entity {entity}.");
continue;
@@ -254,7 +253,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
foreach (var layer in damageVisComp.TargetLayerMapKeys)
{
var layerCount = spriteComponent.AllLayers.Count();
var index = _sprite.LayerMapGet((entity, spriteComponent), layer);
var index = SpriteSystem.LayerMapGet((entity, spriteComponent), layer);
// var layerState = spriteComponent.LayerGetState(index).ToString()!;
if (index + 1 != layerCount)
@@ -325,17 +324,17 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
/// </summary>
private void AddDamageLayerToSprite(Entity<SpriteComponent?> spriteEnt, DamageVisualizerSprite sprite, string state, string mapKey, int? index = null)
{
var newLayer = _sprite.AddLayer(
var newLayer = SpriteSystem.AddLayer(
spriteEnt,
new SpriteSpecifier.Rsi(
new(sprite.Sprite), state
),
index
);
_sprite.LayerMapSet(spriteEnt, mapKey, newLayer);
SpriteSystem.LayerMapSet(spriteEnt, mapKey, newLayer);
if (sprite.Color != null)
_sprite.LayerSetColor(spriteEnt, newLayer, Color.FromHex(sprite.Color));
_sprite.LayerSetVisible(spriteEnt, newLayer, false);
SpriteSystem.LayerSetColor(spriteEnt, newLayer, Color.FromHex(sprite.Color));
SpriteSystem.LayerSetVisible(spriteEnt, newLayer, false);
}
protected override void OnAppearanceChange(EntityUid uid, DamageVisualsComponent damageVisComp, ref AppearanceChangeEvent args)
@@ -410,7 +409,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
damageVisComp.DisabledLayers[layer] = disabled;
if (damageVisComp.TrackAllDamage)
{
_sprite.LayerSetVisible((uid, spriteComponent), $"{layer}trackDamage", !disabled);
SpriteSystem.LayerSetVisible((uid, spriteComponent), $"{layer}trackDamage", !disabled);
continue;
}
@@ -419,7 +418,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
foreach (var damageGroup in damageVisComp.DamageOverlayGroups.Keys)
{
_sprite.LayerSetVisible((uid, spriteComponent), $"{layer}{damageGroup}", !disabled);
SpriteSystem.LayerSetVisible((uid, spriteComponent), $"{layer}{damageGroup}", !disabled);
}
}
}
@@ -462,20 +461,20 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
private void ReorderOverlaySprite(Entity<SpriteComponent> spriteEnt, DamageVisualsComponent damageVisComp, DamageVisualizerSprite sprite, string key, string statePrefix, FixedPoint2 threshold)
{
_sprite.LayerMapTryGet(spriteEnt.AsNullable(), key, out var spriteLayer, false);
SpriteSystem.LayerMapTryGet(spriteEnt.AsNullable(), key, out var spriteLayer, false);
var visibility = spriteEnt.Comp[spriteLayer].Visible;
_sprite.RemoveLayer(spriteEnt.AsNullable(), spriteLayer);
SpriteSystem.RemoveLayer(spriteEnt.AsNullable(), spriteLayer);
if (threshold == FixedPoint2.Zero) // these should automatically be invisible
threshold = damageVisComp.Thresholds[1];
spriteLayer = _sprite.AddLayer(
spriteLayer = SpriteSystem.AddLayer(
spriteEnt.AsNullable(),
new SpriteSpecifier.Rsi(
new(sprite.Sprite),
$"{statePrefix}_{threshold}"
),
spriteLayer);
_sprite.LayerMapSet(spriteEnt.AsNullable(), key, spriteLayer);
_sprite.LayerSetVisible(spriteEnt.AsNullable(), spriteLayer, visibility);
SpriteSystem.LayerMapSet(spriteEnt.AsNullable(), key, spriteLayer);
SpriteSystem.LayerSetVisible(spriteEnt.AsNullable(), spriteLayer, visibility);
// this is somewhat iffy since it constantly reallocates
damageVisComp.TopMostLayerKey = key;
}
@@ -610,7 +609,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
if (!damageVisComp.DisabledLayers[layerMapKey])
{
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
_sprite.LayerMapTryGet(spriteEnt.AsNullable(), $"{layerMapKey}trackDamage", out var spriteLayer, false);
SpriteSystem.LayerMapTryGet(spriteEnt.AsNullable(), $"{layerMapKey}trackDamage", out var spriteLayer, false);
UpdateDamageLayerState(spriteEnt,
spriteLayer,
@@ -621,7 +620,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
else if (!damageVisComp.Overlay)
{
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
_sprite.LayerMapTryGet(spriteEnt.AsNullable(), $"{layerMapKey}", out var spriteLayer, false);
SpriteSystem.LayerMapTryGet(spriteEnt.AsNullable(), $"{layerMapKey}", out var spriteLayer, false);
UpdateDamageLayerState(spriteEnt,
spriteLayer,
@@ -643,7 +642,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
if (damageVisComp.DamageOverlayGroups.ContainsKey(damageGroup) && !damageVisComp.DisabledLayers[layerMapKey])
{
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
_sprite.LayerMapTryGet((entity, spriteComponent), $"{layerMapKey}{damageGroup}", out var spriteLayer, false);
SpriteSystem.LayerMapTryGet((entity, spriteComponent), $"{layerMapKey}{damageGroup}", out var spriteLayer, false);
UpdateDamageLayerState(
(entity, spriteComponent),
@@ -655,7 +654,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
else if (!damageVisComp.Overlay)
{
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
_sprite.LayerMapTryGet((entity, spriteComponent), $"{layerMapKey}", out var spriteLayer, false);
SpriteSystem.LayerMapTryGet((entity, spriteComponent), $"{layerMapKey}", out var spriteLayer, false);
UpdateDamageLayerState(
(entity, spriteComponent),
@@ -670,7 +669,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
/// </summary>
private void UpdateOverlay(Entity<SpriteComponent> spriteEnt, FixedPoint2 threshold)
{
_sprite.LayerMapTryGet(spriteEnt.AsNullable(), $"DamageOverlay", out var spriteLayer, false);
SpriteSystem.LayerMapTryGet(spriteEnt.AsNullable(), $"DamageOverlay", out var spriteLayer, false);
UpdateDamageLayerState(spriteEnt,
spriteLayer,
@@ -690,7 +689,7 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
{
if (damageVisComp.DamageOverlayGroups.ContainsKey(damageGroup))
{
_sprite.LayerMapTryGet((entity, spriteComponent), $"DamageOverlay{damageGroup}", out var spriteLayer, false);
SpriteSystem.LayerMapTryGet((entity, spriteComponent), $"DamageOverlay{damageGroup}", out var spriteLayer, false);
UpdateDamageLayerState(
(entity, spriteComponent),
@@ -711,15 +710,15 @@ public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponen
{
if (threshold == 0)
{
_sprite.LayerSetVisible(spriteEnt.AsNullable(), spriteLayer, false);
SpriteSystem.LayerSetVisible(spriteEnt.AsNullable(), spriteLayer, false);
}
else
{
if (!spriteEnt.Comp[spriteLayer].Visible)
{
_sprite.LayerSetVisible(spriteEnt.AsNullable(), spriteLayer, true);
SpriteSystem.LayerSetVisible(spriteEnt.AsNullable(), spriteLayer, true);
}
_sprite.LayerSetRsiState(spriteEnt.AsNullable(), spriteLayer, $"{statePrefix}_{threshold}");
SpriteSystem.LayerSetRsiState(spriteEnt.AsNullable(), spriteLayer, $"{statePrefix}_{threshold}");
}
}
}

View File

@@ -6,8 +6,6 @@ namespace Content.Client.DamageState;
public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, DamageStateVisualsComponent component, ref AppearanceChangeEvent args)
{
var sprite = args.Sprite;
@@ -25,18 +23,18 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
// Brain no worky rn so this was just easier.
foreach (var key in new[] { DamageStateVisualLayers.Base, DamageStateVisualLayers.BaseUnshaded })
{
if (!_sprite.LayerMapTryGet((uid, sprite), key, out _, false)) continue;
if (!SpriteSystem.LayerMapTryGet((uid, sprite), key, out _, false)) continue;
_sprite.LayerSetVisible((uid, sprite), key, false);
SpriteSystem.LayerSetVisible((uid, sprite), key, false);
}
foreach (var (key, state) in layers)
{
// Inheritance moment.
if (!_sprite.LayerMapTryGet((uid, sprite), key, out _, false)) continue;
if (!SpriteSystem.LayerMapTryGet((uid, sprite), key, out _, false)) continue;
_sprite.LayerSetVisible((uid, sprite), key, true);
_sprite.LayerSetRsiState((uid, sprite), key, state);
SpriteSystem.LayerSetVisible((uid, sprite), key, true);
SpriteSystem.LayerSetRsiState((uid, sprite), key, state);
}
// So they don't draw over mobs anymore
@@ -45,12 +43,12 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem<DamageStateVi
if (sprite.DrawDepth > (int)DrawDepth.DeadMobs)
{
component.OriginalDrawDepth = sprite.DrawDepth;
_sprite.SetDrawDepth((uid, sprite), (int)DrawDepth.DeadMobs);
SpriteSystem.SetDrawDepth((uid, sprite), (int)DrawDepth.DeadMobs);
}
}
else if (component.OriginalDrawDepth != null)
{
_sprite.SetDrawDepth((uid, sprite), component.OriginalDrawDepth.Value);
SpriteSystem.SetDrawDepth((uid, sprite), component.OriginalDrawDepth.Value);
component.OriginalDrawDepth = null;
}
}

View File

@@ -2,6 +2,7 @@ using System.Numerics;
using Content.Client.Actions;
using Content.Client.Decals.Overlays;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Decals;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -21,9 +22,12 @@ public sealed class DecalPlacementSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public static readonly EntProtoId DecalAction = "BaseMappingDecalAction";
private string? _decalId;
private Color _decalColor = Color.White;
private Angle _decalAngle = Angle.Zero;
@@ -152,19 +156,12 @@ public sealed class DecalPlacementSystem : EntitySystem
Cleanable = _cleanable,
};
var actionId = Spawn(null);
AddComp(actionId, new WorldTargetActionComponent
{
// non-unique actions may be considered duplicates when saving/loading.
Icon = decalProto.Sprite,
Repeat = true,
ClientExclusive = true,
CheckCanAccess = false,
CheckCanInteract = false,
Range = -1,
Event = actionEvent,
IconColor = _decalColor,
});
var actionId = Spawn(DecalAction);
var action = Comp<ActionComponent>(actionId);
var ent = (actionId, action);
_actions.SetEvent(actionId, actionEvent);
_actions.SetIcon(ent, decalProto.Sprite);
_actions.SetIconColor(ent, _decalColor);
_metaData.SetEntityName(actionId, $"{_decalId} ({_decalColor.ToHex()}, {(int) _decalAngle.Degrees})");

View File

@@ -11,7 +11,6 @@ namespace Content.Client.Electrocution;
public sealed class ElectrocutionHUDVisualizerSystem : VisualizerSystem<ElectrocutionHUDVisualsComponent>
{
[Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -59,7 +58,7 @@ public sealed class ElectrocutionHUDVisualizerSystem : VisualizerSystem<Electroc
if (!AppearanceSystem.TryGetData<bool>(uid, ElectrifiedVisuals.IsElectrified, out var electrified, appearanceComp))
continue;
_sprite.LayerSetVisible((uid, spriteComp), ElectrifiedLayers.HUD, electrified);
SpriteSystem.LayerSetVisible((uid, spriteComp), ElectrifiedLayers.HUD, electrified);
}
}
@@ -70,7 +69,7 @@ public sealed class ElectrocutionHUDVisualizerSystem : VisualizerSystem<Electroc
var electrifiedQuery = AllEntityQuery<ElectrocutionHUDVisualsComponent, AppearanceComponent, SpriteComponent>();
while (electrifiedQuery.MoveNext(out var uid, out _, out _, out var spriteComp))
{
_sprite.LayerSetVisible((uid, spriteComp), ElectrifiedLayers.HUD, false);
SpriteSystem.LayerSetVisible((uid, spriteComp), ElectrifiedLayers.HUD, false);
}
}
@@ -84,6 +83,6 @@ public sealed class ElectrocutionHUDVisualizerSystem : VisualizerSystem<Electroc
return;
var player = _playerMan.LocalEntity;
_sprite.LayerSetVisible((uid, args.Sprite), ElectrifiedLayers.HUD, electrified && HasComp<ShowElectrocutionHUDComponent>(player));
SpriteSystem.LayerSetVisible((uid, args.Sprite), ElectrifiedLayers.HUD, electrified && HasComp<ShowElectrocutionHUDComponent>(player));
}
}

View File

@@ -1,3 +0,0 @@
using Content.Shared.Ensnaring.Components;

View File

@@ -28,12 +28,15 @@ public sealed partial class GuideEntityEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
private readonly TagSystem _tagSystem;
private readonly ExamineSystem _examineSystem;
private readonly GuidebookSystem _guidebookSystem;
private readonly ISawmill _sawmill;
public bool Interactive;
public Entity<SpriteComponent>? Sprite => View.Entity == null || View.Sprite == null
@@ -53,6 +56,7 @@ public sealed partial class GuideEntityEmbed : BoxContainer, IDocumentTag
_tagSystem = _systemManager.GetEntitySystem<TagSystem>();
_examineSystem = _systemManager.GetEntitySystem<ExamineSystem>();
_guidebookSystem = _systemManager.GetEntitySystem<GuidebookSystem>();
_sawmill = _logManager.GetSawmill("guidebook.entity");
MouseFilter = MouseFilterMode.Stop;
}
@@ -135,7 +139,7 @@ public sealed partial class GuideEntityEmbed : BoxContainer, IDocumentTag
{
if (!args.TryGetValue("Entity", out var proto))
{
Logger.Error("Entity embed tag is missing entity prototype argument");
_sawmill.Error("Entity embed tag is missing entity prototype argument");
control = null;
return false;
}

View File

@@ -24,7 +24,7 @@ public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag,
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
private readonly ISawmill _sawmill = default!;
public IPrototype? RepresentedPrototype { get; private set; }
@@ -34,7 +34,7 @@ public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag,
IoCManager.InjectDependencies(this);
MouseFilter = MouseFilterMode.Stop;
_sawmill = _logManager.GetSawmill("guidemicrowaveembed");
_sawmill = _logManager.GetSawmill("guidebook.microwave");
}
public GuideMicrowaveEmbed(string recipe) : this()

View File

@@ -15,12 +15,16 @@ namespace Content.Client.Guidebook.Controls;
[UsedImplicitly]
public sealed partial class GuideMicrowaveGroupEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ISawmill _sawmill;
public GuideMicrowaveGroupEmbed()
{
Orientation = LayoutOrientation.Vertical;
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("guidebook.microwave_group");
MouseFilter = MouseFilterMode.Stop;
}
@@ -34,7 +38,7 @@ public sealed partial class GuideMicrowaveGroupEmbed : BoxContainer, IDocumentTa
control = null;
if (!args.TryGetValue("Group", out var group))
{
Logger.Error("Microwave group embed tag is missing group argument");
_sawmill.Error("Microwave group embed tag is missing group argument");
return false;
}

View File

@@ -25,9 +25,11 @@ namespace Content.Client.Guidebook.Controls;
public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISearchableControl, IPrototypeRepresentationControl
{
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ChemistryGuideDataSystem _chemistryGuideData;
private readonly ISawmill _sawmill;
public IPrototype? RepresentedPrototype { get; private set; }
@@ -35,6 +37,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("guidebook.reagent");
_chemistryGuideData = _systemManager.GetEntitySystem<ChemistryGuideDataSystem>();
MouseFilter = MouseFilterMode.Stop;
}
@@ -64,13 +67,13 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
control = null;
if (!args.TryGetValue("Reagent", out var id))
{
Logger.Error("Reagent embed tag is missing reagent prototype argument");
_sawmill.Error("Reagent embed tag is missing reagent prototype argument");
return false;
}
if (!_prototype.TryIndex<ReagentPrototype>(id, out var reagent))
{
Logger.Error($"Specified reagent prototype \"{id}\" is not a valid reagent prototype");
_sawmill.Error($"Specified reagent prototype \"{id}\" is not a valid reagent prototype");
return false;
}

View File

@@ -17,12 +17,16 @@ namespace Content.Client.Guidebook.Controls;
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideReagentGroupEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ISawmill _sawmill;
public GuideReagentGroupEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("guidebook.reagent_group");
MouseFilter = MouseFilterMode.Stop;
}
@@ -42,7 +46,7 @@ public sealed partial class GuideReagentGroupEmbed : BoxContainer, IDocumentTag
control = null;
if (!args.TryGetValue("Group", out var group))
{
Logger.Error("Reagent group embed tag is missing group argument");
_sawmill.Error("Reagent group embed tag is missing group argument");
return false;
}

View File

@@ -17,12 +17,16 @@ namespace Content.Client.Guidebook.Controls;
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideTechDisciplineEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ISawmill _sawmill;
public GuideTechDisciplineEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("guidebook.tech_discipline");
MouseFilter = MouseFilterMode.Stop;
}
@@ -42,7 +46,7 @@ public sealed partial class GuideTechDisciplineEmbed : BoxContainer, IDocumentTa
control = null;
if (!args.TryGetValue("Discipline", out var group))
{
Logger.Error("Technology discipline embed tag is missing discipline argument");
_sawmill.Error("Technology discipline embed tag is missing discipline argument");
return false;
}

View File

@@ -22,10 +22,12 @@ namespace Content.Client.Guidebook.Controls;
public sealed partial class GuideTechnologyEmbed : BoxContainer, IDocumentTag, ISearchableControl
{
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly ResearchSystem _research;
private readonly SpriteSystem _sprite;
private readonly ISawmill _sawmill;
public GuideTechnologyEmbed()
{
@@ -33,6 +35,7 @@ public sealed partial class GuideTechnologyEmbed : BoxContainer, IDocumentTag, I
IoCManager.InjectDependencies(this);
_research = _systemManager.GetEntitySystem<ResearchSystem>();
_sprite = _systemManager.GetEntitySystem<SpriteSystem>();
_sawmill = _logManager.GetSawmill("guidebook.technology");
MouseFilter = MouseFilterMode.Stop;
}
@@ -61,13 +64,13 @@ public sealed partial class GuideTechnologyEmbed : BoxContainer, IDocumentTag, I
control = null;
if (!args.TryGetValue("Technology", out var id))
{
Logger.Error("Technology embed tag is missing technology prototype argument");
_sawmill.Error("Technology embed tag is missing technology prototype argument");
return false;
}
if (!_prototype.TryIndex<TechnologyPrototype>(id, out var technology))
{
Logger.Error($"Specified technology prototype \"{id}\" is not a valid technology prototype");
_sawmill.Error($"Specified technology prototype \"{id}\" is not a valid technology prototype");
return false;
}

View File

@@ -35,7 +35,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler, IA
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = Logger.GetSawmill("Guidebook");
_sawmill = Logger.GetSawmill("guidebook");
Tree.OnSelectedItemChanged += OnSelectionChanged;
@@ -232,7 +232,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler, IA
{
// TODO GUIDEBOOK Maybe allow duplicate entries?
// E.g., for adding medicine under both chemicals & the chemist job
Logger.Error($"Adding duplicate guide entry: {id}");
_sawmill.Error($"Adding duplicate guide entry: {id}");
return null;
}

View File

@@ -0,0 +1,5 @@
using Content.Shared.Implants;
namespace Content.Client.Implants;
public sealed partial class ChameleonControllerSystem : SharedChameleonControllerSystem;

View File

@@ -0,0 +1,49 @@
using Content.Shared.Clothing;
using Content.Shared.Implants;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Timing;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Implants.UI;
[UsedImplicitly]
public sealed class ChameleonControllerBoundUserInterface : BoundUserInterface
{
private readonly UseDelaySystem _delay;
[ViewVariables]
private ChameleonControllerMenu? _menu;
public ChameleonControllerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_delay = EntMan.System<UseDelaySystem>();
}
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<ChameleonControllerMenu>();
_menu.OnJobSelected += OnJobSelected;
}
private void OnJobSelected(ProtoId<ChameleonOutfitPrototype> outfit)
{
if (!EntMan.TryGetComponent<UseDelayComponent>(Owner, out var useDelayComp))
return;
if (!_delay.TryResetDelay((Owner, useDelayComp), true))
return;
SendMessage(new ChameleonControllerSelectedOutfitMessage(outfit));
if (!_delay.TryGetDelayInfo((Owner, useDelayComp), out var delay) || _menu == null)
return;
_menu._lockedUntil = DateTime.Now.Add(delay.Length);
_menu.UpdateGrid(true);
}
}

View File

@@ -0,0 +1,12 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'chameleon-controller-ui-window-name'}"
MinSize="250 300"
SetSize="850 700">
<BoxContainer Orientation="Vertical" Margin="7 0 0 0">
<ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="3" Margin="0 5" >
</GridContainer>
</ScrollContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,157 @@
using System.Linq;
using System.Numerics;
using Content.Client.Roles;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Implants;
using Content.Shared.StatusIcon;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Implants.UI;
[GenerateTypedNameReferences]
public sealed partial class ChameleonControllerMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly SpriteSystem _sprite;
private readonly JobSystem _job;
// List of all the job protos that you can select!
private IEnumerable<ChameleonOutfitPrototype> _outfits;
// Lock the UI until this time
public DateTime? _lockedUntil;
private static readonly ProtoId<JobIconPrototype> UnknownIcon = "JobIconUnknown";
private static readonly LocId UnknownDepartment = "department-Unknown";
public event Action<ProtoId<ChameleonOutfitPrototype>>? OnJobSelected;
public ChameleonControllerMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
_job = _entityManager.System<JobSystem>();
_outfits = _prototypeManager.EnumeratePrototypes<ChameleonOutfitPrototype>();
UpdateGrid();
}
/// <summary>
/// Fill the grid with the correct job icons and buttons.
/// </summary>
/// <param name="disabled">Set to true to disable all the buttons.</param>
public void UpdateGrid(bool disabled = false)
{
Grid.RemoveAllChildren();
// Dictionary to easily put outfits in departments.
// Department name -> UI element holding that department.
var departments = new Dictionary<string, BoxContainer>();
departments.Add(UnknownDepartment, CreateDepartment(UnknownDepartment));
// Go through every outfit and add them to the correct department.
foreach (var outfit in _outfits)
{
_prototypeManager.TryIndex(outfit.Job, out var jobProto);
var name = outfit.LoadoutName ?? outfit.Name ?? jobProto?.Name ?? "Prototype has no name or job.";
var jobIconId = outfit.Icon ?? jobProto?.Icon ?? UnknownIcon;
var jobIconProto = _prototypeManager.Index(jobIconId);
var outfitButton = CreateOutfitButton(disabled, name, jobIconProto, outfit.ID);
if (outfit.Job != null && _job.TryGetLowestWeightDepartment(outfit.Job, out var departmentPrototype))
{
if (!departments.ContainsKey(departmentPrototype.Name))
departments.Add(departmentPrototype.Name, CreateDepartment(departmentPrototype.Name));
departments[departmentPrototype.Name].AddChild(outfitButton);
}
else
{
departments[UnknownDepartment].AddChild(outfitButton);
}
}
// Sort the departments by their weight.
var departmentList = departments.ToList();
departmentList.Sort((a, b) => a.Value.ChildCount.CompareTo(b.Value.ChildCount));
// Actually add the departments to the window.
foreach (var department in departmentList)
{
Grid.AddChild(department.Value);
}
}
private BoxContainer CreateDepartment(string name)
{
var departmentContainer = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
departmentContainer.AddChild(new Label
{
Text = Loc.GetString(name),
});
return departmentContainer;
}
private BoxContainer CreateOutfitButton(bool disabled, string name, JobIconPrototype jobIconProto, ProtoId<ChameleonOutfitPrototype> outfitProto)
{
var outfitButton = new BoxContainer();
var button = new Button
{
HorizontalExpand = true,
StyleClasses = {StyleBase.ButtonSquare},
ToolTip = Loc.GetString(name),
Text = Loc.GetString(name),
Margin = new Thickness(0, 0, 15, 0),
Disabled = disabled,
};
var jobIconTexture = new TextureRect
{
Texture = _sprite.Frame0(jobIconProto.Icon),
TextureScale = new Vector2(2.5f, 2.5f),
Stretch = TextureRect.StretchMode.KeepCentered,
Margin = new Thickness(0, 0, 5, 0),
};
outfitButton.AddChild(jobIconTexture);
outfitButton.AddChild(button);
button.OnPressed += _ => JobButtonPressed(outfitProto);
return outfitButton;
}
private void JobButtonPressed(ProtoId<ChameleonOutfitPrototype> outfit)
{
OnJobSelected?.Invoke(outfit);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_lockedUntil == null || DateTime.Now < _lockedUntil)
return;
_lockedUntil = null;
UpdateGrid();
}
}

View File

@@ -6,8 +6,6 @@ namespace Content.Client.Light.EntitySystems;
public sealed class EmergencyLightSystem : VisualizerSystem<EmergencyLightComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, EmergencyLightComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
@@ -16,13 +14,13 @@ public sealed class EmergencyLightSystem : VisualizerSystem<EmergencyLightCompon
if (!AppearanceSystem.TryGetData<bool>(uid, EmergencyLightVisuals.On, out var on, args.Component))
on = false;
_sprite.LayerSetVisible((uid, args.Sprite), EmergencyLightVisualLayers.LightOff, !on);
_sprite.LayerSetVisible((uid, args.Sprite), EmergencyLightVisualLayers.LightOn, on);
SpriteSystem.LayerSetVisible((uid, args.Sprite), EmergencyLightVisualLayers.LightOff, !on);
SpriteSystem.LayerSetVisible((uid, args.Sprite), EmergencyLightVisualLayers.LightOn, on);
if (AppearanceSystem.TryGetData<Color>(uid, EmergencyLightVisuals.Color, out var color, args.Component))
{
_sprite.LayerSetColor((uid, args.Sprite), EmergencyLightVisualLayers.LightOn, color);
_sprite.LayerSetColor((uid, args.Sprite), EmergencyLightVisualLayers.LightOff, color);
SpriteSystem.LayerSetColor((uid, args.Sprite), EmergencyLightVisualLayers.LightOn, color);
SpriteSystem.LayerSetColor((uid, args.Sprite), EmergencyLightVisualLayers.LightOff, color);
}
}
}

View File

@@ -10,7 +10,6 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly LightBehaviorSystem _lightBehavior = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -54,37 +53,37 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
comp.PlayingStream = _audioSystem.PlayPvs(
comp.LoopedSound, uid)?.Entity;
if (_sprite.LayerMapTryGet((uid, args.Sprite), ExpendableLightVisualLayers.Overlay, out var layerIdx, true))
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), ExpendableLightVisualLayers.Overlay, out var layerIdx, true))
{
if (!string.IsNullOrWhiteSpace(comp.IconStateLit))
_sprite.LayerSetRsiState((uid, args.Sprite), layerIdx, comp.IconStateLit);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layerIdx, comp.IconStateLit);
if (!string.IsNullOrWhiteSpace(comp.SpriteShaderLit))
args.Sprite.LayerSetShader(layerIdx, comp.SpriteShaderLit);
else
args.Sprite.LayerSetShader(layerIdx, null, null);
if (comp.GlowColorLit.HasValue)
_sprite.LayerSetColor((uid, args.Sprite), layerIdx, comp.GlowColorLit.Value);
_sprite.LayerSetVisible((uid, args.Sprite), layerIdx, true);
SpriteSystem.LayerSetColor((uid, args.Sprite), layerIdx, comp.GlowColorLit.Value);
SpriteSystem.LayerSetVisible((uid, args.Sprite), layerIdx, true);
}
if (comp.GlowColorLit.HasValue)
_sprite.LayerSetColor((uid, args.Sprite), ExpendableLightVisualLayers.Glow, comp.GlowColorLit.Value);
_sprite.LayerSetVisible((uid, args.Sprite), ExpendableLightVisualLayers.Glow, true);
SpriteSystem.LayerSetColor((uid, args.Sprite), ExpendableLightVisualLayers.Glow, comp.GlowColorLit.Value);
SpriteSystem.LayerSetVisible((uid, args.Sprite), ExpendableLightVisualLayers.Glow, true);
break;
case ExpendableLightState.Dead:
comp.PlayingStream = _audioSystem.Stop(comp.PlayingStream);
if (_sprite.LayerMapTryGet((uid, args.Sprite), ExpendableLightVisualLayers.Overlay, out layerIdx, true))
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), ExpendableLightVisualLayers.Overlay, out layerIdx, true))
{
if (!string.IsNullOrWhiteSpace(comp.IconStateSpent))
_sprite.LayerSetRsiState((uid, args.Sprite), layerIdx, comp.IconStateSpent);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layerIdx, comp.IconStateSpent);
if (!string.IsNullOrWhiteSpace(comp.SpriteShaderSpent))
args.Sprite.LayerSetShader(layerIdx, comp.SpriteShaderSpent);
else
args.Sprite.LayerSetShader(layerIdx, null, null);
}
_sprite.LayerSetVisible((uid, args.Sprite), ExpendableLightVisualLayers.Glow, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), ExpendableLightVisualLayers.Glow, false);
break;
}
}

View File

@@ -5,8 +5,6 @@ namespace Content.Client.Light.Visualizers;
public sealed class LightBulbSystem : VisualizerSystem<LightBulbComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
@@ -18,13 +16,13 @@ public sealed class LightBulbSystem : VisualizerSystem<LightBulbComponent>
switch (state)
{
case LightBulbState.Normal:
_sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState);
break;
case LightBulbState.Broken:
_sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState);
break;
case LightBulbState.Burned:
_sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState);
break;
}
}
@@ -32,7 +30,7 @@ public sealed class LightBulbSystem : VisualizerSystem<LightBulbComponent>
// also update sprites color
if (AppearanceSystem.TryGetData<Color>(uid, LightBulbVisuals.Color, out var color, args.Component))
{
_sprite.SetColor((uid, args.Sprite), color);
SpriteSystem.SetColor((uid, args.Sprite), color);
}
}
}

View File

@@ -44,7 +44,7 @@ public sealed class HandheldLightSystem : SharedHandheldLightSystem
return;
}
if (!_appearance.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, args.Component))
if (!_appearance.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, args.Component))
{
return;
}

View File

@@ -11,7 +11,6 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
@@ -28,16 +27,16 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
return;
if (comp.SpriteStateMap.TryGetValue(state, out var spriteState))
_sprite.LayerSetRsiState((uid, args.Sprite), PoweredLightLayers.Base, spriteState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PoweredLightLayers.Base, spriteState);
if (_sprite.LayerExists((uid, args.Sprite), PoweredLightLayers.Glow))
if (SpriteSystem.LayerExists((uid, args.Sprite), PoweredLightLayers.Glow))
{
if (TryComp<PointLightComponent>(uid, out var light))
{
_sprite.LayerSetColor((uid, args.Sprite), PoweredLightLayers.Glow, light.Color);
SpriteSystem.LayerSetColor((uid, args.Sprite), PoweredLightLayers.Glow, light.Color);
}
_sprite.LayerSetVisible((uid, args.Sprite), PoweredLightLayers.Glow, state == PoweredLightState.On);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PoweredLightLayers.Glow, state == PoweredLightState.On);
}
SetBlinkingAnimation(

View File

@@ -32,7 +32,6 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IFileDialogManager _dialogManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
@@ -269,7 +268,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
_configurationManager,
EntityManager,
_dialogManager,
_logManager,
LogManager,
_playerManager,
_prototypeManager,
_resourceCache,

View File

@@ -6,8 +6,6 @@ namespace Content.Client.Lock.Visualizers;
public sealed class LockVisualizerSystem : VisualizerSystem<LockVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, LockVisualsComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null
@@ -22,14 +20,14 @@ public sealed class LockVisualizerSystem : VisualizerSystem<LockVisualsComponent
if (AppearanceSystem.TryGetData<bool>(uid, StorageVisuals.Open, out var open, args.Component))
{
_sprite.LayerSetVisible((uid, args.Sprite), LockVisualLayers.Lock, !open);
SpriteSystem.LayerSetVisible((uid, args.Sprite), LockVisualLayers.Lock, !open);
}
else if (!(bool)unlockedStateExist!)
_sprite.LayerSetVisible((uid, args.Sprite), LockVisualLayers.Lock, locked);
SpriteSystem.LayerSetVisible((uid, args.Sprite), LockVisualLayers.Lock, locked);
if (!open && (bool)unlockedStateExist!)
{
_sprite.LayerSetRsiState((uid, args.Sprite), LockVisualLayers.Lock, locked ? comp.StateLocked : comp.StateUnlocked);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LockVisualLayers.Lock, locked ? comp.StateLocked : comp.StateUnlocked);
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Content.Client.Machines.Components;
/// <summary>
/// Component attached to all multipart machine ghosts
/// Intended for client side usage only, but used on prototypes.
/// </summary>
[RegisterComponent]
public sealed partial class MultipartMachineGhostComponent : Component
{
/// <summary>
/// Machine this particular ghost is linked to.
/// </summary>
public EntityUid? LinkedMachine = null;
}

View File

@@ -0,0 +1,109 @@
using Content.Client.Examine;
using Content.Client.Machines.Components;
using Content.Shared.Machines.Components;
using Content.Shared.Machines.EntitySystems;
using Robust.Client.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Spawners;
namespace Content.Client.Machines.EntitySystems;
/// <summary>
/// Client side handling of multipart machines.
/// Handles client side examination events to show the expected layout of the machine
/// based on the origin of the main entity.
/// </summary>
public sealed class MultipartMachineSystem : SharedMultipartMachineSystem
{
private readonly EntProtoId _ghostPrototype = "MultipartMachineGhost";
private readonly Color _partiallyTransparent = new Color(255, 255, 255, 180);
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly ISerializationManager _serialization= default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MultipartMachineComponent, ClientExaminedEvent>(OnMachineExamined);
SubscribeLocalEvent<MultipartMachineComponent, AfterAutoHandleStateEvent>(OnHandleState);
SubscribeLocalEvent<MultipartMachineGhostComponent, TimedDespawnEvent>(OnGhostDespawned);
}
/// <summary>
/// Handles spawning several ghost sprites to show where the different parts of the machine
/// should go and the rotations they're expected to have.
/// Can only show one set of ghost parts at a time and their location depends on the current map/grid
/// location of the origin machine.
/// </summary>
/// <param name="ent">Entity/Component that has been inspected.</param>
/// <param name="args">Args for the event.</param>
private void OnMachineExamined(Entity<MultipartMachineComponent> ent, ref ClientExaminedEvent args)
{
if (ent.Comp.Ghosts.Count != 0)
{
// Already showing some part ghosts
return;
}
foreach (var part in ent.Comp.Parts.Values)
{
if (part.Entity.HasValue)
continue;
var entityCoords = new EntityCoordinates(ent.Owner, part.Offset);
var ghostEnt = Spawn(_ghostPrototype, entityCoords);
if (!XformQuery.TryGetComponent(ghostEnt, out var xform))
break;
xform.LocalRotation = part.Rotation;
Comp<MultipartMachineGhostComponent>(ghostEnt).LinkedMachine = ent;
ent.Comp.Ghosts.Add(ghostEnt);
if (part.GhostProto == null)
continue;
var entProto = _prototype.Index(part.GhostProto.Value);
if (!entProto.Components.TryGetComponent("Sprite", out var s) || s is not SpriteComponent protoSprite)
return;
var ghostSprite = EnsureComp<SpriteComponent>(ghostEnt);
_serialization.CopyTo(protoSprite, ref ghostSprite, notNullableOverride: true);
_sprite.SetColor((ghostEnt, ghostSprite), _partiallyTransparent);
_metaData.SetEntityName(ghostEnt, entProto.Name);
_metaData.SetEntityDescription(ghostEnt, entProto.Description);
}
}
private void OnHandleState(Entity<MultipartMachineComponent> ent, ref AfterAutoHandleStateEvent args)
{
foreach (var part in ent.Comp.Parts.Values)
{
part.Entity = part.NetEntity.HasValue ? EnsureEntity<MultipartMachinePartComponent>(part.NetEntity.Value, ent) : null;
}
}
/// <summary>
/// Handles when a ghost part despawns after its short lifetime.
/// Will attempt to remove itself from the list of known ghost entities in the main multipart
/// machine component.
/// </summary>
/// <param name="ent">Ghost entity that has been despawned.</param>
/// <param name="args">Args for the event.</param>
private void OnGhostDespawned(Entity<MultipartMachineGhostComponent> ent, ref TimedDespawnEvent args)
{
if (!TryComp<MultipartMachineComponent>(ent.Comp.LinkedMachine, out var machine))
return;
machine.Ghosts.Remove(ent);
}
}

View File

@@ -4,8 +4,8 @@ using Content.Shared.Mapping;
using Content.Shared.Maps;
using Robust.Client.Placement;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using static Robust.Shared.Utility.SpriteSpecifier;
namespace Content.Client.Mapping;
@@ -14,16 +14,10 @@ public sealed partial class MappingSystem : EntitySystem
[Dependency] private readonly IPlacementManager _placementMan = default!;
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
/// <summary>
/// The icon to use for space tiles.
/// </summary>
private readonly SpriteSpecifier _spaceIcon = new Texture(new ("Tiles/cropped_parallax.png"));
/// <summary>
/// The icon to use for entity-eraser.
/// </summary>
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
public static readonly EntProtoId SpawnAction = "BaseMappingSpawnAction";
public static readonly EntProtoId EraserAction = "ActionMappingEraser";
public override void Initialize()
{
@@ -38,90 +32,46 @@ public sealed partial class MappingSystem : EntitySystem
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd
/// prefer if it were to function more like DecalPlacementSystem.
/// </summary>
private void OnFillActionSlot(FillActionSlotEvent ev)
private void OnFillActionSlot(FillActionSlotEvent args)
{
if (!_placementMan.IsActive)
return;
if (ev.Action != null)
if (args.Action != null)
return;
var actionEvent = new StartPlacementActionEvent();
ITileDefinition? tileDef = null;
if (_placementMan.CurrentPermission != null)
if (_placementMan.CurrentPermission is {} permission)
{
actionEvent.EntityType = _placementMan.CurrentPermission.EntityType;
actionEvent.PlacementOption = _placementMan.CurrentPermission.PlacementOption;
var ev = new StartPlacementActionEvent()
{
EntityType = permission.EntityType,
PlacementOption = permission.PlacementOption,
};
var action = Spawn(SpawnAction);
if (_placementMan.CurrentPermission.IsTile)
{
tileDef = _tileMan[_placementMan.CurrentPermission.TileType];
actionEvent.TileId = tileDef.ID;
if (_tileMan[_placementMan.CurrentPermission.TileType] is not ContentTileDefinition tileDef)
return;
if (!tileDef.MapAtmosphere && tileDef.Sprite is {} sprite)
_actions.SetIcon(action, new SpriteSpecifier.Texture(sprite));
ev.TileId = tileDef.ID;
_metaData.SetEntityName(action, Loc.GetString(tileDef.Name));
}
else if (permission.EntityType is {} id)
{
_actions.SetIcon(action, new SpriteSpecifier.EntityPrototype(id));
_metaData.SetEntityName(action, id);
}
_actions.SetEvent(action, ev);
args.Action = action;
}
else if (_placementMan.Eraser)
{
actionEvent.Eraser = true;
args.Action = Spawn(EraserAction);
}
else
return;
InstantActionComponent action;
string name;
if (tileDef != null)
{
if (tileDef is not ContentTileDefinition contentTileDef)
return;
var tileIcon = contentTileDef.MapAtmosphere
? _spaceIcon
: new Texture(contentTileDef.Sprite!.Value);
action = new InstantActionComponent
{
ClientExclusive = true,
CheckCanInteract = false,
Event = actionEvent,
Icon = tileIcon
};
name = Loc.GetString(tileDef.Name);
}
else if (actionEvent.Eraser)
{
action = new InstantActionComponent
{
ClientExclusive = true,
CheckCanInteract = false,
Event = actionEvent,
Icon = _deleteIcon,
};
name = Loc.GetString("action-name-mapping-erase");
}
else
{
if (string.IsNullOrWhiteSpace(actionEvent.EntityType))
return;
action = new InstantActionComponent
{
ClientExclusive = true,
CheckCanInteract = false,
Event = actionEvent,
Icon = new EntityPrototype(actionEvent.EntityType),
};
name = actionEvent.EntityType;
}
var actionId = Spawn(null);
AddComp<Component>(actionId, action);
_metaData.SetEntityName(actionId, name);
ev.Action = actionId;
}
private void OnStartPlacementAction(StartPlacementActionEvent args)

View File

@@ -34,10 +34,6 @@ public sealed class JetpackSystem : SharedJetpackSystem
{
Appearance.TryGetData<bool>(uid, JetpackVisuals.Enabled, out var enabled, args.Component);
var state = "icon" + (enabled ? "-on" : "");
if (args.Sprite != null)
_sprite.LayerSetRsiState((uid, args.Sprite), 0, state);
if (TryComp<ClothingComponent>(uid, out var clothing))
_clothing.SetEquippedPrefix(uid, enabled ? "on" : null, clothing);
}

View File

@@ -0,0 +1,7 @@
<Control xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical">
<Label Name="TitleLabel" Access="Public" />
<Label Name="ExampleLabel" Access="Public" />
<ColorSelectorSliders Name="Slider" Access="Public" HorizontalExpand="True" />
</BoxContainer>
</Control>

View File

@@ -0,0 +1,31 @@
using Content.Client.Options.UI;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
namespace Content.Client.Options.UI;
/// <summary>
/// Standard UI control used for color sliders in the options menu. Intended for use with <see cref="OptionsTabControlRow"/>.
/// </summary>
/// <seealso cref="OptionsTabControlRow.AddOptionColorSlider"/>
[GenerateTypedNameReferences]
public sealed partial class OptionColorSlider : Control
{
/// <summary>
/// The text describing what this slider affects.
/// </summary>
public string? Title
{
get => TitleLabel.Text;
set => TitleLabel.Text = value;
}
/// <summary>
/// The example text showing the current color of the slider.
/// </summary>
public string? Example
{
get => ExampleLabel.Text;
set => ExampleLabel.Text = value;
}
}

View File

@@ -121,6 +121,19 @@ public sealed partial class OptionsTabControlRow : Control
return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent));
}
/// <summary>
/// Add a color slider option, backed by a simple string CVar.
/// </summary>
/// <param name="cVar">The CVar represented by the slider.</param>
/// <param name="slider">The UI control for the option.</param>
/// <returns>The option instance backing the added option.</returns>
public OptionColorSliderCVar AddOptionColorSlider(
CVarDef<string> cVar,
OptionColorSlider slider)
{
return AddOption(new OptionColorSliderCVar(this, _cfg, cVar, slider));
}
/// <summary>
/// Add a slider option, backed by a simple integer CVar.
/// </summary>
@@ -518,6 +531,58 @@ public sealed class OptionSliderFloatCVar : BaseOptionCVar<float>
}
}
/// <summary>
/// Implementation of a CVar option that simply corresponds with a string <see cref="OptionColorSlider"/>.
/// </summary>
/// <seealso cref="OptionsTabControlRow"/>
public sealed class OptionColorSliderCVar : BaseOptionCVar<string>
{
private readonly OptionColorSlider _slider;
protected override string Value
{
get => _slider.Slider.Color.ToHex();
set
{
_slider.Slider.Color = Color.FromHex(value);
UpdateLabelColor();
}
}
/// <summary>
/// Creates a new instance of this type.
/// </summary>
/// <remarks>
/// <para>
/// It is generally more convenient to call overloads on <see cref="OptionsTabControlRow"/>
/// such as <see cref="OptionsTabControlRow.AddOptionPercentSlider"/> instead of instantiating this type directly.
/// </para>
/// </remarks>
/// <param name="controller">The control row that owns this option.</param>
/// <param name="cfg">The configuration manager to get and set values from.</param>
/// <param name="cVar">The CVar that is being controlled by this option.</param>
/// <param name="slider">The UI control for the option.</param>
public OptionColorSliderCVar(
OptionsTabControlRow controller,
IConfigurationManager cfg,
CVarDef<string> cVar,
OptionColorSlider slider) : base(controller, cfg, cVar)
{
_slider = slider;
slider.Slider.OnColorChanged += _ =>
{
ValueChanged();
UpdateLabelColor();
};
}
private void UpdateLabelColor()
{
_slider.ExampleLabel.FontColorOverride = Color.FromHex(Value);
}
}
/// <summary>
/// Implementation of a CVar option that simply corresponds with an integer <see cref="OptionSlider"/>.
/// </summary>

View File

@@ -14,6 +14,10 @@
<ui:OptionSlider Name="SpeechBubbleTextOpacitySlider" Title="{Loc 'ui-options-speech-bubble-text-opacity'}" />
<ui:OptionSlider Name="SpeechBubbleSpeakerOpacitySlider" Title="{Loc 'ui-options-speech-bubble-speaker-opacity'}" />
<ui:OptionSlider Name="SpeechBubbleBackgroundOpacitySlider" Title="{Loc 'ui-options-speech-bubble-background-opacity'}" />
<CheckBox Name="AutoFillHighlightsCheckBox" Text="{Loc 'ui-options-auto-fill-highlights'}" />
<ui:OptionColorSlider Name="HighlightsColorSlider"
Title="{Loc 'ui-options-highlights-color'}"
Example="{Loc 'ui-options-highlights-color-example'}"/>
<Label Text="{Loc 'ui-options-accessability-header-content'}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="CensorNudityCheckBox" Text="{Loc 'ui-options-censor-nudity'}" />

View File

@@ -20,6 +20,8 @@ public sealed partial class AccessibilityTab : Control
Control.AddOptionPercentSlider(CCVars.SpeechBubbleTextOpacity, SpeechBubbleTextOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleSpeakerOpacity, SpeechBubbleSpeakerOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleBackgroundOpacity, SpeechBubbleBackgroundOpacitySlider);
Control.AddOptionCheckBox(CCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox);
Control.AddOptionColorSlider(CCVars.ChatHighlightsColor, HighlightsColorSlider);
Control.AddOptionCheckBox(CCVars.AccessibilityClientCensorNudity, CensorNudityCheckBox);

View File

@@ -1,5 +1,4 @@
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
@@ -7,9 +6,7 @@ namespace Content.Client.Overlays;
public sealed partial class BlackAndWhiteOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
@@ -22,17 +19,6 @@ public sealed partial class BlackAndWhiteOverlay : Overlay
ZIndex = 10; // draw this over the DamageOverlay, RainbowOverlay etc.
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp))
return false;
if (args.Viewport.Eye != eyeComp.Eye)
return false;
return true;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)

View File

@@ -1,7 +1,6 @@
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays;
using Robust.Client.Graphics;
using Robust.Client.Player;
namespace Content.Client.Overlays;

View File

@@ -0,0 +1,33 @@
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client.Overlays;
public sealed partial class NoirOverlay : Overlay
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true;
private readonly ShaderInstance _noirShader;
public NoirOverlay()
{
IoCManager.InjectDependencies(this);
_noirShader = _prototypeManager.Index<ShaderPrototype>("Noir").InstanceUnique();
ZIndex = 9; // draw this over the DamageOverlay, RainbowOverlay etc, but before the black and white shader
}
protected override void Draw(in OverlayDrawArgs args)
{
if (ScreenTexture == null)
return;
var handle = args.WorldHandle;
_noirShader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
handle.UseShader(_noirShader);
handle.DrawRect(args.WorldBounds, Color.White);
handle.UseShader(null);
}
}

View File

@@ -0,0 +1,33 @@
using Content.Shared.Inventory.Events;
using Content.Shared.Overlays;
using Robust.Client.Graphics;
namespace Content.Client.Overlays;
public sealed partial class NoirOverlaySystem : EquipmentHudSystem<NoirOverlayComponent>
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
private NoirOverlay _overlay = default!;
public override void Initialize()
{
base.Initialize();
_overlay = new();
}
protected override void UpdateInternal(RefreshEquipmentHudEvent<NoirOverlayComponent> component)
{
base.UpdateInternal(component);
_overlayMan.AddOverlay(_overlay);
}
protected override void DeactivateInternal()
{
base.DeactivateInternal();
_overlayMan.RemoveOverlay(_overlay);
}
}

View File

@@ -6,20 +6,19 @@ namespace Content.Client.PDA;
public sealed class PdaVisualizerSystem : VisualizerSystem<PdaVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, PdaVisualsComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (AppearanceSystem.TryGetData<string>(uid, PdaVisuals.PdaType, out var pdaType, args.Component))
_sprite.LayerSetRsiState((uid, args.Sprite), PdaVisualLayers.Base, pdaType);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PdaVisualLayers.Base, pdaType);
if (AppearanceSystem.TryGetData<bool>(uid, UnpoweredFlashlightVisuals.LightOn, out var isFlashlightOn, args.Component))
_sprite.LayerSetVisible((uid, args.Sprite), PdaVisualLayers.Flashlight, isFlashlightOn);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PdaVisualLayers.Flashlight, isFlashlightOn);
if (AppearanceSystem.TryGetData<bool>(uid, PdaVisuals.IdCardInserted, out var isCardInserted, args.Component))
_sprite.LayerSetVisible((uid, args.Sprite), PdaVisualLayers.IdLight, isCardInserted);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PdaVisualLayers.IdLight, isCardInserted);
}
public enum PdaVisualLayers : byte

View File

@@ -5,8 +5,6 @@ namespace Content.Client.Paper;
public sealed class EnvelopeSystem : VisualizerSystem<EnvelopeComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
@@ -23,9 +21,9 @@ public sealed class EnvelopeSystem : VisualizerSystem<EnvelopeComponent>
if (!Resolve(ent.Owner, ref sprite))
return;
_sprite.LayerSetVisible((ent.Owner, sprite), EnvelopeVisualLayers.Open, ent.Comp.State == EnvelopeComponent.EnvelopeState.Open);
_sprite.LayerSetVisible((ent.Owner, sprite), EnvelopeVisualLayers.Sealed, ent.Comp.State == EnvelopeComponent.EnvelopeState.Sealed);
_sprite.LayerSetVisible((ent.Owner, sprite), EnvelopeVisualLayers.Torn, ent.Comp.State == EnvelopeComponent.EnvelopeState.Torn);
SpriteSystem.LayerSetVisible((ent.Owner, sprite), EnvelopeVisualLayers.Open, ent.Comp.State == EnvelopeComponent.EnvelopeState.Open);
SpriteSystem.LayerSetVisible((ent.Owner, sprite), EnvelopeVisualLayers.Sealed, ent.Comp.State == EnvelopeComponent.EnvelopeState.Sealed);
SpriteSystem.LayerSetVisible((ent.Owner, sprite), EnvelopeVisualLayers.Torn, ent.Comp.State == EnvelopeComponent.EnvelopeState.Torn);
}
public enum EnvelopeVisualLayers : byte

View File

@@ -6,26 +6,24 @@ namespace Content.Client.Paper.UI;
public sealed class PaperVisualizerSystem : VisualizerSystem<PaperVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, PaperVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (AppearanceSystem.TryGetData<PaperStatus>(uid, PaperVisuals.Status, out var writingStatus, args.Component))
_sprite.LayerSetVisible((uid, args.Sprite), PaperVisualLayers.Writing, writingStatus == PaperStatus.Written);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PaperVisualLayers.Writing, writingStatus == PaperStatus.Written);
if (AppearanceSystem.TryGetData<string>(uid, PaperVisuals.Stamp, out var stampState, args.Component))
{
if (stampState != string.Empty)
{
_sprite.LayerSetRsiState((uid, args.Sprite), PaperVisualLayers.Stamp, stampState);
_sprite.LayerSetVisible((uid, args.Sprite), PaperVisualLayers.Stamp, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PaperVisualLayers.Stamp, stampState);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PaperVisualLayers.Stamp, true);
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), PaperVisualLayers.Stamp, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PaperVisualLayers.Stamp, false);
}
}

View File

@@ -5,14 +5,12 @@ namespace Content.Client.ParticleAccelerator;
public sealed class ParticleAcceleratorPartVisualizerSystem : VisualizerSystem<ParticleAcceleratorPartVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, ParticleAcceleratorPartVisualsComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!_sprite.LayerMapTryGet((uid, args.Sprite), ParticleAcceleratorVisualLayers.Unlit, out var index, false))
if (!SpriteSystem.LayerMapTryGet((uid, args.Sprite), ParticleAcceleratorVisualLayers.Unlit, out var index, false))
return;
if (!AppearanceSystem.TryGetData<ParticleAcceleratorVisualState>(uid, ParticleAcceleratorVisuals.VisualState, out var state, args.Component))
@@ -22,12 +20,12 @@ public sealed class ParticleAcceleratorPartVisualizerSystem : VisualizerSystem<P
if (state != ParticleAcceleratorVisualState.Unpowered)
{
_sprite.LayerSetVisible((uid, args.Sprite), index, true);
_sprite.LayerSetRsiState((uid, args.Sprite), index, comp.StateBase + comp.StatesSuffixes[state]);
SpriteSystem.LayerSetVisible((uid, args.Sprite), index, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), index, comp.StateBase + comp.StatesSuffixes[state]);
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), index, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), index, false);
}
}
}

View File

@@ -127,7 +127,7 @@
<Control/>
<ui:PASegmentControl Name="EndCapTexture" BaseState="end_cap"/>
<Control/>
<ui:PASegmentControl Name="ControlBoxTexture" BaseState="control_box"/>
<ui:PASegmentControl Name="ControlBoxTexture" BaseState="control_box" DefaultVisible="True"/>
<ui:PASegmentControl Name="FuelChamberTexture" BaseState="fuel_chamber"/>
<Control/>
<Control/>

View File

@@ -268,6 +268,7 @@ public sealed class PASegmentControl : Control
private RSI? _rsi;
public string BaseState { get; set; } = "control_box";
public bool DefaultVisible { get; set; } = false;
public PASegmentControl()
{
@@ -283,12 +284,14 @@ public sealed class PASegmentControl : Control
_rsi = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>($"/Textures/Structures/Power/Generation/PA/{BaseState}.rsi").RSI;
MinSize = _rsi.Size;
_base.Texture = _rsi["completed"].Frame0;
SetVisible(DefaultVisible);
_unlit.Visible = DefaultVisible;
}
public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
{
_base.ShaderOverride = exists ? null : _greyScaleShader;
_base.ModulateSelfOverride = exists ? null : new Color(127, 127, 127);
SetVisible(exists);
if (!state.Enabled || !exists)
{
@@ -319,4 +322,23 @@ public sealed class PASegmentControl : Control
_unlit.Texture = rState.Frame0;
}
/// <summary>
/// Adds/Removes the shading to the part in the control menu based on the
/// input state.
/// </summary>
/// <param name="state">True if the part exists, false otherwise</param>
private void SetVisible(bool state)
{
if (state)
{
_base.ShaderOverride = null;
_base.ModulateSelfOverride = null;
}
else
{
_base.ShaderOverride = _greyScaleShader;
_base.ModulateSelfOverride = new Color(127, 127, 127);
}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Shared.Alert;
using Content.Shared.CCVar;
using Content.Shared.Friction;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems;

View File

@@ -6,7 +6,6 @@ namespace Content.Client.Power.APC;
public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
{
[Dependency] private readonly SharedPointLightSystem _lights = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, ApcVisualsComponent comp, ref AppearanceChangeEvent args)
{
@@ -14,8 +13,8 @@ public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
return;
// get the mapped layer index of the first lock layer and the first channel layer
var lockIndicatorOverlayStart = _sprite.LayerMapGet((uid, args.Sprite), ApcVisualLayers.InterfaceLock);
var channelIndicatorOverlayStart = _sprite.LayerMapGet((uid, args.Sprite), ApcVisualLayers.Equipment);
var lockIndicatorOverlayStart = SpriteSystem.LayerMapGet((uid, args.Sprite), ApcVisualLayers.InterfaceLock);
var channelIndicatorOverlayStart = SpriteSystem.LayerMapGet((uid, args.Sprite), ApcVisualLayers.Equipment);
// Handle APC screen overlay:
if (!AppearanceSystem.TryGetData<ApcChargeState>(uid, ApcVisuals.ChargeState, out var chargeState, args.Component))
@@ -23,7 +22,7 @@ public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
if (chargeState >= 0 && chargeState < ApcChargeState.NumStates)
{
_sprite.LayerSetRsiState((uid, args.Sprite), ApcVisualLayers.ChargeState, $"{comp.ScreenPrefix}-{comp.ScreenSuffixes[(sbyte)chargeState]}");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), ApcVisualLayers.ChargeState, $"{comp.ScreenPrefix}-{comp.ScreenSuffixes[(sbyte)chargeState]}");
// LockState does nothing currently. The backend doesn't exist.
if (AppearanceSystem.TryGetData<byte>(uid, ApcVisuals.LockState, out var lockStates, args.Component))
@@ -32,8 +31,8 @@ public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
{
var layer = (byte)lockIndicatorOverlayStart + i;
var lockState = (sbyte)((lockStates >> (i << (sbyte)ApcLockState.LogWidth)) & (sbyte)ApcLockState.All);
_sprite.LayerSetRsiState((uid, args.Sprite), layer, $"{comp.LockPrefix}{i}-{comp.LockSuffixes[lockState]}");
_sprite.LayerSetVisible((uid, args.Sprite), layer, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layer, $"{comp.LockPrefix}{i}-{comp.LockSuffixes[lockState]}");
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, true);
}
}
@@ -44,8 +43,8 @@ public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
{
var layer = (byte)channelIndicatorOverlayStart + i;
var channelState = (sbyte)((channelStates >> (i << (sbyte)ApcChannelState.LogWidth)) & (sbyte)ApcChannelState.All);
_sprite.LayerSetRsiState((uid, args.Sprite), layer, $"{comp.ChannelPrefix}{i}-{comp.ChannelSuffixes[channelState]}");
_sprite.LayerSetVisible((uid, args.Sprite), layer, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), layer, $"{comp.ChannelPrefix}{i}-{comp.ChannelSuffixes[channelState]}");
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, true);
}
}
@@ -57,16 +56,16 @@ public sealed class ApcVisualizerSystem : VisualizerSystem<ApcVisualsComponent>
else
{
/// Overrides all of the lock and channel indicators.
_sprite.LayerSetRsiState((uid, args.Sprite), ApcVisualLayers.ChargeState, comp.EmaggedScreenState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), ApcVisualLayers.ChargeState, comp.EmaggedScreenState);
for (var i = 0; i < comp.LockIndicators; ++i)
{
var layer = (byte)lockIndicatorOverlayStart + i;
_sprite.LayerSetVisible((uid, args.Sprite), layer, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, false);
}
for (var i = 0; i < comp.ChannelIndicators; ++i)
{
var layer = (byte)channelIndicatorOverlayStart + i;
_sprite.LayerSetVisible((uid, args.Sprite), layer, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, false);
}
if (TryComp<PointLightComponent>(uid, out var light))

View File

@@ -6,8 +6,6 @@ namespace Content.Client.Power.SMES;
public sealed class SmesVisualizerSystem : VisualizerSystem<SmesComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, SmesComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
@@ -15,12 +13,12 @@ public sealed class SmesVisualizerSystem : VisualizerSystem<SmesComponent>
if (!AppearanceSystem.TryGetData<int>(uid, SmesVisuals.LastChargeLevel, out var level, args.Component) || level == 0)
{
_sprite.LayerSetVisible((uid, args.Sprite), SmesVisualLayers.Charge, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), SmesVisualLayers.Charge, false);
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), SmesVisualLayers.Charge, true);
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Charge, $"{comp.ChargeOverlayPrefix}{level}");
SpriteSystem.LayerSetVisible((uid, args.Sprite), SmesVisualLayers.Charge, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Charge, $"{comp.ChargeOverlayPrefix}{level}");
}
if (!AppearanceSystem.TryGetData<ChargeState>(uid, SmesVisuals.LastChargeState, out var state, args.Component))
@@ -29,16 +27,16 @@ public sealed class SmesVisualizerSystem : VisualizerSystem<SmesComponent>
switch (state)
{
case ChargeState.Still:
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Input, $"{comp.InputOverlayPrefix}0");
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Output, $"{comp.OutputOverlayPrefix}1");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Input, $"{comp.InputOverlayPrefix}0");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Output, $"{comp.OutputOverlayPrefix}1");
break;
case ChargeState.Charging:
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Input, $"{comp.InputOverlayPrefix}1");
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Output, $"{comp.OutputOverlayPrefix}1");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Input, $"{comp.InputOverlayPrefix}1");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Output, $"{comp.OutputOverlayPrefix}1");
break;
case ChargeState.Discharging:
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Input, $"{comp.InputOverlayPrefix}0");
_sprite.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Output, $"{comp.OutputOverlayPrefix}2");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Input, $"{comp.InputOverlayPrefix}0");
SpriteSystem.LayerSetRsiState((uid, args.Sprite), SmesVisualLayers.Output, $"{comp.OutputOverlayPrefix}2");
break;
}
}

View File

@@ -5,8 +5,6 @@ namespace Content.Client.PowerCell;
public sealed class PowerChargerVisualizerSystem : VisualizerSystem<PowerChargerVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, PowerChargerVisualsComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
@@ -16,22 +14,22 @@ public sealed class PowerChargerVisualizerSystem : VisualizerSystem<PowerCharger
if (AppearanceSystem.TryGetData<bool>(uid, CellVisual.Occupied, out var occupied, args.Component) && occupied)
{
// TODO: don't throw if it doesn't have a full state
_sprite.LayerSetRsiState((uid, args.Sprite), PowerChargerVisualLayers.Base, comp.OccupiedState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PowerChargerVisualLayers.Base, comp.OccupiedState);
}
else
{
_sprite.LayerSetRsiState((uid, args.Sprite), PowerChargerVisualLayers.Base, comp.EmptyState);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PowerChargerVisualLayers.Base, comp.EmptyState);
}
// Update lighting
if (AppearanceSystem.TryGetData<CellChargerStatus>(uid, CellVisual.Light, out var status, args.Component)
&& comp.LightStates.TryGetValue(status, out var lightState))
{
_sprite.LayerSetRsiState((uid, args.Sprite), PowerChargerVisualLayers.Light, lightState);
_sprite.LayerSetVisible((uid, args.Sprite), PowerChargerVisualLayers.Light, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), PowerChargerVisualLayers.Light, lightState);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PowerChargerVisualLayers.Light, true);
}
else
_sprite.LayerSetVisible((uid, args.Sprite), PowerChargerVisualLayers.Light, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), PowerChargerVisualLayers.Light, false);
}
}

View File

@@ -59,7 +59,7 @@ namespace Content.Client.Radiation.Overlays
shd?.SetParameter("positionInput", tempCoords);
shd?.SetParameter("range", instance.Range);
var life = (_gameTiming.RealTime - instance.Start).TotalSeconds / instance.Duration;
shd?.SetParameter("life", (float) life);
shd?.SetParameter("life", (float)life);
// There's probably a very good reason not to do this.
// Oh well!

View File

@@ -0,0 +1,5 @@
using Content.Shared.Rootable;
namespace Content.Client.Rootable;
public sealed class RootableSystem : SharedRootableSystem;

View File

@@ -20,11 +20,15 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private readonly ISawmill _sawmill;
public SalvageExpeditionConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("salvage.expedition.console");
}
protected override void Open()
@@ -111,7 +115,7 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf
string LogAndReturnDefaultFactionDescription(string faction)
{
Logger.Error($"Description is null or white space for SalvageFactionPrototype: {faction}");
_sawmill.Error($"Description is null or white space for SalvageFactionPrototype: {faction}");
return Loc.GetString(_protoManager.Index<SalvageFactionPrototype>(faction).ID);
}
@@ -150,7 +154,7 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf
string LogAndReturnDefaultBiomDescription(string biome)
{
Logger.Error($"Description is null or white space for SalvageBiomeModPrototype: {biome}");
_sawmill.Error($"Description is null or white space for SalvageBiomeModPrototype: {biome}");
return Loc.GetString(_protoManager.Index<SalvageBiomeModPrototype>(biome).ID);
}

View File

@@ -8,8 +8,6 @@ namespace Content.Client.Shuttles;
/// </summary>
public sealed class ThrusterSystem : VisualizerSystem<ThrusterComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
/// <summary>
/// Updates whether or not the thruster is visibly active/thrusting.
/// </summary>
@@ -19,7 +17,7 @@ public sealed class ThrusterSystem : VisualizerSystem<ThrusterComponent>
|| !AppearanceSystem.TryGetData<bool>(uid, ThrusterVisualState.State, out var state, args.Component))
return;
_sprite.LayerSetVisible((uid, args.Sprite), ThrusterVisualLayers.ThrustOn, state);
SpriteSystem.LayerSetVisible((uid, args.Sprite), ThrusterVisualLayers.ThrustOn, state);
SetThrusting(
uid,
state && AppearanceSystem.TryGetData<bool>(uid, ThrusterVisualState.Thrusting, out var thrusting, args.Component) && thrusting,
@@ -32,14 +30,14 @@ public sealed class ThrusterSystem : VisualizerSystem<ThrusterComponent>
/// </summary>
private void SetThrusting(EntityUid uid, bool value, SpriteComponent sprite)
{
if (_sprite.LayerMapTryGet((uid, sprite), ThrusterVisualLayers.Thrusting, out var thrustingLayer, false))
if (SpriteSystem.LayerMapTryGet((uid, sprite), ThrusterVisualLayers.Thrusting, out var thrustingLayer, false))
{
_sprite.LayerSetVisible((uid, sprite), thrustingLayer, value);
SpriteSystem.LayerSetVisible((uid, sprite), thrustingLayer, value);
}
if (_sprite.LayerMapTryGet((uid, sprite), ThrusterVisualLayers.ThrustingUnshaded, out var unshadedLayer, false))
if (SpriteSystem.LayerMapTryGet((uid, sprite), ThrusterVisualLayers.ThrustingUnshaded, out var unshadedLayer, false))
{
_sprite.LayerSetVisible((uid, sprite), unshadedLayer, value);
SpriteSystem.LayerSetVisible((uid, sprite), unshadedLayer, value);
}
}
}

View File

@@ -8,8 +8,6 @@ namespace Content.Client.Storage.Systems;
/// <inheritdoc cref="StorageContainerVisualsComponent"/>
public sealed class StorageContainerVisualsSystem : VisualizerSystem<StorageContainerVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
protected override void OnAppearanceChange(EntityUid uid, StorageContainerVisualsComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
@@ -23,7 +21,7 @@ public sealed class StorageContainerVisualsSystem : VisualizerSystem<StorageCont
var fraction = used / (float)capacity;
if (!_sprite.LayerMapTryGet((uid, args.Sprite), component.FillLayer, out var fillLayer, false))
if (!SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.FillLayer, out var fillLayer, false))
return;
var closestFillSprite = Math.Min(ContentHelpers.RoundToNearestLevels(fraction, 1, component.MaxFillLevels + 1),
@@ -34,13 +32,13 @@ public sealed class StorageContainerVisualsSystem : VisualizerSystem<StorageCont
if (component.FillBaseName == null)
return;
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, true);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, true);
var stateName = component.FillBaseName + closestFillSprite;
_sprite.LayerSetRsiState((uid, args.Sprite), fillLayer, stateName);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), fillLayer, stateName);
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), fillLayer, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), fillLayer, false);
}
}
}

View File

@@ -5,8 +5,6 @@ namespace Content.Client.Storage.Visualizers;
public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStorageVisualsComponent>
{
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
@@ -25,7 +23,7 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
_sprite.LayerSetRsiState((uid, sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
SpriteSystem.LayerSetRsiState((uid, sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
}
protected override void OnAppearanceChange(EntityUid uid, EntityStorageVisualsComponent comp, ref AppearanceChangeEvent args)
@@ -35,41 +33,41 @@ public sealed class EntityStorageVisualizerSystem : VisualizerSystem<EntityStora
return;
// Open/Closed state for the storage entity.
if (_sprite.LayerMapTryGet((uid, args.Sprite), StorageVisualLayers.Door, out _, false))
if (SpriteSystem.LayerMapTryGet((uid, args.Sprite), StorageVisualLayers.Door, out _, false))
{
if (open)
{
if (comp.OpenDrawDepth != null)
_sprite.SetDrawDepth((uid, args.Sprite), comp.OpenDrawDepth.Value);
SpriteSystem.SetDrawDepth((uid, args.Sprite), comp.OpenDrawDepth.Value);
if (comp.StateDoorOpen != null)
{
_sprite.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Door, comp.StateDoorOpen);
_sprite.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Door, comp.StateDoorOpen);
SpriteSystem.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, true);
}
else
{
_sprite.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, false);
}
if (comp.StateBaseOpen != null)
_sprite.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseOpen);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseOpen);
}
else
{
if (comp.ClosedDrawDepth != null)
_sprite.SetDrawDepth((uid, args.Sprite), comp.ClosedDrawDepth.Value);
SpriteSystem.SetDrawDepth((uid, args.Sprite), comp.ClosedDrawDepth.Value);
if (comp.StateDoorClosed != null)
{
_sprite.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Door, comp.StateDoorClosed);
_sprite.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, true);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Door, comp.StateDoorClosed);
SpriteSystem.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, true);
}
else
_sprite.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, false);
SpriteSystem.LayerSetVisible((uid, args.Sprite), StorageVisualLayers.Door, false);
if (comp.StateBaseClosed != null)
_sprite.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
SpriteSystem.LayerSetRsiState((uid, args.Sprite), StorageVisualLayers.Base, comp.StateBaseClosed);
}
}
}

View File

@@ -141,11 +141,8 @@ public sealed partial class StoreMenu : DefaultWindow
else if (listing.ProductAction != null)
{
var actionId = _entityManager.Spawn(listing.ProductAction);
if (_entityManager.System<ActionsSystem>().TryGetActionData(actionId, out var action) &&
action.Icon != null)
{
texture = spriteSys.Frame0(action.Icon);
}
if (_entityManager.System<ActionsSystem>().GetAction(actionId)?.Comp?.Icon is {} icon)
texture = spriteSys.Frame0(icon);
}
var listingInStock = GetListingPriceString(listing);

View File

@@ -27,7 +27,6 @@ namespace Content.Client.TextScreen;
public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsComponent>
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
/// <summary>
/// Contains char/state Key/Value pairs. <br/>
@@ -90,11 +89,11 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
for (var i = 0; i < screen.RowLength; i++)
{
_sprite.LayerMapReserve((uid, sprite), TimerMapKey + i);
SpriteSystem.LayerMapReserve((uid, sprite), TimerMapKey + i);
timer.LayerStatesToDraw.Add(TimerMapKey + i, null);
_sprite.LayerSetRsi((uid, sprite), TimerMapKey + i, new ResPath(TextPath));
_sprite.LayerSetColor((uid, sprite), TimerMapKey + i, screen.Color);
_sprite.LayerSetRsiState((uid, sprite), TimerMapKey + i, DefaultState);
SpriteSystem.LayerSetRsi((uid, sprite), TimerMapKey + i, new ResPath(TextPath));
SpriteSystem.LayerSetColor((uid, sprite), TimerMapKey + i, screen.Color);
SpriteSystem.LayerSetRsiState((uid, sprite), TimerMapKey + i, DefaultState);
}
}
@@ -154,7 +153,7 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
return;
foreach (var key in timer.LayerStatesToDraw.Keys)
_sprite.RemoveLayer((uid, sprite), key);
SpriteSystem.RemoveLayer((uid, sprite), key);
RemComp<TextScreenTimerComponent>(uid);
@@ -190,7 +189,7 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
return;
foreach (var key in component.LayerStatesToDraw.Keys)
_sprite.RemoveLayer((uid, sprite), key);
SpriteSystem.RemoveLayer((uid, sprite), key);
component.LayerStatesToDraw.Clear();
@@ -198,11 +197,11 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
for (var i = 0; i < component.RowLength; i++)
{
var key = TextMapKey + row + i;
_sprite.LayerMapReserve((uid, sprite), key);
SpriteSystem.LayerMapReserve((uid, sprite), key);
component.LayerStatesToDraw.Add(key, null);
_sprite.LayerSetRsi((uid, sprite), key, new ResPath(TextPath));
_sprite.LayerSetColor((uid, sprite), key, component.Color);
_sprite.LayerSetRsiState((uid, sprite), key, DefaultState);
SpriteSystem.LayerSetRsi((uid, sprite), key, new ResPath(TextPath));
SpriteSystem.LayerSetColor((uid, sprite), key, component.Color);
SpriteSystem.LayerSetRsiState((uid, sprite), key, DefaultState);
}
}
@@ -228,7 +227,7 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
for (var chr = 0; chr < min; chr++)
{
component.LayerStatesToDraw[TextMapKey + rowIdx + chr] = GetStateFromChar(row[chr]);
_sprite.LayerSetOffset(
SpriteSystem.LayerSetOffset(
(uid, sprite),
TextMapKey + rowIdx + chr,
Vector2.Multiply(
@@ -259,7 +258,7 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
for (var i = 0; i < min; i++)
{
timer.LayerStatesToDraw[TimerMapKey + i] = GetStateFromChar(time[i]);
_sprite.LayerSetOffset(
SpriteSystem.LayerSetOffset(
(uid, sprite),
TimerMapKey + i,
Vector2.Multiply(
@@ -279,7 +278,7 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
return;
foreach (var (key, state) in layerStates.Where(pairs => pairs.Value != null))
_sprite.LayerSetRsiState((uid, sprite), key, state);
SpriteSystem.LayerSetRsiState((uid, sprite), key, state);
}
public override void Update(float frameTime)

View File

@@ -1,32 +0,0 @@
using Content.Shared.Hands.Components;
namespace Content.Client.Toggleable;
/// <summary>
/// Component that handles the toggling the visuals of some light emitting entity.
/// </summary>
/// <remarks>
/// This will toggle the visibility of layers on an entity's sprite, the in-hand visuals, and the clothing/equipment
/// visuals. This will modify the color of any attached point lights.
/// </remarks>
[RegisterComponent]
public sealed partial class ToggleableLightVisualsComponent : Component
{
/// <summary>
/// Sprite layer that will have its visibility toggled when this item is toggled.
/// </summary>
[DataField("spriteLayer")]
public string? SpriteLayer = "light";
/// <summary>
/// Layers to add to the sprite of the player that is holding this entity (while the component is toggled on).
/// </summary>
[DataField("inhandVisuals")]
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
/// <summary>
/// Layers to add to the sprite of the player that is wearing this entity (while the component is toggled on).
/// </summary>
[DataField("clothingVisuals")]
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
}

View File

@@ -1,127 +0,0 @@
using Content.Client.Clothing;
using Content.Client.Items.Systems;
using Content.Shared.Clothing;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Toggleable;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
using System.Linq;
namespace Content.Client.Toggleable;
public sealed class ToggleableLightVisualsSystem : VisualizerSystem<ToggleableLightVisualsComponent>
{
[Dependency] private readonly SharedItemSystem _itemSys = default!;
[Dependency] private readonly SharedPointLightSystem _lights = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToggleableLightVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals, after: new[] { typeof(ItemSystem) });
SubscribeLocalEvent<ToggleableLightVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals, after: new[] { typeof(ClientClothingSystem) });
}
protected override void OnAppearanceChange(EntityUid uid, ToggleableLightVisualsComponent component, ref AppearanceChangeEvent args)
{
if (!AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, args.Component))
return;
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, args.Component);
// Update the item's sprite
if (args.Sprite != null && component.SpriteLayer != null && _sprite.LayerMapTryGet((uid, args.Sprite), component.SpriteLayer, out var layer, false))
{
_sprite.LayerSetVisible((uid, args.Sprite), layer, enabled);
if (modulate)
_sprite.LayerSetColor((uid, args.Sprite), layer, color);
}
// Update any point-lights
if (TryComp(uid, out PointLightComponent? light))
{
DebugTools.Assert(!light.NetSyncEnabled, "light visualizers require point lights without net-sync");
_lights.SetEnabled(uid, enabled, light);
if (enabled && modulate)
{
_lights.SetColor(uid, color, light);
}
}
// update clothing & in-hand visuals.
_itemSys.VisualsChanged(uid);
}
/// <summary>
/// Add the unshaded light overlays to any clothing sprites.
/// </summary>
private void OnGetEquipmentVisuals(EntityUid uid, ToggleableLightVisualsComponent component, GetEquipmentVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
return;
List<PrototypeLayerData>? layers = null;
// attempt to get species specific data
if (inventory.SpeciesId != null)
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
// No species specific data. Try to default to generic data.
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
return;
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
var i = 0;
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? $"{args.Slot}-toggle" : $"{args.Slot}-toggle-{i}";
i++;
}
if (modulate)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
private void OnGetHeldVisuals(EntityUid uid, ToggleableLightVisualsComponent component, GetInhandVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableLightVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!component.InhandVisuals.TryGetValue(args.Location, out var layers))
return;
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
var i = 0;
var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}-toggle";
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? defaultKey : $"{defaultKey}-{i}";
i++;
}
if (modulate)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Hands.Components;
namespace Content.Client.Toggleable;
/// <summary>
/// Component that handles toggling the visuals of an entity, including layers on an entity's sprite,
/// the in-hand visuals, and the clothing/equipment visuals.
/// </summary>
/// <see cref="ToggleableVisualsSystem"/>
[RegisterComponent]
public sealed partial class ToggleableVisualsComponent : Component
{
/// <summary>
/// Sprite layer that will have its visibility toggled when this item is toggled.
/// </summary>
[DataField(required: true)]
public string? SpriteLayer;
/// <summary>
/// Layers to add to the sprite of the player that is holding this entity (while the component is toggled on).
/// </summary>
[DataField]
public Dictionary<HandLocation, List<PrototypeLayerData>> InhandVisuals = new();
/// <summary>
/// Layers to add to the sprite of the player that is wearing this entity (while the component is toggled on).
/// </summary>
[DataField]
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
}

View File

@@ -0,0 +1,140 @@
using System.Linq;
using Content.Client.Clothing;
using Content.Client.Items.Systems;
using Content.Shared.Clothing;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Light.Components;
using Content.Shared.Toggleable;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
namespace Content.Client.Toggleable;
/// <summary>
/// Implements the behavior of <see cref="ToggleableVisualsComponent"/> by reacting to
/// <see cref="AppearanceChangeEvent"/>, for the sprite directly; <see cref="OnGetHeldVisuals"/> for the
/// in-hand visuals; and <see cref="OnGetEquipmentVisuals"/> for the clothing visuals.
/// </summary>
/// <see cref="ToggleableVisualsComponent"/>
public sealed class ToggleableVisualsSystem : VisualizerSystem<ToggleableVisualsComponent>
{
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ToggleableVisualsComponent, GetInhandVisualsEvent>(OnGetHeldVisuals,
after: [typeof(ItemSystem)]);
SubscribeLocalEvent<ToggleableVisualsComponent, GetEquipmentVisualsEvent>(OnGetEquipmentVisuals,
after: [typeof(ClientClothingSystem)]);
}
protected override void OnAppearanceChange(EntityUid uid,
ToggleableVisualsComponent component,
ref AppearanceChangeEvent args)
{
if (!AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, args.Component))
return;
var modulateColor =
AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, args.Component);
// Update the item's sprite
if (args.Sprite != null && component.SpriteLayer != null &&
SpriteSystem.LayerMapTryGet((uid, args.Sprite), component.SpriteLayer, out var layer, false))
{
SpriteSystem.LayerSetVisible((uid, args.Sprite), layer, enabled);
if (modulateColor)
SpriteSystem.LayerSetColor((uid, args.Sprite), component.SpriteLayer, color);
}
// If there's a `ItemTogglePointLightComponent` that says to apply the color to attached lights, do so.
if (TryComp<ItemTogglePointLightComponent>(uid, out var toggleLights) &&
TryComp(uid, out PointLightComponent? light))
{
DebugTools.Assert(!light.NetSyncEnabled,
$"{typeof(ItemTogglePointLightComponent)} requires point lights without net-sync");
_pointLight.SetEnabled(uid, enabled, light);
if (modulateColor && toggleLights.ToggleableVisualsColorModulatesLights)
{
_pointLight.SetColor(uid, color, light);
}
}
// update clothing & in-hand visuals.
_item.VisualsChanged(uid);
}
private void OnGetEquipmentVisuals(EntityUid uid,
ToggleableVisualsComponent component,
GetEquipmentVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
return;
List<PrototypeLayerData>? layers = null;
// attempt to get species specific data
if (inventory.SpeciesId != null)
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
// No species specific data. Try to default to generic data.
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
return;
var modulateColor = AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, appearance);
var i = 0;
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? $"{args.Slot}-toggle" : $"{args.Slot}-toggle-{i}";
i++;
}
if (modulateColor)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
private void OnGetHeldVisuals(EntityUid uid, ToggleableVisualsComponent component, GetInhandVisualsEvent args)
{
if (!TryComp(uid, out AppearanceComponent? appearance)
|| !AppearanceSystem.TryGetData<bool>(uid, ToggleableVisuals.Enabled, out var enabled, appearance)
|| !enabled)
return;
if (!component.InhandVisuals.TryGetValue(args.Location, out var layers))
return;
var modulateColor = AppearanceSystem.TryGetData<Color>(uid, ToggleableVisuals.Color, out var color, appearance);
var i = 0;
var defaultKey = $"inhand-{args.Location.ToString().ToLowerInvariant()}-toggle";
foreach (var layer in layers)
{
var key = layer.MapKeys?.FirstOrDefault();
if (key == null)
{
key = i == 0 ? defaultKey : $"{defaultKey}-{i}";
i++;
}
if (modulateColor)
layer.Color = color;
args.Layers.Add((key, layer));
}
}
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.TurretController;
namespace Content.Client.TurretController;
/// <inheritdoc/>
public sealed class DeployableTurretControllerSystem : SharedDeployableTurretControllerSystem
{
}

View File

@@ -0,0 +1,125 @@
<ui:TurretControllerWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.TurretController"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:access="clr-namespace:Content.Client.Access.UI"
SetWidth="550"
Resizable="False"
MouseFilter="Stop">
<PanelContainer Name="Background" StyleClasses="PdaBackgroundRect" ModulateSelfOverride="#4a5466"/>
<PanelContainer Name="Border" StyleClasses="PdaBorderRect" />
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<!--Header-->
<BoxContainer SetHeight="26" Margin="4 2 8 0" Orientation="Horizontal" HorizontalAlignment="Right">
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" Modulate="#646464" VerticalAlignment="Center" Margin="0 4 4 0"/>
</BoxContainer>
<!--Content-->
<Control Margin="18 0" RectClipContent="True" VerticalExpand="true"
HorizontalExpand="True">
<PanelContainer Name="ContentBorder" StyleClasses="PdaBackground"/>
<Control Name="ContentsContainer" Margin="3 3" Modulate="#FFFFFF">
<!-- Screen Background -->
<PanelContainer Name="ContentBackground" StyleClasses="PdaContentBackground"/>
<!-- Screen foreground -->
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'turret-controls-window-title'}" StyleClasses="ConsoleHeading"
HorizontalAlignment="Center" Margin="0 5 0 0" />
<!-- Linked devices -->
<PanelContainer Margin="10 5 10 5">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderColor="#FFFFFF" BorderThickness="2" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" MinHeight="195" Margin="5 5 5 5">
<Label Name="TurretStatusHeader" Text="{Loc 'turret-controls-window-turret-status-label'}" StyleClasses="ConsoleSubHeading"
HorizontalAlignment="Center" />
<PanelContainer StyleClasses="LowDivider" HorizontalExpand="True" Margin="-5 5 -5 5" SetHeight="2">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#FFFFFF" />
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<Label Name="NoLinkedTurretsText" Text="{Loc 'turret-controls-window-no-turrets'}" StyleClasses="ConsoleText"
HorizontalAlignment="Center" ReservesSpace="False"/>
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Name="LinkedTurretsContainer" Orientation="Vertical" Visible="False" ReservesSpace="False">
<!-- Populated with C# code -->
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</BoxContainer>
</PanelContainer>
<!-- Armament controls -->
<PanelContainer Margin="10 0 10 5">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderColor="#FFFFFF" BorderThickness="2" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'turret-controls-window-armament-controls-label'}" StyleClasses="ConsoleSubHeading"
HorizontalAlignment="Center" Margin="0 5 0 5" />
<PanelContainer StyleClasses="LowDivider" HorizontalExpand="True" SetHeight="2">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#FFFFFF" />
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer Orientation="Horizontal" Margin="10 10 10 10">
<controls:MonotoneButton Name="SafeButton" Text="{Loc 'turret-controls-window-safe'}"
StyleClasses="OpenRight" Pressed="False" ToggleMode="True" HorizontalExpand="True"/>
<controls:MonotoneButton Name="StunButton" Text="{Loc 'turret-controls-window-stun'}"
StyleClasses="OpenBoth" Pressed="False" ToggleMode="True" HorizontalExpand="True"/>
<controls:MonotoneButton Name="LethalButton" Text="{Loc 'turret-controls-window-lethal'}"
StyleClasses="OpenLeft" Pressed="False" ToggleMode="True" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
<!-- Targeting controls -->
<PanelContainer Name="TargetingControlsPanel" Margin="10 0 10 10" VerticalExpand="True" HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderColor="#FFFFFF" BorderThickness="2" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<Label Text="{Loc 'turret-controls-window-targeting-controls-label'}" StyleClasses="ConsoleSubHeading"
HorizontalAlignment="Center" Margin="0 5 0 5" />
<PanelContainer StyleClasses="LowDivider" HorizontalExpand="True" SetHeight="2">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#FFFFFF" />
</PanelContainer.PanelOverride>
</PanelContainer>
<!-- Access configuration -->
<access:GroupedAccessLevelChecklist Name="AccessConfiguration"/>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</Control>
</Control>
<!--Footer-->
<BoxContainer Orientation="Horizontal" SetHeight="28">
<Label Text="⚠" Margin="0 0 4 4" HorizontalExpand="True" HorizontalAlignment="Right"/>
<Label Name="Footer" Text="{Loc 'turret-controls-window-footer'}"
HorizontalAlignment="Center" Margin="0 0 0 4"/>
<Label Text="⚠" Margin="4 0 0 4" HorizontalExpand="True" HorizontalAlignment="Left"/>
</BoxContainer>
</BoxContainer>
</ui:TurretControllerWindow>

View File

@@ -0,0 +1,202 @@
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
using Content.Shared.TurretController;
using Content.Shared.Turrets;
using Robust.Client.AutoGenerated;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Numerics;
namespace Content.Client.TurretController;
[GenerateTypedNameReferences]
public sealed partial class TurretControllerWindow : BaseWindow
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
private readonly AccessReaderSystem _accessReaderSystem;
private EntityUid? _owner;
// Button groups
private readonly ButtonGroup _armamentButtons = new();
// Events
public event Action<HashSet<ProtoId<AccessLevelPrototype>>, bool>? OnAccessLevelsChangedEvent;
public event Action<TurretArmamentSetting>? OnArmamentSettingChangedEvent;
// Colors
private static readonly Dictionary<TurretArmamentSetting, Color> ThemeColors = new()
{
[TurretArmamentSetting.Safe] = Color.FromHex("#33e633"),
[TurretArmamentSetting.Stun] = Color.FromHex("#dfb827"),
[TurretArmamentSetting.Lethal] = Color.FromHex("#da2a2a")
};
public TurretControllerWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_accessReaderSystem = _entManager.System<AccessReaderSystem>();
CloseButton.OnPressed += _ => Close();
// Set up armament buttons
SafeButton.OnToggled += args => OnArmamentButtonPressed(SafeButton, TurretArmamentSetting.Safe);
StunButton.OnToggled += args => OnArmamentButtonPressed(StunButton, TurretArmamentSetting.Stun);
LethalButton.OnToggled += args => OnArmamentButtonPressed(LethalButton, TurretArmamentSetting.Lethal);
SafeButton.Group = _armamentButtons;
StunButton.Group = _armamentButtons;
LethalButton.Group = _armamentButtons;
SafeButton.Label.AddStyleClass("ConsoleText");
StunButton.Label.AddStyleClass("ConsoleText");
LethalButton.Label.AddStyleClass("ConsoleText");
// Set up access configuration buttons
AccessConfiguration.SetMonotone(true);
AccessConfiguration.SetLabelStyleClass("ConsoleText");
AccessConfiguration.OnAccessLevelsChangedEvent += OnAccessLevelsChanged;
// Override footer font
var smallFont = _cache.NotoStack(size: 8);
Footer.FontOverride = smallFont;
}
private void OnAccessLevelsChanged(HashSet<ProtoId<AccessLevelPrototype>> accessLevels, bool isPressed)
{
OnAccessLevelsChangedEvent?.Invoke(accessLevels, isPressed);
}
private void OnArmamentButtonPressed(MonotoneButton pressedButton, TurretArmamentSetting setting)
{
UpdateTheme(setting);
OnArmamentSettingChangedEvent?.Invoke(setting);
}
private void Initialize()
{
RefreshLinkedTurrets(new());
if (_entManager.TryGetComponent<DeployableTurretControllerComponent>(_owner, out var turretController))
{
AccessConfiguration.SetAccessGroups(turretController.AccessGroups);
AccessConfiguration.SetAccessLevels(turretController.AccessLevels);
UpdateTheme((TurretArmamentSetting)turretController.ArmamentState);
}
if (_entManager.TryGetComponent<TurretTargetSettingsComponent>(_owner, out var turretTargetSettings))
{
RefreshAccessControls(turretTargetSettings.ExemptAccessLevels);
}
}
public void SetOwner(EntityUid owner)
{
_owner = owner;
Initialize();
}
private void UpdateTheme(TurretArmamentSetting setting)
{
var setPressedOn = setting switch
{
TurretArmamentSetting.Safe => SafeButton,
TurretArmamentSetting.Stun => StunButton,
TurretArmamentSetting.Lethal => LethalButton,
_ => throw new NotImplementedException(),
};
setPressedOn.Pressed = true;
var canInteract = IsLocalPlayerAllowedToInteract();
SafeButton.Disabled = !SafeButton.Pressed && !canInteract;
StunButton.Disabled = !StunButton.Pressed && !canInteract;
LethalButton.Disabled = !LethalButton.Pressed && !canInteract;
ContentsContainer.Modulate = ThemeColors[setting];
}
public void UpdateState(DeployableTurretControllerBoundInterfaceState state)
{
if (_entManager.TryGetComponent<DeployableTurretControllerComponent>(_owner, out var turretController))
UpdateTheme((TurretArmamentSetting)turretController.ArmamentState);
if (_entManager.TryGetComponent<TurretTargetSettingsComponent>(_owner, out var turretTargetSettings))
RefreshAccessControls(turretTargetSettings.ExemptAccessLevels);
RefreshLinkedTurrets(state.TurretStateByAddress);
}
public void RefreshLinkedTurrets(Dictionary<string, string> turretStates)
{
var turretCount = turretStates.Count;
var hasTurrets = turretCount > 0;
NoLinkedTurretsText.Visible = !hasTurrets;
LinkedTurretsContainer.Visible = hasTurrets;
LinkedTurretsContainer.RemoveAllChildren();
foreach (var (address, state) in turretStates)
{
var text = Loc.GetString(
"turret-controls-window-turret-status",
("device", address),
("status", Loc.GetString(state))
);
var label = new Label
{
Text = text,
HorizontalAlignment = HAlignment.Left,
Margin = new Thickness(10f, 0f, 10f, 0f),
HorizontalExpand = true,
SetHeight = 20f,
};
label.AddStyleClass("ConsoleText");
LinkedTurretsContainer.AddChild(label);
}
TurretStatusHeader.Text = Loc.GetString("turret-controls-window-turret-status-label", ("count", turretCount));
}
public void RefreshAccessControls(HashSet<ProtoId<AccessLevelPrototype>> exemptAccessLevels)
{
AccessConfiguration.SetActiveAccessLevels(exemptAccessLevels);
AccessConfiguration.SetLocalPlayerAccessibility(IsLocalPlayerAllowedToInteract());
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
{
return DragMode.Move;
}
private bool IsLocalPlayerAllowedToInteract()
{
if (_owner == null || _playerManager.LocalSession?.AttachedEntity == null)
return false;
return _accessReaderSystem.IsAllowed(_playerManager.LocalSession.AttachedEntity.Value, _owner.Value);
}
public enum TurretArmamentSetting
{
Safe = -1,
Stun = 0,
Lethal = 1,
}
}

View File

@@ -0,0 +1,44 @@
using Content.Shared.Access;
using Content.Shared.TurretController;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.TurretController;
public sealed class TurretControllerWindowBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private TurretControllerWindow? _window;
protected override void Open()
{
base.Open();
_window = this.CreateWindow<TurretControllerWindow>();
_window.SetOwner(Owner);
_window.OpenCentered();
_window.OnAccessLevelsChangedEvent += OnAccessLevelChanged;
_window.OnArmamentSettingChangedEvent += OnArmamentSettingChanged;
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not DeployableTurretControllerBoundInterfaceState { } castState)
return;
_window?.UpdateState(castState);
}
private void OnAccessLevelChanged(HashSet<ProtoId<AccessLevelPrototype>> accessLevels, bool enabled)
{
SendPredictedMessage(new DeployableTurretExemptAccessLevelChangedMessage(accessLevels, enabled));
}
private void OnArmamentSettingChanged(TurretControllerWindow.TurretArmamentSetting setting)
{
SendPredictedMessage(new DeployableTurretArmamentSettingChangedMessage((int)setting));
}
}

View File

@@ -84,9 +84,6 @@ public sealed partial class DeployableTurretSystem : SharedDeployableTurretSyste
if (_animation.HasRunningAnimation(ent, animPlayer, DeployableTurretComponent.AnimationKey))
return;
if (state == ent.Comp.VisualState)
return;
var targetState = state & DeployableTurretState.Deployed;
var destinationState = ent.Comp.VisualState & DeployableTurretState.Deployed;

View File

@@ -7,7 +7,7 @@ namespace Content.Client.UserInterface.Controls;
/// <summary>
/// A button intended for use with a monotone color palette
/// </summary>
public sealed class MonotoneButton : ContainerButton
public sealed class MonotoneButton : Button
{
/// <summary>
/// Specifies the color of the label text when the button is pressed.
@@ -15,43 +15,9 @@ public sealed class MonotoneButton : ContainerButton
[ViewVariables]
public Color AltTextColor { set; get; } = new Color(0.2f, 0.2f, 0.2f);
/// <summary>
/// The label that holds the button text.
/// </summary>
public Label Label { get; }
/// <summary>
/// The text displayed by the button.
/// </summary>
[PublicAPI, ViewVariables]
public string? Text { get => Label.Text; set => Label.Text = value; }
/// <summary>
/// How to align the text inside the button.
/// </summary>
[PublicAPI, ViewVariables]
public AlignMode TextAlign { get => Label.Align; set => Label.Align = value; }
/// <summary>
/// If true, the button will allow shrinking and clip text
/// to prevent the text from going outside the bounds of the button.
/// If false, the minimum size will always fit the contained text.
/// </summary>
[PublicAPI, ViewVariables]
public bool ClipText
{
get => Label.ClipText;
set => Label.ClipText = value;
}
public MonotoneButton()
{
Label = new Label
{
StyleClasses = { StyleClassButton }
};
AddChild(Label);
RemoveStyleClass("button");
UpdateAppearance();
}

View File

@@ -12,6 +12,7 @@ using Content.Client.UserInterface.Systems.Actions.Widgets;
using Content.Client.UserInterface.Systems.Actions.Windows;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Input;
using Robust.Client.GameObjects;
@@ -162,142 +163,33 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
if (_playerManager.LocalEntity is not { } user)
return false;
if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp))
if (!EntityManager.TryGetComponent<ActionsComponent>(user, out var comp))
return false;
if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) ||
baseAction is not BaseTargetActionComponent action)
if (_actionsSystem.GetAction(actionId) is not {} action ||
!EntityManager.TryGetComponent<TargetActionComponent>(action, out var target))
{
return false;
}
// Is the action currently valid?
if (!action.Enabled
|| action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
if (!_actionsSystem.ValidAction(action))
{
// The user is targeting with this action, but it is not valid. Maybe mark this click as
// handled and prevent further interactions.
return !action.InteractOnMiss;
return !target.InteractOnMiss;
}
switch (action)
var ev = new ActionTargetAttemptEvent(args, (user, comp), action);
EntityManager.EventBus.RaiseLocalEvent(action, ref ev);
if (!ev.Handled)
{
case WorldTargetActionComponent mapTarget:
return TryTargetWorld(args, actionId, mapTarget, user, comp) || !mapTarget.InteractOnMiss;
case EntityTargetActionComponent entTarget:
return TryTargetEntity(args, actionId, entTarget, user, comp) || !entTarget.InteractOnMiss;
case EntityWorldTargetActionComponent entMapTarget:
return TryTargetEntityWorld(args, actionId, entMapTarget, user, comp) || !entMapTarget.InteractOnMiss;
default:
Logger.Error($"Unknown targeting action: {actionId.GetType()}");
return false;
}
}
private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, WorldTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
{
if (_actionsSystem == null)
return false;
var coords = args.Coordinates;
if (!_actionsSystem.ValidateWorldTarget(user, coords, (actionId, action)))
{
// Invalid target.
if (action.DeselectOnMiss)
StopTargeting();
Log.Error($"Action {EntityManager.ToPrettyString(actionId)} did not handle ActionTargetAttemptEvent!");
return false;
}
if (action.ClientExclusive)
{
if (action.Event != null)
{
action.Event.Target = coords;
}
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
}
else
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetCoordinates(coords)));
if (!action.Repeat)
StopTargeting();
return true;
}
private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, EntityTargetActionComponent action, EntityUid user, ActionsComponent actionComp)
{
if (_actionsSystem == null)
return false;
var entity = args.EntityUid;
if (!_actionsSystem.ValidateEntityTarget(user, entity, (actionId, action)))
{
if (action.DeselectOnMiss)
StopTargeting();
return false;
}
if (action.ClientExclusive)
{
if (action.Event != null)
{
action.Event.Target = entity;
}
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
}
else
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(args.EntityUid)));
if (!action.Repeat)
StopTargeting();
return true;
}
private bool TryTargetEntityWorld(in PointerInputCmdArgs args,
EntityUid actionId,
EntityWorldTargetActionComponent action,
EntityUid user,
ActionsComponent actionComp)
{
if (_actionsSystem == null)
return false;
var entity = args.EntityUid;
var coords = args.Coordinates;
if (!_actionsSystem.ValidateEntityWorldTarget(user, entity, coords, (actionId, action)))
{
if (action.DeselectOnMiss)
StopTargeting();
return false;
}
if (action.ClientExclusive)
{
if (action.Event != null)
{
action.Event.Entity = entity;
action.Event.Coords = coords;
}
_actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime);
}
else
EntityManager.RaisePredictiveEvent(new RequestPerformActionEvent(EntityManager.GetNetEntity(actionId), EntityManager.GetNetEntity(args.EntityUid), EntityManager.GetNetCoordinates(coords)));
if (!action.Repeat)
// stop targeting when needed
if (ev.FoundTarget ? !target.Repeat : target.DeselectOnMiss)
StopTargeting();
return true;
@@ -305,36 +197,26 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
public void UnloadButton()
{
if (ActionButton == null)
{
return;
}
ActionButton.OnPressed -= ActionButtonPressed;
if (ActionButton != null)
ActionButton.OnPressed -= ActionButtonPressed;
}
public void LoadButton()
{
if (ActionButton == null)
{
return;
}
ActionButton.OnPressed += ActionButtonPressed;
if (ActionButton != null)
ActionButton.OnPressed += ActionButtonPressed;
}
private void OnWindowOpened()
{
if (ActionButton != null)
ActionButton.SetClickPressed(true);
ActionButton?.SetClickPressed(true);
SearchAndDisplay();
}
private void OnWindowClosed()
{
if (ActionButton != null)
ActionButton.SetClickPressed(false);
ActionButton?.SetClickPressed(false);
}
public void OnStateExited(GameplayState state)
@@ -351,35 +233,33 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
private void TriggerAction(int index)
{
if (_actionsSystem == null ||
!_actions.TryGetValue(index, out var actionId) ||
!_actionsSystem.TryGetActionData(actionId, out var baseAction))
if (!_actions.TryGetValue(index, out var actionId) ||
_actionsSystem?.GetAction(actionId) is not {} action)
{
return;
}
if (baseAction is BaseTargetActionComponent action)
ToggleTargeting(actionId.Value, action);
// TODO: probably should have a clientside event raised for flexibility
if (EntityManager.TryGetComponent<TargetActionComponent>(action, out var target))
ToggleTargeting((action, action, target));
else
_actionsSystem?.TriggerAction(actionId.Value, baseAction);
_actionsSystem?.TriggerAction(action);
}
private void OnActionAdded(EntityUid actionId)
{
if (_actionsSystem == null ||
!_actionsSystem.TryGetActionData(actionId, out var action))
{
if (_actionsSystem?.GetAction(actionId) is not {} action)
return;
}
// TODO: event
// if the action is toggled when we add it, start targeting
if (action is BaseTargetActionComponent targetAction && action.Toggled)
StartTargeting(actionId, targetAction);
if (action.Comp.Toggled && EntityManager.TryGetComponent<TargetActionComponent>(actionId, out var target))
StartTargeting((action, action, target));
if (_actions.Contains(actionId))
if (_actions.Contains(action))
return;
_actions.Add(actionId);
_actions.Add(action);
}
private void OnActionRemoved(EntityUid actionId)
@@ -437,15 +317,16 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
}
}
private bool MatchesFilter(BaseActionComponent action, Filters filter)
private bool MatchesFilter(Entity<ActionComponent> ent, Filters filter)
{
var (uid, comp) = ent;
return filter switch
{
Filters.Enabled => action.Enabled,
Filters.Item => action.Container != null && action.Container != _playerManager.LocalEntity,
Filters.Innate => action.Container == null || action.Container == _playerManager.LocalEntity,
Filters.Instant => action is InstantActionComponent,
Filters.Targeted => action is BaseTargetActionComponent,
Filters.Enabled => comp.Enabled,
Filters.Item => comp.Container != null && comp.Container != _playerManager.LocalEntity,
Filters.Innate => comp.Container == null || comp.Container == _playerManager.LocalEntity,
Filters.Instant => EntityManager.HasComponent<InstantActionComponent>(uid),
Filters.Targeted => EntityManager.HasComponent<TargetActionComponent>(uid),
_ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null)
};
}
@@ -456,7 +337,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
_window.ResultsGrid.RemoveAllChildren();
}
private void PopulateActions(IEnumerable<(EntityUid Id, BaseActionComponent Comp)> actions)
private void PopulateActions(IEnumerable<Entity<ActionComponent>> actions)
{
if (_window is not { Disposed: false, IsOpen: true })
return;
@@ -478,7 +359,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
if (i < existing.Count)
{
existing[i++].UpdateData(action.Id, _actionsSystem);
existing[i++].UpdateData(action, _actionsSystem);
continue;
}
@@ -486,7 +367,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
button.ActionPressed += OnWindowActionPressed;
button.ActionUnpressed += OnWindowActionUnPressed;
button.ActionFocusExited += OnWindowActionFocusExisted;
button.UpdateData(action.Id, _actionsSystem);
button.UpdateData(action, _actionsSystem);
_window.ResultsGrid.AddChild(button);
}
@@ -525,13 +406,13 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
actions = actions.Where(action =>
{
if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action.Comp, filter)))
if (filters.Count > 0 && filters.Any(filter => !MatchesFilter(action, filter)))
return false;
if (action.Comp.Keywords.Any(keyword => search.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
return true;
var name = EntityManager.GetComponent<MetaDataComponent>(action.Id).EntityName;
var name = EntityManager.GetComponent<MetaDataComponent>(action).EntityName;
if (name.Contains(search, StringComparison.OrdinalIgnoreCase))
return true;
@@ -581,7 +462,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
private void DragAction()
{
if (_menuDragHelper.Dragged is not {ActionId: {} action} dragged)
if (_menuDragHelper.Dragged is not {Action: {} action} dragged)
{
_menuDragHelper.EndDrag();
return;
@@ -591,7 +472,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
var currentlyHovered = UIManager.MouseGetControl(_input.MouseScreenPosition);
if (currentlyHovered is ActionButton button)
{
swapAction = button.ActionId;
swapAction = button.Action;
SetAction(button, action, false);
}
@@ -665,16 +546,13 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
private void HandleActionPressed(GUIBoundKeyEventArgs args, ActionButton button)
{
args.Handle();
if (button.ActionId != null)
if (button.Action != null)
{
_menuDragHelper.MouseDown(button);
return;
}
var ev = new FillActionSlotEvent();
EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
if (ev.Action != null)
SetAction(button, ev.Action);
// good job
}
private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button)
@@ -700,12 +578,13 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
_menuDragHelper.EndDrag();
if (!_actionsSystem.TryGetActionData(button.ActionId, out var baseAction))
if (button.Action is not {} action)
return;
if (baseAction is not BaseTargetActionComponent action)
// TODO: make this an event
if (!EntityManager.TryGetComponent<TargetActionComponent>(action, out var target))
{
_actionsSystem?.TriggerAction(button.ActionId.Value, baseAction);
_actionsSystem?.TriggerAction(action);
return;
}
@@ -714,7 +593,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
// if we're clicking the same thing we're already targeting for, then we simply cancel
// targeting
ToggleTargeting(button.ActionId.Value, action);
ToggleTargeting((action, action.Comp, target));
}
private bool OnMenuBeginDrag()
@@ -722,16 +601,16 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
// TODO ACTIONS
// The dragging icon shuld be based on the entity's icon style. I.e. if the action has a large icon texture,
// and a small item/provider sprite, then the dragged icon should be the big texture, not the provider.
if (_actionsSystem != null && _actionsSystem.TryGetActionData(_menuDragHelper.Dragged?.ActionId, out var action))
if (_menuDragHelper.Dragged?.Action is {} action)
{
if (EntityManager.TryGetComponent(action.EntityIcon, out SpriteComponent? sprite)
if (EntityManager.TryGetComponent(action.Comp.EntityIcon, out SpriteComponent? sprite)
&& sprite.Icon?.GetFrame(RsiDirection.South, 0) is {} frame)
{
_dragShadow.Texture = frame;
}
else if (action.Icon != null)
else if (action.Comp.Icon is {} icon)
{
_dragShadow.Texture = _spriteSystem.Frame0(action.Icon);
_dragShadow.Texture = _spriteSystem.Frame0(icon);
}
else
{
@@ -898,33 +777,35 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
/// If currently targeting with no slot or a different slot, switches to
/// targeting with the specified slot.
/// </summary>
private void ToggleTargeting(EntityUid actionId, BaseTargetActionComponent action)
private void ToggleTargeting(Entity<ActionComponent, TargetActionComponent> ent)
{
if (SelectingTargetFor == actionId)
if (SelectingTargetFor == ent)
{
StopTargeting();
return;
}
StartTargeting(actionId, action);
StartTargeting(ent);
}
/// <summary>
/// Puts us in targeting mode, where we need to pick either a target point or entity
/// </summary>
private void StartTargeting(EntityUid actionId, BaseTargetActionComponent action)
private void StartTargeting(Entity<ActionComponent, TargetActionComponent> ent)
{
var (uid, action, target) = ent;
// If we were targeting something else we should stop
StopTargeting();
SelectingTargetFor = actionId;
SelectingTargetFor = uid;
// TODO inform the server
action.Toggled = true;
_actionsSystem?.SetToggled(uid, true);
// override "held-item" overlay
var provider = action.Container;
if (action.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
if (target.TargetingIndicator && _overlays.TryGetOverlay<ShowHandItemOverlay>(out var handOverlay))
{
if (action.ItemIconStyle == ItemActionIconStyle.BigItem && action.Container != null)
{
@@ -940,7 +821,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
foreach (var button in _container.GetButtons())
{
if (button.ActionId == actionId)
if (button.Action?.Owner == uid)
button.UpdateIcons();
}
}
@@ -950,19 +831,19 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
// - Add a yes/no checkmark where the HandItemOverlay usually is
// Highlight valid entity targets
if (action is not EntityTargetActionComponent entityAction)
if (!EntityManager.TryGetComponent<EntityTargetActionComponent>(uid, out var entity))
return;
Func<EntityUid, bool>? predicate = null;
var attachedEnt = entityAction.AttachedEntity;
var attachedEnt = action.AttachedEntity;
if (!entityAction.CanTargetSelf)
if (!entity.CanTargetSelf)
predicate = e => e != attachedEnt;
var range = entityAction.CheckCanAccess ? action.Range : -1;
var range = target.CheckCanAccess ? target.Range : -1;
_interactionOutline?.SetEnabled(false);
_targetOutline?.Enable(range, entityAction.CheckCanAccess, predicate, entityAction.Whitelist, entityAction.Blacklist, null);
_targetOutline?.Enable(range, target.CheckCanAccess, predicate, entity.Whitelist, entity.Blacklist, null);
}
/// <summary>
@@ -974,11 +855,8 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
return;
var oldAction = SelectingTargetFor;
if (_actionsSystem != null && _actionsSystem.TryGetActionData(oldAction, out var action))
{
// TODO inform the server
action.Toggled = false;
}
// TODO inform the server
_actionsSystem?.SetToggled(oldAction, false);
SelectingTargetFor = null;
@@ -989,7 +867,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{
foreach (var button in _container.GetButtons())
{
if (button.ActionId == oldAction)
if (button.Action?.Owner == oldAction)
button.UpdateIcons();
}
}

View File

@@ -4,6 +4,7 @@ using Content.Client.Actions.UI;
using Content.Client.Cooldown;
using Content.Client.Stylesheets;
using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Robust.Client.GameObjects;
@@ -54,8 +55,7 @@ public sealed class ActionButton : Control, IEntityControl
private Texture? _buttonBackgroundTexture;
public EntityUid? ActionId { get; private set; }
private BaseActionComponent? _action;
public Entity<ActionComponent>? Action { get; private set; }
public bool Locked { get; set; }
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
@@ -193,7 +193,7 @@ public sealed class ActionButton : Control, IEntityControl
private Control? SupplyTooltip(Control sender)
{
if (!_entities.TryGetComponent(ActionId, out MetaDataComponent? metadata))
if (!_entities.TryGetComponent(Action, out MetaDataComponent? metadata))
return null;
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
@@ -201,14 +201,14 @@ public sealed class ActionButton : Control, IEntityControl
FormattedMessage? chargesText = null;
// TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you.
if (_entities.TryGetComponent(ActionId, out LimitedChargesComponent? actionCharges))
if (_entities.TryGetComponent(Action, out LimitedChargesComponent? actionCharges))
{
var charges = _sharedChargesSys.GetCurrentCharges((ActionId.Value, actionCharges, null));
var charges = _sharedChargesSys.GetCurrentCharges((Action.Value, actionCharges, null));
chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}"));
if (_entities.TryGetComponent(ActionId, out AutoRechargeComponent? autoRecharge))
if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge))
{
var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((ActionId.Value, actionCharges, autoRecharge));
var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge));
chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}"));
}
}
@@ -223,7 +223,7 @@ public sealed class ActionButton : Control, IEntityControl
private void UpdateItemIcon()
{
if (_action is not {EntityIcon: { } entity} ||
if (Action?.Comp is not {EntityIcon: { } entity} ||
!_entities.HasComponent<SpriteComponent>(entity))
{
_bigItemSpriteView.Visible = false;
@@ -233,7 +233,7 @@ public sealed class ActionButton : Control, IEntityControl
}
else
{
switch (_action.ItemIconStyle)
switch (Action?.Comp.ItemIconStyle)
{
case ItemActionIconStyle.BigItem:
_bigItemSpriteView.Visible = true;
@@ -259,17 +259,17 @@ public sealed class ActionButton : Control, IEntityControl
private void SetActionIcon(Texture? texture)
{
if (_action == null || texture == null)
if (Action?.Comp is not {} action || texture == null)
{
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
else if (_action.EntityIcon != null && _action.ItemIconStyle == ItemActionIconStyle.BigItem)
else if (action.EntityIcon != null && action.ItemIconStyle == ItemActionIconStyle.BigItem)
{
_smallActionIcon.Texture = texture;
_smallActionIcon.Modulate = _action.IconColor;
_smallActionIcon.Modulate = action.IconColor;
_smallActionIcon.Visible = true;
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
@@ -277,7 +277,7 @@ public sealed class ActionButton : Control, IEntityControl
else
{
_bigActionIcon.Texture = texture;
_bigActionIcon.Modulate = _action.IconColor;
_bigActionIcon.Modulate = action.IconColor;
_bigActionIcon.Visible = true;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
@@ -289,7 +289,7 @@ public sealed class ActionButton : Control, IEntityControl
UpdateItemIcon();
UpdateBackground();
if (_action == null)
if (Action is not {} action)
{
SetActionIcon(null);
return;
@@ -297,29 +297,27 @@ public sealed class ActionButton : Control, IEntityControl
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
_spriteSys ??= _entities.System<SpriteSystem>();
if ((_controller.SelectingTargetFor == ActionId || _action.Toggled))
var icon = action.Comp.Icon;
if (_controller.SelectingTargetFor == action || action.Comp.Toggled)
{
if (_action.IconOn != null)
SetActionIcon(_spriteSys.Frame0(_action.IconOn));
else if (_action.Icon != null)
SetActionIcon(_spriteSys.Frame0(_action.Icon));
else
SetActionIcon(null);
if (action.Comp.IconOn is {} iconOn)
icon = iconOn;
if (_action.BackgroundOn != null)
_buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);
if (action.Comp.BackgroundOn is {} background)
_buttonBackgroundTexture = _spriteSys.Frame0(background);
}
else
{
SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null);
_buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
}
SetActionIcon(icon != null ? _spriteSys.Frame0(icon) : null);
}
public void UpdateBackground()
{
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
if (_action != null ||
if (Action != null ||
_controller.IsDragging && GetPositionInParent() == Parent?.ChildCount - 1)
{
Button.Texture = _buttonBackgroundTexture;
@@ -333,9 +331,7 @@ public sealed class ActionButton : Control, IEntityControl
public bool TryReplaceWith(EntityUid actionId, ActionsSystem system)
{
if (Locked)
{
return false;
}
UpdateData(actionId, system);
return true;
@@ -343,16 +339,15 @@ public sealed class ActionButton : Control, IEntityControl
public void UpdateData(EntityUid? actionId, ActionsSystem system)
{
ActionId = actionId;
system.TryGetActionData(actionId, out _action);
Label.Visible = actionId != null;
Action = system.GetAction(actionId);
Label.Visible = Action != null;
UpdateIcons();
}
public void ClearData()
{
ActionId = null;
_action = null;
Action = null;
Cooldown.Visible = false;
Cooldown.Progress = 1;
Label.Visible = false;
@@ -365,19 +360,15 @@ public sealed class ActionButton : Control, IEntityControl
UpdateBackground();
Cooldown.Visible = _action != null && _action.Cooldown != null;
if (_action == null)
Cooldown.Visible = Action?.Comp.Cooldown != null;
if (Action?.Comp is not {} action)
return;
if (_action.Cooldown != null)
{
Cooldown.FromTime(_action.Cooldown.Value.Start, _action.Cooldown.Value.End);
}
if (action.Cooldown is {} cooldown)
Cooldown.FromTime(cooldown.Start, cooldown.End);
if (ActionId != null && _toggled != _action.Toggled)
{
_toggled = _action.Toggled;
}
if (_toggled != action.Toggled)
_toggled = action.Toggled;
}
protected override void MouseEntered()
@@ -404,7 +395,7 @@ public sealed class ActionButton : Control, IEntityControl
public void Depress(GUIBoundKeyEventArgs args, bool depress)
{
// action can still be toggled if it's allowed to stay selected
if (_action is not {Enabled: true})
if (Action?.Comp is not {Enabled: true})
return;
_depressed = depress;
@@ -414,17 +405,17 @@ public sealed class ActionButton : Control, IEntityControl
public void DrawModeChanged()
{
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
HighlightRect.Visible = _beingHovered && (_action != null || _controller.IsDragging);
HighlightRect.Visible = _beingHovered && (Action != null || _controller.IsDragging);
// always show the normal empty button style if no action in this slot
if (_action == null)
if (Action?.Comp is not {} action)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
return;
}
// show a hover only if the action is usable or another action is being dragged on top of this
if (_beingHovered && (_controller.IsDragging || _action!.Enabled))
if (_beingHovered && (_controller.IsDragging || action.Enabled))
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
}
@@ -439,16 +430,16 @@ public sealed class ActionButton : Control, IEntityControl
}
// if it's toggled on, always show the toggled on style (currently same as depressed style)
if (_action.Toggled || _controller.SelectingTargetFor == ActionId)
if (action.Toggled || _controller.SelectingTargetFor == Action?.Owner)
{
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
SetOnlyStylePseudoClass(_action.IconOn != null
SetOnlyStylePseudoClass(action.IconOn != null
? ContainerButton.StylePseudoClassNormal
: ContainerButton.StylePseudoClassPressed);
return;
}
if (!_action.Enabled)
if (!action.Enabled)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
return;
@@ -457,5 +448,5 @@ public sealed class ActionButton : Control, IEntityControl
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
}
EntityUid? IEntityControl.UiEntity => ActionId;
EntityUid? IEntityControl.UiEntity => Action;
}

View File

@@ -48,6 +48,8 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
private bool _bwoinkSoundEnabled;
private string? _aHelpSound;
protected override string SawmillName => "c.s.go.es.bwoink";
public override void Initialize()
{
base.Initialize();
@@ -129,7 +131,7 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
private void ReceivedBwoink(object? sender, SharedBwoinkSystem.BwoinkTextMessage message)
{
Logger.InfoS("c.s.go.es.bwoink", $"@{message.UserId}: {message.Text}");
Log.Info($"@{message.UserId}: {message.Text}");
var localPlayer = _playerManager.LocalSession;
if (localPlayer == null)
{

View File

@@ -28,21 +28,16 @@ namespace Content.Client.UserInterface.Systems.Character;
public sealed class CharacterUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CharacterInfoSystem>
{
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_sawmill = _logMan.GetSawmill("character");
SubscribeNetworkEvent<MindRoleTypeChangedEvent>(OnRoleTypeChanged);
}
@@ -222,7 +217,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
return;
if (!_prototypeManager.TryIndex(mind.RoleType, out var proto))
_sawmill.Error($"Player '{_player.LocalSession}' has invalid Role Type '{mind.RoleType}'. Displaying default instead");
Log.Error($"Player '{_player.LocalSession}' has invalid Role Type '{mind.RoleType}'. Displaying default instead");
_window.RoleType.Text = Loc.GetString(proto?.Name ?? "role-type-crew-aligned-name");
_window.RoleType.FontColorOverride = proto?.Color ?? Color.White;

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